Compare commits
163 Commits
Author | SHA1 | Date | |
---|---|---|---|
24007f1515 | |||
3d7445f359 | |||
34760afe77 | |||
417209b057 | |||
9026378b86 | |||
9b3dca1683 | |||
cde593a935 | |||
f0755260a6 | |||
582e1eb4f8 | |||
aaadda3e0f | |||
9d7f5b5b6e | |||
99040597dc | |||
d9794216dd | |||
84bb6056d3 | |||
dfed2daa8e | |||
1521ec8071 | |||
bf7ae178ef | |||
dc7f96c6da | |||
c6959bb0bc | |||
d4dd6c84bc | |||
e59678360c | |||
1b6fa0c7d8 | |||
95a5936daf | |||
477d4117ce | |||
444f119e50 | |||
fa13a2874e | |||
24ce325e31 | |||
a52a1901c4 | |||
45aee607e3 | |||
c263016939 | |||
741915b1f8 | |||
6f2534ba82 | |||
43635071d9 | |||
22f06ecd4e | |||
7efe83eba8 | |||
a5b732e197 | |||
f404aaf768 | |||
e1f8177834 | |||
cff391a7a9 | |||
9cd7608a53 | |||
6950a06532 | |||
0e6c2ec556 | |||
479fc50d9a | |||
a29a8f7ed9 | |||
83cf637f9d | |||
5dbb4bf6be | |||
f1f227b746 | |||
b96cac16c6 | |||
f58fdafdcd | |||
b7b39f8284 | |||
7a173a6692 | |||
b042f98f0f | |||
0bb260bec9 | |||
024ab8ff69 | |||
50cb61f3bd | |||
f2befb7c48 | |||
dd80263490 | |||
73c44f8726 | |||
ca85095273 | |||
b5196aa541 | |||
b649ae0a1e | |||
4deb7c3270 | |||
b10da976c7 | |||
27a2e614a5 | |||
e7a931e9ba | |||
979bc31868 | |||
33cf4066d8 | |||
009444951a | |||
9e27e14692 | |||
bc00633135 | |||
9a7aa181ed | |||
6cc90cbeb4 | |||
59de852484 | |||
c9d5e9ac05 | |||
59ce3b5fc0 | |||
b71f9d0a08 | |||
55a4c3c08d | |||
c79db1dc99 | |||
a5bd27661b | |||
9a0d0a7124 | |||
454939adad | |||
37c02d2539 | |||
5f8407b4b1 | |||
abeb10cc8c | |||
4dca81403b | |||
6ba6a34df2 | |||
6d14fe9c30 | |||
c0c4637c77 | |||
752d34f603 | |||
0009ed0921 | |||
953de22233 | |||
1477aef39c | |||
2cca8f82e1 | |||
9094af025d | |||
0ba608bd1e | |||
67f5d497d5 | |||
3cc614c024 | |||
7c20bab1c5 | |||
a2a3f43fd0 | |||
9f17e3e1f8 | |||
c1a2fc22f4 | |||
f1f19369a3 | |||
e49f25af09 | |||
bc28571174 | |||
2beae1dcd3 | |||
39a0b1e29e | |||
0f603ffb0a | |||
8fe5835e09 | |||
5fed7a3a0c | |||
28ea694791 | |||
45b0991841 | |||
505d9904af | |||
da385f9295 | |||
214de77a41 | |||
7b2db3755e | |||
44791cc9f3 | |||
a14b94c96f | |||
db1cf5c2ce | |||
1a060a6c7b | |||
ff719fbe2d | |||
94e9ab7f67 | |||
06a96e8b77 | |||
d43c3dc968 | |||
e3c5efa929 | |||
8db9d93d23 | |||
517bb94b8b | |||
c804f55d82 | |||
c96a25c9b9 | |||
4c57726be7 | |||
8f723d7131 | |||
a7e10c0fb9 | |||
15e73e1cad | |||
a17192ee99 | |||
27200d1fb0 | |||
9ddceae824 | |||
d1961e0938 | |||
033432d6fb | |||
c98f0ba55b | |||
76993d2532 | |||
62e3f2d8e1 | |||
9806cab090 | |||
0d9fbe2d41 | |||
016db76306 | |||
d469084596 | |||
bb4decd522 | |||
caca8e81c2 | |||
bae08b6966 | |||
212a816598 | |||
1fd9cb5e2a | |||
6d3ea65e03 | |||
7d8c3c1c81 | |||
47ddbff817 | |||
783132a012 | |||
f456d62d3c | |||
8b4e572e16 | |||
51ef3ec656 | |||
35a3618a42 | |||
aa59725176 | |||
a544cfd4f0 | |||
61b37f87dc | |||
42b3b9eec0 | |||
0c4a9a2e70 | |||
6f7619a11d |
@ -1,6 +1,6 @@
|
||||
# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
**/[Bb]in/
|
||||
**/[Oo]bj/
|
||||
node_modules/
|
||||
dist/
|
||||
|
||||
@ -121,4 +121,4 @@ bower_components
|
||||
output
|
||||
|
||||
.vs
|
||||
BTCPayServer.Tests/
|
||||
**/launchSettings.json
|
||||
|
149
.editorconfig
Normal file
149
.editorconfig
Normal file
@ -0,0 +1,149 @@
|
||||
# editorconfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Default settings:
|
||||
# A newline ending every file
|
||||
# Use 4 spaces as indentation
|
||||
[*]
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[project.json]
|
||||
indent_size = 2
|
||||
|
||||
# C# files
|
||||
[*.cs]
|
||||
# New line preferences
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
csharp_new_line_within_query_expression_clauses = true
|
||||
|
||||
# Indentation preferences
|
||||
csharp_indent_block_contents = true
|
||||
csharp_indent_braces = false
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_switch_labels = true
|
||||
csharp_indent_labels = flush_left
|
||||
|
||||
# avoid this. unless absolutely necessary
|
||||
dotnet_style_qualification_for_field = false:suggestion
|
||||
dotnet_style_qualification_for_property = false:suggestion
|
||||
dotnet_style_qualification_for_method = false:suggestion
|
||||
dotnet_style_qualification_for_event = false:suggestion
|
||||
|
||||
# only use var when it's obvious what the variable type is
|
||||
csharp_style_var_for_built_in_types = false:none
|
||||
csharp_style_var_when_type_is_apparent = false:none
|
||||
csharp_style_var_elsewhere = false:suggestion
|
||||
|
||||
# use language keywords instead of BCL types
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
|
||||
# name all constant fields using PascalCase
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
|
||||
|
||||
dotnet_naming_symbols.constant_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.constant_fields.required_modifiers = const
|
||||
|
||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
||||
|
||||
# internal and private fields should be _camelCase
|
||||
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
|
||||
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
|
||||
dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
|
||||
|
||||
dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
|
||||
|
||||
dotnet_naming_style.camel_case_underscore_style.required_prefix = _
|
||||
dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
|
||||
|
||||
# Code style defaults
|
||||
dotnet_sort_system_directives_first = true
|
||||
csharp_preserve_single_line_blocks = true
|
||||
csharp_preserve_single_line_statements = false
|
||||
|
||||
# Expression-level preferences
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
|
||||
# Expression-bodied members
|
||||
csharp_style_expression_bodied_methods = false:none
|
||||
csharp_style_expression_bodied_constructors = false:none
|
||||
csharp_style_expression_bodied_operators = false:none
|
||||
csharp_style_expression_bodied_properties = true:none
|
||||
csharp_style_expression_bodied_indexers = true:none
|
||||
csharp_style_expression_bodied_accessors = true:none
|
||||
|
||||
# Pattern matching
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
|
||||
# Null checking preferences
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_after_comma = true
|
||||
csharp_space_after_dot = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_after_semicolon_in_for_statement = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_around_declaration_statements = do_not_ignore
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_before_comma = false
|
||||
csharp_space_before_dot = false
|
||||
csharp_space_before_open_square_brackets = false
|
||||
csharp_space_before_semicolon_in_for_statement = false
|
||||
csharp_space_between_empty_square_brackets = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_between_square_brackets = false
|
||||
|
||||
# C++ Files
|
||||
[*.{cpp,h,in}]
|
||||
curly_bracket_next_line = true
|
||||
indent_brace_style = Allman
|
||||
|
||||
# Xml project files
|
||||
[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
|
||||
indent_size = 2
|
||||
|
||||
# Xml build files
|
||||
[*.builds]
|
||||
indent_size = 2
|
||||
|
||||
# Xml files
|
||||
[*.{xml,stylecop,resx,ruleset}]
|
||||
indent_size = 2
|
||||
|
||||
# Xml config files
|
||||
[*.{props,targets,config,nuspec}]
|
||||
indent_size = 2
|
||||
|
||||
# Shell scripts
|
||||
[*.sh]
|
||||
end_of_line = lf
|
||||
[*.{cmd, bat}]
|
||||
end_of_line = crlf
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -47,7 +47,6 @@ dlldata.c
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
**/Properties/launchSettings.json
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
|
@ -7,14 +7,22 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0-preview-20170720-02" />
|
||||
<PackageReference Include="NBitcoin.TestFramework" Version="1.4.4" />
|
||||
<PackageReference Include="xunit" Version="2.2.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.3.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BTCPayServer\BTCPayServer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update=".dockerignore">
|
||||
<DependentUpon>Dockerfile</DependentUpon>
|
||||
</None>
|
||||
<None Update="docker-compose.yml">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,6 +1,6 @@
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Tests.Mocks;
|
||||
@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Tests;
|
||||
using NBXplorer;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using System;
|
||||
@ -31,110 +30,134 @@ using Xunit;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class BTCPayServerTester : IDisposable
|
||||
{
|
||||
private string _Directory;
|
||||
public class BTCPayServerTester : IDisposable
|
||||
{
|
||||
private string _Directory;
|
||||
|
||||
public BTCPayServerTester(string scope)
|
||||
{
|
||||
this._Directory = scope ?? throw new ArgumentNullException(nameof(scope));
|
||||
}
|
||||
public BTCPayServerTester(string scope)
|
||||
{
|
||||
this._Directory = scope ?? throw new ArgumentNullException(nameof(scope));
|
||||
}
|
||||
|
||||
public Uri NBXplorerUri
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string CookieFile
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public Uri ServerUri
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public Uri NBXplorerUri
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string CookieFile
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public Uri ServerUri
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public ExtKey HDPrivateKey
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public ExtKey HDPrivateKey
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
IWebHost _Host;
|
||||
public void Start()
|
||||
{
|
||||
if(!Directory.Exists(_Directory))
|
||||
Directory.CreateDirectory(_Directory);
|
||||
public string Postgres
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
HDPrivateKey = new ExtKey();
|
||||
var port = Utils.FreeTcpPort();
|
||||
StringBuilder config = new StringBuilder();
|
||||
config.AppendLine($"regtest=1");
|
||||
config.AppendLine($"port={port}");
|
||||
config.AppendLine($"explorer.url={NBXplorerUri.AbsoluteUri}");
|
||||
config.AppendLine($"explorer.cookiefile={CookieFile}");
|
||||
config.AppendLine($"hdpubkey={HDPrivateKey.Neuter().ToString(Network.RegTest)}");
|
||||
File.WriteAllText(Path.Combine(_Directory, "settings.config"), config.ToString());
|
||||
IWebHost _Host;
|
||||
public int Port
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
ServerUri = new Uri("http://127.0.0.1:" + port + "/");
|
||||
public void Start()
|
||||
{
|
||||
if (!Directory.Exists(_Directory))
|
||||
Directory.CreateDirectory(_Directory);
|
||||
|
||||
var conf = new DefaultConfiguration() { Logger = Logs.LogProvider.CreateLogger("Console") }.CreateConfiguration(new[] { "--datadir", _Directory });
|
||||
HDPrivateKey = new ExtKey();
|
||||
StringBuilder config = new StringBuilder();
|
||||
config.AppendLine($"regtest=1");
|
||||
config.AppendLine($"port={Port}");
|
||||
config.AppendLine($"explorer.url={NBXplorerUri.AbsoluteUri}");
|
||||
config.AppendLine($"explorer.cookiefile={CookieFile}");
|
||||
config.AppendLine($"hdpubkey={HDPrivateKey.Neuter().ToString(Network.RegTest)}");
|
||||
if (Postgres != null)
|
||||
config.AppendLine($"postgres=" + Postgres);
|
||||
File.WriteAllText(Path.Combine(_Directory, "settings.config"), config.ToString());
|
||||
|
||||
_Host = new WebHostBuilder()
|
||||
.UseConfiguration(conf)
|
||||
.ConfigureServices(s =>
|
||||
{
|
||||
s.AddSingleton<IRateProvider>(new MockRateProvider(new Rate("USD", 5000m)));
|
||||
s.AddLogging(l =>
|
||||
{
|
||||
l.SetMinimumLevel(LogLevel.Information)
|
||||
.AddFilter("Microsoft", LogLevel.Error)
|
||||
.AddProvider(Logs.LogProvider);
|
||||
});
|
||||
})
|
||||
.UseKestrel()
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
_Host.Start();
|
||||
Runtime = (BTCPayServerRuntime)_Host.Services.GetService(typeof(BTCPayServerRuntime));
|
||||
var watcher = (InvoiceWatcher)_Host.Services.GetService(typeof(InvoiceWatcher));
|
||||
watcher.PollInterval = TimeSpan.FromMilliseconds(50);
|
||||
}
|
||||
ServerUri = new Uri("http://" + HostName + ":" + Port + "/");
|
||||
|
||||
public BTCPayServerRuntime Runtime
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
var conf = new DefaultConfiguration() { Logger = Logs.LogProvider.CreateLogger("Console") }.CreateConfiguration(new[] { "--datadir", _Directory });
|
||||
|
||||
public T GetController<T>(string userId = null) where T : Controller
|
||||
{
|
||||
var context = new DefaultHttpContext();
|
||||
context.Request.Host = new HostString("127.0.0.1");
|
||||
context.Request.Scheme = "http";
|
||||
context.Request.Protocol = "http";
|
||||
if(userId != null)
|
||||
{
|
||||
context.User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, userId) }));
|
||||
}
|
||||
var scope = (IServiceScopeFactory)_Host.Services.GetService(typeof(IServiceScopeFactory));
|
||||
var provider = scope.CreateScope().ServiceProvider;
|
||||
context.RequestServices = provider;
|
||||
_Host = new WebHostBuilder()
|
||||
.UseConfiguration(conf)
|
||||
.ConfigureServices(s =>
|
||||
{
|
||||
s.AddSingleton<IRateProvider>(new MockRateProvider(new Rate("USD", 5000m)));
|
||||
s.AddLogging(l =>
|
||||
{
|
||||
l.SetMinimumLevel(LogLevel.Information)
|
||||
.AddFilter("Microsoft", LogLevel.Error)
|
||||
.AddFilter("Hangfire", LogLevel.Error)
|
||||
.AddProvider(Logs.LogProvider);
|
||||
});
|
||||
})
|
||||
.UseKestrel()
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
_Host.Start();
|
||||
InvoiceRepository = (InvoiceRepository)_Host.Services.GetService(typeof(InvoiceRepository));
|
||||
|
||||
var httpAccessor = provider.GetRequiredService<IHttpContextAccessor>();
|
||||
httpAccessor.HttpContext = context;
|
||||
var waiter = ((NBXplorerWaiterAccessor)_Host.Services.GetService(typeof(NBXplorerWaiterAccessor))).Instance;
|
||||
while(waiter.State != NBXplorerState.Ready)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
public string HostName
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
public InvoiceRepository InvoiceRepository { get; private set; }
|
||||
|
||||
var controller = (T)ActivatorUtilities.CreateInstance(provider, typeof(T));
|
||||
controller.Url = new UrlHelperMock();
|
||||
controller.ControllerContext = new ControllerContext()
|
||||
{
|
||||
HttpContext = context
|
||||
};
|
||||
return controller;
|
||||
}
|
||||
public T GetService<T>()
|
||||
{
|
||||
return _Host.Services.GetRequiredService<T>();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if(_Host != null)
|
||||
_Host.Dispose();
|
||||
}
|
||||
}
|
||||
public T GetController<T>(string userId = null) where T : Controller
|
||||
{
|
||||
var context = new DefaultHttpContext();
|
||||
context.Request.Host = new HostString("127.0.0.1");
|
||||
context.Request.Scheme = "http";
|
||||
context.Request.Protocol = "http";
|
||||
if (userId != null)
|
||||
{
|
||||
context.User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, userId) }));
|
||||
}
|
||||
var scope = (IServiceScopeFactory)_Host.Services.GetService(typeof(IServiceScopeFactory));
|
||||
var provider = scope.CreateScope().ServiceProvider;
|
||||
context.RequestServices = provider;
|
||||
|
||||
var httpAccessor = provider.GetRequiredService<IHttpContextAccessor>();
|
||||
httpAccessor.HttpContext = context;
|
||||
|
||||
var controller = (T)ActivatorUtilities.CreateInstance(provider, typeof(T));
|
||||
|
||||
controller.Url = new UrlHelperMock(new Uri($"http://{HostName}:{Port}/"));
|
||||
controller.ControllerContext = new ControllerContext()
|
||||
{
|
||||
HttpContext = context
|
||||
};
|
||||
return controller;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_Host != null)
|
||||
_Host.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,65 +11,65 @@ using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class CustomServer : IDisposable
|
||||
{
|
||||
TaskCompletionSource<bool> _Evt = null;
|
||||
IWebHost _Host = null;
|
||||
CancellationTokenSource _Closed = new CancellationTokenSource();
|
||||
public CustomServer()
|
||||
{
|
||||
var port = Utils.FreeTcpPort();
|
||||
_Host = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.Run(req =>
|
||||
{
|
||||
while(_Act == null)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
_Closed.Token.ThrowIfCancellationRequested();
|
||||
}
|
||||
_Act(req);
|
||||
_Act = null;
|
||||
_Evt.TrySetResult(true);
|
||||
req.Response.StatusCode = 200;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
})
|
||||
.UseKestrel()
|
||||
.UseUrls("http://127.0.0.1:" + port)
|
||||
.Build();
|
||||
_Host.Start();
|
||||
}
|
||||
public class CustomServer : IDisposable
|
||||
{
|
||||
TaskCompletionSource<bool> _Evt = null;
|
||||
IWebHost _Host = null;
|
||||
CancellationTokenSource _Closed = new CancellationTokenSource();
|
||||
public CustomServer()
|
||||
{
|
||||
var port = Utils.FreeTcpPort();
|
||||
_Host = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.Run(req =>
|
||||
{
|
||||
while (_Act == null)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
_Closed.Token.ThrowIfCancellationRequested();
|
||||
}
|
||||
_Act(req);
|
||||
_Act = null;
|
||||
_Evt.TrySetResult(true);
|
||||
req.Response.StatusCode = 200;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
})
|
||||
.UseKestrel()
|
||||
.UseUrls("http://127.0.0.1:" + port)
|
||||
.Build();
|
||||
_Host.Start();
|
||||
}
|
||||
|
||||
public Uri GetUri()
|
||||
{
|
||||
return new Uri(_Host.ServerFeatures.Get<IServerAddressesFeature>().Addresses.First());
|
||||
}
|
||||
public Uri GetUri()
|
||||
{
|
||||
return new Uri(_Host.ServerFeatures.Get<IServerAddressesFeature>().Addresses.First());
|
||||
}
|
||||
|
||||
Action<HttpContext> _Act;
|
||||
public void ProcessNextRequest(Action<HttpContext> act)
|
||||
{
|
||||
var source = new TaskCompletionSource<bool>();
|
||||
CancellationTokenSource cancellation = new CancellationTokenSource(20000);
|
||||
cancellation.Token.Register(() => source.TrySetCanceled());
|
||||
source = new TaskCompletionSource<bool>();
|
||||
_Evt = source;
|
||||
_Act = act;
|
||||
try
|
||||
{
|
||||
_Evt.Task.GetAwaiter().GetResult();
|
||||
}
|
||||
catch(TaskCanceledException)
|
||||
{
|
||||
throw new Xunit.Sdk.XunitException("Callback to the webserver was expected, check if the callback url is accessible from internet");
|
||||
}
|
||||
}
|
||||
Action<HttpContext> _Act;
|
||||
public void ProcessNextRequest(Action<HttpContext> act)
|
||||
{
|
||||
var source = new TaskCompletionSource<bool>();
|
||||
CancellationTokenSource cancellation = new CancellationTokenSource(20000);
|
||||
cancellation.Token.Register(() => source.TrySetCanceled());
|
||||
source = new TaskCompletionSource<bool>();
|
||||
_Evt = source;
|
||||
_Act = act;
|
||||
try
|
||||
{
|
||||
_Evt.Task.GetAwaiter().GetResult();
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
throw new Xunit.Sdk.XunitException("Callback to the webserver was expected, check if the callback url is accessible from internet");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_Closed.Cancel();
|
||||
_Host.Dispose();
|
||||
}
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
_Closed.Cancel();
|
||||
_Host.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
12
BTCPayServer.Tests/Dockerfile
Normal file
12
BTCPayServer.Tests/Dockerfile
Normal file
@ -0,0 +1,12 @@
|
||||
FROM microsoft/dotnet:2.0.0-sdk
|
||||
WORKDIR /app
|
||||
# caches restore result by copying csproj file separately
|
||||
COPY BTCPayServer.Tests/BTCPayServer.Tests.csproj BTCPayServer.Tests/BTCPayServer.Tests.csproj
|
||||
COPY BTCPayServer/BTCPayServer.csproj BTCPayServer/BTCPayServer.csproj
|
||||
|
||||
WORKDIR /app/BTCPayServer.Tests
|
||||
RUN dotnet restore
|
||||
# copies the rest of your code
|
||||
COPY . ../.
|
||||
|
||||
ENTRYPOINT ["dotnet", "test"]
|
37
BTCPayServer.Tests/EclairTester.cs
Normal file
37
BTCPayServer.Tests/EclairTester.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Eclair;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class EclairTester
|
||||
{
|
||||
ServerTester parent;
|
||||
public EclairTester(ServerTester parent, string environmentName, string defaultRPC, string defaultHost)
|
||||
{
|
||||
this.parent = parent;
|
||||
RPC = new EclairRPCClient(new Uri(parent.GetEnvironment(environmentName, defaultRPC)), parent.Network);
|
||||
P2PHost = parent.GetEnvironment(environmentName + "_HOST", defaultHost);
|
||||
}
|
||||
|
||||
public EclairRPCClient RPC { get; }
|
||||
public string P2PHost { get; }
|
||||
|
||||
NodeInfo _NodeInfo;
|
||||
public async Task<NodeInfo> GetNodeInfoAsync()
|
||||
{
|
||||
if (_NodeInfo != null)
|
||||
return _NodeInfo;
|
||||
var info = await RPC.GetInfoAsync();
|
||||
_NodeInfo = new NodeInfo(info.NodeId, P2PHost, info.Port);
|
||||
return _NodeInfo;
|
||||
}
|
||||
|
||||
public NodeInfo GetNodeInfo()
|
||||
{
|
||||
return GetNodeInfoAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
@ -6,84 +6,84 @@ using Xunit.Abstractions;
|
||||
|
||||
namespace BTCPayServer.Tests.Logging
|
||||
{
|
||||
public interface ILog
|
||||
{
|
||||
void LogInformation(string msg);
|
||||
}
|
||||
public interface ILog
|
||||
{
|
||||
void LogInformation(string msg);
|
||||
}
|
||||
|
||||
public class XUnitLogProvider : ILoggerProvider
|
||||
{
|
||||
ITestOutputHelper _Helper;
|
||||
public XUnitLogProvider(ITestOutputHelper helper)
|
||||
{
|
||||
_Helper = helper;
|
||||
}
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return new XUnitLog(_Helper) { Name = categoryName };
|
||||
}
|
||||
public class XUnitLogProvider : ILoggerProvider
|
||||
{
|
||||
ITestOutputHelper _Helper;
|
||||
public XUnitLogProvider(ITestOutputHelper helper)
|
||||
{
|
||||
_Helper = helper;
|
||||
}
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return new XUnitLog(_Helper) { Name = categoryName };
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
public class XUnitLog : ILog, ILogger, IDisposable
|
||||
{
|
||||
ITestOutputHelper _Helper;
|
||||
public XUnitLog(ITestOutputHelper helper)
|
||||
{
|
||||
_Helper = helper;
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
public string Name
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
||||
public class XUnitLog : ILog, ILogger, IDisposable
|
||||
{
|
||||
ITestOutputHelper _Helper;
|
||||
public XUnitLog(ITestOutputHelper helper)
|
||||
{
|
||||
_Helper = helper;
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
public string Name
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
public IDisposable BeginScope<TState>(TState state)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.Append(formatter(state, exception));
|
||||
if(exception != null)
|
||||
{
|
||||
builder.AppendLine();
|
||||
builder.Append(exception.ToString());
|
||||
}
|
||||
LogInformation(builder.ToString());
|
||||
}
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void LogInformation(string msg)
|
||||
{
|
||||
if(msg != null)
|
||||
_Helper.WriteLine(Name + ": " + msg);
|
||||
}
|
||||
}
|
||||
public class Logs
|
||||
{
|
||||
public static ILog Tester
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public static XUnitLogProvider LogProvider
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.Append(formatter(state, exception));
|
||||
if (exception != null)
|
||||
{
|
||||
builder.AppendLine();
|
||||
builder.Append(exception.ToString());
|
||||
}
|
||||
LogInformation(builder.ToString());
|
||||
}
|
||||
|
||||
public void LogInformation(string msg)
|
||||
{
|
||||
if (msg != null)
|
||||
_Helper.WriteLine(DateTimeOffset.UtcNow + " :" + Name + ": " + msg);
|
||||
}
|
||||
}
|
||||
public class Logs
|
||||
{
|
||||
public static ILog Tester
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public static XUnitLogProvider LogProvider
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,33 +6,38 @@ using Microsoft.AspNetCore.Mvc.Routing;
|
||||
|
||||
namespace BTCPayServer.Tests.Mocks
|
||||
{
|
||||
public class UrlHelperMock : IUrlHelper
|
||||
{
|
||||
public ActionContext ActionContext => throw new NotImplementedException();
|
||||
public class UrlHelperMock : IUrlHelper
|
||||
{
|
||||
Uri _BaseUrl;
|
||||
public UrlHelperMock(Uri baseUrl)
|
||||
{
|
||||
_BaseUrl = baseUrl;
|
||||
}
|
||||
public ActionContext ActionContext => throw new NotImplementedException();
|
||||
|
||||
public string Action(UrlActionContext actionContext)
|
||||
{
|
||||
return "http://127.0.0.1/mock";
|
||||
}
|
||||
public string Action(UrlActionContext actionContext)
|
||||
{
|
||||
return $"{_BaseUrl}mock";
|
||||
}
|
||||
|
||||
public string Content(string contentPath)
|
||||
{
|
||||
return "http://127.0.0.1/mock";
|
||||
}
|
||||
public string Content(string contentPath)
|
||||
{
|
||||
return $"{_BaseUrl}{contentPath}";
|
||||
}
|
||||
|
||||
public bool IsLocalUrl(string url)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
public bool IsLocalUrl(string url)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public string Link(string routeName, object values)
|
||||
{
|
||||
return "http://127.0.0.1/mock";
|
||||
}
|
||||
public string Link(string routeName, object values)
|
||||
{
|
||||
return _BaseUrl.AbsoluteUri;
|
||||
}
|
||||
|
||||
public string RouteUrl(UrlRouteContext routeContext)
|
||||
{
|
||||
return "http://127.0.0.1/mock";
|
||||
}
|
||||
}
|
||||
public string RouteUrl(UrlRouteContext routeContext)
|
||||
{
|
||||
return _BaseUrl.AbsoluteUri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,112 +0,0 @@
|
||||
using NBitcoin;
|
||||
using NBitcoin.Tests;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Xunit;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class NBXplorerTester : IDisposable
|
||||
{
|
||||
private string _Directory;
|
||||
|
||||
public NBXplorerTester(string scope)
|
||||
{
|
||||
if(scope == null)
|
||||
throw new ArgumentNullException(nameof(scope));
|
||||
this._Directory = scope;
|
||||
}
|
||||
|
||||
Process _Process;
|
||||
|
||||
public CoreNode Node
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
ProcessLauncher launcher = new ProcessLauncher();
|
||||
launcher.GoTo("Repositories", true);
|
||||
if(!launcher.GoTo(new[] { "nxbplorer", "NBXplorer" }))
|
||||
{
|
||||
launcher.Run("git", "clone https://github.com/dgarage/NBXplorer nxbplorer");
|
||||
Assert.True(launcher.GoTo(new[] { "nxbplorer", "NBXplorer" }), "Could not clone nxbplorer");
|
||||
}
|
||||
launcher.PushDirectory();
|
||||
if(!launcher.GoTo(new[] { "bin", "Release", "netcoreapp2.0" }) || !launcher.Exists("NBXplorer.dll"))
|
||||
{
|
||||
launcher.PopDirectory();
|
||||
launcher.Run("git", "pull");
|
||||
launcher.Run("git", "checkout master");
|
||||
try
|
||||
{
|
||||
//Need to do that or VS insist in adding this repo as submodules :/
|
||||
Utils.DeleteDirectory(Path.GetFullPath(Path.Combine(launcher.CurrentDirectory, "..", ".git")));
|
||||
}
|
||||
catch { }
|
||||
|
||||
launcher.Run("dotnet", "build /p:Configuration=Release");
|
||||
Assert.True(launcher.GoTo(new[] { "bin", "Release", "netcoreapp2.0" }), "Could not build NBXplorer");
|
||||
|
||||
launcher.AssertExists("NBXplorer.dll");
|
||||
}
|
||||
|
||||
var port = Utils.FreeTcpPort();
|
||||
var launcher2 = new ProcessLauncher();
|
||||
launcher2.GoTo(_Directory, true);
|
||||
launcher2.GoTo("nbxplorer-datadir", true);
|
||||
StringBuilder config = new StringBuilder();
|
||||
config.AppendLine($"regtest=1");
|
||||
config.AppendLine($"port={port}");
|
||||
config.AppendLine($"rpc.url={Node.RPCUri.AbsoluteUri}");
|
||||
config.AppendLine($"rpc.auth={Node.AuthenticationString}");
|
||||
config.AppendLine($"node.endpoint={Node.NodeEndpoint.Address}:{Node.NodeEndpoint.Port}");
|
||||
File.WriteAllText(Path.Combine(launcher2.CurrentDirectory, "settings.config"), config.ToString());
|
||||
_Process = launcher.Start("dotnet", $"NBXplorer.dll --datadir \"{launcher2.CurrentDirectory}\"");
|
||||
ExplorerClient = new NBXplorer.ExplorerClient(Node.Network, new Uri($"http://127.0.0.1:{port}/"));
|
||||
CookieFile = Path.Combine(launcher2.CurrentDirectory, ".cookie");
|
||||
File.Create(CookieFile).Close(); //Will be wipedout when the client starts
|
||||
ExplorerClient.SetCookieAuth(CookieFile);
|
||||
try
|
||||
{
|
||||
var cancellationSource = new CancellationTokenSource(10000);
|
||||
ExplorerClient.WaitServerStarted(cancellationSource.Token);
|
||||
}
|
||||
catch(OperationCanceledException)
|
||||
{
|
||||
Assert.False(_Process.HasExited, "NBXplorer failed to launch");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public NBXplorer.ExplorerClient ExplorerClient
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string CookieFile
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public static NBXplorerTester Create([CallerMemberNameAttribute] string scope = null)
|
||||
{
|
||||
return new NBXplorerTester(scope);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if(_Process != null && !_Process.HasExited)
|
||||
_Process.Kill();
|
||||
}
|
||||
}
|
||||
}
|
@ -8,105 +8,105 @@ using Xunit;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class ProcessLauncher
|
||||
{
|
||||
string _CurrentDirectory;
|
||||
public string CurrentDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
return _CurrentDirectory;
|
||||
}
|
||||
}
|
||||
public ProcessLauncher()
|
||||
{
|
||||
_CurrentDirectory = Directory.GetCurrentDirectory();
|
||||
}
|
||||
public bool GoTo(string[] directories, bool createIfNotExists = false)
|
||||
{
|
||||
var original = _CurrentDirectory;
|
||||
foreach(var dir in directories)
|
||||
{
|
||||
var newDirectory = Path.Combine(_CurrentDirectory, dir);
|
||||
if(!Directory.Exists(newDirectory))
|
||||
{
|
||||
if(createIfNotExists)
|
||||
Directory.CreateDirectory(newDirectory);
|
||||
else
|
||||
{
|
||||
_CurrentDirectory = original;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_CurrentDirectory = newDirectory;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public class ProcessLauncher
|
||||
{
|
||||
string _CurrentDirectory;
|
||||
public string CurrentDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
return _CurrentDirectory;
|
||||
}
|
||||
}
|
||||
public ProcessLauncher()
|
||||
{
|
||||
_CurrentDirectory = Directory.GetCurrentDirectory();
|
||||
}
|
||||
public bool GoTo(string[] directories, bool createIfNotExists = false)
|
||||
{
|
||||
var original = _CurrentDirectory;
|
||||
foreach (var dir in directories)
|
||||
{
|
||||
var newDirectory = Path.Combine(_CurrentDirectory, dir);
|
||||
if (!Directory.Exists(newDirectory))
|
||||
{
|
||||
if (createIfNotExists)
|
||||
Directory.CreateDirectory(newDirectory);
|
||||
else
|
||||
{
|
||||
_CurrentDirectory = original;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_CurrentDirectory = newDirectory;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Stack<string> _Directories = new Stack<string>();
|
||||
public void PushDirectory()
|
||||
{
|
||||
_Directories.Push(_CurrentDirectory);
|
||||
}
|
||||
Stack<string> _Directories = new Stack<string>();
|
||||
public void PushDirectory()
|
||||
{
|
||||
_Directories.Push(_CurrentDirectory);
|
||||
}
|
||||
|
||||
public void PopDirectory()
|
||||
{
|
||||
_CurrentDirectory = _Directories.Pop();
|
||||
}
|
||||
public void PopDirectory()
|
||||
{
|
||||
_CurrentDirectory = _Directories.Pop();
|
||||
}
|
||||
|
||||
public bool GoTo(string directory, bool createIfNotExists = false)
|
||||
{
|
||||
return GoTo(new[] { directory }, createIfNotExists);
|
||||
}
|
||||
public bool GoTo(string directory, bool createIfNotExists = false)
|
||||
{
|
||||
return GoTo(new[] { directory }, createIfNotExists);
|
||||
}
|
||||
|
||||
public void Run(string processName, string args)
|
||||
{
|
||||
Start(processName, args).WaitForExit();
|
||||
}
|
||||
public void Run(string processName, string args)
|
||||
{
|
||||
Start(processName, args).WaitForExit();
|
||||
}
|
||||
|
||||
public Process Start(string processName, string args)
|
||||
{
|
||||
Logs.Tester.LogInformation($"Running [{processName} {args}] from {_CurrentDirectory}");
|
||||
StringBuilder builder = new StringBuilder();
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
WorkingDirectory = _CurrentDirectory,
|
||||
FileName = processName,
|
||||
Arguments = args,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
}
|
||||
};
|
||||
try
|
||||
{
|
||||
process.OutputDataReceived += (s, e) =>
|
||||
{
|
||||
Logs.Tester.LogInformation(e.Data);
|
||||
};
|
||||
process.ErrorDataReceived += (s, e) =>
|
||||
{
|
||||
Logs.Tester.LogInformation(e.Data);
|
||||
};
|
||||
process.Start();
|
||||
process.BeginErrorReadLine();
|
||||
process.BeginOutputReadLine();
|
||||
}
|
||||
catch(Exception ex) { throw new Exception($"You need to install {processName} for this test (info : {ex.Message})"); }
|
||||
return process;
|
||||
}
|
||||
public Process Start(string processName, string args)
|
||||
{
|
||||
Logs.Tester.LogInformation($"Running [{processName} {args}] from {_CurrentDirectory}");
|
||||
StringBuilder builder = new StringBuilder();
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
WorkingDirectory = _CurrentDirectory,
|
||||
FileName = processName,
|
||||
Arguments = args,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
}
|
||||
};
|
||||
try
|
||||
{
|
||||
process.OutputDataReceived += (s, e) =>
|
||||
{
|
||||
Logs.Tester.LogInformation(e.Data);
|
||||
};
|
||||
process.ErrorDataReceived += (s, e) =>
|
||||
{
|
||||
Logs.Tester.LogInformation(e.Data);
|
||||
};
|
||||
process.Start();
|
||||
process.BeginErrorReadLine();
|
||||
process.BeginOutputReadLine();
|
||||
}
|
||||
catch (Exception ex) { throw new Exception($"You need to install {processName} for this test (info : {ex.Message})"); }
|
||||
return process;
|
||||
}
|
||||
|
||||
public void AssertExists(string file)
|
||||
{
|
||||
var path = Path.Combine(_CurrentDirectory, file);
|
||||
Assert.True(File.Exists(path), $"The file {path} should exist");
|
||||
}
|
||||
public void AssertExists(string file)
|
||||
{
|
||||
var path = Path.Combine(_CurrentDirectory, file);
|
||||
Assert.True(File.Exists(path), $"The file {path} should exist");
|
||||
}
|
||||
|
||||
public bool Exists(string file)
|
||||
{
|
||||
var path = Path.Combine(_CurrentDirectory, file);
|
||||
return File.Exists(path);
|
||||
}
|
||||
}
|
||||
public bool Exists(string file)
|
||||
{
|
||||
var path = Path.Combine(_CurrentDirectory, file);
|
||||
return File.Exists(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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"
|
||||
}
|
||||
}
|
||||
}
|
46
BTCPayServer.Tests/README.md
Normal file
46
BTCPayServer.Tests/README.md
Normal file
@ -0,0 +1,46 @@
|
||||
# How to run the tests
|
||||
|
||||
The tests depends on having a proper environment running with Postgres, Bitcoind, NBxplorer configured.
|
||||
You can however use the `docker-compose.yml` of this folder to get it running.
|
||||
|
||||
This is running a bitcoind instance on regtest, a private bitcoin blockchain for testing on which you can generate blocks yourself.
|
||||
|
||||
```
|
||||
docker-compose up nbxplorer
|
||||
```
|
||||
|
||||
You can run the tests while it is running through your favorite IDE, or with
|
||||
|
||||
```
|
||||
dotnet test
|
||||
```
|
||||
|
||||
Once you want to stop
|
||||
|
||||
```
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
If you want to stop, and remove all existing data
|
||||
|
||||
```
|
||||
docker-compose down -v
|
||||
```
|
||||
|
||||
You can run the tests inside a container by running
|
||||
|
||||
```
|
||||
docker-compose run --rm tests
|
||||
```
|
||||
|
||||
## Send commands to bitcoind
|
||||
|
||||
You can call bitcoin-cli inside the container with `docker exec`, for example, if you want to send `0.23111090` to `mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf`:
|
||||
```
|
||||
docker exec -ti btcpayserver_dev_bitcoind bitcoin-cli -regtest -conf="/data/bitcoin.conf" -datadir="/data" sendtoaddress "mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf" 0.23111090
|
||||
```
|
||||
|
||||
If you are using Powershell:
|
||||
```
|
||||
.\docker-bitcoin-cli.ps1 sendtoaddress "mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf" 0.23111090
|
||||
```
|
@ -1,89 +1,271 @@
|
||||
using BTCPayServer.Controllers;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Models.AccountViewModels;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Tests;
|
||||
using NBitcoin.RPC;
|
||||
using NBitpayClient;
|
||||
using NBXplorer;
|
||||
using NBXplorer.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using BTCPayServer.Eclair;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class ServerTester : IDisposable
|
||||
{
|
||||
public static ServerTester Create([CallerMemberNameAttribute]string scope = null)
|
||||
{
|
||||
return new ServerTester(scope);
|
||||
}
|
||||
public class ServerTester : IDisposable
|
||||
{
|
||||
public static ServerTester Create([CallerMemberNameAttribute]string scope = null)
|
||||
{
|
||||
return new ServerTester(scope);
|
||||
}
|
||||
|
||||
string _Directory;
|
||||
NodeBuilder _Builder;
|
||||
string _Directory;
|
||||
public ServerTester(string scope)
|
||||
{
|
||||
_Directory = scope;
|
||||
}
|
||||
|
||||
public ServerTester(string scope)
|
||||
{
|
||||
_Directory = scope;
|
||||
}
|
||||
public bool Dockerized
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if(Directory.Exists(_Directory))
|
||||
Utils.DeleteDirectory(_Directory);
|
||||
if(!Directory.Exists(_Directory))
|
||||
Directory.CreateDirectory(_Directory);
|
||||
_Builder = NodeBuilder.Create(_Directory, "0.14.2");
|
||||
ExplorerNode = _Builder.CreateNode(false);
|
||||
ExplorerNode.WhiteBind = true;
|
||||
ExplorerNode.Start();
|
||||
ExplorerNode.CreateRPCClient().Generate(101);
|
||||
ExplorerTester = NBXplorerTester.Create(Path.Combine(_Directory, "explorer"));
|
||||
ExplorerTester.Node = ExplorerNode;
|
||||
ExplorerTester.Start();
|
||||
public void Start()
|
||||
{
|
||||
if (Directory.Exists(_Directory))
|
||||
Utils.DeleteDirectory(_Directory);
|
||||
if (!Directory.Exists(_Directory))
|
||||
Directory.CreateDirectory(_Directory);
|
||||
|
||||
PayTester = new BTCPayServerTester(Path.Combine(_Directory, "pay"))
|
||||
{
|
||||
NBXplorerUri = ExplorerTester.ExplorerClient.Address,
|
||||
CookieFile = ExplorerTester.CookieFile
|
||||
};
|
||||
PayTester.Start();
|
||||
}
|
||||
|
||||
public TestAccount CreateAccount()
|
||||
{
|
||||
return new TestAccount(this);
|
||||
}
|
||||
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);
|
||||
ExplorerClient = new ExplorerClient(Network, new Uri(GetEnvironment("TESTS_NBXPLORERURL", "http://127.0.0.1:32838/")));
|
||||
PayTester = new BTCPayServerTester(Path.Combine(_Directory, "pay"))
|
||||
{
|
||||
NBXplorerUri = ExplorerClient.Address,
|
||||
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();
|
||||
|
||||
public CoreNode ExplorerNode
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
MerchantEclair = new EclairTester(this, "TEST_ECLAIR1", "http://127.0.0.1:30992/", "eclair1");
|
||||
CustomerEclair = new EclairTester(this, "TEST_ECLAIR2", "http://127.0.0.1:30993/", "eclair2");
|
||||
}
|
||||
|
||||
public BTCPayServerTester PayTester
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public NBXplorerTester ExplorerTester
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public Network Network
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = Network.RegTest;
|
||||
/// <summary>
|
||||
/// This will setup a channel going from customer to merchant
|
||||
/// </summary>
|
||||
public void PrepareLightning()
|
||||
{
|
||||
PrepareLightningAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if(PayTester != null)
|
||||
PayTester.Dispose();
|
||||
if(ExplorerTester != null)
|
||||
ExplorerTester.Dispose();
|
||||
if(_Builder != null)
|
||||
_Builder.Dispose();
|
||||
}
|
||||
}
|
||||
public async Task PrepareLightningAsync()
|
||||
{
|
||||
// Activate segwit
|
||||
var blockCount = ExplorerNode.GetBlockCountAsync();
|
||||
// Fetch node info, but that in cache
|
||||
var merchant = MerchantEclair.GetNodeInfoAsync();
|
||||
var customer = CustomerEclair.GetNodeInfoAsync();
|
||||
var channels = CustomerEclair.RPC.ChannelsAsync();
|
||||
var connect = CustomerEclair.RPC.ConnectAsync(merchant.Result);
|
||||
await Task.WhenAll(blockCount, merchant, customer, channels, connect);
|
||||
|
||||
// Mine until segwit is activated
|
||||
if (blockCount.Result <= 432)
|
||||
{
|
||||
ExplorerNode.Generate(433 - blockCount.Result);
|
||||
}
|
||||
}
|
||||
|
||||
public EclairTester MerchantEclair { get; set; }
|
||||
public EclairTester CustomerEclair { get; set; }
|
||||
|
||||
internal string GetEnvironment(string variable, string defaultValue)
|
||||
{
|
||||
var var = Environment.GetEnvironmentVariable(variable);
|
||||
return String.IsNullOrEmpty(var) ? defaultValue : var;
|
||||
}
|
||||
|
||||
public TestAccount NewAccount()
|
||||
{
|
||||
return new TestAccount(this);
|
||||
}
|
||||
|
||||
public bool FakeCallback
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public RPCClient ExplorerNode
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ExplorerClient ExplorerClient
|
||||
{
|
||||
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().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().GetAwaiter().GetResult();
|
||||
HttpRequestMessage message = new HttpRequestMessage();
|
||||
message.Method = HttpMethod.Post;
|
||||
message.RequestUri = uri;
|
||||
_Http.SendAsync(message).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public BTCPayServerTester PayTester
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Network Network
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = Network.RegTest;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (PayTester != null)
|
||||
PayTester.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Models.AccountViewModels;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
@ -10,60 +10,87 @@ using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class TestAccount
|
||||
{
|
||||
ServerTester parent;
|
||||
public TestAccount(ServerTester parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
BitPay = new Bitpay(new Key(), parent.PayTester.ServerUri);
|
||||
}
|
||||
ServerTester parent;
|
||||
public TestAccount(ServerTester parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
BitPay = new Bitpay(new Key(), parent.PayTester.ServerUri);
|
||||
}
|
||||
|
||||
public void GrantAccess()
|
||||
{
|
||||
GrantAccessAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
public async Task GrantAccessAsync()
|
||||
{
|
||||
var extKey = new ExtKey().GetWif(parent.Network);
|
||||
var pairingCode = BitPay.RequestClientAuthorization("test", Facade.Merchant);
|
||||
var account = parent.PayTester.GetController<AccountController>();
|
||||
await account.Register(new RegisterViewModel()
|
||||
{
|
||||
Email = "Bob@toto.com",
|
||||
ConfirmPassword = "Kitten0@",
|
||||
Password = "Kitten0@",
|
||||
});
|
||||
UserId = account.RegisteredUserId;
|
||||
public void GrantAccess()
|
||||
{
|
||||
GrantAccessAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
var store = parent.PayTester.GetController<StoresController>(account.RegisteredUserId);
|
||||
await store.CreateStore(new CreateStoreViewModel() { Name = "Test Store" });
|
||||
StoreId = store.CreatedStoreId;
|
||||
public void Register()
|
||||
{
|
||||
RegisterAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
await store.UpdateStore(StoreId, new StoreViewModel()
|
||||
{
|
||||
ExtPubKey = extKey.Neuter().ToString(),
|
||||
SpeedPolicy = SpeedPolicy.MediumSpeed
|
||||
});
|
||||
Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString()));
|
||||
await store.Pair(pairingCode.ToString(), StoreId);
|
||||
}
|
||||
public BitcoinExtKey ExtKey
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Bitpay BitPay
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string UserId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public async Task GrantAccessAsync()
|
||||
{
|
||||
await RegisterAsync();
|
||||
var store = await CreateStoreAsync();
|
||||
var pairingCode = BitPay.RequestClientAuthorization("test", Facade.Merchant);
|
||||
Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString()));
|
||||
await store.Pair(pairingCode.ToString(), StoreId);
|
||||
}
|
||||
public StoresController CreateStore()
|
||||
{
|
||||
return CreateStoreAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
public async Task<StoresController> CreateStoreAsync()
|
||||
{
|
||||
ExtKey = new ExtKey().GetWif(parent.Network);
|
||||
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()
|
||||
{
|
||||
DerivationScheme = DerivationScheme.ToString(),
|
||||
SpeedPolicy = SpeedPolicy.MediumSpeed
|
||||
}, "Save");
|
||||
return store;
|
||||
}
|
||||
|
||||
public string StoreId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
public DerivationStrategyBase DerivationScheme { get; set; }
|
||||
|
||||
private async Task RegisterAsync()
|
||||
{
|
||||
var account = parent.PayTester.GetController<AccountController>();
|
||||
await account.Register(new RegisterViewModel()
|
||||
{
|
||||
Email = Guid.NewGuid() + "@toto.com",
|
||||
ConfirmPassword = "Kitten0@",
|
||||
Password = "Kitten0@",
|
||||
});
|
||||
UserId = account.RegisteredUserId;
|
||||
}
|
||||
|
||||
public Bitpay BitPay
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string UserId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string StoreId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,283 +9,477 @@ using System.Threading;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Newtonsoft.Json;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using BTCPayServer.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using BTCPayServer.Authentication;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.EntityFrameworkCore.Extensions;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using BTCPayServer.Eclair;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class UnitTest1
|
||||
{
|
||||
public UnitTest1(ITestOutputHelper helper)
|
||||
{
|
||||
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
|
||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||
}
|
||||
public class UnitTest1
|
||||
{
|
||||
public UnitTest1(ITestOutputHelper helper)
|
||||
{
|
||||
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
|
||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanCalculateCryptoDue()
|
||||
{
|
||||
var entity = new InvoiceEntity();
|
||||
entity.TxFee = Money.Coins(0.1m);
|
||||
entity.Rate = 5000;
|
||||
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
||||
[Fact]
|
||||
public void CanCalculateCryptoDue()
|
||||
{
|
||||
var entity = new InvoiceEntity();
|
||||
entity.TxFee = Money.Coins(0.1m);
|
||||
entity.Rate = 5000;
|
||||
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
||||
|
||||
Assert.Equal(Money.Coins(1.1m), entity.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.1m), entity.GetTotalCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.1m), entity.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.1m), entity.GetTotalCryptoDue());
|
||||
|
||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.5m), new Key()) });
|
||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.5m), new Key()), Accounted = true });
|
||||
|
||||
//Since we need to spend one more txout, it should be 1.1 - 0,5 + 0.1
|
||||
Assert.Equal(Money.Coins(0.7m), entity.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.2m), entity.GetTotalCryptoDue());
|
||||
//Since we need to spend one more txout, it should be 1.1 - 0,5 + 0.1
|
||||
Assert.Equal(Money.Coins(0.7m), entity.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.2m), entity.GetTotalCryptoDue());
|
||||
|
||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()) });
|
||||
Assert.Equal(Money.Coins(0.6m), entity.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()), Accounted = true });
|
||||
Assert.Equal(Money.Coins(0.6m), entity.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
||||
|
||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.6m), new Key()) });
|
||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.6m), new Key()), Accounted = true });
|
||||
|
||||
Assert.Equal(Money.Zero, entity.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
||||
Assert.Equal(Money.Zero, entity.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
||||
|
||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()) });
|
||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()), Accounted = true });
|
||||
|
||||
Assert.Equal(Money.Zero, entity.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
||||
}
|
||||
Assert.Equal(Money.Zero, entity.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanPayUsingBIP70()
|
||||
{
|
||||
using(var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var user = tester.CreateAccount();
|
||||
user.GrantAccess();
|
||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Price = 5000.0,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
//RedirectURL = redirect + "redirect",
|
||||
//NotificationURL = CallbackUri + "/notification",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
[Fact]
|
||||
public void CanPayUsingBIP70()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Buyer = new Buyer() { email = "test@fwf.com" },
|
||||
Price = 5000.0,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
//RedirectURL = redirect + "redirect",
|
||||
//NotificationURL = CallbackUri + "/notification",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
Assert.False(invoice.Refundable);
|
||||
Assert.False(invoice.Refundable);
|
||||
|
||||
var url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP72);
|
||||
var request = url.GetPaymentRequest();
|
||||
var payment = request.CreatePayment();
|
||||
var url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP72);
|
||||
var request = url.GetPaymentRequest();
|
||||
var payment = request.CreatePayment();
|
||||
|
||||
Transaction tx = new Transaction();
|
||||
tx.Outputs.AddRange(request.Details.Outputs.Select(o => new TxOut(o.Amount, o.Script)));
|
||||
var cashCow = tester.ExplorerNode.CreateRPCClient();
|
||||
tx = cashCow.FundRawTransaction(tx).Transaction;
|
||||
tx = cashCow.SignRawTransaction(tx);
|
||||
Transaction tx = new Transaction();
|
||||
tx.Outputs.AddRange(request.Details.Outputs.Select(o => new TxOut(o.Amount, o.Script)));
|
||||
var cashCow = tester.ExplorerNode;
|
||||
tx = cashCow.FundRawTransaction(tx).Transaction;
|
||||
tx = cashCow.SignRawTransaction(tx);
|
||||
|
||||
payment.Transactions.Add(tx);
|
||||
payment.Transactions.Add(tx);
|
||||
|
||||
payment.RefundTo.Add(new PaymentOutput(Money.Coins(1.0m), new Key().ScriptPubKey));
|
||||
var ack = payment.SubmitPayment();
|
||||
Assert.NotNull(ack);
|
||||
payment.RefundTo.Add(new PaymentOutput(Money.Coins(1.0m), new Key().ScriptPubKey));
|
||||
var ack = payment.SubmitPayment();
|
||||
Assert.NotNull(ack);
|
||||
|
||||
Eventually(() =>
|
||||
{
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("paid", localInvoice.Status);
|
||||
Assert.True(localInvoice.Refundable);
|
||||
});
|
||||
}
|
||||
}
|
||||
Eventually(() =>
|
||||
{
|
||||
tester.SimulateCallback(url.Address);
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("paid", localInvoice.Status);
|
||||
Assert.True(localInvoice.Refundable);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanSendIPN()
|
||||
{
|
||||
using(var callbackServer = new CustomServer())
|
||||
{
|
||||
using(var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var acc = tester.CreateAccount();
|
||||
acc.GrantAccess();
|
||||
var invoice = acc.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Price = 5.0,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
NotificationURL = callbackServer.GetUri().AbsoluteUri,
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
});
|
||||
BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21);
|
||||
tester.ExplorerNode.CreateRPCClient().SendToAddress(url.Address, url.Amount);
|
||||
callbackServer.ProcessNextRequest((ctx) =>
|
||||
{
|
||||
var ipn = new StreamReader(ctx.Request.Body).ReadToEnd();
|
||||
JsonConvert.DeserializeObject<InvoicePaymentNotification>(ipn); //can deserialize
|
||||
});
|
||||
var invoice2 = acc.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.NotNull(invoice2);
|
||||
}
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
public void CanUseLightMoney()
|
||||
{
|
||||
var light = LightMoney.MilliSatoshis(1);
|
||||
Assert.Equal("0.00000000001", light.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvoiceFlowThroughDifferentStatesCorrectly()
|
||||
{
|
||||
using(var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var user = tester.CreateAccount();
|
||||
Assert.False(user.BitPay.TestAccess(Facade.Merchant));
|
||||
user.GrantAccess();
|
||||
Assert.True(user.BitPay.TestAccess(Facade.Merchant));
|
||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Price = 5000.0,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
//RedirectURL = redirect + "redirect",
|
||||
//NotificationURL = CallbackUri + "/notification",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
[Fact]
|
||||
public void CanSendLightningPayment()
|
||||
{
|
||||
|
||||
var textSearchResult = tester.PayTester.Runtime.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
StoreId = user.StoreId,
|
||||
TextSearch = invoice.OrderId
|
||||
}).GetAwaiter().GetResult();
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
tester.PrepareLightning();
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Equal(1, textSearchResult.Length);
|
||||
[Fact]
|
||||
public void CanUseServerInitiatedPairingCode()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var acc = tester.NewAccount();
|
||||
acc.Register();
|
||||
acc.CreateStore();
|
||||
|
||||
textSearchResult = tester.PayTester.Runtime.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
StoreId = user.StoreId,
|
||||
TextSearch = invoice.Id
|
||||
}).GetAwaiter().GetResult();
|
||||
var controller = tester.PayTester.GetController<StoresController>(acc.UserId);
|
||||
var token = (RedirectToActionResult)controller.CreateToken(acc.StoreId, new Models.StoreViewModels.CreateTokenViewModel()
|
||||
{
|
||||
Facade = Facade.Merchant.ToString(),
|
||||
Label = "bla",
|
||||
PublicKey = null
|
||||
}).GetAwaiter().GetResult();
|
||||
|
||||
Assert.Equal(1, textSearchResult.Length);
|
||||
var pairingCode = (string)token.RouteValues["pairingCode"];
|
||||
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal(Money.Coins(0), invoice.BtcPaid);
|
||||
Assert.Equal("new", invoice.Status);
|
||||
Assert.Equal(false, (bool)((JValue)invoice.ExceptionStatus).Value);
|
||||
acc.BitPay.AuthorizeClient(new PairingCode(pairingCode)).GetAwaiter().GetResult();
|
||||
Assert.True(acc.BitPay.TestAccess(Facade.Merchant));
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Equal(1, user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime).Length);
|
||||
Assert.Equal(0, user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime + TimeSpan.FromDays(1)).Length);
|
||||
Assert.Equal(1, user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime - TimeSpan.FromDays(5)).Length);
|
||||
Assert.Equal(1, user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime - TimeSpan.FromDays(5), invoice.InvoiceTime.DateTime).Length);
|
||||
Assert.Equal(0, user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime - TimeSpan.FromDays(5), invoice.InvoiceTime.DateTime - TimeSpan.FromDays(1)).Length);
|
||||
[Fact]
|
||||
public void CanSendIPN()
|
||||
{
|
||||
using (var callbackServer = new CustomServer())
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var acc = tester.NewAccount();
|
||||
acc.GrantAccess();
|
||||
var invoice = acc.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Price = 5.0,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
NotificationURL = callbackServer.GetUri().AbsoluteUri,
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
});
|
||||
BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21);
|
||||
tester.ExplorerNode.SendToAddress(url.Address, url.Amount);
|
||||
Thread.Sleep(5000);
|
||||
tester.SimulateCallback(url.Address);
|
||||
callbackServer.ProcessNextRequest((ctx) =>
|
||||
{
|
||||
var ipn = new StreamReader(ctx.Request.Body).ReadToEnd();
|
||||
JsonConvert.DeserializeObject<InvoicePaymentNotification>(ipn); //can deserialize
|
||||
});
|
||||
var invoice2 = acc.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.NotNull(invoice2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CantPairTwiceWithSamePubkey()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var acc = tester.NewAccount();
|
||||
acc.Register();
|
||||
var store = acc.CreateStore();
|
||||
var pairingCode = acc.BitPay.RequestClientAuthorization("test", Facade.Merchant);
|
||||
Assert.IsType<RedirectToActionResult>(store.Pair(pairingCode.ToString(), acc.StoreId).GetAwaiter().GetResult());
|
||||
|
||||
pairingCode = acc.BitPay.RequestClientAuthorization("test1", Facade.Merchant);
|
||||
var store2 = acc.CreateStore();
|
||||
store2.Pair(pairingCode.ToString(), store2.CreatedStoreId).GetAwaiter().GetResult();
|
||||
Assert.Contains(nameof(PairingResult.ReusedKey), store2.StatusMessage);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanRBFPayment()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Price = 5000.0,
|
||||
Currency = "USD"
|
||||
}, Facade.Merchant);
|
||||
|
||||
var payment1 = Money.Coins(0.04m);
|
||||
var payment2 = Money.Coins(0.08m);
|
||||
var tx1 = new uint256(tester.ExplorerNode.SendCommand("sendtoaddress", new object[]
|
||||
{
|
||||
invoice.BitcoinAddress.ToString(),
|
||||
payment1.ToString(),
|
||||
null, //comment
|
||||
null, //comment_to
|
||||
false, //subtractfeefromamount
|
||||
true, //replaceable
|
||||
}).ResultString);
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, tester.Network);
|
||||
|
||||
Eventually(() =>
|
||||
{
|
||||
tester.SimulateCallback(invoiceAddress);
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.Equal(payment1, invoice.BtcPaid);
|
||||
invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, tester.Network);
|
||||
});
|
||||
|
||||
var tx = tester.ExplorerNode.GetRawTransaction(new uint256(tx1));
|
||||
foreach (var input in tx.Inputs)
|
||||
{
|
||||
input.ScriptSig = Script.Empty; //Strip signatures
|
||||
}
|
||||
var change = tx.Outputs.First(o => o.Value != payment1);
|
||||
var output = tx.Outputs.First(o => o.Value == payment1);
|
||||
output.Value = payment2;
|
||||
output.ScriptPubKey = invoiceAddress.ScriptPubKey;
|
||||
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);
|
||||
Eventually(() =>
|
||||
{
|
||||
tester.SimulateCallback(invoiceAddress);
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.Equal(payment2, invoice.BtcPaid);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanParseFilter()
|
||||
{
|
||||
var filter = "storeid:abc status:abed blabhbalh ";
|
||||
var search = new SearchString(filter);
|
||||
Assert.Equal("storeid:abc status:abed blabhbalh", search.ToString());
|
||||
Assert.Equal("blabhbalh", search.TextSearch);
|
||||
Assert.Equal("abc", search.Filters["storeid"]);
|
||||
Assert.Equal("abed", search.Filters["status"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvoiceFlowThroughDifferentStatesCorrectly()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var user = tester.NewAccount();
|
||||
Assert.False(user.BitPay.TestAccess(Facade.Merchant));
|
||||
user.GrantAccess();
|
||||
Assert.True(user.BitPay.TestAccess(Facade.Merchant));
|
||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Price = 5000.0,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
//RedirectURL = redirect + "redirect",
|
||||
//NotificationURL = CallbackUri + "/notification",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
var repo = tester.PayTester.GetService<InvoiceRepository>();
|
||||
var ctx = tester.PayTester.GetService<ApplicationDbContextFactory>().CreateContext();
|
||||
|
||||
Eventually(() =>
|
||||
{
|
||||
var textSearchResult = tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
StoreId = user.StoreId,
|
||||
TextSearch = invoice.OrderId
|
||||
}).GetAwaiter().GetResult();
|
||||
Assert.Equal(1, textSearchResult.Length);
|
||||
textSearchResult = tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
StoreId = user.StoreId,
|
||||
TextSearch = invoice.Id
|
||||
}).GetAwaiter().GetResult();
|
||||
|
||||
Assert.Equal(1, textSearchResult.Length);
|
||||
});
|
||||
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal(Money.Coins(0), invoice.BtcPaid);
|
||||
Assert.Equal("new", invoice.Status);
|
||||
Assert.Equal(false, (bool)((JValue)invoice.ExceptionStatus).Value);
|
||||
|
||||
Assert.Equal(1, user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime).Length);
|
||||
Assert.Equal(0, user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime + TimeSpan.FromDays(1)).Length);
|
||||
Assert.Equal(1, user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime - TimeSpan.FromDays(5)).Length);
|
||||
Assert.Equal(1, user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime - TimeSpan.FromDays(5), invoice.InvoiceTime.DateTime).Length);
|
||||
Assert.Equal(0, user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime - TimeSpan.FromDays(5), invoice.InvoiceTime.DateTime - TimeSpan.FromDays(1)).Length);
|
||||
|
||||
|
||||
var firstPayment = Money.Coins(0.04m);
|
||||
var firstPayment = Money.Coins(0.04m);
|
||||
|
||||
var txFee = Money.Zero;
|
||||
var txFee = Money.Zero;
|
||||
|
||||
var rate = user.BitPay.GetRates();
|
||||
var rate = user.BitPay.GetRates();
|
||||
|
||||
var cashCow = tester.ExplorerNode.CreateRPCClient();
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||
var cashCow = tester.ExplorerNode;
|
||||
|
||||
Money secondPayment = Money.Zero;
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||
var iii = ctx.AddressInvoices.ToArray();
|
||||
Assert.True(IsMapped(invoice, ctx));
|
||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||
|
||||
Eventually(() =>
|
||||
{
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("paidPartial", localInvoice.Status);
|
||||
Assert.Equal(firstPayment, localInvoice.BtcPaid);
|
||||
txFee = localInvoice.BtcDue - invoice.BtcDue;
|
||||
Assert.Equal("paidPartial", localInvoice.ExceptionStatus);
|
||||
secondPayment = localInvoice.BtcDue;
|
||||
});
|
||||
var invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult();
|
||||
Assert.Equal(1, invoiceEntity.HistoricalAddresses.Length);
|
||||
Assert.Null(invoiceEntity.HistoricalAddresses[0].UnAssigned);
|
||||
|
||||
cashCow.SendToAddress(invoiceAddress, secondPayment);
|
||||
Money secondPayment = Money.Zero;
|
||||
|
||||
Eventually(() =>
|
||||
{
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("paid", localInvoice.Status);
|
||||
Assert.Equal(firstPayment + secondPayment, localInvoice.BtcPaid);
|
||||
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
||||
Assert.Equal(false, (bool)((JValue)localInvoice.ExceptionStatus).Value);
|
||||
});
|
||||
Eventually(() =>
|
||||
{
|
||||
tester.SimulateCallback(invoiceAddress);
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("new", localInvoice.Status);
|
||||
Assert.Equal(firstPayment, localInvoice.BtcPaid);
|
||||
txFee = localInvoice.BtcDue - invoice.BtcDue;
|
||||
Assert.Equal("paidPartial", localInvoice.ExceptionStatus);
|
||||
Assert.NotEqual(localInvoice.BitcoinAddress, invoice.BitcoinAddress); //New address
|
||||
Assert.True(IsMapped(invoice, ctx));
|
||||
Assert.True(IsMapped(localInvoice, ctx));
|
||||
|
||||
cashCow.Generate(1); //The user has medium speed settings, so 1 conf is enough to be confirmed
|
||||
invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult();
|
||||
var historical1 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.Address == invoice.BitcoinAddress.ToString());
|
||||
Assert.NotNull(historical1.UnAssigned);
|
||||
var historical2 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.Address == localInvoice.BitcoinAddress.ToString());
|
||||
Assert.Null(historical2.UnAssigned);
|
||||
invoiceAddress = BitcoinAddress.Create(localInvoice.BitcoinAddress, cashCow.Network);
|
||||
secondPayment = localInvoice.BtcDue;
|
||||
});
|
||||
|
||||
Eventually(() =>
|
||||
{
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("confirmed", localInvoice.Status);
|
||||
});
|
||||
cashCow.SendToAddress(invoiceAddress, secondPayment);
|
||||
|
||||
cashCow.Generate(5); //Now should be complete
|
||||
Eventually(() =>
|
||||
{
|
||||
tester.SimulateCallback(invoiceAddress);
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("paid", localInvoice.Status);
|
||||
Assert.Equal(firstPayment + secondPayment, localInvoice.BtcPaid);
|
||||
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
||||
Assert.Equal(localInvoice.BitcoinAddress, invoiceAddress.ToString()); //no new address generated
|
||||
Assert.True(IsMapped(localInvoice, ctx));
|
||||
Assert.False((bool)((JValue)localInvoice.ExceptionStatus).Value);
|
||||
});
|
||||
|
||||
Eventually(() =>
|
||||
{
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("complete", localInvoice.Status);
|
||||
});
|
||||
cashCow.Generate(1); //The user has medium speed settings, so 1 conf is enough to be confirmed
|
||||
|
||||
invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Price = 5000.0,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
//RedirectURL = redirect + "redirect",
|
||||
//NotificationURL = CallbackUri + "/notification",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||
Eventually(() =>
|
||||
{
|
||||
tester.SimulateCallback();
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("confirmed", localInvoice.Status);
|
||||
});
|
||||
|
||||
cashCow.SendToAddress(invoiceAddress, invoice.BtcDue + Money.Coins(1));
|
||||
cashCow.Generate(5); //Now should be complete
|
||||
|
||||
Eventually(() =>
|
||||
{
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("paidOver", localInvoice.Status);
|
||||
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
||||
Assert.Equal("paidOver", (string)((JValue)localInvoice.ExceptionStatus).Value);
|
||||
});
|
||||
Eventually(() =>
|
||||
{
|
||||
tester.SimulateCallback();
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("complete", localInvoice.Status);
|
||||
Assert.NotEqual(0.0, localInvoice.Rate);
|
||||
});
|
||||
|
||||
cashCow.Generate(1);
|
||||
invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Price = 5000.0,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
//RedirectURL = redirect + "redirect",
|
||||
//NotificationURL = CallbackUri + "/notification",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||
|
||||
Eventually(() =>
|
||||
{
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("confirmed", localInvoice.Status);
|
||||
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
||||
Assert.Equal("paidOver", (string)((JValue)localInvoice.ExceptionStatus).Value);
|
||||
});
|
||||
}
|
||||
}
|
||||
cashCow.SendToAddress(invoiceAddress, invoice.BtcDue + Money.Coins(1));
|
||||
|
||||
private void Eventually(Action act)
|
||||
{
|
||||
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
||||
while(true)
|
||||
{
|
||||
try
|
||||
{
|
||||
act();
|
||||
break;
|
||||
}
|
||||
catch(XunitException) when(!cts.Token.IsCancellationRequested)
|
||||
{
|
||||
cts.Token.WaitHandle.WaitOne(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Eventually(() =>
|
||||
{
|
||||
tester.SimulateCallback(invoiceAddress);
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("paid", localInvoice.Status);
|
||||
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
||||
Assert.Equal("paidOver", (string)((JValue)localInvoice.ExceptionStatus).Value);
|
||||
});
|
||||
|
||||
cashCow.Generate(1);
|
||||
|
||||
Eventually(() =>
|
||||
{
|
||||
tester.SimulateCallback();
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("confirmed", localInvoice.Status);
|
||||
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
||||
Assert.Equal("paidOver", (string)((JValue)localInvoice.ExceptionStatus).Value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckRatesProvider()
|
||||
{
|
||||
var coinAverage = new CoinAverageRateProvider();
|
||||
var jpy = coinAverage.GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||
var jpy2 = new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/"))).GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||
|
||||
var cached = new CachedRateProvider(coinAverage, new MemoryCache(new MemoryCacheOptions() { ExpirationScanFrequency = TimeSpan.FromSeconds(1.0) }));
|
||||
cached.CacheSpan = TimeSpan.FromSeconds(10);
|
||||
var a = cached.GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||
var b = cached.GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||
//Manually check that cache get hit after 10 sec
|
||||
var c = cached.GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx)
|
||||
{
|
||||
var h = BitcoinAddress.Create(invoice.BitcoinAddress).ScriptPubKey.Hash.ToString();
|
||||
return ctx.AddressInvoices.FirstOrDefault(i => i.InvoiceDataId == invoice.Id && i.Address == h) != null;
|
||||
}
|
||||
|
||||
private void Eventually(Action act)
|
||||
{
|
||||
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
act();
|
||||
break;
|
||||
}
|
||||
catch (XunitException) when (!cts.Token.IsCancellationRequested)
|
||||
{
|
||||
cts.Token.WaitHandle.WaitOne(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
50
BTCPayServer.Tests/UnitTestPeusa.cs
Normal file
50
BTCPayServer.Tests/UnitTestPeusa.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitpayClient;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Xunit;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
// Helper class for testing functionality and generating data needed during coding/debuging
|
||||
public class UnitTestPeusa
|
||||
{
|
||||
// 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);
|
||||
|
||||
// go to invoice.Url
|
||||
Console.WriteLine(invoice.Url);
|
||||
}
|
||||
|
||||
// Generating Extended public key to use on http://localhost:14142/stores/{storeId}
|
||||
[Fact]
|
||||
public void GeneratePubkey()
|
||||
{
|
||||
var network = Network.RegTest;
|
||||
|
||||
ExtKey masterKey = new ExtKey();
|
||||
Console.WriteLine("Master key : " + masterKey.ToString(network));
|
||||
ExtPubKey masterPubKey = masterKey.Neuter();
|
||||
|
||||
ExtPubKey pubkey = masterPubKey.Derive(0);
|
||||
Console.WriteLine("PubKey " + 0 + " : " + pubkey.ToString(network));
|
||||
}
|
||||
}
|
||||
}
|
@ -10,54 +10,54 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
public class Utils
|
||||
{
|
||||
public static int FreeTcpPort()
|
||||
{
|
||||
TcpListener l = new TcpListener(IPAddress.Loopback, 0);
|
||||
l.Start();
|
||||
int port = ((IPEndPoint)l.LocalEndpoint).Port;
|
||||
l.Stop();
|
||||
return port;
|
||||
}
|
||||
public static int FreeTcpPort()
|
||||
{
|
||||
TcpListener l = new TcpListener(IPAddress.Loopback, 0);
|
||||
l.Start();
|
||||
int port = ((IPEndPoint)l.LocalEndpoint).Port;
|
||||
l.Stop();
|
||||
return port;
|
||||
}
|
||||
|
||||
// http://stackoverflow.com/a/14933880/2061103
|
||||
public static void DeleteDirectory(string destinationDir)
|
||||
{
|
||||
const int magicDust = 10;
|
||||
for(var gnomes = 1; gnomes <= magicDust; gnomes++)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(destinationDir, true);
|
||||
}
|
||||
catch(DirectoryNotFoundException)
|
||||
{
|
||||
return; // good!
|
||||
}
|
||||
catch(IOException)
|
||||
{
|
||||
if(gnomes == magicDust)
|
||||
throw;
|
||||
// System.IO.IOException: The directory is not empty
|
||||
System.Diagnostics.Debug.WriteLine("Gnomes prevent deletion of {0}! Applying magic dust, attempt #{1}.", destinationDir, gnomes);
|
||||
// http://stackoverflow.com/a/14933880/2061103
|
||||
public static void DeleteDirectory(string destinationDir)
|
||||
{
|
||||
const int magicDust = 10;
|
||||
for (var gnomes = 1; gnomes <= magicDust; gnomes++)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(destinationDir, true);
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
return; // good!
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
if (gnomes == magicDust)
|
||||
throw;
|
||||
// System.IO.IOException: The directory is not empty
|
||||
System.Diagnostics.Debug.WriteLine("Gnomes prevent deletion of {0}! Applying magic dust, attempt #{1}.", destinationDir, gnomes);
|
||||
|
||||
// see http://stackoverflow.com/questions/329355/cannot-delete-directory-with-directory-deletepath-true for more magic
|
||||
Thread.Sleep(100);
|
||||
continue;
|
||||
}
|
||||
catch(UnauthorizedAccessException)
|
||||
{
|
||||
if(gnomes == magicDust)
|
||||
throw;
|
||||
// Wait, maybe another software make us authorized a little later
|
||||
System.Diagnostics.Debug.WriteLine("Gnomes prevent deletion of {0}! Applying magic dust, attempt #{1}.", destinationDir, gnomes);
|
||||
// see http://stackoverflow.com/questions/329355/cannot-delete-directory-with-directory-deletepath-true for more magic
|
||||
Thread.Sleep(100);
|
||||
continue;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
if (gnomes == magicDust)
|
||||
throw;
|
||||
// Wait, maybe another software make us authorized a little later
|
||||
System.Diagnostics.Debug.WriteLine("Gnomes prevent deletion of {0}! Applying magic dust, attempt #{1}.", destinationDir, gnomes);
|
||||
|
||||
// see http://stackoverflow.com/questions/329355/cannot-delete-directory-with-directory-deletepath-true for more magic
|
||||
Thread.Sleep(100);
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// depending on your use case, consider throwing an exception here
|
||||
}
|
||||
}
|
||||
// see http://stackoverflow.com/questions/329355/cannot-delete-directory-with-directory-deletepath-true for more magic
|
||||
Thread.Sleep(100);
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// depending on your use case, consider throwing an exception here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1
BTCPayServer.Tests/docker-bitcoin-cli.ps1
Normal file
1
BTCPayServer.Tests/docker-bitcoin-cli.ps1
Normal file
@ -0,0 +1 @@
|
||||
docker exec -ti btcpayserver_dev_bitcoind bitcoin-cli -regtest -conf="/data/bitcoin.conf" -datadir="/data" $args
|
132
BTCPayServer.Tests/docker-compose.yml
Normal file
132
BTCPayServer.Tests/docker-compose.yml
Normal file
@ -0,0 +1,132 @@
|
||||
version: "3"
|
||||
|
||||
# Run `docker-compose up dev` for bootstrapping your development environment
|
||||
# Doing so will expose eclair API, NBXplorer, Bitcoind RPC and postgres port to the host so that tests can Run,
|
||||
# The Visual Studio launch setting `Docker-Regtest` is configured to use this environment.
|
||||
services:
|
||||
|
||||
tests:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: BTCPayServer.Tests/Dockerfile
|
||||
environment:
|
||||
TESTS_RPCCONNECTION: server=http://bitcoind:43782;ceiwHEbqWI83:DwubwWsoo3
|
||||
TESTS_NBXPLORERURL: http://nbxplorer:32838/
|
||||
TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver
|
||||
TESTS_FAKECALLBACK: 'false'
|
||||
TESTS_PORT: 80
|
||||
TESTS_HOSTNAME: tests
|
||||
TEST_ECLAIR1: http://eclair1:8080/
|
||||
TEST_ECLAIR2: http://eclair2:8080/
|
||||
expose:
|
||||
- "80"
|
||||
links:
|
||||
- bitcoind
|
||||
- nbxplorer
|
||||
- eclair1
|
||||
- eclair2
|
||||
extra_hosts:
|
||||
- "tests:127.0.0.1"
|
||||
|
||||
# The dev container is not actually used, it is just handy to run `docker-compose up dev` to start all services
|
||||
dev:
|
||||
image: nicolasdorier/docker-bitcoin:0.15.0.1
|
||||
environment:
|
||||
BITCOIN_EXTRA_ARGS: |
|
||||
regtest=1
|
||||
connect=bitcoind:39388
|
||||
links:
|
||||
- bitcoind
|
||||
- nbxplorer
|
||||
- eclair1
|
||||
- eclair2
|
||||
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:1.0.0.32
|
||||
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_BIND: 0.0.0.0:32838
|
||||
NBXPLORER_VERBOSE: 1
|
||||
NBXPLORER_NOAUTH: 1
|
||||
links:
|
||||
- bitcoind
|
||||
- postgres
|
||||
|
||||
eclair1:
|
||||
image: acinq/eclair:latest
|
||||
environment:
|
||||
JAVA_OPTS: >
|
||||
-Xmx512m
|
||||
-Declair.printToConsole
|
||||
-Declair.bitcoind.host=bitcoind
|
||||
-Declair.bitcoind.rpcport=43782
|
||||
-Declair.bitcoind.rpcuser=ceiwHEbqWI83
|
||||
-Declair.bitcoind.rpcpassword=DwubwWsoo3
|
||||
-Declair.bitcoind.zmq=tcp://bitcoind:29000
|
||||
-Declair.chain=regtest
|
||||
-Declair.api.binding-ip=0.0.0.0
|
||||
links:
|
||||
- bitcoind
|
||||
ports:
|
||||
- "30992:8080" # api port
|
||||
expose:
|
||||
- "9735" # server port
|
||||
- "8080" # api port
|
||||
|
||||
eclair2:
|
||||
image: acinq/eclair:latest
|
||||
environment:
|
||||
JAVA_OPTS: >
|
||||
-Xmx512m
|
||||
-Declair.printToConsole
|
||||
-Declair.bitcoind.host=bitcoind
|
||||
-Declair.bitcoind.rpcport=43782
|
||||
-Declair.bitcoind.rpcuser=ceiwHEbqWI83
|
||||
-Declair.bitcoind.rpcpassword=DwubwWsoo3
|
||||
-Declair.bitcoind.zmq=tcp://bitcoind:29000
|
||||
-Declair.chain=regtest
|
||||
-Declair.api.binding-ip=0.0.0.0
|
||||
links:
|
||||
- bitcoind
|
||||
ports:
|
||||
- "30993:8080" # api port
|
||||
expose:
|
||||
- "9735" # server port
|
||||
- "8080" # api port
|
||||
|
||||
bitcoind:
|
||||
container_name: btcpayserver_dev_bitcoind
|
||||
image: nicolasdorier/docker-bitcoin:0.15.0.1
|
||||
environment:
|
||||
BITCOIN_EXTRA_ARGS: |
|
||||
rpcuser=ceiwHEbqWI83
|
||||
rpcpassword=DwubwWsoo3
|
||||
regtest=1
|
||||
server=1
|
||||
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" # RPC
|
||||
expose:
|
||||
- "43782" # RPC
|
||||
- "39388" # P2P
|
||||
- "29000" # zmq
|
||||
|
||||
postgres:
|
||||
image: postgres:9.6.5
|
||||
ports:
|
||||
- "39372:5432"
|
||||
expose:
|
||||
- "5432"
|
@ -1,123 +0,0 @@
|
||||
# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
node_modules/
|
||||
dist/
|
||||
|
||||
# mstest test results
|
||||
TestResults
|
||||
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.docstates
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
x64/
|
||||
*_i.c
|
||||
*_p.c
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*
|
||||
|
||||
# NCrunch
|
||||
*.ncrunch*
|
||||
.*crunch*.local.xml
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish
|
||||
|
||||
# Publish Web Output
|
||||
*.Publish.xml
|
||||
|
||||
# NuGet Packages Directory
|
||||
packages
|
||||
|
||||
# Windows Azure Build Output
|
||||
csx
|
||||
*.build.csdef
|
||||
|
||||
# Windows Store app package directory
|
||||
AppPackages/
|
||||
|
||||
# Others
|
||||
[Bb]in
|
||||
[Oo]bj
|
||||
sql
|
||||
TestResults
|
||||
[Tt]est[Rr]esult*
|
||||
*.Cache
|
||||
ClientBin
|
||||
[Ss]tyle[Cc]op.*
|
||||
~$*
|
||||
*.dbmdl
|
||||
Generated_Code #added for RIA/Silverlight projects
|
||||
|
||||
# Backup & report files from converting an old project file to a newer
|
||||
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
|
||||
src/Rapporteringsregisteret.Web/assets/less/*.css
|
||||
|
||||
MetricResults/
|
||||
*.sln.ide/
|
||||
|
||||
_configs/
|
||||
|
||||
# vnext stuff
|
||||
bower_components
|
||||
output
|
||||
|
||||
.vs
|
@ -9,27 +9,27 @@ namespace BTCPayServer.Authentication
|
||||
{
|
||||
public class BitIdentity : IIdentity
|
||||
{
|
||||
public BitIdentity(PubKey key)
|
||||
{
|
||||
PubKey = key;
|
||||
_Name = Encoders.Base58Check.EncodeData(Encoders.Hex.DecodeData("0f02" + key.Hash.ToString()));
|
||||
SIN = NBitpayClient.Extensions.BitIdExtensions.GetBitIDSIN(key);
|
||||
}
|
||||
string _Name;
|
||||
public BitIdentity(PubKey key)
|
||||
{
|
||||
PubKey = key;
|
||||
_Name = Encoders.Base58Check.EncodeData(Encoders.Hex.DecodeData("0f02" + key.Hash.ToString()));
|
||||
SIN = NBitpayClient.Extensions.BitIdExtensions.GetBitIDSIN(key);
|
||||
}
|
||||
string _Name;
|
||||
|
||||
public string SIN
|
||||
{
|
||||
get;
|
||||
}
|
||||
public PubKey PubKey
|
||||
{
|
||||
get;
|
||||
}
|
||||
public string SIN
|
||||
{
|
||||
get;
|
||||
}
|
||||
public PubKey PubKey
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public string AuthenticationType => "BitID";
|
||||
public string AuthenticationType => "BitID";
|
||||
|
||||
public bool IsAuthenticated => true;
|
||||
public bool IsAuthenticated => true;
|
||||
|
||||
public string Name => _Name;
|
||||
}
|
||||
public string Name => _Name;
|
||||
}
|
||||
}
|
||||
|
@ -8,53 +8,43 @@ namespace BTCPayServer.Authentication
|
||||
{
|
||||
public class BitTokenEntity
|
||||
{
|
||||
public string Name
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Value
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public DateTimeOffset DateCreated
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public bool Active
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string PairedId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Label
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public DateTimeOffset PairingTime
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string SIN
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string Facade
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Value
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string StoreId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Label
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public DateTimeOffset PairingTime
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string SIN
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public BitTokenEntity Clone(Facade facade)
|
||||
{
|
||||
return new BitTokenEntity()
|
||||
{
|
||||
Active = Active,
|
||||
DateCreated = DateCreated,
|
||||
Label = Label,
|
||||
Name = Name,
|
||||
PairedId = PairedId,
|
||||
PairingTime = PairingTime,
|
||||
SIN = SIN,
|
||||
Value = Value
|
||||
};
|
||||
}
|
||||
}
|
||||
public BitTokenEntity Clone(Facade facade)
|
||||
{
|
||||
return new BitTokenEntity()
|
||||
{
|
||||
Label = Label,
|
||||
Facade = Facade,
|
||||
StoreId = StoreId,
|
||||
PairingTime = PairingTime,
|
||||
SIN = SIN,
|
||||
Value = Value
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,47 +4,47 @@ using System.Text;
|
||||
|
||||
namespace BTCPayServer.Authentication
|
||||
{
|
||||
public class PairingCodeEntity
|
||||
{
|
||||
public string Id
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string Facade
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string Label
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string SIN
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public DateTimeOffset PairingTime
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public DateTimeOffset PairingExpiration
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string Token
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public class PairingCodeEntity
|
||||
{
|
||||
public string Id
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string Facade
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string Label
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string SIN
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public DateTimeOffset CreatedTime
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public DateTimeOffset Expiration
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string TokenValue
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool IsExpired()
|
||||
{
|
||||
return DateTimeOffset.UtcNow > PairingExpiration;
|
||||
}
|
||||
}
|
||||
public bool IsExpired()
|
||||
{
|
||||
return DateTimeOffset.UtcNow > Expiration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using DBreeze;
|
||||
using BTCPayServer.Data;
|
||||
using DBreeze;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using Newtonsoft.Json;
|
||||
@ -6,177 +7,202 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Query;
|
||||
using System.Linq;
|
||||
|
||||
namespace BTCPayServer.Authentication
|
||||
{
|
||||
public class TokenRepository
|
||||
{
|
||||
public TokenRepository(DBreezeEngine engine)
|
||||
{
|
||||
_Engine = engine;
|
||||
}
|
||||
public enum PairingResult
|
||||
{
|
||||
Partial,
|
||||
Complete,
|
||||
ReusedKey,
|
||||
Expired
|
||||
}
|
||||
|
||||
public class TokenRepository
|
||||
{
|
||||
ApplicationDbContextFactory _Factory;
|
||||
public TokenRepository(ApplicationDbContextFactory dbFactory)
|
||||
{
|
||||
if (dbFactory == null)
|
||||
throw new ArgumentNullException(nameof(dbFactory));
|
||||
_Factory = dbFactory;
|
||||
}
|
||||
|
||||
public async Task<BitTokenEntity[]> GetTokens(string sin)
|
||||
{
|
||||
using (var ctx = _Factory.CreateContext())
|
||||
{
|
||||
return (await ctx.PairedSINData
|
||||
.Where(p => p.SIN == sin)
|
||||
.ToListAsync())
|
||||
.Select(p => CreateTokenEntity(p))
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private BitTokenEntity CreateTokenEntity(PairedSINData data)
|
||||
{
|
||||
return new BitTokenEntity()
|
||||
{
|
||||
Label = data.Label,
|
||||
Facade = data.Facade,
|
||||
Value = data.Id,
|
||||
SIN = data.SIN,
|
||||
PairingTime = data.PairingTime,
|
||||
StoreId = data.StoreDataId
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<string> CreatePairingCodeAsync()
|
||||
{
|
||||
string pairingCodeId = null;
|
||||
while (true)
|
||||
{
|
||||
pairingCodeId = Encoders.Base58.EncodeData(RandomUtils.GetBytes(6));
|
||||
if (pairingCodeId.Length == 7) // woocommerce plugin check for exactly 7 digits
|
||||
break;
|
||||
}
|
||||
using (var ctx = _Factory.CreateContext())
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var expiration = DateTime.UtcNow + TimeSpan.FromMinutes(15);
|
||||
ctx.PairingCodes.Add(new PairingCodeData()
|
||||
{
|
||||
Id = pairingCodeId,
|
||||
DateCreated = now,
|
||||
Expiration = expiration,
|
||||
TokenValue = Encoders.Base58.EncodeData(RandomUtils.GetBytes(32))
|
||||
});
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
return pairingCodeId;
|
||||
}
|
||||
|
||||
public async Task<PairingCodeEntity> UpdatePairingCode(PairingCodeEntity pairingCodeEntity)
|
||||
{
|
||||
using (var ctx = _Factory.CreateContext())
|
||||
{
|
||||
var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeEntity.Id);
|
||||
pairingCode.Label = pairingCodeEntity.Label;
|
||||
pairingCode.Facade = pairingCodeEntity.Facade;
|
||||
await ctx.SaveChangesAsync();
|
||||
return CreatePairingCodeEntity(pairingCode);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<PairingResult> PairWithStoreAsync(string pairingCodeId, string storeId)
|
||||
{
|
||||
using (var ctx = _Factory.CreateContext())
|
||||
{
|
||||
var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId);
|
||||
if (pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow)
|
||||
return PairingResult.Expired;
|
||||
pairingCode.StoreDataId = storeId;
|
||||
var result = await ActivateIfComplete(ctx, pairingCode);
|
||||
await ctx.SaveChangesAsync();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<PairingResult> PairWithSINAsync(string pairingCodeId, string sin)
|
||||
{
|
||||
using (var ctx = _Factory.CreateContext())
|
||||
{
|
||||
var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId);
|
||||
if (pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow)
|
||||
return PairingResult.Expired;
|
||||
pairingCode.SIN = sin;
|
||||
var result = await ActivateIfComplete(ctx, pairingCode);
|
||||
await ctx.SaveChangesAsync();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private readonly DBreezeEngine _Engine;
|
||||
public DBreezeEngine Engine
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Engine;
|
||||
}
|
||||
}
|
||||
private async Task<PairingResult> ActivateIfComplete(ApplicationDbContext ctx, PairingCodeData pairingCode)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(pairingCode.SIN) && !string.IsNullOrEmpty(pairingCode.StoreDataId))
|
||||
{
|
||||
ctx.PairingCodes.Remove(pairingCode);
|
||||
|
||||
public Task<BitTokenEntity[]> GetTokens(string sin)
|
||||
{
|
||||
List<BitTokenEntity> tokens = new List<BitTokenEntity>();
|
||||
using(var tx = _Engine.GetTransaction())
|
||||
{
|
||||
tx.ValuesLazyLoadingIsOn = false;
|
||||
foreach(var row in tx.SelectForward<string, byte[]>($"T_{sin}"))
|
||||
{
|
||||
var token = ToObject<BitTokenEntity>(row.Value);
|
||||
tokens.Add(token);
|
||||
}
|
||||
}
|
||||
return Task.FromResult(tokens.ToArray());
|
||||
}
|
||||
|
||||
public Task<BitTokenEntity> CreateToken(string sin, string tokenName)
|
||||
{
|
||||
var token = new BitTokenEntity
|
||||
{
|
||||
Name = tokenName,
|
||||
Value = Encoders.Base58.EncodeData(RandomUtils.GetBytes(32)),
|
||||
DateCreated = DateTimeOffset.UtcNow
|
||||
};
|
||||
using(var tx = _Engine.GetTransaction())
|
||||
{
|
||||
tx.Insert<string, byte[]>($"T_{sin}", token.Name, ToBytes(token));
|
||||
tx.Commit();
|
||||
}
|
||||
return Task.FromResult(token);
|
||||
}
|
||||
|
||||
public Task<bool> PairWithAsync(string pairingCode, string pairedId)
|
||||
{
|
||||
if(pairedId == null)
|
||||
throw new ArgumentNullException(nameof(pairedId));
|
||||
using(var tx = _Engine.GetTransaction())
|
||||
{
|
||||
var row = tx.Select<string, byte[]>("PairingCodes", pairingCode);
|
||||
if(row == null || !row.Exists)
|
||||
return Task.FromResult(false);
|
||||
tx.RemoveKey<string>("PairingCodes", pairingCode);
|
||||
try
|
||||
{
|
||||
var pairingEntity = ToObject<PairingCodeEntity>(row.Value);
|
||||
if(pairingEntity.IsExpired())
|
||||
return Task.FromResult(false);
|
||||
row = tx.Select<string, byte[]>($"T_{pairingEntity.SIN}", pairingEntity.Facade);
|
||||
if(row == null || !row.Exists)
|
||||
return Task.FromResult(false);
|
||||
var token = ToObject<BitTokenEntity>(row.Value);
|
||||
if(token.Active)
|
||||
return Task.FromResult(false);
|
||||
token.Active = true;
|
||||
token.PairedId = pairedId;
|
||||
token.SIN = pairingEntity.SIN;
|
||||
token.Label = pairingEntity.Label;
|
||||
token.PairingTime = DateTimeOffset.UtcNow;
|
||||
tx.Insert($"TbP_{pairedId}", token.Value, ToBytes(token));
|
||||
tx.Insert($"T_{pairingEntity.SIN}", pairingEntity.Facade, ToBytes(token));
|
||||
}
|
||||
finally
|
||||
{
|
||||
tx.Commit();
|
||||
}
|
||||
}
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public Task<BitTokenEntity[]> GetTokensByPairedIdAsync(string pairedId)
|
||||
{
|
||||
List<BitTokenEntity> tokens = new List<BitTokenEntity>();
|
||||
using(var tx = _Engine.GetTransaction())
|
||||
{
|
||||
tx.ValuesLazyLoadingIsOn = false;
|
||||
foreach(var row in tx.SelectForward<string, byte[]>($"TbP_{pairedId}"))
|
||||
{
|
||||
tokens.Add(ToObject<BitTokenEntity>(row.Value));
|
||||
}
|
||||
}
|
||||
return Task.FromResult(tokens.ToArray());
|
||||
}
|
||||
|
||||
public Task<PairingCodeEntity> GetPairingAsync(string pairingCode)
|
||||
{
|
||||
using(var tx = _Engine.GetTransaction())
|
||||
{
|
||||
var row = tx.Select<string, byte[]>("PairingCodes", pairingCode);
|
||||
if(row == null || !row.Exists)
|
||||
return Task.FromResult<PairingCodeEntity>(null);
|
||||
var pairingEntity = ToObject<PairingCodeEntity>(row.Value);
|
||||
if(pairingEntity.IsExpired())
|
||||
return Task.FromResult<PairingCodeEntity>(null);
|
||||
return Task.FromResult(pairingEntity);
|
||||
}
|
||||
}
|
||||
public Task<PairingCodeEntity> AddPairingCodeAsync(PairingCodeEntity pairingCodeEntity)
|
||||
{
|
||||
pairingCodeEntity = Clone(pairingCodeEntity);
|
||||
pairingCodeEntity.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(6));
|
||||
using(var tx = _Engine.GetTransaction())
|
||||
{
|
||||
tx.Insert("PairingCodes", pairingCodeEntity.Id, ToBytes(pairingCodeEntity));
|
||||
tx.Commit();
|
||||
}
|
||||
return Task.FromResult(pairingCodeEntity);
|
||||
}
|
||||
|
||||
private byte[] ToBytes<T>(T obj)
|
||||
{
|
||||
return ZipUtils.Zip(JsonConvert.SerializeObject(obj));
|
||||
}
|
||||
private T ToObject<T>(byte[] value)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(ZipUtils.Unzip(value));
|
||||
}
|
||||
|
||||
private T Clone<T>(T obj)
|
||||
{
|
||||
return ToObject<T>(ToBytes(obj));
|
||||
}
|
||||
// Can have concurrency issues... but no harm can be done
|
||||
var alreadyUsed = await ctx.PairedSINData.Where(p => p.SIN == pairingCode.SIN && p.StoreDataId != pairingCode.StoreDataId).AnyAsync();
|
||||
if (alreadyUsed)
|
||||
return PairingResult.ReusedKey;
|
||||
await ctx.PairedSINData.AddAsync(new PairedSINData()
|
||||
{
|
||||
Id = pairingCode.TokenValue,
|
||||
PairingTime = DateTime.UtcNow,
|
||||
Facade = pairingCode.Facade,
|
||||
Label = pairingCode.Label,
|
||||
StoreDataId = pairingCode.StoreDataId,
|
||||
SIN = pairingCode.SIN
|
||||
});
|
||||
return PairingResult.Complete;
|
||||
}
|
||||
return PairingResult.Partial;
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> DeleteToken(string sin, string tokenName, string storeId)
|
||||
{
|
||||
var token = await GetToken(sin, tokenName);
|
||||
if(token == null || (token.PairedId != null && token.PairedId != storeId))
|
||||
return false;
|
||||
using(var tx = _Engine.GetTransaction())
|
||||
{
|
||||
tx.RemoveKey<string>($"T_{sin}", tokenName);
|
||||
if(token.PairedId != null)
|
||||
tx.RemoveKey<string>($"TbP_" + token.PairedId, token.Value);
|
||||
tx.Commit();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public async Task<BitTokenEntity[]> GetTokensByStoreIdAsync(string storeId)
|
||||
{
|
||||
using (var ctx = _Factory.CreateContext())
|
||||
{
|
||||
return (await ctx.PairedSINData.Where(p => p.StoreDataId == storeId).ToListAsync())
|
||||
.Select(c => CreateTokenEntity(c))
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private Task<BitTokenEntity> GetToken(string sin, string tokenName)
|
||||
{
|
||||
using(var tx = _Engine.GetTransaction())
|
||||
{
|
||||
tx.ValuesLazyLoadingIsOn = true;
|
||||
var row = tx.Select<string, byte[]>($"T_{sin}", tokenName);
|
||||
if(row == null || !row.Exists)
|
||||
return Task.FromResult<BitTokenEntity>(null);
|
||||
var token = ToObject<BitTokenEntity>(row.Value);
|
||||
if(!token.Active)
|
||||
return Task.FromResult<BitTokenEntity>(null);
|
||||
return Task.FromResult(token);
|
||||
}
|
||||
}
|
||||
public async Task<PairingCodeEntity> GetPairingAsync(string pairingCode)
|
||||
{
|
||||
using (var ctx = _Factory.CreateContext())
|
||||
{
|
||||
return CreatePairingCodeEntity(await ctx.PairingCodes.FindAsync(pairingCode));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private PairingCodeEntity CreatePairingCodeEntity(PairingCodeData data)
|
||||
{
|
||||
if (data == null)
|
||||
return null;
|
||||
return new PairingCodeEntity()
|
||||
{
|
||||
Facade = data.Facade,
|
||||
Id = data.Id,
|
||||
Label = data.Label,
|
||||
Expiration = data.Expiration,
|
||||
CreatedTime = data.DateCreated,
|
||||
TokenValue = data.TokenValue,
|
||||
SIN = data.SIN
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> DeleteToken(string tokenId)
|
||||
{
|
||||
using (var ctx = _Factory.CreateContext())
|
||||
{
|
||||
var token = await ctx.PairedSINData.FindAsync(tokenId);
|
||||
if (token == null)
|
||||
return false;
|
||||
ctx.PairedSINData.Remove(token);
|
||||
await ctx.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<BitTokenEntity> GetToken(string tokenId)
|
||||
{
|
||||
using (var ctx = _Factory.CreateContext())
|
||||
{
|
||||
var token = await ctx.PairedSINData.FindAsync(tokenId);
|
||||
return CreateTokenEntity(token);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<Version>1.0.0.0</Version>
|
||||
<Version>1.0.0.48</Version>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Build\dockerfiles\**" />
|
||||
@ -18,13 +18,13 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Hangfire" Version="1.6.17" />
|
||||
<PackageReference Include="Hangfire.MemoryStorage" Version="1.5.1" />
|
||||
<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.38" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.9" />
|
||||
<PackageReference Include="NBitcoin" Version="4.0.0.50" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.13" />
|
||||
<PackageReference Include="DBreeze" Version="1.87.0" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="1.0.0.12" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="1.0.0.20" />
|
||||
<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" />
|
||||
@ -34,9 +34,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.1" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -46,7 +46,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="wwwroot\img\bitcoin-symbol.svg" />
|
||||
<None Include="wwwroot\js\core.js" />
|
||||
<None Include="wwwroot\js\creative.js" />
|
||||
<None Include="wwwroot\js\creative.min.js" />
|
||||
@ -100,4 +99,4 @@
|
||||
<ItemGroup>
|
||||
<Folder Include="Build\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
@ -4,15 +4,15 @@ using System.Text;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class BitpayHttpException : Exception
|
||||
{
|
||||
public BitpayHttpException(int code, string message) : base(message)
|
||||
{
|
||||
StatusCode = code;
|
||||
}
|
||||
public int StatusCode
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
public class BitpayHttpException : Exception
|
||||
{
|
||||
public BitpayHttpException(int code, string message) : base(message)
|
||||
{
|
||||
StatusCode = code;
|
||||
}
|
||||
public int StatusCode
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
19
BTCPayServer/CompositeDisposable.cs
Normal file
19
BTCPayServer/CompositeDisposable.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class CompositeDisposable : IDisposable
|
||||
{
|
||||
List<IDisposable> _Disposables = new List<IDisposable>();
|
||||
public void Add(IDisposable disposable) { _Disposables.Add(disposable); }
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var d in _Disposables)
|
||||
d.Dispose();
|
||||
_Disposables.Clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -12,61 +12,63 @@ using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace BTCPayServer.Configuration
|
||||
{
|
||||
public class BTCPayServerOptions
|
||||
{
|
||||
public Network Network
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public Uri Explorer
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public class BTCPayServerOptions
|
||||
{
|
||||
public Network Network
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public Uri Explorer
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string CookieFile
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string ConfigurationFile
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public string DataDir
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public List<IPEndPoint> Listen
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string CookieFile
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string ConfigurationFile
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public string DataDir
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public List<IPEndPoint> Listen
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public void LoadArgs(IConfiguration conf)
|
||||
{
|
||||
var networkInfo = DefaultConfiguration.GetNetwork(conf);
|
||||
Network = networkInfo?.Network;
|
||||
if(Network == null)
|
||||
throw new ConfigException("Invalid network");
|
||||
public void LoadArgs(IConfiguration conf)
|
||||
{
|
||||
var networkInfo = DefaultConfiguration.GetNetwork(conf);
|
||||
Network = networkInfo?.Network;
|
||||
if (Network == null)
|
||||
throw new ConfigException("Invalid network");
|
||||
|
||||
DataDir = conf.GetOrDefault<string>("datadir", networkInfo.DefaultDataDirectory);
|
||||
Logs.Configuration.LogInformation("Network: " + Network);
|
||||
DataDir = conf.GetOrDefault<string>("datadir", networkInfo.DefaultDataDirectory);
|
||||
Logs.Configuration.LogInformation("Network: " + Network);
|
||||
|
||||
Explorer = conf.GetOrDefault<Uri>("explorer.url", networkInfo.DefaultExplorerUrl);
|
||||
CookieFile = conf.GetOrDefault<string>("explorer.cookiefile", networkInfo.DefaultExplorerCookieFile);
|
||||
RequireHttps = conf.GetOrDefault<bool>("requirehttps", false);
|
||||
PostgresConnectionString = conf.GetOrDefault<string>("postgres", null);
|
||||
}
|
||||
|
||||
public bool RequireHttps
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string PostgresConnectionString
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
Explorer = conf.GetOrDefault<Uri>("explorer.url", networkInfo.DefaultExplorerUrl);
|
||||
CookieFile = conf.GetOrDefault<string>("explorer.cookiefile", networkInfo.DefaultExplorerCookieFile);
|
||||
PostgresConnectionString = conf.GetOrDefault<string>("postgres", null);
|
||||
ExternalUrl = conf.GetOrDefault<Uri>("externalurl", null);
|
||||
InternalUrl = conf.GetOrDefault<Uri>("internalurl", null);
|
||||
}
|
||||
public string PostgresConnectionString
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public Uri ExternalUrl
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public Uri InternalUrl { get; private set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,128 +0,0 @@
|
||||
using BTCPayServer.Authentication;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using BTCPayServer.Logging;
|
||||
using DBreeze;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
|
||||
namespace BTCPayServer.Configuration
|
||||
{
|
||||
public class BTCPayServerRuntime : IDisposable
|
||||
{
|
||||
public ExplorerClient Explorer
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public void Configure(BTCPayServerOptions opts)
|
||||
{
|
||||
ConfigureAsync(opts).GetAwaiter().GetResult();
|
||||
}
|
||||
public async Task ConfigureAsync(BTCPayServerOptions opts)
|
||||
{
|
||||
Network = opts.Network;
|
||||
Explorer = new ExplorerClient(opts.Network, opts.Explorer);
|
||||
|
||||
if(!Explorer.SetCookieAuth(opts.CookieFile))
|
||||
Explorer.SetNoAuth();
|
||||
|
||||
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
||||
try
|
||||
{
|
||||
Logs.Configuration.LogInformation("Trying to connect to explorer " + Explorer.Address.AbsoluteUri);
|
||||
await Explorer.WaitServerStartedAsync(cts.Token).ConfigureAwait(false);
|
||||
Logs.Configuration.LogInformation("Connection successfull");
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
throw new ConfigException($"Could not connect to NBXplorer, {ex.Message}");
|
||||
}
|
||||
DBreezeEngine db = new DBreezeEngine(CreateDBPath(opts, "TokensDB"));
|
||||
_Resources.Add(db);
|
||||
TokenRepository = new TokenRepository(db);
|
||||
|
||||
db = new DBreezeEngine(CreateDBPath(opts, "InvoiceDB"));
|
||||
_Resources.Add(db);
|
||||
|
||||
ApplicationDbContextFactory dbContext = null;
|
||||
if(opts.PostgresConnectionString == null)
|
||||
{
|
||||
var connStr = "Data Source=" + Path.Combine(opts.DataDir, "sqllite.db");
|
||||
Logs.Configuration.LogInformation($"SQLite DB used ({connStr})");
|
||||
dbContext = new ApplicationDbContextFactory(DatabaseType.Sqlite, connStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logs.Configuration.LogInformation($"Postgres DB used ({opts.PostgresConnectionString})");
|
||||
dbContext = new ApplicationDbContextFactory(DatabaseType.Postgres, opts.PostgresConnectionString);
|
||||
}
|
||||
DBFactory = dbContext;
|
||||
InvoiceRepository = new InvoiceRepository(dbContext, db, Network);
|
||||
|
||||
|
||||
db = new DBreezeEngine(CreateDBPath(opts, "AddressMapping"));
|
||||
_Resources.Add(db);
|
||||
Wallet = new BTCPayWallet(Explorer, db);
|
||||
}
|
||||
|
||||
private static string CreateDBPath(BTCPayServerOptions opts, string name)
|
||||
{
|
||||
var dbpath = Path.Combine(opts.DataDir, name);
|
||||
if(!Directory.Exists(dbpath))
|
||||
Directory.CreateDirectory(dbpath);
|
||||
return dbpath;
|
||||
}
|
||||
|
||||
List<IDisposable> _Resources = new List<IDisposable>();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock(_Resources)
|
||||
{
|
||||
foreach(var r in _Resources)
|
||||
{
|
||||
r.Dispose();
|
||||
}
|
||||
_Resources.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public Network Network
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public TokenRepository TokenRepository
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public InvoiceRepository InvoiceRepository
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public BTCPayWallet Wallet
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public ApplicationDbContextFactory DBFactory
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -5,11 +5,11 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Configuration
|
||||
{
|
||||
public class ConfigException : Exception
|
||||
{
|
||||
public ConfigException(string message) : base(message)
|
||||
{
|
||||
public class ConfigException : Exception
|
||||
{
|
||||
public ConfigException(string message) : base(message)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,44 +9,44 @@ using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace BTCPayServer.Configuration
|
||||
{
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
public static T GetOrDefault<T>(this IConfiguration configuration, string key, T defaultValue)
|
||||
{
|
||||
var str = configuration[key] ?? configuration[key.Replace(".", string.Empty)];
|
||||
if(str == null)
|
||||
return defaultValue;
|
||||
if(typeof(T) == typeof(bool))
|
||||
{
|
||||
var trueValues = new[] { "1", "true" };
|
||||
var falseValues = new[] { "0", "false" };
|
||||
if(trueValues.Contains(str, StringComparer.OrdinalIgnoreCase))
|
||||
return (T)(object)true;
|
||||
if(falseValues.Contains(str, StringComparer.OrdinalIgnoreCase))
|
||||
return (T)(object)false;
|
||||
throw new FormatException();
|
||||
}
|
||||
else if(typeof(T) == typeof(Uri))
|
||||
return (T)(object)new Uri(str, UriKind.Absolute);
|
||||
else if(typeof(T) == typeof(string))
|
||||
return (T)(object)str;
|
||||
else if(typeof(T) == typeof(IPEndPoint))
|
||||
{
|
||||
var separator = str.LastIndexOf(":");
|
||||
if(separator == -1)
|
||||
throw new FormatException();
|
||||
var ip = str.Substring(0, separator);
|
||||
var port = str.Substring(separator + 1);
|
||||
return (T)(object)new IPEndPoint(IPAddress.Parse(ip), int.Parse(port));
|
||||
}
|
||||
else if(typeof(T) == typeof(int))
|
||||
{
|
||||
return (T)(object)int.Parse(str, CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Configuration value does not support time " + typeof(T).Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
public static T GetOrDefault<T>(this IConfiguration configuration, string key, T defaultValue)
|
||||
{
|
||||
var str = configuration[key] ?? configuration[key.Replace(".", string.Empty)];
|
||||
if (str == null)
|
||||
return defaultValue;
|
||||
if (typeof(T) == typeof(bool))
|
||||
{
|
||||
var trueValues = new[] { "1", "true" };
|
||||
var falseValues = new[] { "0", "false" };
|
||||
if (trueValues.Contains(str, StringComparer.OrdinalIgnoreCase))
|
||||
return (T)(object)true;
|
||||
if (falseValues.Contains(str, StringComparer.OrdinalIgnoreCase))
|
||||
return (T)(object)false;
|
||||
throw new FormatException();
|
||||
}
|
||||
else if (typeof(T) == typeof(Uri))
|
||||
return (T)(object)new Uri(str, UriKind.Absolute);
|
||||
else if (typeof(T) == typeof(string))
|
||||
return (T)(object)str;
|
||||
else if (typeof(T) == typeof(IPEndPoint))
|
||||
{
|
||||
var separator = str.LastIndexOf(":");
|
||||
if (separator == -1)
|
||||
throw new FormatException();
|
||||
var ip = str.Substring(0, separator);
|
||||
var port = str.Substring(separator + 1);
|
||||
return (T)(object)new IPEndPoint(IPAddress.Parse(ip), int.Parse(port));
|
||||
}
|
||||
else if (typeof(T) == typeof(int))
|
||||
{
|
||||
return (T)(object)int.Parse(str, CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Configuration value does not support time " + typeof(T).Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,88 +13,87 @@ using CommandLine;
|
||||
|
||||
namespace BTCPayServer.Configuration
|
||||
{
|
||||
public class DefaultConfiguration : StandardConfiguration.DefaultConfiguration
|
||||
{
|
||||
protected override CommandLineApplication CreateCommandLineApplicationCore()
|
||||
{
|
||||
CommandLineApplication app = new CommandLineApplication(true)
|
||||
{
|
||||
FullName = "NBXplorer\r\nLightweight block explorer for tracking HD wallets",
|
||||
Name = "NBXplorer"
|
||||
};
|
||||
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("--requirehttps", $"Will redirect to https version of the website (default: false)", CommandOptionType.BoolValue);
|
||||
app.Option("--postgres", $"Connection string to postgres database (default: sqlite is used)", CommandOptionType.SingleValue);
|
||||
app.Option("--explorerurl", $"Url of the NBxplorer (default: : Default setting of NBXplorer for the network)", CommandOptionType.SingleValue);
|
||||
app.Option("--explorercookiefile", $"Path to the cookie file (default: Default setting of NBXplorer for the network)", CommandOptionType.SingleValue);
|
||||
public class DefaultConfiguration : StandardConfiguration.DefaultConfiguration
|
||||
{
|
||||
protected override CommandLineApplication CreateCommandLineApplicationCore()
|
||||
{
|
||||
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("--postgres", $"Connection string to postgres database (default: sqlite is used)", CommandOptionType.SingleValue);
|
||||
app.Option("--explorerurl", $"Url of the NBxplorer (default: : Default setting of NBXplorer for the network)", CommandOptionType.SingleValue);
|
||||
app.Option("--explorercookiefile", $"Path to the cookie file (default: Default setting of NBXplorer for the network)", 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);
|
||||
app.Option("--internalurl", $"The expected internal url of this service, this set NBXplorer callback addresses (default: empty, use the incoming HTTP request to figure out)", CommandOptionType.SingleValue);
|
||||
return app;
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
public override string EnvironmentVariablePrefix => "BTCPAY_";
|
||||
|
||||
public override string EnvironmentVariablePrefix => "BTCPAY_";
|
||||
protected override string GetDefaultDataDir(IConfiguration conf)
|
||||
{
|
||||
return GetNetwork(conf).DefaultDataDirectory;
|
||||
}
|
||||
|
||||
protected override string GetDefaultDataDir(IConfiguration conf)
|
||||
{
|
||||
return GetNetwork(conf).DefaultDataDirectory;
|
||||
}
|
||||
protected override string GetDefaultConfigurationFile(IConfiguration conf)
|
||||
{
|
||||
var network = GetNetwork(conf);
|
||||
var dataDir = conf["datadir"];
|
||||
if (dataDir == null)
|
||||
return network.DefaultConfigurationFile;
|
||||
var fileName = Path.GetFileName(network.DefaultConfigurationFile);
|
||||
return Path.Combine(dataDir, fileName);
|
||||
}
|
||||
|
||||
protected override string GetDefaultConfigurationFile(IConfiguration conf)
|
||||
{
|
||||
var network = GetNetwork(conf);
|
||||
var dataDir = conf["datadir"];
|
||||
if(dataDir == null)
|
||||
return network.DefaultConfigurationFile;
|
||||
var fileName = Path.GetFileName(network.DefaultConfigurationFile);
|
||||
return Path.Combine(dataDir, fileName);
|
||||
}
|
||||
public static NetworkInformation GetNetwork(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;
|
||||
}
|
||||
|
||||
public static NetworkInformation GetNetwork(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 net = conf.GetOrDefault<bool>("regtest", false) ? Network.RegTest :
|
||||
conf.GetOrDefault<bool>("testnet", false) ? Network.TestNet : Network.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 NetworkInformation.GetNetworkByName(net.Name);
|
||||
}
|
||||
|
||||
protected override string GetDefaultConfigurationFileTemplate(IConfiguration conf)
|
||||
{
|
||||
var network = GetNetwork(conf);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.AppendLine("### Global settings ###");
|
||||
builder.AppendLine("#testnet=0");
|
||||
builder.AppendLine("#regtest=0");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine("### Server settings ###");
|
||||
builder.AppendLine("#requirehttps=0");
|
||||
builder.AppendLine("#port=" + network.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 ###");
|
||||
builder.AppendLine("#explorer.url=" + network.DefaultExplorerUrl.AbsoluteUri);
|
||||
builder.AppendLine("#explorer.cookiefile=" + network.DefaultExplorerCookieFile);
|
||||
return builder.ToString();
|
||||
}
|
||||
protected override string GetDefaultConfigurationFileTemplate(IConfiguration conf)
|
||||
{
|
||||
var network = GetNetwork(conf);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.AppendLine("### Global settings ###");
|
||||
builder.AppendLine("#testnet=0");
|
||||
builder.AppendLine("#regtest=0");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine("### Server settings ###");
|
||||
builder.AppendLine("#port=" + network.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 ###");
|
||||
builder.AppendLine("#explorer.url=" + network.DefaultExplorerUrl.AbsoluteUri);
|
||||
builder.AppendLine("#explorer.cookiefile=" + network.DefaultExplorerCookieFile);
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override IPEndPoint GetDefaultEndpoint(IConfiguration conf)
|
||||
{
|
||||
return new IPEndPoint(IPAddress.Parse("127.0.0.1"), GetNetwork(conf).DefaultPort);
|
||||
}
|
||||
}
|
||||
protected override IPEndPoint GetDefaultEndpoint(IConfiguration conf)
|
||||
{
|
||||
return new IPEndPoint(IPAddress.Parse("127.0.0.1"), GetNetwork(conf).DefaultPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,96 +8,96 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Configuration
|
||||
{
|
||||
public class NetworkInformation
|
||||
{
|
||||
static NetworkInformation()
|
||||
{
|
||||
_Networks = new Dictionary<string, NetworkInformation>();
|
||||
foreach(var network in Network.GetNetworks())
|
||||
{
|
||||
NetworkInformation info = new NetworkInformation();
|
||||
info.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", network.Name);
|
||||
info.DefaultConfigurationFile = Path.Combine(info.DefaultDataDirectory, "settings.config");
|
||||
info.DefaultExplorerCookieFile = Path.Combine(StandardConfiguration.DefaultDataDirectory.GetDirectory("NBXplorer", network.Name, false), ".cookie");
|
||||
info.Network = network;
|
||||
info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24446", UriKind.Absolute);
|
||||
info.DefaultPort = 23002;
|
||||
_Networks.Add(network.Name, info);
|
||||
if(network == Network.Main)
|
||||
{
|
||||
info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24444", UriKind.Absolute);
|
||||
Main = info;
|
||||
info.DefaultPort = 23000;
|
||||
}
|
||||
if(network == Network.TestNet)
|
||||
{
|
||||
info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24445", UriKind.Absolute);
|
||||
info.DefaultPort = 23001;
|
||||
}
|
||||
}
|
||||
}
|
||||
public class NetworkInformation
|
||||
{
|
||||
static NetworkInformation()
|
||||
{
|
||||
_Networks = new Dictionary<string, NetworkInformation>();
|
||||
foreach (var network in Network.GetNetworks())
|
||||
{
|
||||
NetworkInformation info = new NetworkInformation();
|
||||
info.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", network.Name);
|
||||
info.DefaultConfigurationFile = Path.Combine(info.DefaultDataDirectory, "settings.config");
|
||||
info.DefaultExplorerCookieFile = Path.Combine(StandardConfiguration.DefaultDataDirectory.GetDirectory("NBXplorer", network.Name, false), ".cookie");
|
||||
info.Network = network;
|
||||
info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24446", UriKind.Absolute);
|
||||
info.DefaultPort = 23002;
|
||||
_Networks.Add(network.Name, info);
|
||||
if (network == Network.Main)
|
||||
{
|
||||
info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24444", UriKind.Absolute);
|
||||
Main = info;
|
||||
info.DefaultPort = 23000;
|
||||
}
|
||||
if (network == Network.TestNet)
|
||||
{
|
||||
info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24445", UriKind.Absolute);
|
||||
info.DefaultPort = 23001;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Dictionary<string, NetworkInformation> _Networks;
|
||||
public static NetworkInformation GetNetworkByName(string name)
|
||||
{
|
||||
var value = _Networks.TryGet(name);
|
||||
if(value != null)
|
||||
return value;
|
||||
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;
|
||||
}
|
||||
//Maybe alias ?
|
||||
var network = Network.GetNetwork(name);
|
||||
if (network != null)
|
||||
{
|
||||
value = _Networks.TryGet(network.Name);
|
||||
if (value != null)
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static NetworkInformation Main
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public Network Network
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string DefaultConfigurationFile
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string DefaultDataDirectory
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public Uri DefaultExplorerUrl
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
public int DefaultPort
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public string DefaultExplorerCookieFile
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
public static NetworkInformation Main
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public Network Network
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string DefaultConfigurationFile
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string DefaultDataDirectory
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public Uri DefaultExplorerUrl
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
public int DefaultPort
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public string DefaultExplorerCookieFile
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Network.ToString();
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return Network.ToString();
|
||||
}
|
||||
|
||||
public static string ToStringAll()
|
||||
{
|
||||
return string.Join(", ", _Networks.Select(n => n.Key).ToArray());
|
||||
}
|
||||
}
|
||||
public static string ToStringAll()
|
||||
{
|
||||
return string.Join(", ", _Networks.Select(n => n.Key).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,51 +12,79 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public class AccessTokenController : Controller
|
||||
{
|
||||
TokenRepository _TokenRepository;
|
||||
public AccessTokenController(TokenRepository tokenRepository)
|
||||
{
|
||||
_TokenRepository = tokenRepository ?? throw new ArgumentNullException(nameof(tokenRepository));
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("tokens")]
|
||||
public async Task<GetTokensResponse> GetTokens()
|
||||
{
|
||||
var tokens = await _TokenRepository.GetTokens(this.GetBitIdentity().SIN);
|
||||
return new GetTokensResponse(tokens);
|
||||
}
|
||||
public class AccessTokenController : Controller
|
||||
{
|
||||
TokenRepository _TokenRepository;
|
||||
public AccessTokenController(TokenRepository tokenRepository)
|
||||
{
|
||||
_TokenRepository = tokenRepository ?? throw new ArgumentNullException(nameof(tokenRepository));
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("tokens")]
|
||||
public async Task<GetTokensResponse> Tokens()
|
||||
{
|
||||
var tokens = await _TokenRepository.GetTokens(this.GetBitIdentity().SIN);
|
||||
return new GetTokensResponse(tokens);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("tokens")]
|
||||
public async Task<DataWrapper<List<PairingCodeResponse>>> GetPairingCode([FromBody] PairingCodeRequest token)
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var pairingEntity = new PairingCodeEntity()
|
||||
{
|
||||
Facade = token.Facade,
|
||||
Label = token.Label,
|
||||
SIN = token.Id,
|
||||
PairingTime = now,
|
||||
PairingExpiration = now + TimeSpan.FromMinutes(15)
|
||||
};
|
||||
var grantedToken = await _TokenRepository.CreateToken(token.Id, token.Facade);
|
||||
pairingEntity.Token = grantedToken.Name;
|
||||
pairingEntity = await _TokenRepository.AddPairingCodeAsync(pairingEntity);
|
||||
[HttpPost]
|
||||
[Route("tokens")]
|
||||
public async Task<DataWrapper<List<PairingCodeResponse>>> Tokens([FromBody] TokenRequest request)
|
||||
{
|
||||
PairingCodeEntity pairingEntity = null;
|
||||
if (string.IsNullOrEmpty(request.PairingCode))
|
||||
{
|
||||
if (string.IsNullOrEmpty(request.Id) || !NBitpayClient.Extensions.BitIdExtensions.ValidateSIN(request.Id))
|
||||
throw new BitpayHttpException(400, "'id' property is required");
|
||||
if (string.IsNullOrEmpty(request.Facade))
|
||||
throw new BitpayHttpException(400, "'facade' property is required");
|
||||
|
||||
var pairingCodes = new List<PairingCodeResponse>
|
||||
{
|
||||
new PairingCodeResponse()
|
||||
{
|
||||
PairingCode = pairingEntity.Id,
|
||||
PairingExpiration = pairingEntity.PairingExpiration,
|
||||
DateCreated = pairingEntity.PairingTime,
|
||||
Facade = grantedToken.Name,
|
||||
Token = grantedToken.Value,
|
||||
Label = pairingEntity.Label
|
||||
}
|
||||
};
|
||||
return DataWrapper.Create(pairingCodes);
|
||||
}
|
||||
}
|
||||
var pairingCode = await _TokenRepository.CreatePairingCodeAsync();
|
||||
await _TokenRepository.PairWithSINAsync(pairingCode, request.Id);
|
||||
pairingEntity = await _TokenRepository.UpdatePairingCode(new PairingCodeEntity()
|
||||
{
|
||||
Id = pairingCode,
|
||||
Facade = request.Facade,
|
||||
Label = request.Label
|
||||
});
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var sin = this.GetBitIdentity(false)?.SIN ?? request.Id;
|
||||
if (string.IsNullOrEmpty(request.Id) || !NBitpayClient.Extensions.BitIdExtensions.ValidateSIN(request.Id))
|
||||
throw new BitpayHttpException(400, "'id' property is required, alternatively, use BitId");
|
||||
|
||||
pairingEntity = await _TokenRepository.GetPairingAsync(request.PairingCode);
|
||||
if (pairingEntity == null)
|
||||
throw new BitpayHttpException(404, "The specified pairingCode is not found");
|
||||
pairingEntity.SIN = sin;
|
||||
|
||||
if (string.IsNullOrEmpty(pairingEntity.Label) && !string.IsNullOrEmpty(request.Label))
|
||||
{
|
||||
pairingEntity.Label = request.Label;
|
||||
await _TokenRepository.UpdatePairingCode(pairingEntity);
|
||||
}
|
||||
|
||||
var result = await _TokenRepository.PairWithSINAsync(request.PairingCode, sin);
|
||||
if (result != PairingResult.Complete && result != PairingResult.Partial)
|
||||
throw new BitpayHttpException(400, $"Error while pairing ({result})");
|
||||
|
||||
}
|
||||
|
||||
var pairingCodes = new List<PairingCodeResponse>
|
||||
{
|
||||
new PairingCodeResponse()
|
||||
{
|
||||
PairingCode = pairingEntity.Id,
|
||||
PairingExpiration = pairingEntity.Expiration,
|
||||
DateCreated = pairingEntity.CreatedTime,
|
||||
Facade = pairingEntity.Facade,
|
||||
Token = pairingEntity.TokenValue,
|
||||
Label = pairingEntity.Label
|
||||
}
|
||||
};
|
||||
return DataWrapper.Create(pairingCodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,503 +18,508 @@ using BTCPayServer.Services.Stores;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[Route("[controller]/[action]")]
|
||||
public class AccountController : Controller
|
||||
{
|
||||
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;
|
||||
[Authorize]
|
||||
[Route("[controller]/[action]")]
|
||||
public class AccountController : Controller
|
||||
{
|
||||
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;
|
||||
|
||||
public AccountController(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
RoleManager<IdentityRole> roleManager,
|
||||
StoreRepository storeRepository,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IEmailSender emailSender,
|
||||
SettingsRepository settingsRepository,
|
||||
ILogger<AccountController> logger)
|
||||
{
|
||||
this.storeRepository = storeRepository;
|
||||
_userManager = userManager;
|
||||
_signInManager = signInManager;
|
||||
_emailSender = emailSender;
|
||||
_logger = logger;
|
||||
_RoleManager = roleManager;
|
||||
_SettingsRepository = settingsRepository;
|
||||
}
|
||||
public AccountController(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
RoleManager<IdentityRole> roleManager,
|
||||
StoreRepository storeRepository,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IEmailSender emailSender,
|
||||
SettingsRepository settingsRepository,
|
||||
ILogger<AccountController> logger)
|
||||
{
|
||||
this.storeRepository = storeRepository;
|
||||
_userManager = userManager;
|
||||
_signInManager = signInManager;
|
||||
_emailSender = emailSender;
|
||||
_logger = logger;
|
||||
_RoleManager = roleManager;
|
||||
_SettingsRepository = settingsRepository;
|
||||
}
|
||||
|
||||
[TempData]
|
||||
public string ErrorMessage
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
[TempData]
|
||||
public string ErrorMessage
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Login(string returnUrl = null)
|
||||
{
|
||||
// Clear the existing external cookie to ensure a clean login process
|
||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Login(string returnUrl = null)
|
||||
{
|
||||
// Clear the existing external cookie to ensure a clean login process
|
||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
||||
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
return View();
|
||||
}
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
|
||||
{
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
if(ModelState.IsValid)
|
||||
{
|
||||
// Require the user to have a confirmed email before they can log on.
|
||||
var user = await _userManager.FindByEmailAsync(model.Email);
|
||||
if(user != null)
|
||||
{
|
||||
if(user.RequiresEmailConfirmation && !await _userManager.IsEmailConfirmedAsync(user))
|
||||
{
|
||||
ModelState.AddModelError(string.Empty,
|
||||
"You must have a confirmed email to log in.");
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
// This doesn't count login failures towards account lockout
|
||||
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
|
||||
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
|
||||
if(result.Succeeded)
|
||||
{
|
||||
_logger.LogInformation("User logged in.");
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
if(result.RequiresTwoFactor)
|
||||
{
|
||||
return RedirectToAction(nameof(LoginWith2fa), new
|
||||
{
|
||||
returnUrl,
|
||||
model.RememberMe
|
||||
});
|
||||
}
|
||||
if(result.IsLockedOut)
|
||||
{
|
||||
_logger.LogWarning("User account locked out.");
|
||||
return RedirectToAction(nameof(Lockout));
|
||||
}
|
||||
else
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
|
||||
{
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
// Require the user to have a confirmed email before they can log on.
|
||||
var user = await _userManager.FindByEmailAsync(model.Email);
|
||||
if (user != null)
|
||||
{
|
||||
if (user.RequiresEmailConfirmation && !await _userManager.IsEmailConfirmedAsync(user))
|
||||
{
|
||||
ModelState.AddModelError(string.Empty,
|
||||
"You must have a confirmed email to log in.");
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
// This doesn't count login failures towards account lockout
|
||||
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
|
||||
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
_logger.LogInformation("User logged in.");
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
if (result.RequiresTwoFactor)
|
||||
{
|
||||
return RedirectToAction(nameof(LoginWith2fa), new
|
||||
{
|
||||
returnUrl,
|
||||
model.RememberMe
|
||||
});
|
||||
}
|
||||
if (result.IsLockedOut)
|
||||
{
|
||||
_logger.LogWarning("User account locked out.");
|
||||
return RedirectToAction(nameof(Lockout));
|
||||
}
|
||||
else
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
// If we got this far, something failed, redisplay form
|
||||
return View(model);
|
||||
}
|
||||
// If we got this far, something failed, redisplay form
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> LoginWith2fa(bool rememberMe, string returnUrl = null)
|
||||
{
|
||||
// Ensure the user has gone through the username & password screen first
|
||||
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> LoginWith2fa(bool rememberMe, string returnUrl = null)
|
||||
{
|
||||
// Ensure the user has gone through the username & password screen first
|
||||
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
||||
|
||||
if(user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load two-factor authentication user.");
|
||||
}
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load two-factor authentication user.");
|
||||
}
|
||||
|
||||
var model = new LoginWith2faViewModel { RememberMe = rememberMe };
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
var model = new LoginWith2faViewModel { RememberMe = rememberMe };
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
|
||||
return View(model);
|
||||
}
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> LoginWith2fa(LoginWith2faViewModel model, bool rememberMe, string returnUrl = null)
|
||||
{
|
||||
if(!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> LoginWith2fa(LoginWith2faViewModel model, bool rememberMe, string returnUrl = null)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
|
||||
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
||||
if(user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var authenticatorCode = model.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
|
||||
var authenticatorCode = model.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
|
||||
|
||||
var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, model.RememberMachine);
|
||||
var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, model.RememberMachine);
|
||||
|
||||
if(result.Succeeded)
|
||||
{
|
||||
_logger.LogInformation("User with ID {UserId} logged in with 2fa.", user.Id);
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
else if(result.IsLockedOut)
|
||||
{
|
||||
_logger.LogWarning("User with ID {UserId} account locked out.", user.Id);
|
||||
return RedirectToAction(nameof(Lockout));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Invalid authenticator code entered for user with ID {UserId}.", user.Id);
|
||||
ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
|
||||
return View();
|
||||
}
|
||||
}
|
||||
if (result.Succeeded)
|
||||
{
|
||||
_logger.LogInformation("User with ID {UserId} logged in with 2fa.", user.Id);
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
else if (result.IsLockedOut)
|
||||
{
|
||||
_logger.LogWarning("User with ID {UserId} account locked out.", user.Id);
|
||||
return RedirectToAction(nameof(Lockout));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Invalid authenticator code entered for user with ID {UserId}.", user.Id);
|
||||
ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
|
||||
return View();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> LoginWithRecoveryCode(string returnUrl = null)
|
||||
{
|
||||
// Ensure the user has gone through the username & password screen first
|
||||
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
||||
if(user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load two-factor authentication user.");
|
||||
}
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> LoginWithRecoveryCode(string returnUrl = null)
|
||||
{
|
||||
// Ensure the user has gone through the username & password screen first
|
||||
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load two-factor authentication user.");
|
||||
}
|
||||
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
|
||||
return View();
|
||||
}
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> LoginWithRecoveryCode(LoginWithRecoveryCodeViewModel model, string returnUrl = null)
|
||||
{
|
||||
if(!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> LoginWithRecoveryCode(LoginWithRecoveryCodeViewModel model, string returnUrl = null)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
|
||||
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
||||
if(user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load two-factor authentication user.");
|
||||
}
|
||||
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load two-factor authentication user.");
|
||||
}
|
||||
|
||||
var recoveryCode = model.RecoveryCode.Replace(" ", string.Empty);
|
||||
var recoveryCode = model.RecoveryCode.Replace(" ", string.Empty);
|
||||
|
||||
var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
|
||||
var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
|
||||
|
||||
if(result.Succeeded)
|
||||
{
|
||||
_logger.LogInformation("User with ID {UserId} logged in with a recovery code.", user.Id);
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
if(result.IsLockedOut)
|
||||
{
|
||||
_logger.LogWarning("User with ID {UserId} account locked out.", user.Id);
|
||||
return RedirectToAction(nameof(Lockout));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Invalid recovery code entered for user with ID {UserId}", user.Id);
|
||||
ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
|
||||
return View();
|
||||
}
|
||||
}
|
||||
if (result.Succeeded)
|
||||
{
|
||||
_logger.LogInformation("User with ID {UserId} logged in with a recovery code.", user.Id);
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
if (result.IsLockedOut)
|
||||
{
|
||||
_logger.LogWarning("User with ID {UserId} account locked out.", user.Id);
|
||||
return RedirectToAction(nameof(Lockout));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Invalid recovery code entered for user with ID {UserId}", user.Id);
|
||||
ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
|
||||
return View();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public IActionResult Lockout()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public IActionResult Lockout()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public IActionResult Register(string returnUrl = null)
|
||||
{
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
return View();
|
||||
}
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Register(string returnUrl = null)
|
||||
{
|
||||
var policies = await _SettingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
|
||||
if (policies.LockSubscription)
|
||||
return RedirectToAction(nameof(HomeController.Index), "Home");
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
|
||||
{
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
if(ModelState.IsValid)
|
||||
{
|
||||
var policies = await _SettingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
|
||||
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, RequiresEmailConfirmation = policies.RequiresConfirmedEmail };
|
||||
var result = await _userManager.CreateAsync(user, model.Password);
|
||||
if(result.Succeeded)
|
||||
{
|
||||
_logger.LogInformation("User created a new account with password.");
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
|
||||
{
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
var policies = await _SettingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
|
||||
if (policies.LockSubscription)
|
||||
return RedirectToAction(nameof(HomeController.Index), "Home");
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, RequiresEmailConfirmation = policies.RequiresConfirmedEmail };
|
||||
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);
|
||||
if(admin.Count == 0)
|
||||
{
|
||||
_logger.LogInformation("Admin created.");
|
||||
await _RoleManager.CreateAsync(new IdentityRole(Roles.ServerAdmin));
|
||||
await _userManager.AddToRoleAsync(user, Roles.ServerAdmin);
|
||||
}
|
||||
var admin = await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin);
|
||||
if (admin.Count == 0)
|
||||
{
|
||||
_logger.LogInformation("Admin created.");
|
||||
await _RoleManager.CreateAsync(new IdentityRole(Roles.ServerAdmin));
|
||||
await _userManager.AddToRoleAsync(user, Roles.ServerAdmin);
|
||||
}
|
||||
|
||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
||||
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
|
||||
RegisteredUserId = user.Id;
|
||||
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
|
||||
_logger.LogInformation("User created a new account with password.");
|
||||
if(!policies.RequiresConfirmedEmail)
|
||||
{
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
TempData["StatusMessage"] = "Account created, please confirm your email";
|
||||
return View();
|
||||
}
|
||||
}
|
||||
AddErrors(result);
|
||||
}
|
||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
||||
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
|
||||
RegisteredUserId = user.Id;
|
||||
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
|
||||
_logger.LogInformation("User created a new account with password.");
|
||||
if (!policies.RequiresConfirmedEmail)
|
||||
{
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
TempData["StatusMessage"] = "Account created, please confirm your email";
|
||||
return View();
|
||||
}
|
||||
}
|
||||
AddErrors(result);
|
||||
}
|
||||
|
||||
// If we got this far, something failed, redisplay form
|
||||
return View(model);
|
||||
}
|
||||
// If we got this far, something failed, redisplay form
|
||||
return View(model);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test property
|
||||
/// </summary>
|
||||
public string RegisteredUserId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
/// <summary>
|
||||
/// Test property
|
||||
/// </summary>
|
||||
public string RegisteredUserId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
await _signInManager.SignOutAsync();
|
||||
_logger.LogInformation("User logged out.");
|
||||
return RedirectToAction(nameof(HomeController.Index), "Home");
|
||||
}
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
await _signInManager.SignOutAsync();
|
||||
_logger.LogInformation("User logged out.");
|
||||
return RedirectToAction(nameof(HomeController.Index), "Home");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public IActionResult ExternalLogin(string provider, string returnUrl = null)
|
||||
{
|
||||
// Request a redirect to the external login provider.
|
||||
var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Account", new
|
||||
{
|
||||
returnUrl
|
||||
});
|
||||
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
|
||||
return Challenge(properties, provider);
|
||||
}
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public IActionResult ExternalLogin(string provider, string returnUrl = null)
|
||||
{
|
||||
// Request a redirect to the external login provider.
|
||||
var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Account", new
|
||||
{
|
||||
returnUrl
|
||||
});
|
||||
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
|
||||
return Challenge(properties, provider);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
|
||||
{
|
||||
if(remoteError != null)
|
||||
{
|
||||
ErrorMessage = $"Error from external provider: {remoteError}";
|
||||
return RedirectToAction(nameof(Login));
|
||||
}
|
||||
var info = await _signInManager.GetExternalLoginInfoAsync();
|
||||
if(info == null)
|
||||
{
|
||||
return RedirectToAction(nameof(Login));
|
||||
}
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
|
||||
{
|
||||
if (remoteError != null)
|
||||
{
|
||||
ErrorMessage = $"Error from external provider: {remoteError}";
|
||||
return RedirectToAction(nameof(Login));
|
||||
}
|
||||
var info = await _signInManager.GetExternalLoginInfoAsync();
|
||||
if (info == null)
|
||||
{
|
||||
return RedirectToAction(nameof(Login));
|
||||
}
|
||||
|
||||
// Sign in the user with this external login provider if the user already has a login.
|
||||
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
|
||||
if(result.Succeeded)
|
||||
{
|
||||
_logger.LogInformation("User logged in with {Name} provider.", info.LoginProvider);
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
if(result.IsLockedOut)
|
||||
{
|
||||
return RedirectToAction(nameof(Lockout));
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the user does not have an account, then ask the user to create an account.
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
ViewData["LoginProvider"] = info.LoginProvider;
|
||||
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
|
||||
return View("ExternalLogin", new ExternalLoginViewModel { Email = email });
|
||||
}
|
||||
}
|
||||
// Sign in the user with this external login provider if the user already has a login.
|
||||
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
_logger.LogInformation("User logged in with {Name} provider.", info.LoginProvider);
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
if (result.IsLockedOut)
|
||||
{
|
||||
return RedirectToAction(nameof(Lockout));
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the user does not have an account, then ask the user to create an account.
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
ViewData["LoginProvider"] = info.LoginProvider;
|
||||
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
|
||||
return View("ExternalLogin", new ExternalLoginViewModel { Email = email });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> ExternalLoginConfirmation(ExternalLoginViewModel model, string returnUrl = null)
|
||||
{
|
||||
if(ModelState.IsValid)
|
||||
{
|
||||
// Get the information about the user from the external login provider
|
||||
var info = await _signInManager.GetExternalLoginInfoAsync();
|
||||
if(info == null)
|
||||
{
|
||||
throw new ApplicationException("Error loading external login information during confirmation.");
|
||||
}
|
||||
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
|
||||
var result = await _userManager.CreateAsync(user);
|
||||
if(result.Succeeded)
|
||||
{
|
||||
result = await _userManager.AddLoginAsync(user, info);
|
||||
if(result.Succeeded)
|
||||
{
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
}
|
||||
AddErrors(result);
|
||||
}
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> ExternalLoginConfirmation(ExternalLoginViewModel model, string returnUrl = null)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
// Get the information about the user from the external login provider
|
||||
var info = await _signInManager.GetExternalLoginInfoAsync();
|
||||
if (info == null)
|
||||
{
|
||||
throw new ApplicationException("Error loading external login information during confirmation.");
|
||||
}
|
||||
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
|
||||
var result = await _userManager.CreateAsync(user);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
result = await _userManager.AddLoginAsync(user, info);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
}
|
||||
AddErrors(result);
|
||||
}
|
||||
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
return View(nameof(ExternalLogin), model);
|
||||
}
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
return View(nameof(ExternalLogin), model);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> ConfirmEmail(string userId, string code)
|
||||
{
|
||||
if(userId == null || code == null)
|
||||
{
|
||||
return RedirectToAction(nameof(HomeController.Index), "Home");
|
||||
}
|
||||
var user = await _userManager.FindByIdAsync(userId);
|
||||
if(user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{userId}'.");
|
||||
}
|
||||
var result = await _userManager.ConfirmEmailAsync(user, code);
|
||||
return View(result.Succeeded ? "ConfirmEmail" : "Error");
|
||||
}
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> ConfirmEmail(string userId, string code)
|
||||
{
|
||||
if (userId == null || code == null)
|
||||
{
|
||||
return RedirectToAction(nameof(HomeController.Index), "Home");
|
||||
}
|
||||
var user = await _userManager.FindByIdAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{userId}'.");
|
||||
}
|
||||
var result = await _userManager.ConfirmEmailAsync(user, code);
|
||||
return View(result.Succeeded ? "ConfirmEmail" : "Error");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public IActionResult ForgotPassword()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public IActionResult ForgotPassword()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
|
||||
{
|
||||
if(ModelState.IsValid)
|
||||
{
|
||||
var user = await _userManager.FindByEmailAsync(model.Email);
|
||||
if(user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
|
||||
{
|
||||
// Don't reveal that the user does not exist or is not confirmed
|
||||
return RedirectToAction(nameof(ForgotPasswordConfirmation));
|
||||
}
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
var user = await _userManager.FindByEmailAsync(model.Email);
|
||||
if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
|
||||
{
|
||||
// Don't reveal that the user does not exist or is not confirmed
|
||||
return RedirectToAction(nameof(ForgotPasswordConfirmation));
|
||||
}
|
||||
|
||||
// For more information on how to enable account confirmation and password reset please
|
||||
// visit https://go.microsoft.com/fwlink/?LinkID=532713
|
||||
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
|
||||
var callbackUrl = Url.ResetPasswordCallbackLink(user.Id, code, Request.Scheme);
|
||||
await _emailSender.SendEmailAsync(model.Email, "Reset Password",
|
||||
$"Please reset your password by clicking here: <a href='{callbackUrl}'>link</a>");
|
||||
return RedirectToAction(nameof(ForgotPasswordConfirmation));
|
||||
}
|
||||
// For more information on how to enable account confirmation and password reset please
|
||||
// visit https://go.microsoft.com/fwlink/?LinkID=532713
|
||||
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
|
||||
var callbackUrl = Url.ResetPasswordCallbackLink(user.Id, code, Request.Scheme);
|
||||
await _emailSender.SendEmailAsync(model.Email, "Reset Password",
|
||||
$"Please reset your password by clicking here: <a href='{callbackUrl}'>link</a>");
|
||||
return RedirectToAction(nameof(ForgotPasswordConfirmation));
|
||||
}
|
||||
|
||||
// If we got this far, something failed, redisplay form
|
||||
return View(model);
|
||||
}
|
||||
// If we got this far, something failed, redisplay form
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public IActionResult ForgotPasswordConfirmation()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public IActionResult ForgotPasswordConfirmation()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public IActionResult ResetPassword(string code = null)
|
||||
{
|
||||
if(code == null)
|
||||
{
|
||||
throw new ApplicationException("A code must be supplied for password reset.");
|
||||
}
|
||||
var model = new ResetPasswordViewModel { Code = code };
|
||||
return View(model);
|
||||
}
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public IActionResult ResetPassword(string code = null)
|
||||
{
|
||||
if (code == null)
|
||||
{
|
||||
throw new ApplicationException("A code must be supplied for password reset.");
|
||||
}
|
||||
var model = new ResetPasswordViewModel { Code = code };
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> ResetPassword(ResetPasswordViewModel model)
|
||||
{
|
||||
if(!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
var user = await _userManager.FindByEmailAsync(model.Email);
|
||||
if(user == null)
|
||||
{
|
||||
// Don't reveal that the user does not exist
|
||||
return RedirectToAction(nameof(ResetPasswordConfirmation));
|
||||
}
|
||||
var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password);
|
||||
if(result.Succeeded)
|
||||
{
|
||||
return RedirectToAction(nameof(ResetPasswordConfirmation));
|
||||
}
|
||||
AddErrors(result);
|
||||
return View();
|
||||
}
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> ResetPassword(ResetPasswordViewModel model)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
var user = await _userManager.FindByEmailAsync(model.Email);
|
||||
if (user == null)
|
||||
{
|
||||
// Don't reveal that the user does not exist
|
||||
return RedirectToAction(nameof(ResetPasswordConfirmation));
|
||||
}
|
||||
var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
return RedirectToAction(nameof(ResetPasswordConfirmation));
|
||||
}
|
||||
AddErrors(result);
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public IActionResult ResetPasswordConfirmation()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public IActionResult ResetPasswordConfirmation()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult AccessDenied()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
[HttpGet]
|
||||
public IActionResult AccessDenied()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
#region Helpers
|
||||
|
||||
private void AddErrors(IdentityResult result)
|
||||
{
|
||||
foreach(var error in result.Errors)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, error.Description);
|
||||
}
|
||||
}
|
||||
private void AddErrors(IdentityResult result)
|
||||
{
|
||||
foreach (var error in result.Errors)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, error.Description);
|
||||
}
|
||||
}
|
||||
|
||||
private IActionResult RedirectToLocal(string returnUrl)
|
||||
{
|
||||
if(Url.IsLocalUrl(returnUrl))
|
||||
{
|
||||
return Redirect(returnUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
return RedirectToAction(nameof(HomeController.Index), "Home");
|
||||
}
|
||||
}
|
||||
private IActionResult RedirectToLocal(string returnUrl)
|
||||
{
|
||||
if (Url.IsLocalUrl(returnUrl))
|
||||
{
|
||||
return Redirect(returnUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
return RedirectToAction(nameof(HomeController.Index), "Home");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
143
BTCPayServer/Controllers/CallbackController.cs
Normal file
143
BTCPayServer/Controllers/CallbackController.cs
Normal file
@ -0,0 +1,143 @@
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Services.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;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Events;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public class CallbackController : Controller
|
||||
{
|
||||
public class CallbackSettings
|
||||
{
|
||||
public string Token
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
SettingsRepository _Settings;
|
||||
Network _Network;
|
||||
ExplorerClient _Explorer;
|
||||
BTCPayServerOptions _Options;
|
||||
EventAggregator _EventAggregator;
|
||||
IServer _Server;
|
||||
public CallbackController(SettingsRepository repo,
|
||||
ExplorerClient explorer,
|
||||
EventAggregator eventAggregator,
|
||||
BTCPayServerOptions options,
|
||||
IServer server,
|
||||
Network network)
|
||||
{
|
||||
_Settings = repo;
|
||||
_Network = network;
|
||||
_Explorer = explorer;
|
||||
_Options = options;
|
||||
_EventAggregator = eventAggregator;
|
||||
_Server = server;
|
||||
}
|
||||
|
||||
[Route("callbacks/transactions")]
|
||||
[HttpPost]
|
||||
public async Task NewTransaction(string token)
|
||||
{
|
||||
await AssertToken(token);
|
||||
//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)
|
||||
{
|
||||
var evt = new TxOutReceivedEvent();
|
||||
evt.ScriptPubKey = output.ScriptPubKey;
|
||||
evt.Address = output.ScriptPubKey.GetDestinationAddress(_Network);
|
||||
_EventAggregator.Publish(evt);
|
||||
}
|
||||
}
|
||||
|
||||
[Route("callbacks/blocks")]
|
||||
[HttpPost]
|
||||
public async Task NewBlock(string token)
|
||||
{
|
||||
await AssertToken(token);
|
||||
_EventAggregator.Publish(new NewBlockEvent());
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
string token = await GetToken();
|
||||
return BuildCallbackUri("callbacks/transactions?token=" + token);
|
||||
}
|
||||
|
||||
public async Task RegisterCallbackUriAsync(DerivationStrategyBase derivationScheme)
|
||||
{
|
||||
var uri = await GetCallbackUriAsync();
|
||||
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()
|
||||
{
|
||||
string token = await GetToken();
|
||||
return BuildCallbackUri("callbacks/blocks?token=" + token);
|
||||
}
|
||||
|
||||
private Uri BuildCallbackUri(string callbackPath)
|
||||
{
|
||||
var address = _Server.Features.Get<IServerAddressesFeature>().Addresses
|
||||
.Select(c => new Uri(TransformToRoutable(c)))
|
||||
.First();
|
||||
var baseUrl = _Options.InternalUrl == null ? address.AbsoluteUri : _Options.InternalUrl.AbsoluteUri;
|
||||
baseUrl = baseUrl.WithTrailingSlash();
|
||||
return new Uri(baseUrl + callbackPath);
|
||||
}
|
||||
|
||||
private string TransformToRoutable(string host)
|
||||
{
|
||||
if (host.StartsWith("http://0.0.0.0"))
|
||||
host = host.Replace("http://0.0.0.0", "http://127.0.0.1");
|
||||
return host;
|
||||
}
|
||||
|
||||
public async Task<Uri> RegisterCallbackBlockUriAsync(Uri uri)
|
||||
{
|
||||
await _Explorer.SubscribeToBlocksAsync(uri);
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
}
|
@ -10,112 +10,129 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using BTCPayServer.Services.Stores;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class InvoiceController
|
||||
[EnableCors("BitpayAPI")]
|
||||
[BitpayAPIConstraint]
|
||||
public class InvoiceControllerAPI : Controller
|
||||
{
|
||||
[HttpPost]
|
||||
[Route("invoices")]
|
||||
[MediaTypeConstraint("application/json")]
|
||||
[BitpayAPIConstraint]
|
||||
public async Task<DataWrapper<InvoiceResponse>> CreateInvoice([FromBody] Invoice invoice)
|
||||
{
|
||||
var bitToken = await CheckTokenPermissionAsync(Facade.Merchant, invoice.Token);
|
||||
var store = await FindStore(bitToken);
|
||||
return await CreateInvoiceCore(invoice, store);
|
||||
}
|
||||
private InvoiceController _InvoiceController;
|
||||
private InvoiceRepository _InvoiceRepository;
|
||||
private TokenRepository _TokenRepository;
|
||||
private StoreRepository _StoreRepository;
|
||||
|
||||
[HttpGet]
|
||||
[Route("invoices/{id}")]
|
||||
[BitpayAPIConstraint]
|
||||
public async Task<DataWrapper<InvoiceResponse>> GetInvoice(string id, string token)
|
||||
{
|
||||
var bitToken = await CheckTokenPermissionAsync(Facade.Merchant, token);
|
||||
var store = await FindStore(bitToken);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(store.Id, id);
|
||||
if(invoice == null)
|
||||
throw new BitpayHttpException(404, "Object not found");
|
||||
public InvoiceControllerAPI(InvoiceController invoiceController,
|
||||
InvoiceRepository invoceRepository,
|
||||
TokenRepository tokenRepository,
|
||||
StoreRepository storeRepository)
|
||||
{
|
||||
this._InvoiceController = invoiceController;
|
||||
this._InvoiceRepository = invoceRepository;
|
||||
this._TokenRepository = tokenRepository;
|
||||
this._StoreRepository = storeRepository;
|
||||
}
|
||||
|
||||
var resp = invoice.EntityToDTO();
|
||||
return new DataWrapper<InvoiceResponse>(resp);
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("invoices")]
|
||||
[MediaTypeConstraint("application/json")]
|
||||
public async Task<DataWrapper<InvoiceResponse>> CreateInvoice([FromBody] Invoice invoice)
|
||||
{
|
||||
var bitToken = await CheckTokenPermissionAsync(Facade.Merchant, invoice.Token);
|
||||
var store = await FindStore(bitToken);
|
||||
return await _InvoiceController.CreateInvoiceCore(invoice, store, HttpContext.Request.GetAbsoluteRoot());
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("invoices")]
|
||||
[BitpayAPIConstraint]
|
||||
public async Task<DataWrapper<InvoiceResponse[]>> GetInvoices(
|
||||
string token,
|
||||
DateTimeOffset? dateStart = null,
|
||||
DateTimeOffset? dateEnd = null,
|
||||
string orderId = null,
|
||||
string itemCode = null,
|
||||
string status = null,
|
||||
int? limit = null,
|
||||
int? offset = null)
|
||||
{
|
||||
if(dateEnd != null)
|
||||
dateEnd = dateEnd.Value + TimeSpan.FromDays(1); //Should include the end day
|
||||
var bitToken = await CheckTokenPermissionAsync(Facade.Merchant, token);
|
||||
var store = await FindStore(bitToken);
|
||||
var query = new InvoiceQuery()
|
||||
{
|
||||
Count = limit,
|
||||
Skip = offset,
|
||||
EndDate = dateEnd,
|
||||
StartDate = dateStart,
|
||||
OrderId = orderId,
|
||||
ItemCode = itemCode,
|
||||
Status = status,
|
||||
StoreId = store.Id
|
||||
};
|
||||
[HttpGet]
|
||||
[Route("invoices/{id}")]
|
||||
public async Task<DataWrapper<InvoiceResponse>> GetInvoice(string id, string token)
|
||||
{
|
||||
var bitToken = await CheckTokenPermissionAsync(Facade.Merchant, token);
|
||||
var store = await FindStore(bitToken);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(store.Id, id);
|
||||
if (invoice == null)
|
||||
throw new BitpayHttpException(404, "Object not found");
|
||||
|
||||
var resp = invoice.EntityToDTO();
|
||||
return new DataWrapper<InvoiceResponse>(resp);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("invoices")]
|
||||
public async Task<DataWrapper<InvoiceResponse[]>> GetInvoices(
|
||||
string token,
|
||||
DateTimeOffset? dateStart = null,
|
||||
DateTimeOffset? dateEnd = null,
|
||||
string orderId = null,
|
||||
string itemCode = null,
|
||||
string status = null,
|
||||
int? limit = null,
|
||||
int? offset = null)
|
||||
{
|
||||
if (dateEnd != null)
|
||||
dateEnd = dateEnd.Value + TimeSpan.FromDays(1); //Should include the end day
|
||||
var bitToken = await CheckTokenPermissionAsync(Facade.Merchant, token);
|
||||
var store = await FindStore(bitToken);
|
||||
var query = new InvoiceQuery()
|
||||
{
|
||||
Count = limit,
|
||||
Skip = offset,
|
||||
EndDate = dateEnd,
|
||||
StartDate = dateStart,
|
||||
OrderId = orderId,
|
||||
ItemCode = itemCode,
|
||||
Status = status,
|
||||
StoreId = store.Id
|
||||
};
|
||||
|
||||
|
||||
var entities = (await _InvoiceRepository.GetInvoices(query))
|
||||
.Select((o) => o.EntityToDTO()).ToArray();
|
||||
var entities = (await _InvoiceRepository.GetInvoices(query))
|
||||
.Select((o) => o.EntityToDTO()).ToArray();
|
||||
|
||||
return DataWrapper.Create(entities);
|
||||
}
|
||||
return DataWrapper.Create(entities);
|
||||
}
|
||||
|
||||
private async Task<BitTokenEntity> CheckTokenPermissionAsync(Facade facade, string expectedToken)
|
||||
{
|
||||
if(facade == null)
|
||||
throw new ArgumentNullException(nameof(facade));
|
||||
private async Task<BitTokenEntity> CheckTokenPermissionAsync(Facade facade, string expectedToken)
|
||||
{
|
||||
if (facade == null)
|
||||
throw new ArgumentNullException(nameof(facade));
|
||||
|
||||
var actualTokens = (await _TokenRepository.GetTokens(this.GetBitIdentity().SIN)).Where(t => t.Active).ToArray();
|
||||
actualTokens = actualTokens.SelectMany(t => GetCompatibleTokens(t)).ToArray();
|
||||
|
||||
var actualToken = actualTokens.FirstOrDefault(a => a.Value.Equals(expectedToken, StringComparison.Ordinal));
|
||||
if(expectedToken == null || actualToken == null)
|
||||
{
|
||||
Logs.PayServer.LogDebug($"No token found for facade {facade} for SIN {this.GetBitIdentity().SIN}");
|
||||
throw new BitpayHttpException(401, $"This endpoint does not support the `{actualTokens.Select(a => a.Name).Concat(new[] { "user" }).FirstOrDefault()}` facade");
|
||||
}
|
||||
return actualToken;
|
||||
}
|
||||
var actualTokens = (await _TokenRepository.GetTokens(this.GetBitIdentity().SIN)).ToArray();
|
||||
actualTokens = actualTokens.SelectMany(t => GetCompatibleTokens(t)).ToArray();
|
||||
|
||||
private IEnumerable<BitTokenEntity> GetCompatibleTokens(BitTokenEntity token)
|
||||
{
|
||||
if(token.Name == Facade.Merchant.ToString())
|
||||
{
|
||||
yield return token.Clone(Facade.User);
|
||||
yield return token.Clone(Facade.PointOfSale);
|
||||
}
|
||||
if(token.Name == Facade.PointOfSale.ToString())
|
||||
{
|
||||
yield return token.Clone(Facade.User);
|
||||
}
|
||||
yield return token;
|
||||
}
|
||||
var actualToken = actualTokens.FirstOrDefault(a => a.Value.Equals(expectedToken, StringComparison.Ordinal));
|
||||
if (expectedToken == null || actualToken == null)
|
||||
{
|
||||
Logs.PayServer.LogDebug($"No token found for facade {facade} for SIN {this.GetBitIdentity().SIN}");
|
||||
throw new BitpayHttpException(401, $"This endpoint does not support the `{actualTokens.Select(a => a.Facade).Concat(new[] { "user" }).FirstOrDefault()}` facade");
|
||||
}
|
||||
return actualToken;
|
||||
}
|
||||
|
||||
private async Task<StoreData> FindStore(BitTokenEntity bitToken)
|
||||
{
|
||||
var store = await _StoreRepository.FindStore(bitToken.PairedId);
|
||||
if(store == null)
|
||||
throw new BitpayHttpException(401, "Unknown store");
|
||||
return store;
|
||||
}
|
||||
private IEnumerable<BitTokenEntity> GetCompatibleTokens(BitTokenEntity token)
|
||||
{
|
||||
if (token.Facade == Facade.Merchant.ToString())
|
||||
{
|
||||
yield return token.Clone(Facade.User);
|
||||
yield return token.Clone(Facade.PointOfSale);
|
||||
}
|
||||
if (token.Facade == Facade.PointOfSale.ToString())
|
||||
{
|
||||
yield return token.Clone(Facade.User);
|
||||
}
|
||||
yield return token;
|
||||
}
|
||||
|
||||
}
|
||||
private async Task<StoreData> FindStore(BitTokenEntity bitToken)
|
||||
{
|
||||
var store = await _StoreRepository.FindStore(bitToken.StoreId);
|
||||
if (store == null)
|
||||
throw new BitpayHttpException(401, "Unknown store");
|
||||
return store;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -14,59 +14,59 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class InvoiceController
|
||||
{
|
||||
[HttpGet]
|
||||
[Route("i/{invoiceId}")]
|
||||
[AcceptMediaTypeConstraint("application/bitcoin-paymentrequest")]
|
||||
public async Task<IActionResult> GetInvoiceRequest(string invoiceId)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if(invoice == null || invoice.IsExpired())
|
||||
return NotFound();
|
||||
[HttpGet]
|
||||
[Route("i/{invoiceId}")]
|
||||
[AcceptMediaTypeConstraint("application/bitcoin-paymentrequest")]
|
||||
public async Task<IActionResult> GetInvoiceRequest(string invoiceId)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if (invoice == null || invoice.IsExpired())
|
||||
return NotFound();
|
||||
|
||||
var dto = invoice.EntityToDTO();
|
||||
PaymentRequest request = new PaymentRequest
|
||||
{
|
||||
DetailsVersion = 1
|
||||
};
|
||||
request.Details.Expires = invoice.ExpirationTime;
|
||||
request.Details.Memo = invoice.ProductInformation.ItemDesc;
|
||||
request.Details.Network = _Network;
|
||||
request.Details.Outputs.Add(new PaymentOutput() { Amount = dto.BTCDue, Script = BitcoinAddress.Create(dto.BitcoinAddress, _Network).ScriptPubKey });
|
||||
request.Details.MerchantData = Encoding.UTF8.GetBytes(invoice.Id);
|
||||
request.Details.Time = DateTimeOffset.UtcNow;
|
||||
request.Details.PaymentUrl = new Uri(invoice.ServerUrl.WithTrailingSlash() + ($"i/{invoice.Id}"), UriKind.Absolute);
|
||||
var dto = invoice.EntityToDTO();
|
||||
PaymentRequest request = new PaymentRequest
|
||||
{
|
||||
DetailsVersion = 1
|
||||
};
|
||||
request.Details.Expires = invoice.ExpirationTime;
|
||||
request.Details.Memo = invoice.ProductInformation.ItemDesc;
|
||||
request.Details.Network = _Network;
|
||||
request.Details.Outputs.Add(new PaymentOutput() { Amount = dto.BTCDue, Script = BitcoinAddress.Create(dto.BitcoinAddress, _Network).ScriptPubKey });
|
||||
request.Details.MerchantData = Encoding.UTF8.GetBytes(invoice.Id);
|
||||
request.Details.Time = DateTimeOffset.UtcNow;
|
||||
request.Details.PaymentUrl = new Uri(invoice.ServerUrl.WithTrailingSlash() + ($"i/{invoice.Id}"), UriKind.Absolute);
|
||||
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
if(store == null)
|
||||
throw new BitpayHttpException(401, "Unknown store");
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
if (store == null)
|
||||
throw new BitpayHttpException(401, "Unknown store");
|
||||
|
||||
if(store.StoreCertificate != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
request.Sign(store.StoreCertificate, PKIType.X509SHA256);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Logs.PayServer.LogWarning(ex, "Error while signing payment request");
|
||||
}
|
||||
}
|
||||
if (store.StoreCertificate != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
request.Sign(store.StoreCertificate, PKIType.X509SHA256);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logs.PayServer.LogWarning(ex, "Error while signing payment request");
|
||||
}
|
||||
}
|
||||
|
||||
return new PaymentRequestActionResult(request);
|
||||
}
|
||||
return new PaymentRequestActionResult(request);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("i/{invoiceId}", Order = 99)]
|
||||
[MediaTypeConstraint("application/bitcoin-payment")]
|
||||
public async Task<IActionResult> PostPayment(string invoiceId)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if(invoice == null || invoice.IsExpired())
|
||||
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());
|
||||
return new PaymentAckActionResult(payment.CreateACK(invoiceId + " is currently processing, thanks for your purchase..."));
|
||||
}
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("i/{invoiceId}", Order = 99)]
|
||||
[MediaTypeConstraint("application/bitcoin-payment")]
|
||||
public async Task<IActionResult> PostPayment(string invoiceId)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if (invoice == null || invoice.IsExpired())
|
||||
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());
|
||||
return new PaymentAckActionResult(payment.CreateACK(invoiceId + " is currently processing, thanks for your purchase..."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using NBitcoin;
|
||||
@ -14,193 +15,374 @@ using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using BTCPayServer.Events;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class InvoiceController
|
||||
{
|
||||
[HttpPost]
|
||||
[Route("invoices/{invoiceId}")]
|
||||
public IActionResult Invoice(string invoiceId, string command)
|
||||
{
|
||||
if (command == "refresh")
|
||||
{
|
||||
_Watcher.Watch(invoiceId);
|
||||
}
|
||||
StatusMessage = "Invoice is state is being refreshed, please refresh the page soon...";
|
||||
return RedirectToAction(nameof(Invoice), new
|
||||
{
|
||||
invoiceId = invoiceId
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("i/{invoiceId}")]
|
||||
[AcceptMediaTypeConstraint("application/bitcoin-paymentrequest", false)]
|
||||
public async Task<IActionResult> Checkout(string invoiceId)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if(invoice == null)
|
||||
return NotFound();
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
var dto = invoice.EntityToDTO();
|
||||
[HttpGet]
|
||||
[Route("invoices/{invoiceId}")]
|
||||
public async Task<IActionResult> Invoice(string invoiceId)
|
||||
{
|
||||
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
UserId = GetUserId(),
|
||||
InvoiceId = invoiceId
|
||||
})).FirstOrDefault();
|
||||
if (invoice == null)
|
||||
return NotFound();
|
||||
|
||||
var model = new PaymentModel()
|
||||
{
|
||||
OrderId = invoice.OrderId,
|
||||
InvoiceId = invoice.Id,
|
||||
BTCAddress = invoice.DepositAddress.ToString(),
|
||||
BTCAmount = (invoice.GetTotalCryptoDue() - invoice.TxFee).ToString(),
|
||||
BTCTotalDue = invoice.GetTotalCryptoDue().ToString(),
|
||||
BTCDue = invoice.GetCryptoDue().ToString(),
|
||||
CustomerEmail = invoice.RefundMail,
|
||||
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
|
||||
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
|
||||
ItemDesc = invoice.ProductInformation.ItemDesc,
|
||||
Rate = invoice.Rate.ToString(),
|
||||
RedirectUrl = invoice.RedirectURL,
|
||||
StoreName = store.StoreName,
|
||||
TxFees = invoice.TxFee.ToString(),
|
||||
InvoiceBitcoinUrl = dto.PaymentUrls.BIP72,
|
||||
TxCount = invoice.GetTxCount(),
|
||||
BTCPaid = invoice.GetTotalPaid().ToString(),
|
||||
Status = invoice.Status
|
||||
};
|
||||
var dto = invoice.EntityToDTO();
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
InvoiceDetailsModel model = new InvoiceDetailsModel()
|
||||
{
|
||||
StoreName = store.StoreName,
|
||||
StoreLink = Url.Action(nameof(StoresController.UpdateStore), "Stores", new { storeId = store.Id }),
|
||||
Id = invoice.Id,
|
||||
Status = invoice.Status,
|
||||
RefundEmail = invoice.RefundMail,
|
||||
CreatedDate = invoice.InvoiceTime,
|
||||
ExpirationDate = invoice.ExpirationTime,
|
||||
OrderId = invoice.OrderId,
|
||||
BuyerInformation = invoice.BuyerInformation,
|
||||
Rate = invoice.Rate,
|
||||
Fiat = dto.Price + " " + dto.Currency,
|
||||
BTC = invoice.GetTotalCryptoDue().ToString() + " BTC",
|
||||
BTCDue = invoice.GetCryptoDue().ToString() + " BTC",
|
||||
BTCPaid = invoice.GetTotalPaid().ToString() + " BTC",
|
||||
NetworkFee = invoice.GetNetworkFee().ToString() + " BTC",
|
||||
NotificationUrl = invoice.NotificationURL,
|
||||
ProductInformation = invoice.ProductInformation,
|
||||
BitcoinAddress = invoice.DepositAddress,
|
||||
PaymentUrl = dto.PaymentUrls.BIP72
|
||||
};
|
||||
|
||||
var expiration = TimeSpan.FromSeconds((double)model.ExpirationSeconds);
|
||||
model.TimeLeft = PrettyPrint(expiration);
|
||||
return View(model);
|
||||
}
|
||||
var payments = invoice
|
||||
.Payments
|
||||
.Select(async payment =>
|
||||
{
|
||||
var m = new InvoiceDetailsModel.Payment();
|
||||
m.DepositAddress = payment.Output.ScriptPubKey.GetDestinationAddress(_Network);
|
||||
m.Confirmations = (await _Explorer.GetTransactionAsync(payment.Outpoint.Hash))?.Confirmations ?? 0;
|
||||
m.TransactionId = payment.Outpoint.Hash.ToString();
|
||||
m.ReceivedTime = payment.ReceivedTime;
|
||||
m.TransactionLink = _Network == Network.Main ? $"https://www.smartbit.com.au/tx/{m.TransactionId}" : $"https://testnet.smartbit.com.au/tx/{m.TransactionId}";
|
||||
return m;
|
||||
})
|
||||
.ToArray();
|
||||
await Task.WhenAll(payments);
|
||||
model.Payments = payments.Select(p => p.GetAwaiter().GetResult()).ToList();
|
||||
model.StatusMessage = StatusMessage;
|
||||
return View(model);
|
||||
}
|
||||
|
||||
private string PrettyPrint(TimeSpan expiration)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if(expiration.Days >= 1)
|
||||
builder.Append(expiration.Days.ToString());
|
||||
if(expiration.Hours >= 1)
|
||||
builder.Append(expiration.Hours.ToString("00"));
|
||||
builder.Append($"{expiration.Minutes.ToString("00")}:{expiration.Seconds.ToString("00")}");
|
||||
return builder.ToString();
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("i/{invoiceId}")]
|
||||
[Route("invoice")]
|
||||
[AcceptMediaTypeConstraint("application/bitcoin-paymentrequest", false)]
|
||||
[XFrameOptionsAttribute(null)]
|
||||
public async Task<IActionResult> Checkout(string invoiceId, string id = null)
|
||||
{
|
||||
//Keep compatibility with Bitpay
|
||||
invoiceId = invoiceId ?? id;
|
||||
id = invoiceId;
|
||||
////
|
||||
|
||||
[HttpGet]
|
||||
[Route("i/{invoiceId}/status")]
|
||||
public async Task<IActionResult> GetStatus(string invoiceId)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if(invoice == null)
|
||||
return NotFound();
|
||||
return Content(invoice.Status);
|
||||
}
|
||||
var model = await GetInvoiceModel(invoiceId);
|
||||
if (model == null)
|
||||
return NotFound();
|
||||
|
||||
[HttpPost]
|
||||
[Route("i/{invoiceId}/UpdateCustomer")]
|
||||
public async Task<IActionResult> UpdateCustomer(string invoiceId, [FromBody]UpdateCustomerModel data)
|
||||
{
|
||||
if(!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
await _InvoiceRepository.UpdateInvoice(invoiceId, data).ConfigureAwait(false);
|
||||
return Ok();
|
||||
}
|
||||
return View(nameof(Checkout), model);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("invoices")]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> ListInvoices(string searchTerm = null, int skip = 0, int count = 20)
|
||||
{
|
||||
var model = new InvoicesModel();
|
||||
foreach(var invoice in await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
TextSearch = searchTerm,
|
||||
Count = count,
|
||||
Skip = skip,
|
||||
UserId = GetUserId()
|
||||
}))
|
||||
{
|
||||
model.SearchTerm = searchTerm;
|
||||
model.Invoices.Add(new InvoiceModel()
|
||||
{
|
||||
Status = invoice.Status,
|
||||
Date = invoice.InvoiceTime,
|
||||
InvoiceId = invoice.Id,
|
||||
AmountCurrency = $"{invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture)} {invoice.ProductInformation.Currency}"
|
||||
});
|
||||
}
|
||||
model.Skip = skip;
|
||||
model.Count = count;
|
||||
model.StatusMessage = StatusMessage;
|
||||
return View(model);
|
||||
}
|
||||
private async Task<PaymentModel> GetInvoiceModel(string invoiceId)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if (invoice == null)
|
||||
return null;
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
var dto = invoice.EntityToDTO();
|
||||
var currency = invoice.ProductInformation.Currency;
|
||||
var model = new PaymentModel()
|
||||
{
|
||||
ServerUrl = HttpContext.Request.GetAbsoluteRoot(),
|
||||
OrderId = invoice.OrderId,
|
||||
InvoiceId = invoice.Id,
|
||||
BtcAddress = invoice.DepositAddress.ToString(),
|
||||
BtcAmount = (invoice.GetTotalCryptoDue() - invoice.TxFee).ToString(),
|
||||
BtcTotalDue = invoice.GetTotalCryptoDue().ToString(),
|
||||
BtcDue = invoice.GetCryptoDue().ToString(),
|
||||
CustomerEmail = invoice.RefundMail,
|
||||
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
|
||||
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
|
||||
ItemDesc = invoice.ProductInformation.ItemDesc,
|
||||
Rate = invoice.Rate.ToString("C", _CurrencyNameTable.GetCurrencyProvider(currency)) + $" ({currency})",
|
||||
MerchantRefLink = invoice.RedirectURL ?? "/",
|
||||
StoreName = store.StoreName,
|
||||
TxFees = invoice.TxFee.ToString(),
|
||||
InvoiceBitcoinUrl = dto.PaymentUrls.BIP72,
|
||||
TxCount = invoice.GetTxCount(),
|
||||
BtcPaid = invoice.GetTotalPaid().ToString(),
|
||||
Status = invoice.Status
|
||||
};
|
||||
|
||||
[HttpGet]
|
||||
[Route("invoices/create")]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> CreateInvoice()
|
||||
{
|
||||
var stores = await GetStores(GetUserId());
|
||||
if(stores.Count() == 0)
|
||||
{
|
||||
StatusMessage = "Error: You need to create at least one store before creating a transaction";
|
||||
return RedirectToAction(nameof(StoresController.ListStores), "Stores");
|
||||
}
|
||||
return View(new CreateInvoiceModel() { Stores = stores });
|
||||
}
|
||||
var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds);
|
||||
model.TimeLeft = PrettyPrint(expiration);
|
||||
return model;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("invoices/create")]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> CreateInvoice(CreateInvoiceModel model)
|
||||
{
|
||||
if(!ModelState.IsValid)
|
||||
{
|
||||
model.Stores = await GetStores(GetUserId(), model.StoreId);
|
||||
return View(model);
|
||||
}
|
||||
var store = await _StoreRepository.FindStore(model.StoreId, GetUserId());
|
||||
if(string.IsNullOrEmpty(store.DerivationStrategy))
|
||||
{
|
||||
StatusMessage = "Error: You need to configure the derivation scheme in order to create an invoice";
|
||||
return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new
|
||||
{
|
||||
storeId = store.Id
|
||||
});
|
||||
}
|
||||
var result = await CreateInvoiceCore(new Invoice()
|
||||
{
|
||||
Price = model.Amount.Value,
|
||||
Currency = "USD",
|
||||
PosData = model.PosData,
|
||||
OrderId = model.OrderId,
|
||||
//RedirectURL = redirect + "redirect",
|
||||
NotificationURL = model.NotificationUrl,
|
||||
ItemDesc = model.ItemDesc,
|
||||
FullNotifications = true,
|
||||
BuyerEmail = model.BuyerEmail,
|
||||
}, store);
|
||||
|
||||
StatusMessage = $"Invoice {result.Data.Id} just created!";
|
||||
return RedirectToAction(nameof(ListInvoices));
|
||||
}
|
||||
private string PrettyPrint(TimeSpan expiration)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (expiration.Days >= 1)
|
||||
builder.Append(expiration.Days.ToString());
|
||||
if (expiration.Hours >= 1)
|
||||
builder.Append(expiration.Hours.ToString("00"));
|
||||
builder.Append($"{expiration.Minutes.ToString("00")}:{expiration.Seconds.ToString("00")}");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private async Task<SelectList> GetStores(string userId, string storeId = null)
|
||||
{
|
||||
return new SelectList(await _StoreRepository.GetStoresByUserId(userId), nameof(StoreData.Id), nameof(StoreData.StoreName), storeId);
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("i/{invoiceId}/status")]
|
||||
public async Task<IActionResult> GetStatus(string invoiceId)
|
||||
{
|
||||
var model = await GetInvoiceModel(invoiceId);
|
||||
if (model == null)
|
||||
return NotFound();
|
||||
return Json(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public IActionResult SearchInvoice(InvoicesModel invoices)
|
||||
{
|
||||
return RedirectToAction(nameof(ListInvoices), new
|
||||
{
|
||||
searchTerm = invoices.SearchTerm,
|
||||
skip = invoices.Skip,
|
||||
count = invoices.Count,
|
||||
});
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("i/{invoiceId}/status/ws")]
|
||||
public async Task<IActionResult> GetStatusWebSocket(string invoiceId)
|
||||
{
|
||||
if (!HttpContext.WebSockets.IsWebSocketRequest)
|
||||
return NotFound();
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if (invoice == null || invoice.Status == "complete" || invoice.Status == "invalid" || invoice.Status == "expired")
|
||||
return NotFound();
|
||||
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
||||
CompositeDisposable leases = new CompositeDisposable();
|
||||
try
|
||||
{
|
||||
_EventAggregator.Subscribe<Events.InvoiceDataChangedEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId));
|
||||
_EventAggregator.Subscribe<Events.InvoicePaymentEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId));
|
||||
_EventAggregator.Subscribe<Events.InvoiceStatusChangedEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId));
|
||||
while (true)
|
||||
{
|
||||
var message = await webSocket.ReceiveAsync(DummyBuffer, default(CancellationToken));
|
||||
if (message.MessageType == WebSocketMessageType.Close)
|
||||
break;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
leases.Dispose();
|
||||
await CloseSocket(webSocket);
|
||||
}
|
||||
return new NoResponse();
|
||||
}
|
||||
|
||||
[TempData]
|
||||
public string StatusMessage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
class NoResponse : IActionResult
|
||||
{
|
||||
public Task ExecuteResultAsync(ActionContext context)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetUserId()
|
||||
{
|
||||
return _UserManager.GetUserId(User);
|
||||
}
|
||||
}
|
||||
ArraySegment<Byte> DummyBuffer = new ArraySegment<Byte>(new Byte[1]);
|
||||
private async Task NotifySocket(WebSocket webSocket, string invoiceId, string expectedId)
|
||||
{
|
||||
if (invoiceId != expectedId || webSocket.State != WebSocketState.Open)
|
||||
return;
|
||||
CancellationTokenSource cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(5000);
|
||||
try
|
||||
{
|
||||
await webSocket.SendAsync(DummyBuffer, WebSocketMessageType.Binary, true, cts.Token);
|
||||
}
|
||||
catch { await CloseSocket(webSocket); }
|
||||
}
|
||||
|
||||
private static async Task CloseSocket(WebSocket webSocket)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (webSocket.State == WebSocketState.Open)
|
||||
{
|
||||
CancellationTokenSource cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(5000);
|
||||
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", cts.Token);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
finally { webSocket.Dispose(); }
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("i/{invoiceId}/UpdateCustomer")]
|
||||
public async Task<IActionResult> UpdateCustomer(string invoiceId, [FromBody]UpdateCustomerModel data)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
await _InvoiceRepository.UpdateInvoice(invoiceId, data).ConfigureAwait(false);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("invoices")]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> ListInvoices(string searchTerm = null, int skip = 0, int count = 20)
|
||||
{
|
||||
var model = new InvoicesModel();
|
||||
var filterString = new SearchString(searchTerm);
|
||||
foreach (var invoice in await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
TextSearch = filterString.TextSearch,
|
||||
Count = count,
|
||||
Skip = skip,
|
||||
UserId = GetUserId(),
|
||||
Status = filterString.Filters.TryGet("status"),
|
||||
StoreId = filterString.Filters.TryGet("storeid")
|
||||
}))
|
||||
{
|
||||
model.SearchTerm = searchTerm;
|
||||
model.Invoices.Add(new InvoiceModel()
|
||||
{
|
||||
Status = invoice.Status,
|
||||
Date = invoice.InvoiceTime,
|
||||
InvoiceId = invoice.Id,
|
||||
AmountCurrency = $"{invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture)} {invoice.ProductInformation.Currency}"
|
||||
});
|
||||
}
|
||||
model.Skip = skip;
|
||||
model.Count = count;
|
||||
model.StatusMessage = StatusMessage;
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("invoices/create")]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> CreateInvoice()
|
||||
{
|
||||
var stores = await GetStores(GetUserId());
|
||||
if (stores.Count() == 0)
|
||||
{
|
||||
StatusMessage = "Error: You need to create at least one store before creating a transaction";
|
||||
return RedirectToAction(nameof(StoresController.ListStores), "Stores");
|
||||
}
|
||||
return View(new CreateInvoiceModel() { Stores = stores });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("invoices/create")]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> CreateInvoice(CreateInvoiceModel model)
|
||||
{
|
||||
model.Stores = await GetStores(GetUserId(), model.StoreId);
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
var store = await _StoreRepository.FindStore(model.StoreId, GetUserId());
|
||||
if (string.IsNullOrEmpty(store.DerivationStrategy))
|
||||
{
|
||||
StatusMessage = "Error: You need to configure the derivation scheme in order to create an invoice";
|
||||
return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new
|
||||
{
|
||||
storeId = store.Id
|
||||
});
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await CreateInvoiceCore(new Invoice()
|
||||
{
|
||||
Price = model.Amount.Value,
|
||||
Currency = model.Currency,
|
||||
PosData = model.PosData,
|
||||
OrderId = model.OrderId,
|
||||
//RedirectURL = redirect + "redirect",
|
||||
NotificationURL = model.NotificationUrl,
|
||||
ItemDesc = model.ItemDesc,
|
||||
FullNotifications = true,
|
||||
BuyerEmail = model.BuyerEmail,
|
||||
}, store, HttpContext.Request.GetAbsoluteRoot());
|
||||
|
||||
StatusMessage = $"Invoice {result.Data.Id} just created!";
|
||||
return RedirectToAction(nameof(ListInvoices));
|
||||
}
|
||||
catch (RateUnavailableException)
|
||||
{
|
||||
ModelState.TryAddModelError(nameof(model.Currency), "Unsupported currency");
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<SelectList> GetStores(string userId, string storeId = null)
|
||||
{
|
||||
return new SelectList(await _StoreRepository.GetStoresByUserId(userId), nameof(StoreData.Id), nameof(StoreData.StoreName), storeId);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public IActionResult SearchInvoice(InvoicesModel invoices)
|
||||
{
|
||||
return RedirectToAction(nameof(ListInvoices), new
|
||||
{
|
||||
searchTerm = invoices.SearchTerm,
|
||||
skip = invoices.Skip,
|
||||
count = invoices.Count,
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("invoices/invalidatepaid")]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> InvalidatePaidInvoice(string invoiceId)
|
||||
{
|
||||
await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoiceId);
|
||||
return RedirectToAction(nameof(ListInvoices));
|
||||
}
|
||||
|
||||
[TempData]
|
||||
public string StatusMessage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private string GetUserId()
|
||||
{
|
||||
return _UserManager.GetUserId(User);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,85 +29,140 @@ using BTCPayServer.Services;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.RegularExpressions;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Validations;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class InvoiceController : Controller
|
||||
{
|
||||
TokenRepository _TokenRepository;
|
||||
InvoiceRepository _InvoiceRepository;
|
||||
BTCPayWallet _Wallet;
|
||||
IRateProvider _RateProvider;
|
||||
private InvoiceWatcher _Watcher;
|
||||
StoreRepository _StoreRepository;
|
||||
Network _Network;
|
||||
UserManager<ApplicationUser> _UserManager;
|
||||
IFeeProvider _FeeProvider;
|
||||
public partial class InvoiceController : Controller
|
||||
{
|
||||
InvoiceRepository _InvoiceRepository;
|
||||
BTCPayWallet _Wallet;
|
||||
IRateProvider _RateProvider;
|
||||
private InvoiceWatcher _Watcher;
|
||||
StoreRepository _StoreRepository;
|
||||
Network _Network;
|
||||
UserManager<ApplicationUser> _UserManager;
|
||||
IFeeProvider _FeeProvider;
|
||||
private CurrencyNameTable _CurrencyNameTable;
|
||||
ExplorerClient _Explorer;
|
||||
EventAggregator _EventAggregator;
|
||||
public InvoiceController(
|
||||
Network network,
|
||||
InvoiceRepository invoiceRepository,
|
||||
CurrencyNameTable currencyNameTable,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
BTCPayWallet wallet,
|
||||
IRateProvider rateProvider,
|
||||
StoreRepository storeRepository,
|
||||
EventAggregator eventAggregator,
|
||||
InvoiceWatcherAccessor watcher,
|
||||
ExplorerClient explorerClient,
|
||||
IFeeProvider feeProvider)
|
||||
{
|
||||
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
||||
_Explorer = explorerClient ?? throw new ArgumentNullException(nameof(explorerClient));
|
||||
_StoreRepository = storeRepository ?? throw new ArgumentNullException(nameof(storeRepository));
|
||||
_Network = network ?? throw new ArgumentNullException(nameof(network));
|
||||
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
|
||||
_Wallet = wallet ?? throw new ArgumentNullException(nameof(wallet));
|
||||
_RateProvider = rateProvider ?? throw new ArgumentNullException(nameof(rateProvider));
|
||||
_Watcher = (watcher ?? throw new ArgumentNullException(nameof(watcher))).Instance;
|
||||
_UserManager = userManager;
|
||||
_FeeProvider = feeProvider ?? throw new ArgumentNullException(nameof(feeProvider));
|
||||
_EventAggregator = eventAggregator;
|
||||
}
|
||||
|
||||
public InvoiceController(
|
||||
Network network,
|
||||
InvoiceRepository invoiceRepository,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
TokenRepository tokenRepository,
|
||||
BTCPayWallet wallet,
|
||||
IRateProvider rateProvider,
|
||||
StoreRepository storeRepository,
|
||||
InvoiceWatcher watcher,
|
||||
IFeeProvider feeProvider)
|
||||
{
|
||||
_StoreRepository = storeRepository ?? throw new ArgumentNullException(nameof(storeRepository));
|
||||
_Network = network ?? throw new ArgumentNullException(nameof(network));
|
||||
_TokenRepository = tokenRepository ?? throw new ArgumentNullException(nameof(tokenRepository));
|
||||
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
|
||||
_Wallet = wallet ?? throw new ArgumentNullException(nameof(wallet));
|
||||
_RateProvider = rateProvider ?? throw new ArgumentNullException(nameof(rateProvider));
|
||||
_Watcher = watcher ?? throw new ArgumentNullException(nameof(watcher));
|
||||
_UserManager = userManager;
|
||||
_FeeProvider = feeProvider ?? throw new ArgumentNullException(nameof(feeProvider));
|
||||
}
|
||||
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl, double expiryMinutes = 15)
|
||||
{
|
||||
//TODO: expiryMinutes (time before a new invoice can become paid) and monitoringMinutes (time before a paid invoice becomes invalid) should be configurable at store level
|
||||
var derivationStrategy = store.DerivationStrategy;
|
||||
var entity = new InvoiceEntity
|
||||
{
|
||||
InvoiceTime = DateTimeOffset.UtcNow,
|
||||
DerivationStrategy = derivationStrategy ?? throw new BitpayHttpException(400, "This store has not configured the derivation strategy")
|
||||
};
|
||||
var storeBlob = store.GetStoreBlob(_Network);
|
||||
Uri notificationUri = Uri.IsWellFormedUriString(invoice.NotificationURL, UriKind.Absolute) ? new Uri(invoice.NotificationURL, UriKind.Absolute) : null;
|
||||
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.MonitoringExpiration = entity.ExpirationTime + TimeSpan.FromMinutes(storeBlob.MonitoringExpiration);
|
||||
entity.OrderId = invoice.OrderId;
|
||||
entity.ServerUrl = serverUrl;
|
||||
entity.FullNotifications = invoice.FullNotifications;
|
||||
entity.NotificationURL = notificationUri?.AbsoluteUri;
|
||||
entity.BuyerInformation = Map<Invoice, BuyerInformation>(invoice);
|
||||
//Another way of passing buyer info to support
|
||||
FillBuyerInfo(invoice.Buyer, entity.BuyerInformation);
|
||||
if (entity?.BuyerInformation?.BuyerEmail != null)
|
||||
{
|
||||
if (!EmailValidator.IsEmail(entity.BuyerInformation.BuyerEmail))
|
||||
throw new BitpayHttpException(400, "Invalid email");
|
||||
entity.RefundMail = entity.BuyerInformation.BuyerEmail;
|
||||
}
|
||||
entity.ProductInformation = Map<Invoice, ProductInformation>(invoice);
|
||||
entity.RedirectURL = invoice.RedirectURL ?? store.StoreWebsite;
|
||||
entity.Status = "new";
|
||||
entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);
|
||||
|
||||
private async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(Invoice invoice, StoreData store)
|
||||
{
|
||||
var derivationStrategy = store.DerivationStrategy;
|
||||
var entity = new InvoiceEntity
|
||||
{
|
||||
InvoiceTime = DateTimeOffset.UtcNow,
|
||||
DerivationStrategy = derivationStrategy ?? throw new BitpayHttpException(400, "This store has not configured the derivation strategy")
|
||||
};
|
||||
Uri notificationUri = Uri.IsWellFormedUriString(invoice.NotificationURL, UriKind.Absolute) ? new Uri(invoice.NotificationURL, UriKind.Absolute) : null;
|
||||
if(notificationUri == null || (notificationUri.Scheme != "http" && notificationUri.Scheme != "https")) //TODO: Filer non routable addresses ?
|
||||
notificationUri = null;
|
||||
EmailAddressAttribute emailValidator = new EmailAddressAttribute();
|
||||
entity.ExpirationTime = entity.InvoiceTime + TimeSpan.FromMinutes(15.0);
|
||||
entity.ServerUrl = HttpContext.Request.GetAbsoluteRoot();
|
||||
entity.FullNotifications = invoice.FullNotifications;
|
||||
entity.NotificationURL = notificationUri?.AbsoluteUri;
|
||||
entity.BuyerInformation = Map<Invoice, BuyerInformation>(invoice);
|
||||
entity.RefundMail = EmailValidator.IsEmail(entity?.BuyerInformation?.BuyerEmail) ? entity.BuyerInformation.BuyerEmail : null;
|
||||
entity.ProductInformation = Map<Invoice, ProductInformation>(invoice);
|
||||
entity.RedirectURL = invoice.RedirectURL ?? store.StoreWebsite;
|
||||
entity.Status = "new";
|
||||
entity.SpeedPolicy = store.SpeedPolicy;
|
||||
entity.TxFee = (await _FeeProvider.GetFeeRateAsync()).GetFee(100); // assume price for 100 bytes
|
||||
entity.Rate = (double)await _RateProvider.GetRateAsync(invoice.Currency);
|
||||
entity.PosData = invoice.PosData;
|
||||
entity.DepositAddress = await _Wallet.ReserveAddressAsync(derivationStrategy);
|
||||
var getFeeRate = _FeeProvider.GetFeeRateAsync();
|
||||
var getRate = _RateProvider.GetRateAsync(invoice.Currency);
|
||||
var getAddress = _Wallet.ReserveAddressAsync(ParseDerivationStrategy(derivationStrategy));
|
||||
entity.TxFee = storeBlob.NetworkFeeDisabled ? Money.Zero : (await getFeeRate).GetFee(100); // assume price for 100 bytes
|
||||
entity.Rate = (double)await getRate;
|
||||
entity.PosData = invoice.PosData;
|
||||
entity.DepositAddress = await getAddress;
|
||||
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity);
|
||||
_Watcher.Watch(entity.Id);
|
||||
var resp = entity.EntityToDTO();
|
||||
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
|
||||
}
|
||||
|
||||
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity);
|
||||
await _Wallet.MapAsync(entity.DepositAddress, entity.Id);
|
||||
await _Watcher.WatchAsync(entity.Id);
|
||||
var resp = entity.EntityToDTO();
|
||||
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
|
||||
}
|
||||
private SpeedPolicy ParseSpeedPolicy(string transactionSpeed, SpeedPolicy defaultPolicy)
|
||||
{
|
||||
if (transactionSpeed == null)
|
||||
return defaultPolicy;
|
||||
var mappings = new Dictionary<string, SpeedPolicy>();
|
||||
mappings.Add("low", SpeedPolicy.LowSpeed);
|
||||
mappings.Add("medium", SpeedPolicy.MediumSpeed);
|
||||
mappings.Add("high", SpeedPolicy.HighSpeed);
|
||||
if (!mappings.TryGetValue(transactionSpeed, out SpeedPolicy policy))
|
||||
policy = defaultPolicy;
|
||||
return policy;
|
||||
}
|
||||
|
||||
private TDest Map<TFrom, TDest>(TFrom data)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<TDest>(JsonConvert.SerializeObject(data));
|
||||
}
|
||||
}
|
||||
private void FillBuyerInfo(Buyer buyer, BuyerInformation buyerInformation)
|
||||
{
|
||||
if (buyer == null)
|
||||
return;
|
||||
buyerInformation.BuyerAddress1 = buyerInformation.BuyerAddress1 ?? buyer.Address1;
|
||||
buyerInformation.BuyerAddress2 = buyerInformation.BuyerAddress2 ?? buyer.Address2;
|
||||
buyerInformation.BuyerCity = buyerInformation.BuyerCity ?? buyer.City;
|
||||
buyerInformation.BuyerCountry = buyerInformation.BuyerCountry ?? buyer.country;
|
||||
buyerInformation.BuyerEmail = buyerInformation.BuyerEmail ?? buyer.email;
|
||||
buyerInformation.BuyerName = buyerInformation.BuyerName ?? buyer.Name;
|
||||
buyerInformation.BuyerPhone = buyerInformation.BuyerPhone ?? buyer.phone;
|
||||
buyerInformation.BuyerState = buyerInformation.BuyerState ?? buyer.State;
|
||||
buyerInformation.BuyerZip = buyerInformation.BuyerZip ?? buyer.zip;
|
||||
}
|
||||
|
||||
private DerivationStrategyBase ParseDerivationStrategy(string derivationStrategy)
|
||||
{
|
||||
return new DerivationStrategyFactory(_Network).Parse(derivationStrategy);
|
||||
}
|
||||
|
||||
private TDest Map<TFrom, TDest>(TFrom data)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<TDest>(JsonConvert.SerializeObject(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -7,34 +7,34 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public class PaymentRequestActionResult : IActionResult
|
||||
{
|
||||
PaymentRequest req;
|
||||
public PaymentRequestActionResult(PaymentRequest req)
|
||||
{
|
||||
this.req = req;
|
||||
}
|
||||
public Task ExecuteResultAsync(ActionContext context)
|
||||
{
|
||||
context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary";
|
||||
context.HttpContext.Response.ContentType = "application/bitcoin-paymentrequest";
|
||||
req.WriteTo(context.HttpContext.Response.Body);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
public class PaymentAckActionResult : IActionResult
|
||||
{
|
||||
PaymentACK req;
|
||||
public PaymentAckActionResult(PaymentACK req)
|
||||
{
|
||||
this.req = req;
|
||||
}
|
||||
public Task ExecuteResultAsync(ActionContext context)
|
||||
{
|
||||
context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary";
|
||||
context.HttpContext.Response.ContentType = "application/bitcoin-paymentack";
|
||||
req.WriteTo(context.HttpContext.Response.Body);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
public class PaymentRequestActionResult : IActionResult
|
||||
{
|
||||
PaymentRequest req;
|
||||
public PaymentRequestActionResult(PaymentRequest req)
|
||||
{
|
||||
this.req = req;
|
||||
}
|
||||
public Task ExecuteResultAsync(ActionContext context)
|
||||
{
|
||||
context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary";
|
||||
context.HttpContext.Response.ContentType = "application/bitcoin-paymentrequest";
|
||||
req.WriteTo(context.HttpContext.Response.Body);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
public class PaymentAckActionResult : IActionResult
|
||||
{
|
||||
PaymentACK req;
|
||||
public PaymentAckActionResult(PaymentACK req)
|
||||
{
|
||||
this.req = req;
|
||||
}
|
||||
public Task ExecuteResultAsync(ActionContext context)
|
||||
{
|
||||
context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary";
|
||||
context.HttpContext.Response.ContentType = "application/bitcoin-paymentack";
|
||||
req.WriteTo(context.HttpContext.Response.Body);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,31 +10,31 @@ using BTCPayServer.Services.Rates;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public class RateController : Controller
|
||||
{
|
||||
IRateProvider _RateProvider;
|
||||
CurrencyNameTable _CurrencyNameTable;
|
||||
public RateController(IRateProvider rateProvider, CurrencyNameTable currencyNameTable)
|
||||
{
|
||||
_RateProvider = rateProvider ?? throw new ArgumentNullException(nameof(rateProvider));
|
||||
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
||||
}
|
||||
public class RateController : Controller
|
||||
{
|
||||
IRateProvider _RateProvider;
|
||||
CurrencyNameTable _CurrencyNameTable;
|
||||
public RateController(IRateProvider rateProvider, CurrencyNameTable currencyNameTable)
|
||||
{
|
||||
_RateProvider = rateProvider ?? throw new ArgumentNullException(nameof(rateProvider));
|
||||
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
||||
}
|
||||
|
||||
[Route("rates")]
|
||||
[HttpGet]
|
||||
[BitpayAPIConstraint]
|
||||
public async Task<DataWrapper<NBitpayClient.Rate[]>> GetRates()
|
||||
{
|
||||
var allRates = (await _RateProvider.GetRatesAsync());
|
||||
return new DataWrapper<NBitpayClient.Rate[]>
|
||||
(allRates.Select(r =>
|
||||
new NBitpayClient.Rate()
|
||||
{
|
||||
Code = r.Currency,
|
||||
Name = _CurrencyNameTable.GetCurrencyData(r.Currency)?.Name,
|
||||
Value = r.Value
|
||||
}).Where(n => n.Name != null).ToArray());
|
||||
[Route("rates")]
|
||||
[HttpGet]
|
||||
[BitpayAPIConstraint]
|
||||
public async Task<DataWrapper<NBitpayClient.Rate[]>> GetRates()
|
||||
{
|
||||
var allRates = (await _RateProvider.GetRatesAsync());
|
||||
return new DataWrapper<NBitpayClient.Rate[]>
|
||||
(allRates.Select(r =>
|
||||
new NBitpayClient.Rate()
|
||||
{
|
||||
Code = r.Currency,
|
||||
Name = _CurrencyNameTable.GetCurrencyData(r.Currency)?.Name,
|
||||
Value = r.Value
|
||||
}).Where(n => n.Name != null).ToArray());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,82 +16,117 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
[Authorize(Roles = Roles.ServerAdmin)]
|
||||
public class ServerController : Controller
|
||||
{
|
||||
private UserManager<ApplicationUser> _UserManager;
|
||||
SettingsRepository _SettingsRepository;
|
||||
[Authorize(Roles = Roles.ServerAdmin)]
|
||||
public class ServerController : Controller
|
||||
{
|
||||
private UserManager<ApplicationUser> _UserManager;
|
||||
SettingsRepository _SettingsRepository;
|
||||
|
||||
public ServerController(UserManager<ApplicationUser> userManager, SettingsRepository settingsRepository)
|
||||
{
|
||||
_UserManager = userManager;
|
||||
_SettingsRepository = settingsRepository;
|
||||
}
|
||||
public ServerController(UserManager<ApplicationUser> userManager, SettingsRepository settingsRepository)
|
||||
{
|
||||
_UserManager = userManager;
|
||||
_SettingsRepository = settingsRepository;
|
||||
}
|
||||
|
||||
[Route("server/users")]
|
||||
public IActionResult ListUsers()
|
||||
{
|
||||
var users = new UsersViewModel();
|
||||
users.Users
|
||||
= _UserManager.Users.Select(u => new UsersViewModel.UserViewModel()
|
||||
{
|
||||
Name = u.UserName,
|
||||
Email = u.Email
|
||||
}).ToList();
|
||||
return View(users);
|
||||
}
|
||||
[Route("server/users")]
|
||||
public IActionResult ListUsers()
|
||||
{
|
||||
var users = new UsersViewModel();
|
||||
users.StatusMessage = StatusMessage;
|
||||
users.Users
|
||||
= _UserManager.Users.Select(u => new UsersViewModel.UserViewModel()
|
||||
{
|
||||
Name = u.UserName,
|
||||
Email = u.Email,
|
||||
Id = u.Id
|
||||
}).ToList();
|
||||
return View(users);
|
||||
}
|
||||
|
||||
[Route("server/emails")]
|
||||
public async Task<IActionResult> Emails()
|
||||
{
|
||||
var data = (await _SettingsRepository.GetSettingAsync<EmailSettings>()) ?? new EmailSettings();
|
||||
return View(new EmailsViewModel() { Settings = data });
|
||||
}
|
||||
|
||||
[Route("server/policies")]
|
||||
public async Task<IActionResult> Policies()
|
||||
{
|
||||
var data = (await _SettingsRepository.GetSettingAsync<PoliciesSettings>()) ?? new PoliciesSettings();
|
||||
return View(data);
|
||||
}
|
||||
[Route("server/policies")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Policies(PoliciesSettings settings)
|
||||
{
|
||||
await _SettingsRepository.UpdateSetting(settings);
|
||||
TempData["StatusMessage"] = "Policies upadated successfully";
|
||||
return View(settings);
|
||||
}
|
||||
[Route("server/users/{userId}/delete")]
|
||||
public async Task<IActionResult> DeleteUser(string userId)
|
||||
{
|
||||
var user = userId == null ? null : await _UserManager.FindByIdAsync(userId);
|
||||
if (user == null)
|
||||
return NotFound();
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Title = "Delete user " + user.Email,
|
||||
Description = "This user will be permanently deleted",
|
||||
Action = "Delete"
|
||||
});
|
||||
}
|
||||
|
||||
[Route("server/emails")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Emails(EmailsViewModel model, string command)
|
||||
{
|
||||
if(command == "Test")
|
||||
{
|
||||
if(!ModelState.IsValid)
|
||||
return View(model);
|
||||
try
|
||||
{
|
||||
var client = model.Settings.CreateSmtpClient();
|
||||
await client.SendMailAsync(model.Settings.From, model.TestEmail, "BTCPay test", "BTCPay test");
|
||||
model.StatusMessage = "Email sent to " + model.TestEmail + ", please, verify you received it";
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
model.StatusMessage = "Error: " + ex.Message;
|
||||
}
|
||||
return View(model);
|
||||
}
|
||||
else
|
||||
{
|
||||
ModelState.Remove(nameof(model.TestEmail));
|
||||
if(!ModelState.IsValid)
|
||||
return View(model);
|
||||
await _SettingsRepository.UpdateSetting(model.Settings);
|
||||
model.StatusMessage = "Email settings saved";
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
[Route("server/users/{userId}/delete")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> DeleteUserPost(string userId)
|
||||
{
|
||||
var user = userId == null ? null : await _UserManager.FindByIdAsync(userId);
|
||||
if (user == null)
|
||||
return NotFound();
|
||||
await _UserManager.DeleteAsync(user);
|
||||
StatusMessage = "User deleted";
|
||||
return RedirectToAction(nameof(ListUsers));
|
||||
}
|
||||
|
||||
[TempData]
|
||||
public string StatusMessage
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Route("server/emails")]
|
||||
public async Task<IActionResult> Emails()
|
||||
{
|
||||
var data = (await _SettingsRepository.GetSettingAsync<EmailSettings>()) ?? new EmailSettings();
|
||||
return View(new EmailsViewModel() { Settings = data });
|
||||
}
|
||||
|
||||
[Route("server/policies")]
|
||||
public async Task<IActionResult> Policies()
|
||||
{
|
||||
var data = (await _SettingsRepository.GetSettingAsync<PoliciesSettings>()) ?? new PoliciesSettings();
|
||||
return View(data);
|
||||
}
|
||||
[Route("server/policies")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Policies(PoliciesSettings settings)
|
||||
{
|
||||
await _SettingsRepository.UpdateSetting(settings);
|
||||
TempData["StatusMessage"] = "Policies upadated successfully";
|
||||
return View(settings);
|
||||
}
|
||||
|
||||
[Route("server/emails")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Emails(EmailsViewModel model, string command)
|
||||
{
|
||||
if (command == "Test")
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return View(model);
|
||||
try
|
||||
{
|
||||
var client = model.Settings.CreateSmtpClient();
|
||||
await client.SendMailAsync(model.Settings.From, model.TestEmail, "BTCPay test", "BTCPay test");
|
||||
model.StatusMessage = "Email sent to " + model.TestEmail + ", please, verify you received it";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
model.StatusMessage = "Error: " + ex.Message;
|
||||
}
|
||||
return View(model);
|
||||
}
|
||||
else
|
||||
{
|
||||
ModelState.Remove(nameof(model.TestEmail));
|
||||
if (!ModelState.IsValid)
|
||||
return View(model);
|
||||
await _SettingsRepository.UpdateSetting(model.Settings);
|
||||
model.StatusMessage = "Email settings saved";
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using BTCPayServer.Authentication;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Services.Stores;
|
||||
@ -8,8 +9,11 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitpayClient;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -17,282 +21,448 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
[Route("stores")]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[Authorize(Policy = "CanAccessStore")]
|
||||
public class StoresController : Controller
|
||||
{
|
||||
public StoresController(
|
||||
StoreRepository repo,
|
||||
TokenRepository tokenRepo,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
AccessTokenController tokenController,
|
||||
BTCPayWallet wallet,
|
||||
IHostingEnvironment env)
|
||||
{
|
||||
_Repo = repo;
|
||||
_TokenRepository = tokenRepo;
|
||||
_UserManager = userManager;
|
||||
_TokenController = tokenController;
|
||||
_Wallet = wallet;
|
||||
_Env = env;
|
||||
}
|
||||
BTCPayWallet _Wallet;
|
||||
AccessTokenController _TokenController;
|
||||
StoreRepository _Repo;
|
||||
TokenRepository _TokenRepository;
|
||||
UserManager<ApplicationUser> _UserManager;
|
||||
IHostingEnvironment _Env;
|
||||
[Route("stores")]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[Authorize(Policy = "CanAccessStore")]
|
||||
[AutoValidateAntiforgeryToken]
|
||||
public class StoresController : Controller
|
||||
{
|
||||
public StoresController(
|
||||
StoreRepository repo,
|
||||
TokenRepository tokenRepo,
|
||||
CallbackController callbackController,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
AccessTokenController tokenController,
|
||||
BTCPayWallet wallet,
|
||||
Network network,
|
||||
IHostingEnvironment env)
|
||||
{
|
||||
_Repo = repo;
|
||||
_TokenRepository = tokenRepo;
|
||||
_UserManager = userManager;
|
||||
_TokenController = tokenController;
|
||||
_Wallet = wallet;
|
||||
_Env = env;
|
||||
_Network = network;
|
||||
_CallbackController = callbackController;
|
||||
}
|
||||
Network _Network;
|
||||
CallbackController _CallbackController;
|
||||
BTCPayWallet _Wallet;
|
||||
AccessTokenController _TokenController;
|
||||
StoreRepository _Repo;
|
||||
TokenRepository _TokenRepository;
|
||||
UserManager<ApplicationUser> _UserManager;
|
||||
IHostingEnvironment _Env;
|
||||
|
||||
[TempData]
|
||||
public string StatusMessage
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
[TempData]
|
||||
public string StatusMessage
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("create")]
|
||||
public IActionResult CreateStore()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("create")]
|
||||
public IActionResult CreateStore()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("create")]
|
||||
public async Task<IActionResult> CreateStore(CreateStoreViewModel vm)
|
||||
{
|
||||
if(!ModelState.IsValid)
|
||||
{
|
||||
return View(vm);
|
||||
}
|
||||
var store = await _Repo.CreateStore(GetUserId(), vm.Name);
|
||||
CreatedStoreId = store.Id;
|
||||
StatusMessage = "Store successfully created";
|
||||
return RedirectToAction(nameof(ListStores));
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("create")]
|
||||
public async Task<IActionResult> CreateStore(CreateStoreViewModel vm)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(vm);
|
||||
}
|
||||
var store = await _Repo.CreateStore(GetUserId(), vm.Name);
|
||||
CreatedStoreId = store.Id;
|
||||
StatusMessage = "Store successfully created";
|
||||
return RedirectToAction(nameof(ListStores));
|
||||
}
|
||||
|
||||
public string CreatedStoreId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string CreatedStoreId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> ListStores()
|
||||
{
|
||||
StoresViewModel result = new StoresViewModel();
|
||||
result.StatusMessage = StatusMessage;
|
||||
var stores = await _Repo.GetStoresByUserId(GetUserId());
|
||||
foreach(var store in stores)
|
||||
{
|
||||
result.Stores.Add(new StoresViewModel.StoreViewModel()
|
||||
{
|
||||
Id = store.Id,
|
||||
Name = store.StoreName,
|
||||
WebSite = store.StoreWebsite
|
||||
});
|
||||
}
|
||||
return View(result);
|
||||
}
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> ListStores()
|
||||
{
|
||||
StoresViewModel result = new StoresViewModel();
|
||||
result.StatusMessage = StatusMessage;
|
||||
var stores = await _Repo.GetStoresByUserId(GetUserId());
|
||||
var balances = stores.Select(async s => string.IsNullOrEmpty(s.DerivationStrategy) ? Money.Zero : await _Wallet.GetBalance(ParseDerivationStrategy(s.DerivationStrategy, null))).ToArray();
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}")]
|
||||
public async Task<IActionResult> UpdateStore(string storeId)
|
||||
{
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
if(store == null)
|
||||
return NotFound();
|
||||
for (int i = 0; i < stores.Length; i++)
|
||||
{
|
||||
var store = stores[i];
|
||||
result.Stores.Add(new StoresViewModel.StoreViewModel()
|
||||
{
|
||||
Id = store.Id,
|
||||
Name = store.StoreName,
|
||||
WebSite = store.StoreWebsite,
|
||||
Balance = await balances[i]
|
||||
});
|
||||
}
|
||||
return View(result);
|
||||
}
|
||||
|
||||
var vm = new StoreViewModel();
|
||||
vm.StoreName = store.StoreName;
|
||||
vm.StoreWebsite = store.StoreWebsite;
|
||||
vm.SpeedPolicy = store.SpeedPolicy;
|
||||
vm.ExtPubKey = store.DerivationStrategy;
|
||||
vm.StatusMessage = StatusMessage;
|
||||
return View(vm);
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("{storeId}/delete")]
|
||||
public async Task<IActionResult> DeleteStore(string storeId)
|
||||
{
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Title = "Delete store " + store.StoreName,
|
||||
Description = "This store will still be accessible to users sharing it",
|
||||
Action = "Delete"
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[Route("{storeId}")]
|
||||
public async Task<IActionResult> UpdateStore(string storeId, StoreViewModel model)
|
||||
{
|
||||
if(!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
if(store == null)
|
||||
return NotFound();
|
||||
[HttpPost]
|
||||
[Route("{storeId}/delete")]
|
||||
public async Task<IActionResult> DeleteStorePost(string storeId)
|
||||
{
|
||||
var userId = GetUserId();
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
await _Repo.RemoveStore(storeId, userId);
|
||||
StatusMessage = "Store removed successfully";
|
||||
return RedirectToAction(nameof(ListStores));
|
||||
}
|
||||
|
||||
bool needUpdate = false;
|
||||
if(store.SpeedPolicy != model.SpeedPolicy)
|
||||
{
|
||||
needUpdate = true;
|
||||
store.SpeedPolicy = model.SpeedPolicy;
|
||||
}
|
||||
if(store.StoreName != model.StoreName)
|
||||
{
|
||||
needUpdate = true;
|
||||
store.StoreName = model.StoreName;
|
||||
}
|
||||
if(store.StoreWebsite != model.StoreWebsite)
|
||||
{
|
||||
needUpdate = true;
|
||||
store.StoreWebsite = model.StoreWebsite;
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("{storeId}")]
|
||||
public async Task<IActionResult> UpdateStore(string storeId)
|
||||
{
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
|
||||
if(store.DerivationStrategy != model.ExtPubKey)
|
||||
{
|
||||
needUpdate = true;
|
||||
try
|
||||
{
|
||||
await _Wallet.TrackAsync(model.ExtPubKey);
|
||||
store.DerivationStrategy = model.ExtPubKey;
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.ExtPubKey), "Invalid Derivation Scheme");
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
var storeBlob = store.GetStoreBlob(_Network);
|
||||
var vm = new StoreViewModel();
|
||||
vm.Id = store.Id;
|
||||
vm.StoreName = store.StoreName;
|
||||
vm.StoreWebsite = store.StoreWebsite;
|
||||
vm.NetworkFee = !storeBlob.NetworkFeeDisabled;
|
||||
vm.SpeedPolicy = store.SpeedPolicy;
|
||||
vm.DerivationScheme = store.DerivationStrategy;
|
||||
vm.StatusMessage = StatusMessage;
|
||||
vm.MonitoringExpiration = storeBlob.MonitoringExpiration;
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
if(needUpdate)
|
||||
{
|
||||
await _Repo.UpdateStore(store);
|
||||
StatusMessage = "Store successfully updated";
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("{storeId}")]
|
||||
public async Task<IActionResult> UpdateStore(string storeId, StoreViewModel model, string command)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
|
||||
return RedirectToAction(nameof(UpdateStore), new
|
||||
{
|
||||
storeId = storeId
|
||||
});
|
||||
}
|
||||
if (command == "Save")
|
||||
{
|
||||
bool needUpdate = false;
|
||||
if (store.SpeedPolicy != model.SpeedPolicy)
|
||||
{
|
||||
needUpdate = true;
|
||||
store.SpeedPolicy = model.SpeedPolicy;
|
||||
}
|
||||
if (store.StoreName != model.StoreName)
|
||||
{
|
||||
needUpdate = true;
|
||||
store.StoreName = model.StoreName;
|
||||
}
|
||||
if (store.StoreWebsite != model.StoreWebsite)
|
||||
{
|
||||
needUpdate = true;
|
||||
store.StoreWebsite = model.StoreWebsite;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/Tokens")]
|
||||
public async Task<IActionResult> ListTokens(string storeId)
|
||||
{
|
||||
var model = new TokensViewModel();
|
||||
var tokens = await _TokenRepository.GetTokensByPairedIdAsync(storeId);
|
||||
model.StatusMessage = StatusMessage;
|
||||
model.Tokens = tokens.Select(t => new TokenViewModel()
|
||||
{
|
||||
Facade = t.Name,
|
||||
Label = t.Label,
|
||||
SIN = t.SIN,
|
||||
Id = t.Value
|
||||
}).ToArray();
|
||||
return View(model);
|
||||
}
|
||||
if (store.DerivationStrategy != model.DerivationScheme)
|
||||
{
|
||||
needUpdate = true;
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(model.DerivationScheme))
|
||||
{
|
||||
var strategy = ParseDerivationStrategy(model.DerivationScheme, model.DerivationSchemeFormat);
|
||||
await _Wallet.TrackAsync(strategy);
|
||||
await _CallbackController.RegisterCallbackUriAsync(strategy);
|
||||
model.DerivationScheme = strategy.ToString();
|
||||
}
|
||||
store.DerivationStrategy = model.DerivationScheme;
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.DerivationScheme), "Invalid Derivation Scheme");
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[Route("{storeId}/Tokens/Create")]
|
||||
public async Task<IActionResult> CreateToken(string storeId, CreateTokenViewModel model)
|
||||
{
|
||||
if(!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
var blob = store.GetStoreBlob(_Network);
|
||||
blob.NetworkFeeDisabled = !model.NetworkFee;
|
||||
blob.MonitoringExpiration = model.MonitoringExpiration;
|
||||
|
||||
var pairingCode = await _TokenController.GetPairingCode(new PairingCodeRequest()
|
||||
{
|
||||
Facade = model.Facade,
|
||||
Label = model.Label,
|
||||
Id = NBitpayClient.Extensions.BitIdExtensions.GetBitIDSIN(new PubKey(model.PublicKey))
|
||||
});
|
||||
|
||||
return RedirectToAction(nameof(RequestPairing), new
|
||||
{
|
||||
pairingCode = pairingCode.Data[0].PairingCode,
|
||||
selectedStore = storeId
|
||||
});
|
||||
}
|
||||
if (store.SetStoreBlob(blob, _Network))
|
||||
{
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/Tokens/Create")]
|
||||
public IActionResult CreateToken()
|
||||
{
|
||||
var model = new CreateTokenViewModel();
|
||||
model.Facade = "merchant";
|
||||
if(_Env.IsDevelopment())
|
||||
{
|
||||
model.PublicKey = new Key().PubKey.ToHex();
|
||||
}
|
||||
return View(model);
|
||||
}
|
||||
if (needUpdate)
|
||||
{
|
||||
await _Repo.UpdateStore(store);
|
||||
StatusMessage = "Store successfully updated";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[Route("{storeId}/Tokens/Delete")]
|
||||
public async Task<IActionResult> DeleteToken(string storeId, string name, string sin)
|
||||
{
|
||||
if(await _TokenRepository.DeleteToken(sin, name, storeId))
|
||||
StatusMessage = "Token revoked";
|
||||
else
|
||||
StatusMessage = "Failure to revoke this token";
|
||||
return RedirectToAction(nameof(ListTokens));
|
||||
}
|
||||
return RedirectToAction(nameof(UpdateStore), new
|
||||
{
|
||||
storeId = storeId
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(model.DerivationScheme))
|
||||
{
|
||||
try
|
||||
{
|
||||
var scheme = ParseDerivationStrategy(model.DerivationScheme, model.DerivationSchemeFormat);
|
||||
var line = scheme.GetLineFor(DerivationFeature.Deposit);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var address = line.Derive((uint)i);
|
||||
model.AddressSamples.Add((line.Path.Derive((uint)i).ToString(), address.ScriptPubKey.GetDestinationAddress(_Network).ToString()));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.DerivationScheme), "Invalid Derivation Scheme");
|
||||
}
|
||||
}
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
private DerivationStrategyBase ParseDerivationStrategy(string derivationScheme, string format)
|
||||
{
|
||||
if (format == "Electrum")
|
||||
{
|
||||
//Unsupported Electrum
|
||||
//var p2wsh_p2sh = 0x295b43fU;
|
||||
//var p2wsh = 0x2aa7ed3U;
|
||||
Dictionary<uint, string[]> electrumMapping = new Dictionary<uint, string[]>();
|
||||
//Source https://github.com/spesmilo/electrum/blob/9edffd17542de5773e7284a8c8a2673c766bb3c3/lib/bitcoin.py
|
||||
var standard = 0x0488b21eU;
|
||||
electrumMapping.Add(standard, new[] { "legacy" });
|
||||
var p2wpkh_p2sh = 0x049d7cb2U;
|
||||
electrumMapping.Add(p2wpkh_p2sh, new string[] { "p2sh" });
|
||||
var p2wpkh = 0x4b24746U;
|
||||
electrumMapping.Add(p2wpkh, new string[] { });
|
||||
|
||||
var data = Encoders.Base58Check.DecodeData(derivationScheme);
|
||||
if (data.Length < 4)
|
||||
throw new FormatException("data.Length < 4");
|
||||
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 == Network.Main ? 0x0488b21eU : 0x043587cf, false);
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
data[i] = standardPrefix[i];
|
||||
|
||||
derivationScheme = new BitcoinExtPubKey(Encoders.Base58Check.EncodeData(data), _Network).ToString();
|
||||
foreach (var label in labels)
|
||||
{
|
||||
derivationScheme = derivationScheme + $"-[{label}]";
|
||||
}
|
||||
}
|
||||
|
||||
return new DerivationStrategyFactory(_Network).Parse(derivationScheme);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/Tokens")]
|
||||
public async Task<IActionResult> ListTokens(string storeId)
|
||||
{
|
||||
var model = new TokensViewModel();
|
||||
var tokens = await _TokenRepository.GetTokensByStoreIdAsync(storeId);
|
||||
model.StatusMessage = StatusMessage;
|
||||
model.Tokens = tokens.Select(t => new TokenViewModel()
|
||||
{
|
||||
Facade = t.Facade,
|
||||
Label = t.Label,
|
||||
SIN = t.SIN,
|
||||
Id = t.Value
|
||||
}).ToArray();
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("/api-tokens")]
|
||||
[Route("{storeId}/Tokens/Create")]
|
||||
public async Task<IActionResult> CreateToken(string storeId, CreateTokenViewModel model)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
model.Label = model.Label ?? String.Empty;
|
||||
if (storeId == null) // Permissions are not checked by Policy if the storeId is not passed by url
|
||||
{
|
||||
storeId = model.StoreId;
|
||||
var userId = GetUserId();
|
||||
if (userId == null)
|
||||
return Unauthorized();
|
||||
var store = await _Repo.FindStore(storeId, userId);
|
||||
if (store == null)
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var tokenRequest = new TokenRequest()
|
||||
{
|
||||
Facade = model.Facade,
|
||||
Label = model.Label,
|
||||
Id = model.PublicKey == null ? null : NBitpayClient.Extensions.BitIdExtensions.GetBitIDSIN(new PubKey(model.PublicKey))
|
||||
};
|
||||
|
||||
string pairingCode = null;
|
||||
if (model.PublicKey == null)
|
||||
{
|
||||
tokenRequest.PairingCode = await _TokenRepository.CreatePairingCodeAsync();
|
||||
await _TokenRepository.UpdatePairingCode(new PairingCodeEntity()
|
||||
{
|
||||
Id = tokenRequest.PairingCode,
|
||||
Facade = model.Facade,
|
||||
Label = model.Label,
|
||||
});
|
||||
await _TokenRepository.PairWithStoreAsync(tokenRequest.PairingCode, storeId);
|
||||
pairingCode = tokenRequest.PairingCode;
|
||||
}
|
||||
else
|
||||
{
|
||||
pairingCode = ((DataWrapper<List<PairingCodeResponse>>)await _TokenController.Tokens(tokenRequest)).Data[0].PairingCode;
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(RequestPairing), new
|
||||
{
|
||||
pairingCode = pairingCode,
|
||||
selectedStore = storeId
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("/api-tokens")]
|
||||
[Route("{storeId}/Tokens/Create")]
|
||||
public async Task<IActionResult> CreateToken(string storeId)
|
||||
{
|
||||
var userId = GetUserId();
|
||||
if (string.IsNullOrWhiteSpace(userId))
|
||||
return Unauthorized();
|
||||
var model = new CreateTokenViewModel();
|
||||
model.Facade = "merchant";
|
||||
ViewBag.HidePublicKey = storeId == null;
|
||||
ViewBag.ShowStores = storeId == null;
|
||||
ViewBag.ShowMenu = storeId != null;
|
||||
model.StoreId = storeId;
|
||||
if (storeId == null)
|
||||
{
|
||||
model.Stores = new SelectList(await _Repo.GetStoresByUserId(userId), nameof(StoreData.Id), nameof(StoreData.StoreName), storeId);
|
||||
}
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
|
||||
[HttpGet]
|
||||
[Route("/api-access-request")]
|
||||
public async Task<IActionResult> RequestPairing(string pairingCode, string selectedStore = null)
|
||||
{
|
||||
var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
|
||||
if(pairing == null)
|
||||
{
|
||||
StatusMessage = "Unknown pairing code";
|
||||
return RedirectToAction(nameof(ListStores));
|
||||
}
|
||||
else
|
||||
{
|
||||
var stores = await _Repo.GetStoresByUserId(GetUserId());
|
||||
return View(new PairingModel()
|
||||
{
|
||||
Id = pairing.Id,
|
||||
Facade = pairing.Facade,
|
||||
Label = pairing.Label,
|
||||
SIN = pairing.SIN,
|
||||
SelectedStore = selectedStore ?? stores.FirstOrDefault()?.Id,
|
||||
Stores = stores.Select(s => new PairingModel.StoreViewModel()
|
||||
{
|
||||
Id = s.Id,
|
||||
Name = string.IsNullOrEmpty(s.StoreName) ? s.Id : s.StoreName
|
||||
}).ToArray()
|
||||
});
|
||||
}
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("{storeId}/Tokens/Delete")]
|
||||
public async Task<IActionResult> DeleteToken(string storeId, string tokenId)
|
||||
{
|
||||
var token = await _TokenRepository.GetToken(tokenId);
|
||||
if (token == null ||
|
||||
token.StoreId != storeId ||
|
||||
!await _TokenRepository.DeleteToken(tokenId))
|
||||
StatusMessage = "Failure to revoke this token";
|
||||
else
|
||||
StatusMessage = "Token revoked";
|
||||
return RedirectToAction(nameof(ListTokens));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[Route("api-access-request")]
|
||||
public async Task<IActionResult> Pair(string pairingCode, string selectedStore)
|
||||
{
|
||||
var store = await _Repo.FindStore(selectedStore, GetUserId());
|
||||
if(store == null)
|
||||
return NotFound();
|
||||
if(pairingCode != null && await _TokenRepository.PairWithAsync(pairingCode, store.Id))
|
||||
{
|
||||
StatusMessage = "Pairing is successfull";
|
||||
return RedirectToAction(nameof(ListTokens), new
|
||||
{
|
||||
storeId = store.Id
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusMessage = "Pairing failed";
|
||||
return RedirectToAction(nameof(ListTokens), new
|
||||
{
|
||||
storeId = store.Id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private string GetUserId()
|
||||
{
|
||||
return _UserManager.GetUserId(User);
|
||||
}
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("/api-access-request")]
|
||||
public async Task<IActionResult> RequestPairing(string pairingCode, string selectedStore = null)
|
||||
{
|
||||
var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
|
||||
if (pairing == null)
|
||||
{
|
||||
StatusMessage = "Unknown pairing code";
|
||||
return RedirectToAction(nameof(ListStores));
|
||||
}
|
||||
else
|
||||
{
|
||||
var stores = await _Repo.GetStoresByUserId(GetUserId());
|
||||
return View(new PairingModel()
|
||||
{
|
||||
Id = pairing.Id,
|
||||
Facade = pairing.Facade,
|
||||
Label = pairing.Label,
|
||||
SIN = pairing.SIN ?? "Server-Initiated Pairing",
|
||||
SelectedStore = selectedStore ?? stores.FirstOrDefault()?.Id,
|
||||
Stores = stores.Select(s => new PairingModel.StoreViewModel()
|
||||
{
|
||||
Id = s.Id,
|
||||
Name = string.IsNullOrEmpty(s.StoreName) ? s.Id : s.StoreName
|
||||
}).ToArray()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("api-access-request")]
|
||||
public async Task<IActionResult> Pair(string pairingCode, string selectedStore)
|
||||
{
|
||||
if (pairingCode == null)
|
||||
return NotFound();
|
||||
var store = await _Repo.FindStore(selectedStore, GetUserId());
|
||||
var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
|
||||
if (store == null || pairing == null)
|
||||
return NotFound();
|
||||
|
||||
var pairingResult = await _TokenRepository.PairWithStoreAsync(pairingCode, store.Id);
|
||||
if (pairingResult == PairingResult.Complete || pairingResult == PairingResult.Partial)
|
||||
{
|
||||
StatusMessage = "Pairing is successfull";
|
||||
if (pairingResult == PairingResult.Partial)
|
||||
StatusMessage = "Server initiated pairing code: " + pairingCode;
|
||||
return RedirectToAction(nameof(ListTokens), new
|
||||
{
|
||||
storeId = store.Id
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusMessage = $"Pairing failed ({pairingResult})";
|
||||
return RedirectToAction(nameof(ListTokens), new
|
||||
{
|
||||
storeId = store.Id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private string GetUserId()
|
||||
{
|
||||
return _UserManager.GetUserId(User);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
98
BTCPayServer/CustomThreadPool.cs
Normal file
98
BTCPayServer/CustomThreadPool.cs
Normal file
@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
class CustomThreadPool : IDisposable
|
||||
{
|
||||
CancellationTokenSource _Cancel = new CancellationTokenSource();
|
||||
TaskCompletionSource<bool> _Exited;
|
||||
int _ExitedCount = 0;
|
||||
Thread[] _Threads;
|
||||
Exception _UnhandledException;
|
||||
BlockingCollection<(Action, TaskCompletionSource<object>)> _Actions = new BlockingCollection<(Action, TaskCompletionSource<object>)>(new ConcurrentQueue<(Action, TaskCompletionSource<object>)>());
|
||||
|
||||
public CustomThreadPool(int threadCount, string threadName)
|
||||
{
|
||||
if (threadCount <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(threadCount));
|
||||
_Exited = new TaskCompletionSource<bool>();
|
||||
_Threads = Enumerable.Range(0, threadCount).Select(_ => new Thread(RunLoop) { Name = threadName }).ToArray();
|
||||
foreach (var t in _Threads)
|
||||
t.Start();
|
||||
}
|
||||
|
||||
public void Do(Action act)
|
||||
{
|
||||
DoAsync(act).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public T Do<T>(Func<T> act)
|
||||
{
|
||||
return DoAsync(act).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<T> DoAsync<T>(Func<T> act)
|
||||
{
|
||||
TaskCompletionSource<object> done = new TaskCompletionSource<object>();
|
||||
_Actions.Add((() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
done.TrySetResult(act());
|
||||
}
|
||||
catch (Exception ex) { done.TrySetException(ex); }
|
||||
}
|
||||
, done));
|
||||
return (T)(await done.Task.ConfigureAwait(false));
|
||||
}
|
||||
|
||||
public Task DoAsync(Action act)
|
||||
{
|
||||
return DoAsync<object>(() =>
|
||||
{
|
||||
act();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
void RunLoop()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var act in _Actions.GetConsumingEnumerable(_Cancel.Token))
|
||||
{
|
||||
act.Item1();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (_Cancel.IsCancellationRequested) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
_Cancel.Cancel();
|
||||
_UnhandledException = ex;
|
||||
}
|
||||
if (Interlocked.Increment(ref _ExitedCount) == _Threads.Length)
|
||||
{
|
||||
foreach (var action in _Actions)
|
||||
{
|
||||
try
|
||||
{
|
||||
action.Item2.TrySetCanceled();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
_Exited.TrySetResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_Cancel.Cancel();
|
||||
_Exited.Task.GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
30
BTCPayServer/Data/AddressInvoiceData.cs
Normal file
30
BTCPayServer/Data/AddressInvoiceData.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class AddressInvoiceData
|
||||
{
|
||||
public string Address
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public InvoiceData InvoiceData
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string InvoiceDataId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DateTimeOffset? CreatedTime
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
@ -10,82 +10,126 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
|
||||
{
|
||||
public ApplicationDbContext()
|
||||
{
|
||||
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
|
||||
{
|
||||
public ApplicationDbContext()
|
||||
{
|
||||
|
||||
}
|
||||
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
}
|
||||
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public DbSet<InvoiceData> Invoices
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public DbSet<InvoiceData> Invoices
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DbSet<RefundAddressesData> RefundAddresses
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public DbSet<HistoricalAddressInvoiceData> HistoricalAddressInvoices
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DbSet<PaymentData> Payments
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public DbSet<PendingInvoiceData> PendingInvoices
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public DbSet<RefundAddressesData> RefundAddresses
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DbSet<StoreData> Stores
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public DbSet<PaymentData> Payments
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DbSet<UserStore> UserStore
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public DbSet<StoreData> Stores
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DbSet<SettingData> Settings
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public DbSet<UserStore> UserStore
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
var isConfigured = optionsBuilder.Options.Extensions.OfType<RelationalOptionsExtension>().Any();
|
||||
if(!isConfigured)
|
||||
optionsBuilder.UseSqlite("Data Source=temp.db");
|
||||
}
|
||||
public DbSet<AddressInvoiceData> AddressInvoices
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
builder.Entity<InvoiceData>()
|
||||
.HasIndex(o => o.StoreDataId);
|
||||
public DbSet<SettingData> Settings
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
builder.Entity<PaymentData>()
|
||||
.HasIndex(o => o.InvoiceDataId);
|
||||
|
||||
builder.Entity<RefundAddressesData>()
|
||||
.HasIndex(o => o.InvoiceDataId);
|
||||
public DbSet<PairingCodeData> PairingCodes
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
builder.Entity<UserStore>()
|
||||
.HasKey(t => new
|
||||
{
|
||||
t.ApplicationUserId,
|
||||
t.StoreDataId
|
||||
});
|
||||
public DbSet<PairedSINData> PairedSINData
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
builder.Entity<UserStore>()
|
||||
.HasOne(pt => pt.ApplicationUser)
|
||||
.WithMany(p => p.UserStores)
|
||||
.HasForeignKey(pt => pt.ApplicationUserId);
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
var isConfigured = optionsBuilder.Options.Extensions.OfType<RelationalOptionsExtension>().Any();
|
||||
if (!isConfigured)
|
||||
optionsBuilder.UseSqlite("Data Source=temp.db");
|
||||
}
|
||||
|
||||
builder.Entity<UserStore>()
|
||||
.HasOne(pt => pt.StoreData)
|
||||
.WithMany(t => t.UserStores)
|
||||
.HasForeignKey(pt => pt.StoreDataId);
|
||||
}
|
||||
}
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
builder.Entity<InvoiceData>()
|
||||
.HasIndex(o => o.StoreDataId);
|
||||
|
||||
builder.Entity<PaymentData>()
|
||||
.HasIndex(o => o.InvoiceDataId);
|
||||
|
||||
builder.Entity<RefundAddressesData>()
|
||||
.HasIndex(o => o.InvoiceDataId);
|
||||
|
||||
builder.Entity<UserStore>()
|
||||
.HasKey(t => new
|
||||
{
|
||||
t.ApplicationUserId,
|
||||
t.StoreDataId
|
||||
});
|
||||
|
||||
builder.Entity<UserStore>()
|
||||
.HasOne(pt => pt.ApplicationUser)
|
||||
.WithMany(p => p.UserStores)
|
||||
.HasForeignKey(pt => pt.ApplicationUserId);
|
||||
|
||||
builder.Entity<UserStore>()
|
||||
.HasOne(pt => pt.StoreData)
|
||||
.WithMany(t => t.UserStores)
|
||||
.HasForeignKey(pt => pt.StoreDataId);
|
||||
|
||||
builder.Entity<AddressInvoiceData>()
|
||||
.HasKey(o => o.Address);
|
||||
|
||||
builder.Entity<PairingCodeData>()
|
||||
.HasKey(o => o.Id);
|
||||
|
||||
builder.Entity<PairedSINData>(b =>
|
||||
{
|
||||
b.HasIndex(o => o.SIN);
|
||||
b.HasIndex(o => o.StoreDataId);
|
||||
});
|
||||
|
||||
builder.Entity<HistoricalAddressInvoiceData>()
|
||||
.HasKey(o => new
|
||||
{
|
||||
o.InvoiceDataId,
|
||||
o.Address
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,42 +9,42 @@ using Hangfire.PostgreSql;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public enum DatabaseType
|
||||
{
|
||||
Sqlite,
|
||||
Postgres
|
||||
}
|
||||
public class ApplicationDbContextFactory
|
||||
{
|
||||
string _ConnectionString;
|
||||
DatabaseType _Type;
|
||||
public ApplicationDbContextFactory(DatabaseType type, string connectionString)
|
||||
{
|
||||
_ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
||||
_Type = type;
|
||||
}
|
||||
public enum DatabaseType
|
||||
{
|
||||
Sqlite,
|
||||
Postgres
|
||||
}
|
||||
public class ApplicationDbContextFactory
|
||||
{
|
||||
string _ConnectionString;
|
||||
DatabaseType _Type;
|
||||
public ApplicationDbContextFactory(DatabaseType type, string connectionString)
|
||||
{
|
||||
_ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
||||
_Type = type;
|
||||
}
|
||||
|
||||
public ApplicationDbContext CreateContext()
|
||||
{
|
||||
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
|
||||
ConfigureBuilder(builder);
|
||||
return new ApplicationDbContext(builder.Options);
|
||||
}
|
||||
public ApplicationDbContext CreateContext()
|
||||
{
|
||||
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
|
||||
ConfigureBuilder(builder);
|
||||
return new ApplicationDbContext(builder.Options);
|
||||
}
|
||||
|
||||
public void ConfigureBuilder(DbContextOptionsBuilder builder)
|
||||
{
|
||||
if(_Type == DatabaseType.Sqlite)
|
||||
builder.UseSqlite(_ConnectionString);
|
||||
else if(_Type == DatabaseType.Postgres)
|
||||
builder.UseNpgsql(_ConnectionString);
|
||||
}
|
||||
public void ConfigureBuilder(DbContextOptionsBuilder builder)
|
||||
{
|
||||
if (_Type == DatabaseType.Sqlite)
|
||||
builder.UseSqlite(_ConnectionString);
|
||||
else if (_Type == DatabaseType.Postgres)
|
||||
builder.UseNpgsql(_ConnectionString);
|
||||
}
|
||||
|
||||
public void ConfigureHangfireBuilder(IGlobalConfiguration builder)
|
||||
{
|
||||
if(_Type == DatabaseType.Sqlite)
|
||||
builder.UseMemoryStorage(); //Sql provider does not support multiple workers
|
||||
else if(_Type == DatabaseType.Postgres)
|
||||
builder.UsePostgreSqlStorage(_ConnectionString);
|
||||
}
|
||||
}
|
||||
public void ConfigureHangfireBuilder(IGlobalConfiguration builder)
|
||||
{
|
||||
if (_Type == DatabaseType.Sqlite)
|
||||
builder.UseMemoryStorage(); //Sql provider does not support multiple workers
|
||||
else if (_Type == DatabaseType.Postgres)
|
||||
builder.UsePostgreSqlStorage(_ConnectionString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
30
BTCPayServer/Data/HistoricalAddressInvoiceData.cs
Normal file
30
BTCPayServer/Data/HistoricalAddressInvoiceData.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class HistoricalAddressInvoiceData
|
||||
{
|
||||
public string InvoiceDataId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Address
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DateTimeOffset Assigned
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DateTimeOffset? UnAssigned
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,65 +6,74 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class InvoiceData
|
||||
{
|
||||
public string StoreDataId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public StoreData StoreData
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public class InvoiceData
|
||||
{
|
||||
public string StoreDataId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public StoreData StoreData
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Id
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Id
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DateTimeOffset Created
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public DateTimeOffset Created
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public List<PaymentData> Payments
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public List<PaymentData> Payments
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public List<RefundAddressesData> RefundAddresses
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public List<RefundAddressesData> RefundAddresses
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public byte[] Blob
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string ItemCode
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string OrderId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string Status
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string ExceptionStatus
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string CustomerEmail
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
public List<HistoricalAddressInvoiceData> HistoricalAddressInvoices
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public byte[] Blob
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string ItemCode
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string OrderId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string Status
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string ExceptionStatus
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string CustomerEmail
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public List<AddressInvoiceData> AddressInvoices
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
39
BTCPayServer/Data/PairedSINData.cs
Normal file
39
BTCPayServer/Data/PairedSINData.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class PairedSINData
|
||||
{
|
||||
public string Id
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Facade
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string StoreDataId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Label
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public DateTimeOffset PairingTime
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string SIN
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
50
BTCPayServer/Data/PairingCodeData.cs
Normal file
50
BTCPayServer/Data/PairingCodeData.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class PairingCodeData
|
||||
{
|
||||
public string Id
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Facade
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string StoreDataId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public DateTimeOffset Expiration
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string Label
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string SIN
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public DateTime DateCreated
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string TokenValue
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
@ -5,25 +5,29 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class PaymentData
|
||||
{
|
||||
public string Id
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public class PaymentData
|
||||
{
|
||||
public string Id
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string InvoiceDataId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public InvoiceData InvoiceData
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string InvoiceDataId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public InvoiceData InvoiceData
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public byte[] Blob
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
public byte[] Blob
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public bool Accounted
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -7,21 +7,21 @@ namespace BTCPayServer.Data
|
||||
{
|
||||
public class RefundAddressesData
|
||||
{
|
||||
public string Id
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string InvoiceDataId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public InvoiceData InvoiceData
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public byte[] Blob
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
public string Id
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string InvoiceDataId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public InvoiceData InvoiceData
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public byte[] Blob
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,14 +7,14 @@ namespace BTCPayServer.Data
|
||||
{
|
||||
public class SettingData
|
||||
{
|
||||
public string Id
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Id
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Value
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
public string Value
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,55 +1,99 @@
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.ComponentModel;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class StoreData
|
||||
{
|
||||
public string Id
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string Id
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public List<UserStore> UserStores
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public List<UserStore> UserStores
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string DerivationStrategy
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string DerivationStrategy
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string StoreName
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string StoreName
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public SpeedPolicy SpeedPolicy
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public SpeedPolicy SpeedPolicy
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string StoreWebsite
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string StoreWebsite
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public byte[] StoreCertificate
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public byte[] StoreCertificate
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[NotMapped]
|
||||
public string Role
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
[NotMapped]
|
||||
public string Role
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public byte[] StoreBlob
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public StoreBlob GetStoreBlob(Network network)
|
||||
{
|
||||
return StoreBlob == null ? new StoreBlob() : new Serializer(network).ToObject<StoreBlob>(Encoding.UTF8.GetString(StoreBlob));
|
||||
}
|
||||
|
||||
public bool SetStoreBlob(StoreBlob storeBlob, Network network)
|
||||
{
|
||||
var original = new Serializer(network).ToString(GetStoreBlob(network));
|
||||
var newBlob = new Serializer(network).ToString(storeBlob);
|
||||
if (original == newBlob)
|
||||
return false;
|
||||
StoreBlob = Encoding.UTF8.GetBytes(newBlob);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class StoreBlob
|
||||
{
|
||||
public StoreBlob()
|
||||
{
|
||||
MonitoringExpiration = 60;
|
||||
}
|
||||
public bool NetworkFeeDisabled
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
[DefaultValue(60)]
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
public int MonitoringExpiration
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,29 +6,29 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class UserStore
|
||||
{
|
||||
public string ApplicationUserId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public ApplicationUser ApplicationUser
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public class UserStore
|
||||
{
|
||||
public string ApplicationUserId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public ApplicationUser ApplicationUser
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string StoreDataId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public StoreData StoreData
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Role
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
public string StoreDataId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public StoreData StoreData
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Role
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +0,0 @@
|
||||
FROM microsoft/aspnetcore-build AS builder
|
||||
WORKDIR /source
|
||||
# caches restore result by copying csproj file separately
|
||||
COPY *.csproj .
|
||||
RUN dotnet restore
|
||||
# copies the rest of your code
|
||||
COPY . .
|
||||
RUN dotnet publish --output /app/ --configuration Release
|
||||
|
||||
FROM microsoft/aspnetcore:2.0.0
|
||||
WORKDIR /app
|
||||
|
||||
RUN mkdir /datadir
|
||||
ENV BTCPAY_DATADIR=/datadir
|
||||
VOLUME /datadir
|
||||
|
||||
COPY --from=builder "/app" .
|
||||
ENTRYPOINT ["dotnet", "BTCPayServer.dll"]
|
14
BTCPayServer/Eclair/AllChannelResponse.cs
Normal file
14
BTCPayServer/Eclair/AllChannelResponse.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Eclair
|
||||
{
|
||||
public class AllChannelResponse
|
||||
{
|
||||
public string ShortChannelId { get; set; }
|
||||
public string NodeId1 { get; set; }
|
||||
public string NodeId2 { get; set; }
|
||||
}
|
||||
}
|
21
BTCPayServer/Eclair/ChannelResponse.cs
Normal file
21
BTCPayServer/Eclair/ChannelResponse.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Eclair
|
||||
{
|
||||
public class ChannelResponse
|
||||
{
|
||||
|
||||
public string NodeId { get; set; }
|
||||
public string ChannelId { get; set; }
|
||||
public string State { get; set; }
|
||||
}
|
||||
public static class ChannelStates
|
||||
{
|
||||
public const string WAIT_FOR_FUNDING_CONFIRMED = "WAIT_FOR_FUNDING_CONFIRMED";
|
||||
|
||||
public const string NORMAL = "NORMAL";
|
||||
}
|
||||
}
|
230
BTCPayServer/Eclair/EclairRPCClient.cs
Normal file
230
BTCPayServer/Eclair/EclairRPCClient.cs
Normal file
@ -0,0 +1,230 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
using NBitcoin.JsonConverters;
|
||||
using NBitcoin.RPC;
|
||||
|
||||
namespace BTCPayServer.Eclair
|
||||
{
|
||||
public class EclairRPCClient
|
||||
{
|
||||
public EclairRPCClient(Uri address, Network network)
|
||||
{
|
||||
if (address == null)
|
||||
throw new ArgumentNullException(nameof(address));
|
||||
if (network == null)
|
||||
throw new ArgumentNullException(nameof(network));
|
||||
Address = address;
|
||||
Network = network;
|
||||
}
|
||||
|
||||
public Network Network { get; private set; }
|
||||
|
||||
|
||||
public GetInfoResponse GetInfo()
|
||||
{
|
||||
return GetInfoAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public Task<GetInfoResponse> GetInfoAsync()
|
||||
{
|
||||
return SendCommandAsync<GetInfoResponse>(new RPCRequest("getinfo", new object[] { }));
|
||||
}
|
||||
|
||||
public async Task<T> SendCommandAsync<T>(RPCRequest request, bool throwIfRPCError = true)
|
||||
{
|
||||
var response = await SendCommandAsync(request, throwIfRPCError);
|
||||
return Serializer.ToObject<T>(response.ResultString, Network);
|
||||
}
|
||||
|
||||
public async Task<RPCResponse> SendCommandAsync(RPCRequest request, bool throwIfRPCError = true)
|
||||
{
|
||||
RPCResponse response = null;
|
||||
HttpWebRequest webRequest = response == null ? CreateWebRequest() : null;
|
||||
if (response == null)
|
||||
{
|
||||
var writer = new StringWriter();
|
||||
request.WriteJSON(writer);
|
||||
writer.Flush();
|
||||
var json = writer.ToString();
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
#if !(PORTABLE || NETCORE)
|
||||
webRequest.ContentLength = bytes.Length;
|
||||
#endif
|
||||
var dataStream = await webRequest.GetRequestStreamAsync().ConfigureAwait(false);
|
||||
await dataStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
|
||||
await dataStream.FlushAsync().ConfigureAwait(false);
|
||||
dataStream.Dispose();
|
||||
}
|
||||
WebResponse webResponse = null;
|
||||
WebResponse errorResponse = null;
|
||||
try
|
||||
{
|
||||
webResponse = response == null ? await webRequest.GetResponseAsync().ConfigureAwait(false) : null;
|
||||
response = response ?? RPCResponse.Load(await ToMemoryStreamAsync(webResponse.GetResponseStream()).ConfigureAwait(false));
|
||||
|
||||
if (throwIfRPCError)
|
||||
response.ThrowIfError();
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
if (ex.Response == null || ex.Response.ContentLength == 0 ||
|
||||
!ex.Response.ContentType.Equals("application/json", StringComparison.Ordinal))
|
||||
throw;
|
||||
errorResponse = ex.Response;
|
||||
response = RPCResponse.Load(await ToMemoryStreamAsync(errorResponse.GetResponseStream()).ConfigureAwait(false));
|
||||
if (throwIfRPCError)
|
||||
response.ThrowIfError();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (errorResponse != null)
|
||||
{
|
||||
errorResponse.Dispose();
|
||||
errorResponse = null;
|
||||
}
|
||||
if (webResponse != null)
|
||||
{
|
||||
webResponse.Dispose();
|
||||
webResponse = null;
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
public AllChannelResponse[] AllChannels()
|
||||
{
|
||||
return AllChannelsAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<AllChannelResponse[]> AllChannelsAsync()
|
||||
{
|
||||
return await SendCommandAsync<AllChannelResponse[]>(new RPCRequest("allchannels", new object[] { })).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public string[] Channels()
|
||||
{
|
||||
return ChannelsAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<string[]> ChannelsAsync()
|
||||
{
|
||||
return await SendCommandAsync<string[]>(new RPCRequest("channels", new object[] { })).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Close(string channelId)
|
||||
{
|
||||
CloseAsync(channelId).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task CloseAsync(string channelId)
|
||||
{
|
||||
if (channelId == null)
|
||||
throw new ArgumentNullException(nameof(channelId));
|
||||
try
|
||||
{
|
||||
await SendCommandAsync(new RPCRequest("close", new object[] { channelId })).ConfigureAwait(false);
|
||||
}
|
||||
catch (RPCException ex) when (ex.Message == "closing already in progress")
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public ChannelResponse Channel(string channelId)
|
||||
{
|
||||
return ChannelAsync(channelId).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<ChannelResponse> ChannelAsync(string channelId)
|
||||
{
|
||||
if (channelId == null)
|
||||
throw new ArgumentNullException(nameof(channelId));
|
||||
return await SendCommandAsync<ChannelResponse>(new RPCRequest("channel", new object[] { channelId })).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public string[] AllNodes()
|
||||
{
|
||||
return AllNodesAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<string[]> AllNodesAsync()
|
||||
{
|
||||
return await SendCommandAsync<string[]>(new RPCRequest("allnodes", new object[] { })).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Uri Address { get; private set; }
|
||||
|
||||
private HttpWebRequest CreateWebRequest()
|
||||
{
|
||||
var webRequest = (HttpWebRequest)WebRequest.Create(Address.AbsoluteUri);
|
||||
webRequest.ContentType = "application/json";
|
||||
webRequest.Method = "POST";
|
||||
return webRequest;
|
||||
}
|
||||
|
||||
|
||||
private async Task<Stream> ToMemoryStreamAsync(Stream stream)
|
||||
{
|
||||
MemoryStream ms = new MemoryStream();
|
||||
await stream.CopyToAsync(ms).ConfigureAwait(false);
|
||||
ms.Position = 0;
|
||||
return ms;
|
||||
}
|
||||
|
||||
public string Open(NodeInfo node, Money fundingSatoshi, LightMoney pushAmount = null)
|
||||
{
|
||||
return OpenAsync(node, fundingSatoshi, pushAmount).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public string Connect(NodeInfo node)
|
||||
{
|
||||
return ConnectAsync(node).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<string> ConnectAsync(NodeInfo node)
|
||||
{
|
||||
if (node == null)
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
return (await SendCommandAsync(new RPCRequest("connect", new object[] { node.NodeId, node.Host, node.Port })).ConfigureAwait(false)).ResultString;
|
||||
}
|
||||
|
||||
public string Receive(LightMoney amount, string description = null)
|
||||
{
|
||||
return ReceiveAsync(amount, description).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<string> ReceiveAsync(LightMoney amount, string description = null)
|
||||
{
|
||||
if (amount == null)
|
||||
throw new ArgumentNullException(nameof(amount));
|
||||
List<object> args = new List<object>();
|
||||
args.Add(amount.MilliSatoshi);
|
||||
if(description != null)
|
||||
{
|
||||
args.Add(description);
|
||||
}
|
||||
return (await SendCommandAsync(new RPCRequest("receive", args.ToArray())).ConfigureAwait(false)).ResultString;
|
||||
}
|
||||
|
||||
public async Task<string> OpenAsync(NodeInfo node, Money fundingSatoshi, LightMoney pushAmount = null)
|
||||
{
|
||||
if (fundingSatoshi == null)
|
||||
throw new ArgumentNullException(nameof(fundingSatoshi));
|
||||
if (node == null)
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
pushAmount = pushAmount ?? LightMoney.Zero;
|
||||
|
||||
var result = await SendCommandAsync(new RPCRequest("open", new object[] { node.NodeId, node.Host, node.Port, fundingSatoshi.Satoshi, pushAmount.MilliSatoshi }));
|
||||
|
||||
return result.ResultString;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
18
BTCPayServer/Eclair/GetInfoResponse.cs
Normal file
18
BTCPayServer/Eclair/GetInfoResponse.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Eclair
|
||||
{
|
||||
public class GetInfoResponse
|
||||
{
|
||||
public string NodeId { get; set; }
|
||||
public string Alias { get; set; }
|
||||
public int Port { get; set; }
|
||||
public uint256 ChainHash { get; set; }
|
||||
public int BlockHeight { get; set; }
|
||||
}
|
||||
}
|
569
BTCPayServer/Eclair/LightMoney.cs
Normal file
569
BTCPayServer/Eclair/LightMoney.cs
Normal file
@ -0,0 +1,569 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Eclair
|
||||
{
|
||||
public enum LightMoneyUnit : ulong
|
||||
{
|
||||
BTC = 100000000000,
|
||||
MilliBTC = 100000000,
|
||||
Bit = 100000,
|
||||
Satoshi = 1000,
|
||||
MilliSatoshi = 1
|
||||
}
|
||||
|
||||
public class LightMoney : IComparable, IComparable<LightMoney>, IEquatable<LightMoney>
|
||||
{
|
||||
|
||||
|
||||
// for decimal.TryParse. None of the NumberStyles' composed values is useful for bitcoin style
|
||||
private const NumberStyles BitcoinStyle =
|
||||
NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite
|
||||
| NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Parse a bitcoin amount (Culture Invariant)
|
||||
/// </summary>
|
||||
/// <param name="bitcoin"></param>
|
||||
/// <param name="nRet"></param>
|
||||
/// <returns></returns>
|
||||
public static bool TryParse(string bitcoin, out LightMoney nRet)
|
||||
{
|
||||
nRet = null;
|
||||
|
||||
decimal value;
|
||||
if (!decimal.TryParse(bitcoin, BitcoinStyle, CultureInfo.InvariantCulture, out value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
nRet = new LightMoney(value, LightMoneyUnit.BTC);
|
||||
return true;
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a bitcoin amount (Culture Invariant)
|
||||
/// </summary>
|
||||
/// <param name="bitcoin"></param>
|
||||
/// <returns></returns>
|
||||
public static LightMoney Parse(string bitcoin)
|
||||
{
|
||||
LightMoney result;
|
||||
if (TryParse(bitcoin, out result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
throw new FormatException("Impossible to parse the string in a bitcoin amount");
|
||||
}
|
||||
|
||||
long _MilliSatoshis;
|
||||
public long MilliSatoshi
|
||||
{
|
||||
get
|
||||
{
|
||||
return _MilliSatoshis;
|
||||
}
|
||||
// used as a central point where long.MinValue checking can be enforced
|
||||
private set
|
||||
{
|
||||
CheckLongMinValue(value);
|
||||
_MilliSatoshis = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get absolute value of the instance
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public LightMoney Abs()
|
||||
{
|
||||
var a = this;
|
||||
if (a < LightMoney.Zero)
|
||||
a = -a;
|
||||
return a;
|
||||
}
|
||||
|
||||
public LightMoney(int satoshis)
|
||||
{
|
||||
MilliSatoshi = satoshis;
|
||||
}
|
||||
|
||||
public LightMoney(uint satoshis)
|
||||
{
|
||||
MilliSatoshi = satoshis;
|
||||
}
|
||||
|
||||
public LightMoney(long satoshis)
|
||||
{
|
||||
MilliSatoshi = satoshis;
|
||||
}
|
||||
|
||||
public LightMoney(ulong satoshis)
|
||||
{
|
||||
// overflow check.
|
||||
// ulong.MaxValue is greater than long.MaxValue
|
||||
checked
|
||||
{
|
||||
MilliSatoshi = (long)satoshis;
|
||||
}
|
||||
}
|
||||
|
||||
public LightMoney(decimal amount, LightMoneyUnit unit)
|
||||
{
|
||||
// sanity check. Only valid units are allowed
|
||||
CheckMoneyUnit(unit, "unit");
|
||||
checked
|
||||
{
|
||||
var satoshi = amount * (long)unit;
|
||||
MilliSatoshi = (long)satoshi;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Split the Money in parts without loss
|
||||
/// </summary>
|
||||
/// <param name="parts">The number of parts (must be more than 0)</param>
|
||||
/// <returns>The splitted money</returns>
|
||||
public IEnumerable<LightMoney> Split(int parts)
|
||||
{
|
||||
if (parts <= 0)
|
||||
throw new ArgumentOutOfRangeException("Parts should be more than 0", "parts");
|
||||
long remain;
|
||||
long result = DivRem(_MilliSatoshis, parts, out remain);
|
||||
|
||||
for (int i = 0; i < parts; i++)
|
||||
{
|
||||
yield return LightMoney.Satoshis(result + (remain > 0 ? 1 : 0));
|
||||
remain--;
|
||||
}
|
||||
}
|
||||
|
||||
private static long DivRem(long a, long b, out long result)
|
||||
{
|
||||
result = a % b;
|
||||
return a / b;
|
||||
}
|
||||
|
||||
public static LightMoney FromUnit(decimal amount, LightMoneyUnit unit)
|
||||
{
|
||||
return new LightMoney(amount, unit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert Money to decimal (same as ToDecimal)
|
||||
/// </summary>
|
||||
/// <param name="unit"></param>
|
||||
/// <returns></returns>
|
||||
public decimal ToUnit(LightMoneyUnit unit)
|
||||
{
|
||||
CheckMoneyUnit(unit, "unit");
|
||||
// overflow safe because (long / int) always fit in decimal
|
||||
// decimal operations are checked by default
|
||||
return (decimal)MilliSatoshi / (int)unit;
|
||||
}
|
||||
/// <summary>
|
||||
/// Convert Money to decimal (same as ToUnit)
|
||||
/// </summary>
|
||||
/// <param name="unit"></param>
|
||||
/// <returns></returns>
|
||||
public decimal ToDecimal(LightMoneyUnit unit)
|
||||
{
|
||||
return ToUnit(unit);
|
||||
}
|
||||
|
||||
public static LightMoney Coins(decimal coins)
|
||||
{
|
||||
// overflow safe.
|
||||
// decimal operations are checked by default
|
||||
return new LightMoney(coins * (ulong)LightMoneyUnit.BTC, LightMoneyUnit.MilliBTC);
|
||||
}
|
||||
|
||||
public static LightMoney Bits(decimal bits)
|
||||
{
|
||||
// overflow safe.
|
||||
// decimal operations are checked by default
|
||||
return new LightMoney(bits * (ulong)LightMoneyUnit.Bit, LightMoneyUnit.MilliBTC);
|
||||
}
|
||||
|
||||
public static LightMoney Cents(decimal cents)
|
||||
{
|
||||
// overflow safe.
|
||||
// decimal operations are checked by default
|
||||
return new LightMoney(cents * (ulong)LightMoneyUnit.Bit, LightMoneyUnit.MilliBTC);
|
||||
}
|
||||
|
||||
public static LightMoney Satoshis(decimal sats)
|
||||
{
|
||||
return new LightMoney(sats * (ulong)LightMoneyUnit.Satoshi, LightMoneyUnit.MilliBTC);
|
||||
}
|
||||
|
||||
public static LightMoney Satoshis(ulong sats)
|
||||
{
|
||||
return new LightMoney(sats);
|
||||
}
|
||||
|
||||
public static LightMoney Satoshis(long sats)
|
||||
{
|
||||
return new LightMoney(sats);
|
||||
}
|
||||
|
||||
public static LightMoney MilliSatoshis(long msats)
|
||||
{
|
||||
return new LightMoney(msats);
|
||||
}
|
||||
|
||||
public static LightMoney MilliSatoshis(ulong msats)
|
||||
{
|
||||
return new LightMoney(msats);
|
||||
}
|
||||
|
||||
#region IEquatable<Money> Members
|
||||
|
||||
public bool Equals(LightMoney other)
|
||||
{
|
||||
if (other == null)
|
||||
return false;
|
||||
return _MilliSatoshis.Equals(other._MilliSatoshis);
|
||||
}
|
||||
|
||||
public int CompareTo(LightMoney other)
|
||||
{
|
||||
if (other == null)
|
||||
return 1;
|
||||
return _MilliSatoshis.CompareTo(other._MilliSatoshis);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IComparable Members
|
||||
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return 1;
|
||||
LightMoney m = obj as LightMoney;
|
||||
if (m != null)
|
||||
return _MilliSatoshis.CompareTo(m._MilliSatoshis);
|
||||
#if !(PORTABLE || NETCORE)
|
||||
return _MilliSatoshis.CompareTo(obj);
|
||||
#else
|
||||
return _Satoshis.CompareTo((long)obj);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public static LightMoney operator -(LightMoney left, LightMoney right)
|
||||
{
|
||||
if (left == null)
|
||||
throw new ArgumentNullException("left");
|
||||
if (right == null)
|
||||
throw new ArgumentNullException("right");
|
||||
return new LightMoney(checked(left._MilliSatoshis - right._MilliSatoshis));
|
||||
}
|
||||
public static LightMoney operator -(LightMoney left)
|
||||
{
|
||||
if (left == null)
|
||||
throw new ArgumentNullException("left");
|
||||
return new LightMoney(checked(-left._MilliSatoshis));
|
||||
}
|
||||
public static LightMoney operator +(LightMoney left, LightMoney right)
|
||||
{
|
||||
if (left == null)
|
||||
throw new ArgumentNullException("left");
|
||||
if (right == null)
|
||||
throw new ArgumentNullException("right");
|
||||
return new LightMoney(checked(left._MilliSatoshis + right._MilliSatoshis));
|
||||
}
|
||||
public static LightMoney operator *(int left, LightMoney right)
|
||||
{
|
||||
if (right == null)
|
||||
throw new ArgumentNullException("right");
|
||||
return LightMoney.Satoshis(checked(left * right._MilliSatoshis));
|
||||
}
|
||||
|
||||
public static LightMoney operator *(LightMoney right, int left)
|
||||
{
|
||||
if (right == null)
|
||||
throw new ArgumentNullException("right");
|
||||
return LightMoney.Satoshis(checked(right._MilliSatoshis * left));
|
||||
}
|
||||
public static LightMoney operator *(long left, LightMoney right)
|
||||
{
|
||||
if (right == null)
|
||||
throw new ArgumentNullException("right");
|
||||
return LightMoney.Satoshis(checked(left * right._MilliSatoshis));
|
||||
}
|
||||
public static LightMoney operator *(LightMoney right, long left)
|
||||
{
|
||||
if (right == null)
|
||||
throw new ArgumentNullException("right");
|
||||
return LightMoney.Satoshis(checked(left * right._MilliSatoshis));
|
||||
}
|
||||
|
||||
public static LightMoney operator /(LightMoney left, long right)
|
||||
{
|
||||
if (left == null)
|
||||
throw new ArgumentNullException("left");
|
||||
return new LightMoney(checked(left._MilliSatoshis / right));
|
||||
}
|
||||
|
||||
public static bool operator <(LightMoney left, LightMoney right)
|
||||
{
|
||||
if (left == null)
|
||||
throw new ArgumentNullException("left");
|
||||
if (right == null)
|
||||
throw new ArgumentNullException("right");
|
||||
return left._MilliSatoshis < right._MilliSatoshis;
|
||||
}
|
||||
public static bool operator >(LightMoney left, LightMoney right)
|
||||
{
|
||||
if (left == null)
|
||||
throw new ArgumentNullException("left");
|
||||
if (right == null)
|
||||
throw new ArgumentNullException("right");
|
||||
return left._MilliSatoshis > right._MilliSatoshis;
|
||||
}
|
||||
public static bool operator <=(LightMoney left, LightMoney right)
|
||||
{
|
||||
if (left == null)
|
||||
throw new ArgumentNullException("left");
|
||||
if (right == null)
|
||||
throw new ArgumentNullException("right");
|
||||
return left._MilliSatoshis <= right._MilliSatoshis;
|
||||
}
|
||||
public static bool operator >=(LightMoney left, LightMoney right)
|
||||
{
|
||||
if (left == null)
|
||||
throw new ArgumentNullException("left");
|
||||
if (right == null)
|
||||
throw new ArgumentNullException("right");
|
||||
return left._MilliSatoshis >= right._MilliSatoshis;
|
||||
}
|
||||
|
||||
public static implicit operator LightMoney(long value)
|
||||
{
|
||||
return new LightMoney(value);
|
||||
}
|
||||
public static implicit operator LightMoney(int value)
|
||||
{
|
||||
return new LightMoney(value);
|
||||
}
|
||||
|
||||
public static implicit operator LightMoney(uint value)
|
||||
{
|
||||
return new LightMoney(value);
|
||||
}
|
||||
|
||||
public static implicit operator LightMoney(ulong value)
|
||||
{
|
||||
return new LightMoney(checked((long)value));
|
||||
}
|
||||
|
||||
public static implicit operator long(LightMoney value)
|
||||
{
|
||||
return value.MilliSatoshi;
|
||||
}
|
||||
|
||||
public static implicit operator ulong(LightMoney value)
|
||||
{
|
||||
return checked((ulong)value.MilliSatoshi);
|
||||
}
|
||||
|
||||
public static implicit operator LightMoney(string value)
|
||||
{
|
||||
return LightMoney.Parse(value);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
LightMoney item = obj as LightMoney;
|
||||
if (item == null)
|
||||
return false;
|
||||
return _MilliSatoshis.Equals(item._MilliSatoshis);
|
||||
}
|
||||
public static bool operator ==(LightMoney a, LightMoney b)
|
||||
{
|
||||
if (Object.ReferenceEquals(a, b))
|
||||
return true;
|
||||
if (((object)a == null) || ((object)b == null))
|
||||
return false;
|
||||
return a._MilliSatoshis == b._MilliSatoshis;
|
||||
}
|
||||
|
||||
public static bool operator !=(LightMoney a, LightMoney b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return _MilliSatoshis.GetHashCode();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns a culture invariant string representation of Bitcoin amount
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return ToString(false, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a culture invariant string representation of Bitcoin amount
|
||||
/// </summary>
|
||||
/// <param name="fplus">True if show + for a positive amount</param>
|
||||
/// <param name="trimExcessZero">True if trim excess zeroes</param>
|
||||
/// <returns></returns>
|
||||
public string ToString(bool fplus, bool trimExcessZero = true)
|
||||
{
|
||||
var fmt = string.Format("{{0:{0}{1}B}}",
|
||||
(fplus ? "+" : null),
|
||||
(trimExcessZero ? "2" : "11"));
|
||||
return string.Format(BitcoinFormatter.Formatter, fmt, _MilliSatoshis);
|
||||
}
|
||||
|
||||
|
||||
static LightMoney _Zero = new LightMoney(0);
|
||||
public static LightMoney Zero
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Zero;
|
||||
}
|
||||
}
|
||||
|
||||
internal class BitcoinFormatter : IFormatProvider, ICustomFormatter
|
||||
{
|
||||
public static readonly BitcoinFormatter Formatter = new BitcoinFormatter();
|
||||
|
||||
public object GetFormat(Type formatType)
|
||||
{
|
||||
return formatType == typeof(ICustomFormatter) ? this : null;
|
||||
}
|
||||
|
||||
public string Format(string format, object arg, IFormatProvider formatProvider)
|
||||
{
|
||||
if (!this.Equals(formatProvider))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var i = 0;
|
||||
var plus = format[i] == '+';
|
||||
if (plus)
|
||||
i++;
|
||||
int decPos = 0;
|
||||
if (int.TryParse(format.Substring(i, 1), out decPos))
|
||||
{
|
||||
i++;
|
||||
}
|
||||
var unit = format[i];
|
||||
var unitToUseInCalc = LightMoneyUnit.BTC;
|
||||
switch (unit)
|
||||
{
|
||||
case 'B':
|
||||
unitToUseInCalc = LightMoneyUnit.BTC;
|
||||
break;
|
||||
}
|
||||
var val = Convert.ToDecimal(arg) / (long)unitToUseInCalc;
|
||||
var zeros = new string('0', decPos);
|
||||
var rest = new string('#', 11 - decPos);
|
||||
var fmt = plus && val > 0 ? "+" : string.Empty;
|
||||
|
||||
fmt += "{0:0" + (decPos > 0 ? "." + zeros + rest : string.Empty) + "}";
|
||||
return string.Format(CultureInfo.InvariantCulture, fmt, val);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tell if amount is almost equal to this instance
|
||||
/// </summary>
|
||||
/// <param name="amount"></param>
|
||||
/// <param name="dust">more or less amount</param>
|
||||
/// <returns>true if equals, else false</returns>
|
||||
public bool Almost(LightMoney amount, LightMoney dust)
|
||||
{
|
||||
if (amount == null)
|
||||
throw new ArgumentNullException("amount");
|
||||
if (dust == null)
|
||||
throw new ArgumentNullException("dust");
|
||||
return (amount - this).Abs() <= dust;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tell if amount is almost equal to this instance
|
||||
/// </summary>
|
||||
/// <param name="amount"></param>
|
||||
/// <param name="margin">error margin (between 0 and 1)</param>
|
||||
/// <returns>true if equals, else false</returns>
|
||||
public bool Almost(LightMoney amount, decimal margin)
|
||||
{
|
||||
if (amount == null)
|
||||
throw new ArgumentNullException("amount");
|
||||
if (margin < 0.0m || margin > 1.0m)
|
||||
throw new ArgumentOutOfRangeException("margin", "margin should be between 0 and 1");
|
||||
var dust = LightMoney.Satoshis((decimal)this.MilliSatoshi * margin);
|
||||
return Almost(amount, dust);
|
||||
}
|
||||
|
||||
public static LightMoney Min(LightMoney a, LightMoney b)
|
||||
{
|
||||
if (a == null)
|
||||
throw new ArgumentNullException("a");
|
||||
if (b == null)
|
||||
throw new ArgumentNullException("b");
|
||||
if (a <= b)
|
||||
return a;
|
||||
return b;
|
||||
}
|
||||
|
||||
public static LightMoney Max(LightMoney a, LightMoney b)
|
||||
{
|
||||
if (a == null)
|
||||
throw new ArgumentNullException("a");
|
||||
if (b == null)
|
||||
throw new ArgumentNullException("b");
|
||||
if (a >= b)
|
||||
return a;
|
||||
return b;
|
||||
}
|
||||
|
||||
private static void CheckLongMinValue(long value)
|
||||
{
|
||||
if (value == long.MinValue)
|
||||
throw new OverflowException("satoshis amount should be greater than long.MinValue");
|
||||
}
|
||||
|
||||
private static void CheckMoneyUnit(LightMoneyUnit value, string paramName)
|
||||
{
|
||||
var typeOfMoneyUnit = typeof(LightMoneyUnit);
|
||||
if (!Enum.IsDefined(typeOfMoneyUnit, value))
|
||||
{
|
||||
throw new ArgumentException("Invalid value for MoneyUnit", paramName);
|
||||
}
|
||||
}
|
||||
|
||||
#region IComparable Members
|
||||
|
||||
int IComparable.CompareTo(object obj)
|
||||
{
|
||||
return this.CompareTo(obj);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
24
BTCPayServer/Eclair/NodeInfo.cs
Normal file
24
BTCPayServer/Eclair/NodeInfo.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Eclair
|
||||
{
|
||||
public class NodeInfo
|
||||
{
|
||||
public NodeInfo(string nodeId, string host, int port)
|
||||
{
|
||||
if (host == null)
|
||||
throw new ArgumentNullException(nameof(host));
|
||||
if (nodeId == null)
|
||||
throw new ArgumentNullException(nameof(nodeId));
|
||||
Port = port;
|
||||
Host = host;
|
||||
NodeId = nodeId;
|
||||
}
|
||||
public string NodeId { get; private set; }
|
||||
public string Host { get; private set; }
|
||||
public int Port { get; private set; }
|
||||
}
|
||||
}
|
129
BTCPayServer/EventAggregator.cs
Normal file
129
BTCPayServer/EventAggregator.cs
Normal file
@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Logging;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public interface IEventAggregatorSubscription : IDisposable
|
||||
{
|
||||
void Unsubscribe();
|
||||
void Resubscribe();
|
||||
}
|
||||
public class EventAggregator : IDisposable
|
||||
{
|
||||
class Subscription : IEventAggregatorSubscription
|
||||
{
|
||||
private EventAggregator aggregator;
|
||||
Type t;
|
||||
public Subscription(EventAggregator aggregator, Type t)
|
||||
{
|
||||
this.aggregator = aggregator;
|
||||
this.t = t;
|
||||
}
|
||||
|
||||
public Action<Object> Act { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (this.aggregator._Subscriptions)
|
||||
{
|
||||
if (this.aggregator._Subscriptions.TryGetValue(t, out Dictionary<Subscription, Action<object>> actions))
|
||||
{
|
||||
if (actions.Remove(this))
|
||||
{
|
||||
if (actions.Count == 0)
|
||||
this.aggregator._Subscriptions.Remove(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Resubscribe()
|
||||
{
|
||||
aggregator.Subscribe(t, this);
|
||||
}
|
||||
|
||||
public void Unsubscribe()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Publish<T>(T evt) where T : class
|
||||
{
|
||||
if (evt == null)
|
||||
throw new ArgumentNullException(nameof(evt));
|
||||
List<Action<object>> actionList = new List<Action<object>>();
|
||||
lock (_Subscriptions)
|
||||
{
|
||||
if (_Subscriptions.TryGetValue(typeof(T), out Dictionary<Subscription, Action<object>> actions))
|
||||
{
|
||||
actionList = actions.Values.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
Logs.Events.LogInformation($"New event: {evt.ToString()}");
|
||||
foreach (var sub in actionList)
|
||||
{
|
||||
try
|
||||
{
|
||||
sub(evt);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logs.Events.LogError(ex, $"Error while calling event handler");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEventAggregatorSubscription Subscribe<T>(Action<IEventAggregatorSubscription, T> subscription)
|
||||
{
|
||||
var eventType = typeof(T);
|
||||
var s = new Subscription(this, eventType);
|
||||
s.Act = (o) => subscription(s, (T)o);
|
||||
return Subscribe(eventType, s);
|
||||
}
|
||||
|
||||
private IEventAggregatorSubscription Subscribe(Type eventType, Subscription subscription)
|
||||
{
|
||||
lock (_Subscriptions)
|
||||
{
|
||||
if (!_Subscriptions.TryGetValue(eventType, out Dictionary<Subscription, Action<object>> actions))
|
||||
{
|
||||
actions = new Dictionary<Subscription, Action<object>>();
|
||||
_Subscriptions.Add(eventType, actions);
|
||||
}
|
||||
actions.Add(subscription, subscription.Act);
|
||||
}
|
||||
return subscription;
|
||||
}
|
||||
|
||||
Dictionary<Type, Dictionary<Subscription, Action<object>>> _Subscriptions = new Dictionary<Type, Dictionary<Subscription, Action<object>>>();
|
||||
|
||||
public IEventAggregatorSubscription Subscribe<T, TReturn>(Func<T, TReturn> subscription)
|
||||
{
|
||||
return Subscribe(new Action<T>((t) => subscription(t)));
|
||||
}
|
||||
|
||||
public IEventAggregatorSubscription Subscribe<T, TReturn>(Func<IEventAggregatorSubscription, T, TReturn> subscription)
|
||||
{
|
||||
return Subscribe(new Action<IEventAggregatorSubscription, T>((sub, t) => subscription(sub, t)));
|
||||
}
|
||||
|
||||
public IEventAggregatorSubscription Subscribe<T>(Action<T> subscription)
|
||||
{
|
||||
return Subscribe(new Action<IEventAggregatorSubscription, T>((sub, t) => subscription(t)));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_Subscriptions)
|
||||
{
|
||||
_Subscriptions.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
BTCPayServer/Events/InvoiceDataChangedEvent.cs
Normal file
17
BTCPayServer/Events/InvoiceDataChangedEvent.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Events
|
||||
{
|
||||
public class InvoiceDataChangedEvent
|
||||
{
|
||||
public string InvoiceId { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Invoice {InvoiceId} data changed";
|
||||
}
|
||||
}
|
||||
}
|
24
BTCPayServer/Events/InvoicePaymentEvent.cs
Normal file
24
BTCPayServer/Events/InvoicePaymentEvent.cs
Normal file
@ -0,0 +1,24 @@
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
30
BTCPayServer/Events/InvoiceStatusChangedEvent.cs
Normal file
30
BTCPayServer/Events/InvoiceStatusChangedEvent.cs
Normal file
@ -0,0 +1,30 @@
|
||||
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}";
|
||||
}
|
||||
}
|
||||
}
|
24
BTCPayServer/Events/NBXplorerStateChangedEvent.cs
Normal file
24
BTCPayServer/Events/NBXplorerStateChangedEvent.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Events
|
||||
{
|
||||
public class NBXplorerStateChangedEvent
|
||||
{
|
||||
public NBXplorerStateChangedEvent(NBXplorerState old, NBXplorerState newState)
|
||||
{
|
||||
NewState = newState;
|
||||
OldState = old;
|
||||
}
|
||||
|
||||
public NBXplorerState NewState { get; set; }
|
||||
public NBXplorerState OldState { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"NBXplorer: {OldState} => {NewState}";
|
||||
}
|
||||
}
|
||||
}
|
15
BTCPayServer/Events/NewBlockEvent.cs
Normal file
15
BTCPayServer/Events/NewBlockEvent.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Events
|
||||
{
|
||||
public class NewBlockEvent
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "New block";
|
||||
}
|
||||
}
|
||||
}
|
20
BTCPayServer/Events/TxOutReceivedEvent.cs
Normal file
20
BTCPayServer/Events/TxOutReceivedEvent.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Events
|
||||
{
|
||||
public class TxOutReceivedEvent
|
||||
{
|
||||
public Script ScriptPubKey { get; set; }
|
||||
public BitcoinAddress Address { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
String address = Address?.ToString() ?? ScriptPubKey.ToHex();
|
||||
return $"{address} received a transaction";
|
||||
}
|
||||
}
|
||||
}
|
@ -1,48 +1,82 @@
|
||||
using BTCPayServer.Authentication;
|
||||
using BTCPayServer.Configuration;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using NBitcoin;
|
||||
using System.Threading.Tasks;
|
||||
using NBXplorer;
|
||||
using NBXplorer.Models;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static string WithTrailingSlash(this string str)
|
||||
{
|
||||
if(str.EndsWith("/"))
|
||||
return str;
|
||||
return str + "/";
|
||||
}
|
||||
public static async Task<Dictionary<uint256, TransactionResult>> GetTransactions(this ExplorerClient client, uint256[] hashes, CancellationToken cts = default(CancellationToken))
|
||||
{
|
||||
hashes = hashes.Distinct().ToArray();
|
||||
var transactions = hashes
|
||||
.Select(async o => await client.GetTransactionAsync(o, cts))
|
||||
.ToArray();
|
||||
await Task.WhenAll(transactions).ConfigureAwait(false);
|
||||
return transactions.Select(t => t.Result).Where(t => t != null).ToDictionary(o => o.Transaction.GetHash());
|
||||
}
|
||||
public static string WithTrailingSlash(this string str)
|
||||
{
|
||||
if (str.EndsWith("/"))
|
||||
return str;
|
||||
return str + "/";
|
||||
}
|
||||
|
||||
public static string GetAbsoluteRoot(this HttpRequest request)
|
||||
{
|
||||
return string.Concat(
|
||||
request.Scheme,
|
||||
"://",
|
||||
request.Host.ToUriComponent(),
|
||||
request.PathBase.ToUriComponent());
|
||||
}
|
||||
public static string GetAbsoluteRoot(this HttpRequest request)
|
||||
{
|
||||
return string.Concat(
|
||||
request.Scheme,
|
||||
"://",
|
||||
request.Host.ToUriComponent(),
|
||||
request.PathBase.ToUriComponent());
|
||||
}
|
||||
|
||||
public static IServiceCollection ConfigureBTCPayServer(this IServiceCollection services, IConfiguration conf)
|
||||
{
|
||||
services.Configure<BTCPayServerOptions>(o =>
|
||||
{
|
||||
o.LoadArgs(conf);
|
||||
});
|
||||
return services;
|
||||
}
|
||||
public static IServiceCollection ConfigureBTCPayServer(this IServiceCollection services, IConfiguration conf)
|
||||
{
|
||||
services.Configure<BTCPayServerOptions>(o =>
|
||||
{
|
||||
o.LoadArgs(conf);
|
||||
});
|
||||
return services;
|
||||
}
|
||||
|
||||
|
||||
public static BitIdentity GetBitIdentity(this Controller controller)
|
||||
{
|
||||
if(!(controller.User.Identity is BitIdentity))
|
||||
throw new UnauthorizedAccessException("no-bitid");
|
||||
return (BitIdentity)controller.User.Identity;
|
||||
}
|
||||
}
|
||||
public static BitIdentity GetBitIdentity(this Controller controller, bool throws = true)
|
||||
{
|
||||
if (!(controller.User.Identity is BitIdentity))
|
||||
return throws ? throw new UnauthorizedAccessException("no-bitid") : (BitIdentity)null;
|
||||
return (BitIdentity)controller.User.Identity;
|
||||
}
|
||||
|
||||
private static JsonSerializerSettings jsonSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
|
||||
public static string ToJson(this object o)
|
||||
{
|
||||
var res = JsonConvert.SerializeObject(o, Formatting.None, jsonSettings);
|
||||
return res;
|
||||
}
|
||||
|
||||
public static HtmlString ToJSVariableModel(this object o, string variableName)
|
||||
{
|
||||
var encodedJson = JavaScriptEncoder.Default.Encode(o.ToJson());
|
||||
return new HtmlString($"var {variableName} = JSON.parse('" + encodedJson + "');");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -7,70 +7,72 @@ using System.Text;
|
||||
|
||||
namespace BTCPayServer.Filters
|
||||
{
|
||||
public class MediaTypeConstraintAttribute : Attribute, IActionConstraint
|
||||
{
|
||||
public MediaTypeConstraintAttribute(string mediaType)
|
||||
{
|
||||
MediaType = mediaType ?? throw new ArgumentNullException(nameof(mediaType));
|
||||
}
|
||||
public class MediaTypeConstraintAttribute : Attribute, IActionConstraint
|
||||
{
|
||||
public MediaTypeConstraintAttribute(string mediaType)
|
||||
{
|
||||
MediaType = mediaType ?? throw new ArgumentNullException(nameof(mediaType));
|
||||
}
|
||||
|
||||
public string MediaType
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string MediaType
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public int Order => 100;
|
||||
public int Order => 100;
|
||||
|
||||
public bool Accept(ActionConstraintContext context)
|
||||
{
|
||||
var match = context.RouteContext.HttpContext.Request.ContentType?.StartsWith(MediaType, StringComparison.Ordinal);
|
||||
return match.HasValue && match.Value;
|
||||
}
|
||||
}
|
||||
public bool Accept(ActionConstraintContext context)
|
||||
{
|
||||
var match = context.RouteContext.HttpContext.Request.ContentType?.StartsWith(MediaType, StringComparison.Ordinal);
|
||||
return match.HasValue && match.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public class BitpayAPIConstraintAttribute : Attribute, IActionConstraint
|
||||
{
|
||||
public BitpayAPIConstraintAttribute(bool isBitpayAPI = true)
|
||||
{
|
||||
IsBitpayAPI = isBitpayAPI;
|
||||
}
|
||||
public class BitpayAPIConstraintAttribute : Attribute, IActionConstraint
|
||||
{
|
||||
public BitpayAPIConstraintAttribute(bool isBitpayAPI = true)
|
||||
{
|
||||
IsBitpayAPI = isBitpayAPI;
|
||||
}
|
||||
|
||||
public bool IsBitpayAPI
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public int Order => 100;
|
||||
public bool IsBitpayAPI
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public int Order => 100;
|
||||
|
||||
public bool Accept(ActionConstraintContext context)
|
||||
{
|
||||
return context.RouteContext.HttpContext.Request.Headers["x-accept-version"].Where(h => h == "2.0.0").Any() == IsBitpayAPI;
|
||||
}
|
||||
}
|
||||
public bool Accept(ActionConstraintContext context)
|
||||
{
|
||||
var hasVersion = context.RouteContext.HttpContext.Request.Headers["x-accept-version"].Where(h => h == "2.0.0").Any();
|
||||
var hasIdentity = context.RouteContext.HttpContext.Request.Headers["x-identity"].Any();
|
||||
return (hasVersion || hasIdentity) == IsBitpayAPI;
|
||||
}
|
||||
}
|
||||
|
||||
public class AcceptMediaTypeConstraintAttribute : Attribute, IActionConstraint
|
||||
{
|
||||
public AcceptMediaTypeConstraintAttribute(string mediaType, bool expectedValue = true)
|
||||
{
|
||||
MediaType = mediaType ?? throw new ArgumentNullException(nameof(mediaType));
|
||||
ExpectedValue = expectedValue;
|
||||
}
|
||||
public class AcceptMediaTypeConstraintAttribute : Attribute, IActionConstraint
|
||||
{
|
||||
public AcceptMediaTypeConstraintAttribute(string mediaType, bool expectedValue = true)
|
||||
{
|
||||
MediaType = mediaType ?? throw new ArgumentNullException(nameof(mediaType));
|
||||
ExpectedValue = expectedValue;
|
||||
}
|
||||
|
||||
public bool ExpectedValue
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public bool ExpectedValue
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string MediaType
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string MediaType
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public int Order => 100;
|
||||
public int Order => 100;
|
||||
|
||||
public bool Accept(ActionConstraintContext context)
|
||||
{
|
||||
var match = context.RouteContext.HttpContext.Request.Headers["Accept"].FirstOrDefault()?.StartsWith(MediaType, StringComparison.Ordinal);
|
||||
return (match.HasValue && match.Value) == ExpectedValue;
|
||||
}
|
||||
}
|
||||
public bool Accept(ActionConstraintContext context)
|
||||
{
|
||||
var hasHeader = context.RouteContext.HttpContext.Request.Headers["Accept"].Any(m => m.StartsWith(MediaType, StringComparison.Ordinal));
|
||||
return hasHeader == ExpectedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
33
BTCPayServer/Filters/XFrameOptionsAttribute.cs
Normal file
33
BTCPayServer/Filters/XFrameOptionsAttribute.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Filters
|
||||
{
|
||||
public class XFrameOptionsAttribute : Attribute, IActionFilter
|
||||
{
|
||||
public XFrameOptionsAttribute(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
public string Value
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
var existing = context.HttpContext.Response.Headers["x-frame-options"].FirstOrDefault();
|
||||
if (existing != null && Value == null)
|
||||
context.HttpContext.Response.Headers.Remove("x-frame-options");
|
||||
else
|
||||
context.HttpContext.Response.Headers["x-frame-options"] = Value;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using BTCPayServer.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -16,7 +17,7 @@ using NBXplorer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Fees;
|
||||
@ -31,171 +32,200 @@ using Microsoft.AspNetCore.Identity;
|
||||
using BTCPayServer.Models;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Authentication;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using BTCPayServer.Logging;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
{
|
||||
public static class BTCPayServerServices
|
||||
{
|
||||
public class OwnStoreAuthorizationRequirement : IAuthorizationRequirement
|
||||
{
|
||||
public OwnStoreAuthorizationRequirement()
|
||||
{
|
||||
}
|
||||
public static class BTCPayServerServices
|
||||
{
|
||||
public class OwnStoreAuthorizationRequirement : IAuthorizationRequirement
|
||||
{
|
||||
public OwnStoreAuthorizationRequirement()
|
||||
{
|
||||
}
|
||||
|
||||
public OwnStoreAuthorizationRequirement(string role)
|
||||
{
|
||||
Role = role;
|
||||
}
|
||||
public OwnStoreAuthorizationRequirement(string role)
|
||||
{
|
||||
Role = role;
|
||||
}
|
||||
|
||||
public string Role
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
public string Role
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public class OwnStoreHandler : AuthorizationHandler<OwnStoreAuthorizationRequirement>
|
||||
{
|
||||
StoreRepository _StoreRepository;
|
||||
UserManager<ApplicationUser> _UserManager;
|
||||
public OwnStoreHandler(StoreRepository storeRepository, UserManager<ApplicationUser> userManager)
|
||||
{
|
||||
_StoreRepository = storeRepository;
|
||||
_UserManager = userManager;
|
||||
}
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OwnStoreAuthorizationRequirement requirement)
|
||||
{
|
||||
object storeId = null;
|
||||
if(!((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).RouteData.Values.TryGetValue("storeId", out storeId))
|
||||
context.Succeed(requirement);
|
||||
else
|
||||
{
|
||||
var store = await _StoreRepository.FindStore((string)storeId, _UserManager.GetUserId(((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).HttpContext.User));
|
||||
if(store != null)
|
||||
if(requirement.Role == null || requirement.Role == store.Role)
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
}
|
||||
}
|
||||
class BTCPayServerConfigureOptions : IConfigureOptions<MvcOptions>
|
||||
{
|
||||
BTCPayServerOptions _Options;
|
||||
public BTCPayServerConfigureOptions(BTCPayServerOptions options)
|
||||
{
|
||||
_Options = options;
|
||||
}
|
||||
public void Configure(MvcOptions options)
|
||||
{
|
||||
if(_Options.RequireHttps)
|
||||
options.Filters.Add(new RequireHttpsAttribute());
|
||||
}
|
||||
}
|
||||
public static IServiceCollection AddBTCPayServer(this IServiceCollection services)
|
||||
{
|
||||
services.AddDbContext<ApplicationDbContext>((provider, o) =>
|
||||
{
|
||||
var factory = provider.GetRequiredService<ApplicationDbContextFactory>();
|
||||
factory.ConfigureBuilder(o);
|
||||
});
|
||||
services.TryAddSingleton<SettingsRepository>();
|
||||
services.TryAddSingleton<InvoicePaymentNotification>();
|
||||
services.TryAddSingleton<BTCPayServerOptions>(o => o.GetRequiredService<IOptions<BTCPayServerOptions>>().Value);
|
||||
services.TryAddSingleton<IConfigureOptions<MvcOptions>, BTCPayServerConfigureOptions>();
|
||||
services.TryAddSingleton(o =>
|
||||
{
|
||||
var runtime = new BTCPayServerRuntime();
|
||||
runtime.Configure(o.GetRequiredService<BTCPayServerOptions>());
|
||||
return runtime;
|
||||
});
|
||||
services.TryAddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().TokenRepository);
|
||||
services.TryAddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().InvoiceRepository);
|
||||
services.TryAddSingleton<Network>(o => o.GetRequiredService<BTCPayServerOptions>().Network);
|
||||
services.TryAddSingleton<ApplicationDbContextFactory>(o => o.GetRequiredService<BTCPayServerRuntime>().DBFactory);
|
||||
services.TryAddSingleton<StoreRepository>();
|
||||
services.TryAddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().Wallet);
|
||||
services.TryAddSingleton<CurrencyNameTable>();
|
||||
services.TryAddSingleton<IFeeProvider>(o => new NBXplorerFeeProvider()
|
||||
{
|
||||
Fallback = new FeeRate(100, 1),
|
||||
BlockTarget = 20,
|
||||
ExplorerClient = o.GetRequiredService<ExplorerClient>()
|
||||
});
|
||||
services.TryAddSingleton<ExplorerClient>(o =>
|
||||
{
|
||||
var runtime = o.GetRequiredService<BTCPayServerRuntime>();
|
||||
return runtime.Explorer;
|
||||
});
|
||||
services.TryAddSingleton<Bitpay>(o =>
|
||||
{
|
||||
if(o.GetRequiredService<BTCPayServerOptions>().Network == Network.Main)
|
||||
return new Bitpay(new Key(), new Uri("https://bitpay.com/"));
|
||||
else
|
||||
return new Bitpay(new Key(), new Uri("https://test.bitpay.com/"));
|
||||
});
|
||||
services.TryAddSingleton<IRateProvider, BitpayRateProvider>();
|
||||
services.TryAddSingleton<InvoiceWatcher>();
|
||||
services.TryAddSingleton<InvoiceNotificationManager>();
|
||||
services.TryAddSingleton<IHostedService>(o => o.GetRequiredService<InvoiceWatcher>());
|
||||
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
|
||||
services.TryAddSingleton<IAuthorizationHandler, OwnStoreHandler>();
|
||||
services.AddTransient<AccessTokenController>();
|
||||
// Add application services.
|
||||
services.AddTransient<IEmailSender, EmailSender>();
|
||||
public class OwnStoreHandler : AuthorizationHandler<OwnStoreAuthorizationRequirement>
|
||||
{
|
||||
StoreRepository _StoreRepository;
|
||||
UserManager<ApplicationUser> _UserManager;
|
||||
public OwnStoreHandler(StoreRepository storeRepository, UserManager<ApplicationUser> userManager)
|
||||
{
|
||||
_StoreRepository = storeRepository;
|
||||
_UserManager = userManager;
|
||||
}
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OwnStoreAuthorizationRequirement requirement)
|
||||
{
|
||||
object storeId = null;
|
||||
if (!((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).RouteData.Values.TryGetValue("storeId", out storeId))
|
||||
context.Succeed(requirement);
|
||||
else if (storeId != null)
|
||||
{
|
||||
var user = _UserManager.GetUserId(((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).HttpContext.User);
|
||||
if (user != null)
|
||||
{
|
||||
var store = await _StoreRepository.FindStore((string)storeId, user);
|
||||
if (store != null)
|
||||
if (requirement.Role == null || requirement.Role == store.Role)
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public static IServiceCollection AddBTCPayServer(this IServiceCollection services)
|
||||
{
|
||||
services.AddDbContext<ApplicationDbContext>((provider, o) =>
|
||||
{
|
||||
var factory = provider.GetRequiredService<ApplicationDbContextFactory>();
|
||||
factory.ConfigureBuilder(o);
|
||||
});
|
||||
services.TryAddSingleton<SettingsRepository>();
|
||||
services.TryAddSingleton<InvoicePaymentNotification>();
|
||||
services.TryAddSingleton<BTCPayServerOptions>(o => o.GetRequiredService<IOptions<BTCPayServerOptions>>().Value);
|
||||
services.TryAddSingleton<InvoiceRepository>(o =>
|
||||
{
|
||||
var opts = o.GetRequiredService<BTCPayServerOptions>();
|
||||
var dbContext = o.GetRequiredService<ApplicationDbContextFactory>();
|
||||
var dbpath = Path.Combine(opts.DataDir, "InvoiceDB");
|
||||
if (!Directory.Exists(dbpath))
|
||||
Directory.CreateDirectory(dbpath);
|
||||
return new InvoiceRepository(dbContext, dbpath, opts.Network);
|
||||
});
|
||||
services.AddSingleton<BTCPayServerEnvironment>();
|
||||
services.TryAddSingleton<TokenRepository>();
|
||||
services.TryAddSingleton<EventAggregator>();
|
||||
services.TryAddSingleton<Network>(o => o.GetRequiredService<BTCPayServerOptions>().Network);
|
||||
services.TryAddSingleton<ApplicationDbContextFactory>(o =>
|
||||
{
|
||||
var opts = o.GetRequiredService<BTCPayServerOptions>();
|
||||
ApplicationDbContextFactory dbContext = null;
|
||||
if (opts.PostgresConnectionString == null)
|
||||
{
|
||||
var connStr = "Data Source=" + Path.Combine(opts.DataDir, "sqllite.db");
|
||||
Logs.Configuration.LogInformation($"SQLite DB used ({connStr})");
|
||||
dbContext = new ApplicationDbContextFactory(DatabaseType.Sqlite, connStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logs.Configuration.LogInformation($"Postgres DB used ({opts.PostgresConnectionString})");
|
||||
dbContext = new ApplicationDbContextFactory(DatabaseType.Postgres, opts.PostgresConnectionString);
|
||||
}
|
||||
return dbContext;
|
||||
});
|
||||
services.TryAddSingleton<StoreRepository>();
|
||||
services.TryAddSingleton<BTCPayWallet>();
|
||||
services.TryAddSingleton<CurrencyNameTable>();
|
||||
services.TryAddSingleton<IFeeProvider>(o => new NBXplorerFeeProvider()
|
||||
{
|
||||
Fallback = new FeeRate(100, 1),
|
||||
BlockTarget = 20,
|
||||
ExplorerClient = o.GetRequiredService<ExplorerClient>()
|
||||
});
|
||||
|
||||
services.AddAuthorization(o =>
|
||||
{
|
||||
o.AddPolicy("CanAccessStore", builder =>
|
||||
{
|
||||
builder.AddRequirements(new OwnStoreAuthorizationRequirement());
|
||||
});
|
||||
services.TryAddSingleton<NBXplorerWaiterAccessor>();
|
||||
services.AddSingleton<IHostedService, NBXplorerWaiter>();
|
||||
services.TryAddSingleton<ExplorerClient>(o =>
|
||||
{
|
||||
var opts = o.GetRequiredService<BTCPayServerOptions>();
|
||||
var explorer = new ExplorerClient(opts.Network, opts.Explorer);
|
||||
if (!explorer.SetCookieAuth(opts.CookieFile))
|
||||
explorer.SetNoAuth();
|
||||
return explorer;
|
||||
});
|
||||
services.TryAddSingleton<Bitpay>(o =>
|
||||
{
|
||||
if (o.GetRequiredService<BTCPayServerOptions>().Network == Network.Main)
|
||||
return new Bitpay(new Key(), new Uri("https://bitpay.com/"));
|
||||
else
|
||||
return new Bitpay(new Key(), new Uri("https://test.bitpay.com/"));
|
||||
});
|
||||
services.TryAddSingleton<IRateProvider>(o =>
|
||||
{
|
||||
var coinaverage = new CoinAverageRateProvider();
|
||||
var bitpay = new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/")));
|
||||
return new CachedRateProvider(new FallbackRateProvider(new IRateProvider[] { coinaverage, bitpay }), o.GetRequiredService<IMemoryCache>()) { CacheSpan = TimeSpan.FromMinutes(1.0) };
|
||||
});
|
||||
|
||||
services.AddSingleton<IHostedService, InvoiceNotificationManager>();
|
||||
|
||||
o.AddPolicy("OwnStore", builder =>
|
||||
{
|
||||
builder.AddRequirements(new OwnStoreAuthorizationRequirement("Owner"));
|
||||
});
|
||||
});
|
||||
services.TryAddSingleton<InvoiceWatcherAccessor>();
|
||||
services.AddSingleton<IHostedService, InvoiceWatcher>();
|
||||
services.TryAddSingleton<Initializer>();
|
||||
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
|
||||
services.TryAddSingleton<IAuthorizationHandler, OwnStoreHandler>();
|
||||
services.AddTransient<AccessTokenController>();
|
||||
services.AddTransient<CallbackController>();
|
||||
services.AddTransient<InvoiceController>();
|
||||
// Add application services.
|
||||
services.AddTransient<IEmailSender, EmailSender>();
|
||||
|
||||
return services;
|
||||
}
|
||||
services.AddAuthorization(o =>
|
||||
{
|
||||
o.AddPolicy("CanAccessStore", builder =>
|
||||
{
|
||||
builder.AddRequirements(new OwnStoreAuthorizationRequirement());
|
||||
});
|
||||
|
||||
public static IApplicationBuilder UsePayServer(this IApplicationBuilder app)
|
||||
{
|
||||
if(app.ApplicationServices.GetRequiredService<BTCPayServerOptions>().RequireHttps)
|
||||
{
|
||||
var options = new RewriteOptions().AddRedirectToHttps();
|
||||
app.UseRewriter(options);
|
||||
}
|
||||
o.AddPolicy("OwnStore", builder =>
|
||||
{
|
||||
builder.AddRequirements(new OwnStoreAuthorizationRequirement("Owner"));
|
||||
});
|
||||
});
|
||||
|
||||
using(var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
|
||||
{
|
||||
//Wait the DB is ready
|
||||
Retry(() =>
|
||||
{
|
||||
scope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate();
|
||||
});
|
||||
}
|
||||
app.UseMiddleware<BTCPayMiddleware>();
|
||||
return app;
|
||||
}
|
||||
return services;
|
||||
}
|
||||
|
||||
static void Retry(Action act)
|
||||
{
|
||||
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
||||
while(true)
|
||||
{
|
||||
try
|
||||
{
|
||||
act();
|
||||
return;
|
||||
}
|
||||
catch
|
||||
{
|
||||
if(cts.IsCancellationRequested)
|
||||
throw;
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public static IApplicationBuilder UsePayServer(this IApplicationBuilder app)
|
||||
{
|
||||
using (var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
|
||||
{
|
||||
//Wait the DB is ready
|
||||
Retry(() =>
|
||||
{
|
||||
scope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
var initialize = app.ApplicationServices.GetService<Initializer>();
|
||||
initialize.Init();
|
||||
app.UseMiddleware<BTCPayMiddleware>();
|
||||
return app;
|
||||
}
|
||||
|
||||
static void Retry(Action act)
|
||||
{
|
||||
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
act();
|
||||
return;
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (cts.IsCancellationRequested)
|
||||
throw;
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -21,83 +21,139 @@ using BTCPayServer.Configuration;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using BTCPayServer.Controllers;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
{
|
||||
public class BTCPayMiddleware
|
||||
{
|
||||
TokenRepository _TokenRepository;
|
||||
RequestDelegate _Next;
|
||||
public BTCPayMiddleware(RequestDelegate next, TokenRepository tokenRepo)
|
||||
{
|
||||
_TokenRepository = tokenRepo ?? throw new ArgumentNullException(nameof(tokenRepo));
|
||||
_Next = next ?? throw new ArgumentNullException(nameof(next));
|
||||
}
|
||||
public class BTCPayMiddleware
|
||||
{
|
||||
TokenRepository _TokenRepository;
|
||||
RequestDelegate _Next;
|
||||
BTCPayServerOptions _Options;
|
||||
|
||||
public async Task Invoke(HttpContext httpContext)
|
||||
{
|
||||
httpContext.Request.Headers.TryGetValue("x-signature", out StringValues values);
|
||||
var sig = values.FirstOrDefault();
|
||||
httpContext.Request.Headers.TryGetValue("x-identity", out values);
|
||||
var id = values.FirstOrDefault();
|
||||
if(!string.IsNullOrEmpty(sig) && !string.IsNullOrEmpty(id))
|
||||
{
|
||||
httpContext.Request.EnableRewind();
|
||||
public BTCPayMiddleware(RequestDelegate next,
|
||||
TokenRepository tokenRepo,
|
||||
BTCPayServerOptions options)
|
||||
{
|
||||
_TokenRepository = tokenRepo ?? throw new ArgumentNullException(nameof(tokenRepo));
|
||||
_Next = next ?? throw new ArgumentNullException(nameof(next));
|
||||
_Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
string body = string.Empty;
|
||||
if(httpContext.Request.ContentLength != 0 && httpContext.Request.Body != null)
|
||||
{
|
||||
using(StreamReader reader = new StreamReader(httpContext.Request.Body, Encoding.UTF8, true, 1024, true))
|
||||
{
|
||||
body = reader.ReadToEnd();
|
||||
}
|
||||
httpContext.Request.Body.Position = 0;
|
||||
}
|
||||
|
||||
var url = httpContext.Request.GetEncodedUrl();
|
||||
try
|
||||
{
|
||||
var key = new PubKey(id);
|
||||
if(BitIdExtensions.CheckBitIDSignature(key, sig, url, body))
|
||||
{
|
||||
var bitid = new BitIdentity(key);
|
||||
httpContext.User = new GenericPrincipal(bitid, new string[0]);
|
||||
Logs.PayServer.LogDebug($"BitId signature check success for SIN {bitid.SIN}");
|
||||
}
|
||||
}
|
||||
catch(FormatException) { }
|
||||
if(!(httpContext.User.Identity is BitIdentity))
|
||||
Logs.PayServer.LogDebug("BitId signature check failed");
|
||||
}
|
||||
public async Task Invoke(HttpContext httpContext)
|
||||
{
|
||||
RewriteHostIfNeeded(httpContext);
|
||||
httpContext.Request.Headers.TryGetValue("x-signature", out StringValues values);
|
||||
var sig = values.FirstOrDefault();
|
||||
httpContext.Request.Headers.TryGetValue("x-identity", out values);
|
||||
var id = values.FirstOrDefault();
|
||||
if (!string.IsNullOrEmpty(sig) && !string.IsNullOrEmpty(id))
|
||||
{
|
||||
httpContext.Request.EnableRewind();
|
||||
|
||||
try
|
||||
{
|
||||
await _Next(httpContext);
|
||||
}
|
||||
catch(UnauthorizedAccessException ex)
|
||||
{
|
||||
await HandleBitpayHttpException(httpContext, new BitpayHttpException(401, ex.Message));
|
||||
}
|
||||
catch(BitpayHttpException ex)
|
||||
{
|
||||
await HandleBitpayHttpException(httpContext, ex);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Logs.PayServer.LogCritical(new EventId(), ex, "Unhandled exception in BTCPayMiddleware");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
string body = string.Empty;
|
||||
if (httpContext.Request.ContentLength != 0 && httpContext.Request.Body != null)
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(httpContext.Request.Body, Encoding.UTF8, true, 1024, true))
|
||||
{
|
||||
body = reader.ReadToEnd();
|
||||
}
|
||||
httpContext.Request.Body.Position = 0;
|
||||
}
|
||||
|
||||
private static async Task HandleBitpayHttpException(HttpContext httpContext, BitpayHttpException ex)
|
||||
{
|
||||
httpContext.Response.StatusCode = ex.StatusCode;
|
||||
using(var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, true))
|
||||
{
|
||||
var result = JsonConvert.SerializeObject(new BitpayErrorsModel(ex));
|
||||
writer.Write(result);
|
||||
await writer.FlushAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
var url = httpContext.Request.GetEncodedUrl();
|
||||
try
|
||||
{
|
||||
var key = new PubKey(id);
|
||||
if (BitIdExtensions.CheckBitIDSignature(key, sig, url, body))
|
||||
{
|
||||
var bitid = new BitIdentity(key);
|
||||
httpContext.User = new GenericPrincipal(bitid, new string[0]);
|
||||
Logs.PayServer.LogDebug($"BitId signature check success for SIN {bitid.SIN}");
|
||||
}
|
||||
}
|
||||
catch (FormatException) { }
|
||||
if (!(httpContext.User.Identity is BitIdentity))
|
||||
Logs.PayServer.LogDebug("BitId signature check failed");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _Next(httpContext);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
await HandleBitpayHttpException(httpContext, new BitpayHttpException(401, ex.Message));
|
||||
}
|
||||
catch (BitpayHttpException ex)
|
||||
{
|
||||
await HandleBitpayHttpException(httpContext, ex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logs.PayServer.LogCritical(new EventId(), ex, "Unhandled exception in BTCPayMiddleware");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void RewriteHostIfNeeded(HttpContext httpContext)
|
||||
{
|
||||
// Make sure that code executing after this point think that the external url has been hit.
|
||||
if (_Options.ExternalUrl != null)
|
||||
{
|
||||
httpContext.Request.Scheme = _Options.ExternalUrl.Scheme;
|
||||
if (_Options.ExternalUrl.IsDefaultPort)
|
||||
httpContext.Request.Host = new HostString(_Options.ExternalUrl.Host);
|
||||
else
|
||||
httpContext.Request.Host = new HostString(_Options.ExternalUrl.Host, _Options.ExternalUrl.Port);
|
||||
}
|
||||
// NGINX pass X-Forwarded-Proto and X-Forwarded-Port, so let's use that to have better guess of the real domain
|
||||
else
|
||||
{
|
||||
ushort? p = null;
|
||||
if (httpContext.Request.Headers.TryGetValue("X-Forwarded-Proto", out StringValues proto))
|
||||
{
|
||||
var scheme = proto.SingleOrDefault();
|
||||
if (scheme != null)
|
||||
{
|
||||
httpContext.Request.Scheme = scheme;
|
||||
if (scheme == "http")
|
||||
p = 80;
|
||||
if (scheme == "https")
|
||||
p = 443;
|
||||
}
|
||||
}
|
||||
if (httpContext.Request.Headers.TryGetValue("X-Forwarded-Port", out StringValues port))
|
||||
{
|
||||
var portString = port.SingleOrDefault();
|
||||
if (portString != null && ushort.TryParse(portString, out ushort pp))
|
||||
{
|
||||
p = pp;
|
||||
}
|
||||
}
|
||||
if (p.HasValue)
|
||||
{
|
||||
bool isDefault = httpContext.Request.Scheme == "http" && p.Value == 80;
|
||||
isDefault |= httpContext.Request.Scheme == "https" && p.Value == 443;
|
||||
if (isDefault)
|
||||
httpContext.Request.Host = new HostString(httpContext.Request.Host.Host);
|
||||
else
|
||||
httpContext.Request.Host = new HostString(httpContext.Request.Host.Host, p.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task HandleBitpayHttpException(HttpContext httpContext, BitpayHttpException ex)
|
||||
{
|
||||
httpContext.Response.StatusCode = ex.StatusCode;
|
||||
using (var writer = new StreamWriter(httpContext.Response.Body, new UTF8Encoding(false), 1024, true))
|
||||
{
|
||||
httpContext.Response.ContentType = "application/json";
|
||||
var result = JsonConvert.SerializeObject(new BitpayErrorsModel(ex));
|
||||
writer.Write(result);
|
||||
await writer.FlushAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,100 +33,124 @@ using Hangfire.Dashboard;
|
||||
using Hangfire.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.ApplicationInsights.AspNetCore.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc.Cors.Internal;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
class NeedRole : IDashboardAuthorizationFilter
|
||||
{
|
||||
string _Role;
|
||||
public NeedRole(string role)
|
||||
{
|
||||
_Role = role;
|
||||
}
|
||||
public bool Authorize([NotNull] DashboardContext context)
|
||||
{
|
||||
return context.GetHttpContext().User.IsInRole(_Role);
|
||||
}
|
||||
}
|
||||
public Startup(IConfiguration conf)
|
||||
{
|
||||
Configuration = conf;
|
||||
}
|
||||
public class Startup
|
||||
{
|
||||
class NeedRole : IDashboardAuthorizationFilter
|
||||
{
|
||||
string _Role;
|
||||
public NeedRole(string role)
|
||||
{
|
||||
_Role = role;
|
||||
}
|
||||
public bool Authorize([NotNull] DashboardContext context)
|
||||
{
|
||||
return context.GetHttpContext().User.IsInRole(_Role);
|
||||
}
|
||||
}
|
||||
public Startup(IConfiguration conf, IHostingEnvironment env)
|
||||
{
|
||||
Configuration = conf;
|
||||
_Env = env;
|
||||
}
|
||||
IHostingEnvironment _Env;
|
||||
public IConfiguration Configuration
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// Big hack, tests fails because Hangfire fail at initializing at the second test run
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.ConfigureBTCPayServer(Configuration);
|
||||
services.AddMemoryCache();
|
||||
services.AddIdentity<ApplicationUser, IdentityRole>()
|
||||
.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
// Big hack, tests fails because Hangfire fail at initializing at the second test run
|
||||
AddHangfireFix(services);
|
||||
services.AddBTCPayServer();
|
||||
services.AddMvc(o =>
|
||||
{
|
||||
o.Filters.Add(new XFrameOptionsAttribute("DENY"));
|
||||
});
|
||||
}
|
||||
|
||||
// Big hack, tests fails if only call AddHangfire because Hangfire fail at initializing at the second test run
|
||||
private void AddHangfireFix(IServiceCollection services)
|
||||
{
|
||||
Action<IGlobalConfiguration> configuration = o =>
|
||||
{
|
||||
var scope = AspNetCoreJobActivator.Current.BeginScope(null);
|
||||
var options = (ApplicationDbContextFactory)scope.Resolve(typeof(ApplicationDbContextFactory));
|
||||
options.ConfigureHangfireBuilder(o);
|
||||
};
|
||||
|
||||
ServiceCollectionDescriptorExtensions.TryAddSingleton<Action<IGlobalConfiguration>>(services, (IServiceProvider serviceProvider) => new Action<IGlobalConfiguration>((config) =>
|
||||
{
|
||||
ILoggerFactory service = ServiceProviderServiceExtensions.GetService<ILoggerFactory>(serviceProvider);
|
||||
if (service != null)
|
||||
{
|
||||
Hangfire.GlobalConfigurationExtensions.UseLogProvider<AspNetCoreLogProvider>(config, new AspNetCoreLogProvider(service));
|
||||
}
|
||||
IServiceScopeFactory service2 = ServiceProviderServiceExtensions.GetService<IServiceScopeFactory>(serviceProvider);
|
||||
if (service2 != null)
|
||||
{
|
||||
Hangfire.GlobalConfigurationExtensions.UseActivator<AspNetCoreJobActivator>(config, new AspNetCoreJobActivator(service2));
|
||||
}
|
||||
configuration(config);
|
||||
}));
|
||||
|
||||
services.AddHangfire(configuration);
|
||||
services.AddCors(o =>
|
||||
{
|
||||
o.AddPolicy("BitpayAPI", b =>
|
||||
{
|
||||
b.AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin();
|
||||
});
|
||||
});
|
||||
|
||||
services.Configure<IOptions<ApplicationInsightsServiceOptions>>(o =>
|
||||
{
|
||||
o.Value.DeveloperMode = _Env.IsDevelopment();
|
||||
});
|
||||
}
|
||||
|
||||
public void Configure(
|
||||
IApplicationBuilder app,
|
||||
IHostingEnvironment env,
|
||||
IServiceProvider prov,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseBrowserLink();
|
||||
}
|
||||
|
||||
|
||||
services.ConfigureBTCPayServer(Configuration);
|
||||
Logs.Configure(loggerFactory);
|
||||
|
||||
services.AddIdentity<ApplicationUser, IdentityRole>()
|
||||
.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||
.AddDefaultTokenProviders();
|
||||
//App insight do not that by itself...
|
||||
loggerFactory.AddApplicationInsights(prov, LogLevel.Information);
|
||||
|
||||
AddHangfireFix(services);
|
||||
services.AddBTCPayServer();
|
||||
services.AddMvc();
|
||||
}
|
||||
|
||||
// Big hack, tests fails if only call AddHangfire because Hangfire fail at initializing at the second test run
|
||||
private void AddHangfireFix(IServiceCollection services)
|
||||
{
|
||||
Action<IGlobalConfiguration> configuration = o =>
|
||||
{
|
||||
var scope = AspNetCoreJobActivator.Current.BeginScope(null);
|
||||
var options = (ApplicationDbContextFactory)scope.Resolve(typeof(ApplicationDbContextFactory));
|
||||
options.ConfigureHangfireBuilder(o);
|
||||
};
|
||||
|
||||
ServiceCollectionDescriptorExtensions.TryAddSingleton<Action<IGlobalConfiguration>>(services, (IServiceProvider serviceProvider) => new Action<IGlobalConfiguration>((config) =>
|
||||
{
|
||||
ILoggerFactory service = ServiceProviderServiceExtensions.GetService<ILoggerFactory>(serviceProvider);
|
||||
if(service != null)
|
||||
{
|
||||
Hangfire.GlobalConfigurationExtensions.UseLogProvider<AspNetCoreLogProvider>(config, new AspNetCoreLogProvider(service));
|
||||
}
|
||||
IServiceScopeFactory service2 = ServiceProviderServiceExtensions.GetService<IServiceScopeFactory>(serviceProvider);
|
||||
if(service2 != null)
|
||||
{
|
||||
Hangfire.GlobalConfigurationExtensions.UseActivator<AspNetCoreJobActivator>(config, new AspNetCoreJobActivator(service2));
|
||||
}
|
||||
configuration(config);
|
||||
}));
|
||||
services.AddHangfire(configuration);
|
||||
}
|
||||
|
||||
public void Configure(
|
||||
IApplicationBuilder app,
|
||||
IHostingEnvironment env,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
if(env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseBrowserLink();
|
||||
}
|
||||
|
||||
|
||||
Logs.Configure(loggerFactory);
|
||||
app.UsePayServer();
|
||||
app.UseStaticFiles();
|
||||
app.UseAuthentication();
|
||||
|
||||
app.UseHangfireServer();
|
||||
app.UseHangfireDashboard("/hangfire", new DashboardOptions() { Authorization = new[] { new NeedRole(Roles.ServerAdmin) } });
|
||||
app.UseMvc(routes =>
|
||||
{
|
||||
routes.MapRoute(
|
||||
name: "default",
|
||||
template: "{controller=Home}/{action=Index}/{id?}");
|
||||
});
|
||||
}
|
||||
}
|
||||
app.UsePayServer();
|
||||
app.UseStaticFiles();
|
||||
app.UseAuthentication();
|
||||
app.UseHangfireServer();
|
||||
app.UseHangfireDashboard("/hangfire", new DashboardOptions() { Authorization = new[] { new NeedRole(Roles.ServerAdmin) } });
|
||||
app.UseWebSockets();
|
||||
app.UseMvc(routes =>
|
||||
{
|
||||
routes.MapRoute(
|
||||
name: "default",
|
||||
template: "{controller=Home}/{action=Index}/{id?}");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
45
BTCPayServer/Initializer.cs
Normal file
45
BTCPayServer/Initializer.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Events;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class Initializer
|
||||
{
|
||||
EventAggregator _Aggregator;
|
||||
CallbackController _CallbackController;
|
||||
public Initializer(EventAggregator aggregator,
|
||||
CallbackController callbackController
|
||||
)
|
||||
{
|
||||
_Aggregator = aggregator;
|
||||
_CallbackController = callbackController;
|
||||
}
|
||||
public void Init()
|
||||
{
|
||||
_Aggregator.Subscribe<NBXplorerStateChangedEvent>(async (s, evt) =>
|
||||
{
|
||||
if (evt.NewState == NBXplorerState.Ready)
|
||||
{
|
||||
s.Unsubscribe();
|
||||
try
|
||||
{
|
||||
var callback = await _CallbackController.GetCallbackBlockUriAsync();
|
||||
await _CallbackController.RegisterCallbackBlockUriAsync(callback);
|
||||
Logs.PayServer.LogInformation($"Registering block callback to " + callback);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logs.PayServer.LogError(ex, "Could not register block callback");
|
||||
s.Resubscribe();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -10,391 +10,391 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Logging
|
||||
{
|
||||
public class CustomConsoleLogProvider : ILoggerProvider
|
||||
{
|
||||
ConsoleLoggerProcessor _Processor = new ConsoleLoggerProcessor();
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return new CustomConsoleLogger(categoryName, (a,b) => true, false, _Processor);
|
||||
}
|
||||
public class CustomConsoleLogProvider : ILoggerProvider
|
||||
{
|
||||
ConsoleLoggerProcessor _Processor = new ConsoleLoggerProcessor();
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return new CustomConsoleLogger(categoryName, (a, b) => true, false, _Processor);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// A variant of ASP.NET Core ConsoleLogger which does not make new line for the category
|
||||
/// </summary>
|
||||
public class CustomConsoleLogger : ILogger
|
||||
{
|
||||
private static readonly string _loglevelPadding = ": ";
|
||||
private static readonly string _messagePadding;
|
||||
private static readonly string _newLineWithMessagePadding;
|
||||
}
|
||||
}
|
||||
|
||||
// ConsoleColor does not have a value to specify the 'Default' color
|
||||
private readonly ConsoleColor? DefaultConsoleColor = null;
|
||||
/// <summary>
|
||||
/// A variant of ASP.NET Core ConsoleLogger which does not make new line for the category
|
||||
/// </summary>
|
||||
public class CustomConsoleLogger : ILogger
|
||||
{
|
||||
private static readonly string _loglevelPadding = ": ";
|
||||
private static readonly string _messagePadding;
|
||||
private static readonly string _newLineWithMessagePadding;
|
||||
|
||||
private readonly ConsoleLoggerProcessor _queueProcessor;
|
||||
private Func<string, LogLevel, bool> _filter;
|
||||
// ConsoleColor does not have a value to specify the 'Default' color
|
||||
private readonly ConsoleColor? DefaultConsoleColor = null;
|
||||
|
||||
[ThreadStatic]
|
||||
private static StringBuilder _logBuilder;
|
||||
private readonly ConsoleLoggerProcessor _queueProcessor;
|
||||
private Func<string, LogLevel, bool> _filter;
|
||||
|
||||
static CustomConsoleLogger()
|
||||
{
|
||||
var logLevelString = GetLogLevelString(LogLevel.Information);
|
||||
_messagePadding = new string(' ', logLevelString.Length + _loglevelPadding.Length);
|
||||
_newLineWithMessagePadding = Environment.NewLine + _messagePadding;
|
||||
}
|
||||
[ThreadStatic]
|
||||
private static StringBuilder _logBuilder;
|
||||
|
||||
public CustomConsoleLogger(string name, Func<string, LogLevel, bool> filter, bool includeScopes, ConsoleLoggerProcessor loggerProcessor)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
Filter = filter ?? ((category, logLevel) => true);
|
||||
IncludeScopes = includeScopes;
|
||||
static CustomConsoleLogger()
|
||||
{
|
||||
var logLevelString = GetLogLevelString(LogLevel.Information);
|
||||
_messagePadding = new string(' ', logLevelString.Length + _loglevelPadding.Length);
|
||||
_newLineWithMessagePadding = Environment.NewLine + _messagePadding;
|
||||
}
|
||||
|
||||
_queueProcessor = loggerProcessor;
|
||||
public CustomConsoleLogger(string name, Func<string, LogLevel, bool> filter, bool includeScopes, ConsoleLoggerProcessor loggerProcessor)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
Filter = filter ?? ((category, logLevel) => true);
|
||||
IncludeScopes = includeScopes;
|
||||
|
||||
if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
Console = new WindowsLogConsole();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console = new AnsiLogConsole(new AnsiSystemConsole());
|
||||
}
|
||||
}
|
||||
_queueProcessor = loggerProcessor;
|
||||
|
||||
public IConsole Console
|
||||
{
|
||||
get
|
||||
{
|
||||
return _queueProcessor.Console;
|
||||
}
|
||||
set
|
||||
{
|
||||
_queueProcessor.Console = value ?? throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
}
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
Console = new WindowsLogConsole();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console = new AnsiLogConsole(new AnsiSystemConsole());
|
||||
}
|
||||
}
|
||||
|
||||
public Func<string, LogLevel, bool> Filter
|
||||
{
|
||||
get
|
||||
{
|
||||
return _filter;
|
||||
}
|
||||
set
|
||||
{
|
||||
_filter = value ?? throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
}
|
||||
public IConsole Console
|
||||
{
|
||||
get
|
||||
{
|
||||
return _queueProcessor.Console;
|
||||
}
|
||||
set
|
||||
{
|
||||
_queueProcessor.Console = value ?? throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IncludeScopes
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public Func<string, LogLevel, bool> Filter
|
||||
{
|
||||
get
|
||||
{
|
||||
return _filter;
|
||||
}
|
||||
set
|
||||
{
|
||||
_filter = value ?? throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get;
|
||||
}
|
||||
public bool IncludeScopes
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||
{
|
||||
if(!IsEnabled(logLevel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
public string Name
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
if(formatter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(formatter));
|
||||
}
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||
{
|
||||
if (!IsEnabled(logLevel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message = formatter(state, exception);
|
||||
if (formatter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(formatter));
|
||||
}
|
||||
|
||||
if(!string.IsNullOrEmpty(message) || exception != null)
|
||||
{
|
||||
WriteMessage(logLevel, Name, eventId.Id, message, exception);
|
||||
}
|
||||
}
|
||||
var message = formatter(state, exception);
|
||||
|
||||
public virtual void WriteMessage(LogLevel logLevel, string logName, int eventId, string message, Exception exception)
|
||||
{
|
||||
var logBuilder = _logBuilder;
|
||||
_logBuilder = null;
|
||||
if (!string.IsNullOrEmpty(message) || exception != null)
|
||||
{
|
||||
WriteMessage(logLevel, Name, eventId.Id, message, exception);
|
||||
}
|
||||
}
|
||||
|
||||
if(logBuilder == null)
|
||||
{
|
||||
logBuilder = new StringBuilder();
|
||||
}
|
||||
public virtual void WriteMessage(LogLevel logLevel, string logName, int eventId, string message, Exception exception)
|
||||
{
|
||||
var logBuilder = _logBuilder;
|
||||
_logBuilder = null;
|
||||
|
||||
var logLevelColors = default(ConsoleColors);
|
||||
var logLevelString = string.Empty;
|
||||
if (logBuilder == null)
|
||||
{
|
||||
logBuilder = new StringBuilder();
|
||||
}
|
||||
|
||||
// Example:
|
||||
// INFO: ConsoleApp.Program[10]
|
||||
// Request received
|
||||
var logLevelColors = default(ConsoleColors);
|
||||
var logLevelString = string.Empty;
|
||||
|
||||
logLevelColors = GetLogLevelConsoleColors(logLevel);
|
||||
logLevelString = GetLogLevelString(logLevel);
|
||||
// category and event id
|
||||
var lenBefore = logBuilder.ToString().Length;
|
||||
logBuilder.Append(_loglevelPadding);
|
||||
logBuilder.Append(logName);
|
||||
logBuilder.Append(": ");
|
||||
var lenAfter = logBuilder.ToString().Length;
|
||||
while(lenAfter++ < 18)
|
||||
logBuilder.Append(" ");
|
||||
// scope information
|
||||
if(IncludeScopes)
|
||||
{
|
||||
GetScopeInformation(logBuilder);
|
||||
}
|
||||
// Example:
|
||||
// INFO: ConsoleApp.Program[10]
|
||||
// Request received
|
||||
|
||||
if(!string.IsNullOrEmpty(message))
|
||||
{
|
||||
// message
|
||||
//logBuilder.Append(_messagePadding);
|
||||
logLevelColors = GetLogLevelConsoleColors(logLevel);
|
||||
logLevelString = GetLogLevelString(logLevel);
|
||||
// category and event id
|
||||
var lenBefore = logBuilder.ToString().Length;
|
||||
logBuilder.Append(_loglevelPadding);
|
||||
logBuilder.Append(logName);
|
||||
logBuilder.Append(": ");
|
||||
var lenAfter = logBuilder.ToString().Length;
|
||||
while (lenAfter++ < 18)
|
||||
logBuilder.Append(" ");
|
||||
// scope information
|
||||
if (IncludeScopes)
|
||||
{
|
||||
GetScopeInformation(logBuilder);
|
||||
}
|
||||
|
||||
var len = logBuilder.Length;
|
||||
logBuilder.AppendLine(message);
|
||||
logBuilder.Replace(Environment.NewLine, _newLineWithMessagePadding, len, message.Length);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
// message
|
||||
//logBuilder.Append(_messagePadding);
|
||||
|
||||
// Example:
|
||||
// System.InvalidOperationException
|
||||
// at Namespace.Class.Function() in File:line X
|
||||
if(exception != null)
|
||||
{
|
||||
// exception message
|
||||
logBuilder.AppendLine(exception.ToString());
|
||||
}
|
||||
var len = logBuilder.Length;
|
||||
logBuilder.AppendLine(message);
|
||||
logBuilder.Replace(Environment.NewLine, _newLineWithMessagePadding, len, message.Length);
|
||||
}
|
||||
|
||||
if(logBuilder.Length > 0)
|
||||
{
|
||||
var hasLevel = !string.IsNullOrEmpty(logLevelString);
|
||||
// Queue log message
|
||||
_queueProcessor.EnqueueMessage(new LogMessageEntry()
|
||||
{
|
||||
Message = logBuilder.ToString(),
|
||||
MessageColor = DefaultConsoleColor,
|
||||
LevelString = hasLevel ? logLevelString : null,
|
||||
LevelBackground = hasLevel ? logLevelColors.Background : null,
|
||||
LevelForeground = hasLevel ? logLevelColors.Foreground : null
|
||||
});
|
||||
}
|
||||
// Example:
|
||||
// System.InvalidOperationException
|
||||
// at Namespace.Class.Function() in File:line X
|
||||
if (exception != null)
|
||||
{
|
||||
// exception message
|
||||
logBuilder.AppendLine(exception.ToString());
|
||||
}
|
||||
|
||||
logBuilder.Clear();
|
||||
if(logBuilder.Capacity > 1024)
|
||||
{
|
||||
logBuilder.Capacity = 1024;
|
||||
}
|
||||
_logBuilder = logBuilder;
|
||||
}
|
||||
if (logBuilder.Length > 0)
|
||||
{
|
||||
var hasLevel = !string.IsNullOrEmpty(logLevelString);
|
||||
// Queue log message
|
||||
_queueProcessor.EnqueueMessage(new LogMessageEntry()
|
||||
{
|
||||
Message = logBuilder.ToString(),
|
||||
MessageColor = DefaultConsoleColor,
|
||||
LevelString = hasLevel ? logLevelString : null,
|
||||
LevelBackground = hasLevel ? logLevelColors.Background : null,
|
||||
LevelForeground = hasLevel ? logLevelColors.Foreground : null
|
||||
});
|
||||
}
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return Filter(Name, logLevel);
|
||||
}
|
||||
logBuilder.Clear();
|
||||
if (logBuilder.Capacity > 1024)
|
||||
{
|
||||
logBuilder.Capacity = 1024;
|
||||
}
|
||||
_logBuilder = logBuilder;
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state)
|
||||
{
|
||||
if(state == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(state));
|
||||
}
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return Filter(Name, logLevel);
|
||||
}
|
||||
|
||||
return ConsoleLogScope.Push(Name, state);
|
||||
}
|
||||
public IDisposable BeginScope<TState>(TState state)
|
||||
{
|
||||
if (state == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(state));
|
||||
}
|
||||
|
||||
private static string GetLogLevelString(LogLevel logLevel)
|
||||
{
|
||||
switch(logLevel)
|
||||
{
|
||||
case LogLevel.Trace:
|
||||
return "trce";
|
||||
case LogLevel.Debug:
|
||||
return "dbug";
|
||||
case LogLevel.Information:
|
||||
return "info";
|
||||
case LogLevel.Warning:
|
||||
return "warn";
|
||||
case LogLevel.Error:
|
||||
return "fail";
|
||||
case LogLevel.Critical:
|
||||
return "crit";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(logLevel));
|
||||
}
|
||||
}
|
||||
return ConsoleLogScope.Push(Name, state);
|
||||
}
|
||||
|
||||
private ConsoleColors GetLogLevelConsoleColors(LogLevel logLevel)
|
||||
{
|
||||
// We must explicitly set the background color if we are setting the foreground color,
|
||||
// since just setting one can look bad on the users console.
|
||||
switch(logLevel)
|
||||
{
|
||||
case LogLevel.Critical:
|
||||
return new ConsoleColors(ConsoleColor.White, ConsoleColor.Red);
|
||||
case LogLevel.Error:
|
||||
return new ConsoleColors(ConsoleColor.Black, ConsoleColor.Red);
|
||||
case LogLevel.Warning:
|
||||
return new ConsoleColors(ConsoleColor.Yellow, ConsoleColor.Black);
|
||||
case LogLevel.Information:
|
||||
return new ConsoleColors(ConsoleColor.DarkGreen, ConsoleColor.Black);
|
||||
case LogLevel.Debug:
|
||||
return new ConsoleColors(ConsoleColor.Gray, ConsoleColor.Black);
|
||||
case LogLevel.Trace:
|
||||
return new ConsoleColors(ConsoleColor.Gray, ConsoleColor.Black);
|
||||
default:
|
||||
return new ConsoleColors(DefaultConsoleColor, DefaultConsoleColor);
|
||||
}
|
||||
}
|
||||
private static string GetLogLevelString(LogLevel logLevel)
|
||||
{
|
||||
switch (logLevel)
|
||||
{
|
||||
case LogLevel.Trace:
|
||||
return "trce";
|
||||
case LogLevel.Debug:
|
||||
return "dbug";
|
||||
case LogLevel.Information:
|
||||
return "info";
|
||||
case LogLevel.Warning:
|
||||
return "warn";
|
||||
case LogLevel.Error:
|
||||
return "fail";
|
||||
case LogLevel.Critical:
|
||||
return "crit";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(logLevel));
|
||||
}
|
||||
}
|
||||
|
||||
private void GetScopeInformation(StringBuilder builder)
|
||||
{
|
||||
var current = ConsoleLogScope.Current;
|
||||
string scopeLog = string.Empty;
|
||||
var length = builder.Length;
|
||||
private ConsoleColors GetLogLevelConsoleColors(LogLevel logLevel)
|
||||
{
|
||||
// We must explicitly set the background color if we are setting the foreground color,
|
||||
// since just setting one can look bad on the users console.
|
||||
switch (logLevel)
|
||||
{
|
||||
case LogLevel.Critical:
|
||||
return new ConsoleColors(ConsoleColor.White, ConsoleColor.Red);
|
||||
case LogLevel.Error:
|
||||
return new ConsoleColors(ConsoleColor.Black, ConsoleColor.Red);
|
||||
case LogLevel.Warning:
|
||||
return new ConsoleColors(ConsoleColor.Yellow, ConsoleColor.Black);
|
||||
case LogLevel.Information:
|
||||
return new ConsoleColors(ConsoleColor.DarkGreen, ConsoleColor.Black);
|
||||
case LogLevel.Debug:
|
||||
return new ConsoleColors(ConsoleColor.Gray, ConsoleColor.Black);
|
||||
case LogLevel.Trace:
|
||||
return new ConsoleColors(ConsoleColor.Gray, ConsoleColor.Black);
|
||||
default:
|
||||
return new ConsoleColors(DefaultConsoleColor, DefaultConsoleColor);
|
||||
}
|
||||
}
|
||||
|
||||
while(current != null)
|
||||
{
|
||||
if(length == builder.Length)
|
||||
{
|
||||
scopeLog = $"=> {current}";
|
||||
}
|
||||
else
|
||||
{
|
||||
scopeLog = $"=> {current} ";
|
||||
}
|
||||
private void GetScopeInformation(StringBuilder builder)
|
||||
{
|
||||
var current = ConsoleLogScope.Current;
|
||||
string scopeLog = string.Empty;
|
||||
var length = builder.Length;
|
||||
|
||||
builder.Insert(length, scopeLog);
|
||||
current = current.Parent;
|
||||
}
|
||||
if(builder.Length > length)
|
||||
{
|
||||
builder.Insert(length, _messagePadding);
|
||||
builder.AppendLine();
|
||||
}
|
||||
}
|
||||
while (current != null)
|
||||
{
|
||||
if (length == builder.Length)
|
||||
{
|
||||
scopeLog = $"=> {current}";
|
||||
}
|
||||
else
|
||||
{
|
||||
scopeLog = $"=> {current} ";
|
||||
}
|
||||
|
||||
private struct ConsoleColors
|
||||
{
|
||||
public ConsoleColors(ConsoleColor? foreground, ConsoleColor? background)
|
||||
{
|
||||
Foreground = foreground;
|
||||
Background = background;
|
||||
}
|
||||
builder.Insert(length, scopeLog);
|
||||
current = current.Parent;
|
||||
}
|
||||
if (builder.Length > length)
|
||||
{
|
||||
builder.Insert(length, _messagePadding);
|
||||
builder.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
public ConsoleColor? Foreground
|
||||
{
|
||||
get;
|
||||
}
|
||||
private struct ConsoleColors
|
||||
{
|
||||
public ConsoleColors(ConsoleColor? foreground, ConsoleColor? background)
|
||||
{
|
||||
Foreground = foreground;
|
||||
Background = background;
|
||||
}
|
||||
|
||||
public ConsoleColor? Background
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
public ConsoleColor? Foreground
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
private class AnsiSystemConsole : IAnsiSystemConsole
|
||||
{
|
||||
public void Write(string message)
|
||||
{
|
||||
System.Console.Write(message);
|
||||
}
|
||||
public ConsoleColor? Background
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteLine(string message)
|
||||
{
|
||||
System.Console.WriteLine(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
private class AnsiSystemConsole : IAnsiSystemConsole
|
||||
{
|
||||
public void Write(string message)
|
||||
{
|
||||
System.Console.Write(message);
|
||||
}
|
||||
|
||||
public class ConsoleLoggerProcessor : IDisposable
|
||||
{
|
||||
private const int _maxQueuedMessages = 1024;
|
||||
public void WriteLine(string message)
|
||||
{
|
||||
System.Console.WriteLine(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly BlockingCollection<LogMessageEntry> _messageQueue = new BlockingCollection<LogMessageEntry>(_maxQueuedMessages);
|
||||
private readonly Task _outputTask;
|
||||
public class ConsoleLoggerProcessor : IDisposable
|
||||
{
|
||||
private const int _maxQueuedMessages = 1024;
|
||||
|
||||
public IConsole Console;
|
||||
private readonly BlockingCollection<LogMessageEntry> _messageQueue = new BlockingCollection<LogMessageEntry>(_maxQueuedMessages);
|
||||
private readonly Task _outputTask;
|
||||
|
||||
public ConsoleLoggerProcessor()
|
||||
{
|
||||
// Start Console message queue processor
|
||||
_outputTask = Task.Factory.StartNew(
|
||||
ProcessLogQueue,
|
||||
this,
|
||||
TaskCreationOptions.LongRunning);
|
||||
}
|
||||
public IConsole Console;
|
||||
|
||||
public virtual void EnqueueMessage(LogMessageEntry message)
|
||||
{
|
||||
if(!_messageQueue.IsAddingCompleted)
|
||||
{
|
||||
try
|
||||
{
|
||||
_messageQueue.Add(message);
|
||||
return;
|
||||
}
|
||||
catch(InvalidOperationException) { }
|
||||
}
|
||||
public ConsoleLoggerProcessor()
|
||||
{
|
||||
// Start Console message queue processor
|
||||
_outputTask = Task.Factory.StartNew(
|
||||
ProcessLogQueue,
|
||||
this,
|
||||
TaskCreationOptions.LongRunning);
|
||||
}
|
||||
|
||||
// Adding is completed so just log the message
|
||||
WriteMessage(message);
|
||||
}
|
||||
public virtual void EnqueueMessage(LogMessageEntry message)
|
||||
{
|
||||
if (!_messageQueue.IsAddingCompleted)
|
||||
{
|
||||
try
|
||||
{
|
||||
_messageQueue.Add(message);
|
||||
return;
|
||||
}
|
||||
catch (InvalidOperationException) { }
|
||||
}
|
||||
|
||||
// for testing
|
||||
internal virtual void WriteMessage(LogMessageEntry message)
|
||||
{
|
||||
if(message.LevelString != null)
|
||||
{
|
||||
Console.Write(message.LevelString, message.LevelBackground, message.LevelForeground);
|
||||
}
|
||||
// Adding is completed so just log the message
|
||||
WriteMessage(message);
|
||||
}
|
||||
|
||||
Console.Write(message.Message, message.MessageColor, message.MessageColor);
|
||||
Console.Flush();
|
||||
}
|
||||
// for testing
|
||||
internal virtual void WriteMessage(LogMessageEntry message)
|
||||
{
|
||||
if (message.LevelString != null)
|
||||
{
|
||||
Console.Write(message.LevelString, message.LevelBackground, message.LevelForeground);
|
||||
}
|
||||
|
||||
private void ProcessLogQueue()
|
||||
{
|
||||
foreach(var message in _messageQueue.GetConsumingEnumerable())
|
||||
{
|
||||
WriteMessage(message);
|
||||
}
|
||||
}
|
||||
Console.Write(message.Message, message.MessageColor, message.MessageColor);
|
||||
Console.Flush();
|
||||
}
|
||||
|
||||
private static void ProcessLogQueue(object state)
|
||||
{
|
||||
var consoleLogger = (ConsoleLoggerProcessor)state;
|
||||
private void ProcessLogQueue()
|
||||
{
|
||||
foreach (var message in _messageQueue.GetConsumingEnumerable())
|
||||
{
|
||||
WriteMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
consoleLogger.ProcessLogQueue();
|
||||
}
|
||||
private static void ProcessLogQueue(object state)
|
||||
{
|
||||
var consoleLogger = (ConsoleLoggerProcessor)state;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_messageQueue.CompleteAdding();
|
||||
consoleLogger.ProcessLogQueue();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_outputTask.Wait(1500); // with timeout in-case Console is locked by user input
|
||||
}
|
||||
catch(TaskCanceledException) { }
|
||||
catch(AggregateException ex) when(ex.InnerExceptions.Count == 1 && ex.InnerExceptions[0] is TaskCanceledException) { }
|
||||
}
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
_messageQueue.CompleteAdding();
|
||||
|
||||
public struct LogMessageEntry
|
||||
{
|
||||
public string LevelString;
|
||||
public ConsoleColor? LevelBackground;
|
||||
public ConsoleColor? LevelForeground;
|
||||
public ConsoleColor? MessageColor;
|
||||
public string Message;
|
||||
}
|
||||
try
|
||||
{
|
||||
_outputTask.Wait(1500); // with timeout in-case Console is locked by user input
|
||||
}
|
||||
catch (TaskCanceledException) { }
|
||||
catch (AggregateException ex) when (ex.InnerExceptions.Count == 1 && ex.InnerExceptions[0] is TaskCanceledException) { }
|
||||
}
|
||||
}
|
||||
|
||||
public struct LogMessageEntry
|
||||
{
|
||||
public string LevelString;
|
||||
public ConsoleColor? LevelBackground;
|
||||
public ConsoleColor? LevelForeground;
|
||||
public ConsoleColor? MessageColor;
|
||||
public string Message;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,48 +7,55 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Logging
|
||||
{
|
||||
public class Logs
|
||||
{
|
||||
static Logs()
|
||||
{
|
||||
Configure(new FuncLoggerFactory(n => NullLogger.Instance));
|
||||
}
|
||||
public static void Configure(ILoggerFactory factory)
|
||||
{
|
||||
Configuration = factory.CreateLogger("Configuration");
|
||||
PayServer = factory.CreateLogger("PayServer");
|
||||
}
|
||||
public static ILogger Configuration
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public static ILogger PayServer
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public const int ColumnLength = 16;
|
||||
}
|
||||
public class Logs
|
||||
{
|
||||
static Logs()
|
||||
{
|
||||
Configure(new FuncLoggerFactory(n => NullLogger.Instance));
|
||||
}
|
||||
public static void Configure(ILoggerFactory factory)
|
||||
{
|
||||
Configuration = factory.CreateLogger("Configuration");
|
||||
PayServer = factory.CreateLogger("PayServer");
|
||||
Events = factory.CreateLogger("Events");
|
||||
}
|
||||
public static ILogger Configuration
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public static ILogger PayServer
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public class FuncLoggerFactory : ILoggerFactory
|
||||
{
|
||||
private Func<string, ILogger> createLogger;
|
||||
public FuncLoggerFactory(Func<string, ILogger> createLogger)
|
||||
{
|
||||
this.createLogger = createLogger;
|
||||
}
|
||||
public void AddProvider(ILoggerProvider provider)
|
||||
{
|
||||
public static ILogger Events
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
}
|
||||
public const int ColumnLength = 16;
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return createLogger(categoryName);
|
||||
}
|
||||
public class FuncLoggerFactory : ILoggerFactory
|
||||
{
|
||||
private Func<string, ILogger> createLogger;
|
||||
public FuncLoggerFactory(Func<string, ILogger> createLogger)
|
||||
{
|
||||
this.createLogger = createLogger;
|
||||
}
|
||||
public void AddProvider(ILoggerProvider provider)
|
||||
{
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return createLogger(categoryName);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
|
@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
|
392
BTCPayServer/Migrations/20171006013443_AddressMapping.Designer.cs
generated
Normal file
392
BTCPayServer/Migrations/20171006013443_AddressMapping.Designer.cs
generated
Normal file
@ -0,0 +1,392 @@
|
||||
// <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("20171006013443_AddressMapping")]
|
||||
partial class AddressMapping
|
||||
{
|
||||
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.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.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
|
||||
}
|
||||
}
|
||||
}
|
41
BTCPayServer/Migrations/20171006013443_AddressMapping.cs
Normal file
41
BTCPayServer/Migrations/20171006013443_AddressMapping.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
public partial class AddressMapping : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AddressInvoices",
|
||||
columns: table => new
|
||||
{
|
||||
Address = table.Column<string>(type: "TEXT", nullable: false),
|
||||
InvoiceDataId = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AddressInvoices", x => x.Address);
|
||||
table.ForeignKey(
|
||||
name: "FK_AddressInvoices_Invoices_InvoiceDataId",
|
||||
column: x => x.InvoiceDataId,
|
||||
principalTable: "Invoices",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AddressInvoices_InvoiceDataId",
|
||||
table: "AddressInvoices",
|
||||
column: "InvoiceDataId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "AddressInvoices");
|
||||
}
|
||||
}
|
||||
}
|
444
BTCPayServer/Migrations/20171010082424_Tokens.Designer.cs
generated
Normal file
444
BTCPayServer/Migrations/20171010082424_Tokens.Designer.cs
generated
Normal file
@ -0,0 +1,444 @@
|
||||
// <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("20171010082424_Tokens")]
|
||||
partial class Tokens
|
||||
{
|
||||
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<string>("Name");
|
||||
|
||||
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>("Name");
|
||||
|
||||
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.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
|
||||
}
|
||||
}
|
||||
}
|
67
BTCPayServer/Migrations/20171010082424_Tokens.cs
Normal file
67
BTCPayServer/Migrations/20171010082424_Tokens.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
public partial class Tokens : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PairedSINData",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Facade = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Label = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Name = table.Column<string>(type: "TEXT", nullable: true),
|
||||
PairingTime = table.Column<DateTimeOffset>(nullable: false),
|
||||
SIN = table.Column<string>(type: "TEXT", nullable: true),
|
||||
StoreDataId = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PairedSINData", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PairingCodes",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
DateCreated = table.Column<DateTime>(nullable: false),
|
||||
Expiration = table.Column<DateTimeOffset>(nullable: false),
|
||||
Facade = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Label = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Name = table.Column<string>(type: "TEXT", nullable: true),
|
||||
SIN = table.Column<string>(type: "TEXT", nullable: true),
|
||||
StoreDataId = table.Column<string>(type: "TEXT", nullable: true),
|
||||
TokenValue = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PairingCodes", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PairedSINData_SIN",
|
||||
table: "PairedSINData",
|
||||
column: "SIN");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PairedSINData_StoreDataId",
|
||||
table: "PairedSINData",
|
||||
column: "StoreDataId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "PairedSINData");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PairingCodes");
|
||||
}
|
||||
}
|
||||
}
|
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.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("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
|
||||
}
|
||||
}
|
||||
}
|
54
BTCPayServer/Migrations/20171012020112_PendingInvoices.cs
Normal file
54
BTCPayServer/Migrations/20171012020112_PendingInvoices.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
public partial class PendingInvoices : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (SupportDropColumn(migrationBuilder.ActiveProvider))
|
||||
{
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
private bool SupportDropColumn(string activeProvider)
|
||||
{
|
||||
return activeProvider != "Microsoft.EntityFrameworkCore.Sqlite";
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
452
BTCPayServer/Migrations/20171023101754_StoreBlob.Designer.cs
generated
Normal file
452
BTCPayServer/Migrations/20171023101754_StoreBlob.Designer.cs
generated
Normal file
@ -0,0 +1,452 @@
|
||||
// <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("20171023101754_StoreBlob")]
|
||||
partial class StoreBlob
|
||||
{
|
||||
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[]>("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()
|
||||
.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
|
||||
}
|
||||
}
|
||||
}
|
24
BTCPayServer/Migrations/20171023101754_StoreBlob.cs
Normal file
24
BTCPayServer/Migrations/20171023101754_StoreBlob.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
public partial class StoreBlob : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<byte[]>(
|
||||
name: "StoreBlob",
|
||||
table: "Stores",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "StoreBlob",
|
||||
table: "Stores");
|
||||
}
|
||||
}
|
||||
}
|
477
BTCPayServer/Migrations/20171024163354_RenewUsedAddresses.Designer.cs
generated
Normal file
477
BTCPayServer/Migrations/20171024163354_RenewUsedAddresses.Designer.cs
generated
Normal file
@ -0,0 +1,477 @@
|
||||
// <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("20171024163354_RenewUsedAddresses")]
|
||||
partial class RenewUsedAddresses
|
||||
{
|
||||
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<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<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.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[]>("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()
|
||||
.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.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/20171024163354_RenewUsedAddresses.cs
Normal file
47
BTCPayServer/Migrations/20171024163354_RenewUsedAddresses.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
public partial class RenewUsedAddresses : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||
name: "CreatedTime",
|
||||
table: "AddressInvoices",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "HistoricalAddressInvoices",
|
||||
columns: table => new
|
||||
{
|
||||
InvoiceDataId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Address = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Assigned = table.Column<DateTimeOffset>(nullable: false),
|
||||
UnAssigned = table.Column<DateTimeOffset>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_HistoricalAddressInvoices", x => new { x.InvoiceDataId, x.Address });
|
||||
table.ForeignKey(
|
||||
name: "FK_HistoricalAddressInvoices_Invoices_InvoiceDataId",
|
||||
column: x => x.InvoiceDataId,
|
||||
principalTable: "Invoices",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "HistoricalAddressInvoices");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CreatedTime",
|
||||
table: "AddressInvoices");
|
||||
}
|
||||
}
|
||||
}
|
479
BTCPayServer/Migrations/20171105235734_PaymentAccounted.Designer.cs
generated
Normal file
479
BTCPayServer/Migrations/20171105235734_PaymentAccounted.Designer.cs
generated
Normal file
@ -0,0 +1,479 @@
|
||||
// <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("20171105235734_PaymentAccounted")]
|
||||
partial class PaymentAccounted
|
||||
{
|
||||
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<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<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.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>("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()
|
||||
.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.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
|
||||
}
|
||||
}
|
||||
}
|
25
BTCPayServer/Migrations/20171105235734_PaymentAccounted.cs
Normal file
25
BTCPayServer/Migrations/20171105235734_PaymentAccounted.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
public partial class PaymentAccounted : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "Accounted",
|
||||
table: "Payments",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Accounted",
|
||||
table: "Payments");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
@ -20,6 +20,37 @@ namespace BTCPayServer.Migrations
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
|
||||
|
||||
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<DateTimeOffset?>("UnAssigned");
|
||||
|
||||
b.HasKey("InvoiceDataId", "Address");
|
||||
|
||||
b.ToTable("HistoricalAddressInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -48,11 +79,61 @@ namespace BTCPayServer.Migrations
|
||||
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<bool>("Accounted");
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
@ -64,6 +145,16 @@ namespace BTCPayServer.Migrations
|
||||
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")
|
||||
@ -101,6 +192,8 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.Property<int>("SpeedPolicy");
|
||||
|
||||
b.Property<byte[]>("StoreBlob");
|
||||
|
||||
b.Property<byte[]>("StoreCertificate");
|
||||
|
||||
b.Property<string>("StoreName");
|
||||
@ -286,6 +379,21 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany()
|
||||
.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")
|
||||
|
@ -8,9 +8,9 @@ namespace BTCPayServer.Models.AccountViewModels
|
||||
{
|
||||
public class LoginWithRecoveryCodeViewModel
|
||||
{
|
||||
[Required]
|
||||
[DataType(DataType.Text)]
|
||||
[Display(Name = "Recovery Code")]
|
||||
public string RecoveryCode { get; set; }
|
||||
[Required]
|
||||
[DataType(DataType.Text)]
|
||||
[Display(Name = "Recovery Code")]
|
||||
public string RecoveryCode { get; set; }
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user