mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Adopt dotnet core editorconfig, big reformating
This commit is contained in:
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
|
||||||
@@ -30,133 +30,133 @@ using Xunit;
|
|||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
public class BTCPayServerTester : IDisposable
|
public class BTCPayServerTester : IDisposable
|
||||||
{
|
{
|
||||||
private string _Directory;
|
private string _Directory;
|
||||||
|
|
||||||
public BTCPayServerTester(string scope)
|
public BTCPayServerTester(string scope)
|
||||||
{
|
{
|
||||||
this._Directory = scope ?? throw new ArgumentNullException(nameof(scope));
|
this._Directory = scope ?? throw new ArgumentNullException(nameof(scope));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uri NBXplorerUri
|
public Uri NBXplorerUri
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string CookieFile
|
public string CookieFile
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public Uri ServerUri
|
public Uri ServerUri
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExtKey HDPrivateKey
|
public ExtKey HDPrivateKey
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Postgres
|
public string Postgres
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
IWebHost _Host;
|
IWebHost _Host;
|
||||||
public int Port
|
public int Port
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
if(!Directory.Exists(_Directory))
|
if (!Directory.Exists(_Directory))
|
||||||
Directory.CreateDirectory(_Directory);
|
Directory.CreateDirectory(_Directory);
|
||||||
|
|
||||||
HDPrivateKey = new ExtKey();
|
HDPrivateKey = new ExtKey();
|
||||||
StringBuilder config = new StringBuilder();
|
StringBuilder config = new StringBuilder();
|
||||||
config.AppendLine($"regtest=1");
|
config.AppendLine($"regtest=1");
|
||||||
config.AppendLine($"port={Port}");
|
config.AppendLine($"port={Port}");
|
||||||
config.AppendLine($"explorer.url={NBXplorerUri.AbsoluteUri}");
|
config.AppendLine($"explorer.url={NBXplorerUri.AbsoluteUri}");
|
||||||
config.AppendLine($"explorer.cookiefile={CookieFile}");
|
config.AppendLine($"explorer.cookiefile={CookieFile}");
|
||||||
config.AppendLine($"hdpubkey={HDPrivateKey.Neuter().ToString(Network.RegTest)}");
|
config.AppendLine($"hdpubkey={HDPrivateKey.Neuter().ToString(Network.RegTest)}");
|
||||||
if(Postgres != null)
|
if (Postgres != null)
|
||||||
config.AppendLine($"postgres=" + Postgres);
|
config.AppendLine($"postgres=" + Postgres);
|
||||||
File.WriteAllText(Path.Combine(_Directory, "settings.config"), config.ToString());
|
File.WriteAllText(Path.Combine(_Directory, "settings.config"), config.ToString());
|
||||||
|
|
||||||
ServerUri = new Uri("http://" + HostName + ":" + Port + "/");
|
ServerUri = new Uri("http://" + HostName + ":" + Port + "/");
|
||||||
|
|
||||||
var conf = new DefaultConfiguration() { Logger = Logs.LogProvider.CreateLogger("Console") }.CreateConfiguration(new[] { "--datadir", _Directory });
|
var conf = new DefaultConfiguration() { Logger = Logs.LogProvider.CreateLogger("Console") }.CreateConfiguration(new[] { "--datadir", _Directory });
|
||||||
|
|
||||||
_Host = new WebHostBuilder()
|
_Host = new WebHostBuilder()
|
||||||
.UseConfiguration(conf)
|
.UseConfiguration(conf)
|
||||||
.ConfigureServices(s =>
|
.ConfigureServices(s =>
|
||||||
{
|
{
|
||||||
s.AddSingleton<IRateProvider>(new MockRateProvider(new Rate("USD", 5000m)));
|
s.AddSingleton<IRateProvider>(new MockRateProvider(new Rate("USD", 5000m)));
|
||||||
s.AddLogging(l =>
|
s.AddLogging(l =>
|
||||||
{
|
{
|
||||||
l.SetMinimumLevel(LogLevel.Information)
|
l.SetMinimumLevel(LogLevel.Information)
|
||||||
.AddFilter("Microsoft", LogLevel.Error)
|
.AddFilter("Microsoft", LogLevel.Error)
|
||||||
.AddFilter("Hangfire", LogLevel.Error)
|
.AddFilter("Hangfire", LogLevel.Error)
|
||||||
.AddProvider(Logs.LogProvider);
|
.AddProvider(Logs.LogProvider);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.UseKestrel()
|
.UseKestrel()
|
||||||
.UseStartup<Startup>()
|
.UseStartup<Startup>()
|
||||||
.Build();
|
.Build();
|
||||||
_Host.Start();
|
_Host.Start();
|
||||||
Runtime = (BTCPayServerRuntime)_Host.Services.GetService(typeof(BTCPayServerRuntime));
|
Runtime = (BTCPayServerRuntime)_Host.Services.GetService(typeof(BTCPayServerRuntime));
|
||||||
var watcher = (InvoiceWatcher)_Host.Services.GetService(typeof(InvoiceWatcher));
|
var watcher = (InvoiceWatcher)_Host.Services.GetService(typeof(InvoiceWatcher));
|
||||||
watcher.PollInterval = TimeSpan.FromMilliseconds(500);
|
watcher.PollInterval = TimeSpan.FromMilliseconds(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BTCPayServerRuntime Runtime
|
public BTCPayServerRuntime Runtime
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string HostName
|
public string HostName
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
internal set;
|
internal set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public T GetService<T>()
|
public T GetService<T>()
|
||||||
{
|
{
|
||||||
return _Host.Services.GetRequiredService<T>();
|
return _Host.Services.GetRequiredService<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public T GetController<T>(string userId = null) where T : Controller
|
public T GetController<T>(string userId = null) where T : Controller
|
||||||
{
|
{
|
||||||
var context = new DefaultHttpContext();
|
var context = new DefaultHttpContext();
|
||||||
context.Request.Host = new HostString("127.0.0.1");
|
context.Request.Host = new HostString("127.0.0.1");
|
||||||
context.Request.Scheme = "http";
|
context.Request.Scheme = "http";
|
||||||
context.Request.Protocol = "http";
|
context.Request.Protocol = "http";
|
||||||
if(userId != null)
|
if (userId != null)
|
||||||
{
|
{
|
||||||
context.User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, userId) }));
|
context.User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, userId) }));
|
||||||
}
|
}
|
||||||
var scope = (IServiceScopeFactory)_Host.Services.GetService(typeof(IServiceScopeFactory));
|
var scope = (IServiceScopeFactory)_Host.Services.GetService(typeof(IServiceScopeFactory));
|
||||||
var provider = scope.CreateScope().ServiceProvider;
|
var provider = scope.CreateScope().ServiceProvider;
|
||||||
context.RequestServices = provider;
|
context.RequestServices = provider;
|
||||||
|
|
||||||
var httpAccessor = provider.GetRequiredService<IHttpContextAccessor>();
|
var httpAccessor = provider.GetRequiredService<IHttpContextAccessor>();
|
||||||
httpAccessor.HttpContext = context;
|
httpAccessor.HttpContext = context;
|
||||||
|
|
||||||
var controller = (T)ActivatorUtilities.CreateInstance(provider, typeof(T));
|
var controller = (T)ActivatorUtilities.CreateInstance(provider, typeof(T));
|
||||||
|
|
||||||
controller.Url = new UrlHelperMock(new Uri($"http://{HostName}:{Port}/"));
|
controller.Url = new UrlHelperMock(new Uri($"http://{HostName}:{Port}/"));
|
||||||
controller.ControllerContext = new ControllerContext()
|
controller.ControllerContext = new ControllerContext()
|
||||||
{
|
{
|
||||||
HttpContext = context
|
HttpContext = context
|
||||||
};
|
};
|
||||||
return controller;
|
return controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if(_Host != null)
|
if (_Host != null)
|
||||||
_Host.Dispose();
|
_Host.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,65 +11,65 @@ using Microsoft.AspNetCore.Hosting.Server.Features;
|
|||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
public class CustomServer : IDisposable
|
public class CustomServer : IDisposable
|
||||||
{
|
{
|
||||||
TaskCompletionSource<bool> _Evt = null;
|
TaskCompletionSource<bool> _Evt = null;
|
||||||
IWebHost _Host = null;
|
IWebHost _Host = null;
|
||||||
CancellationTokenSource _Closed = new CancellationTokenSource();
|
CancellationTokenSource _Closed = new CancellationTokenSource();
|
||||||
public CustomServer()
|
public CustomServer()
|
||||||
{
|
{
|
||||||
var port = Utils.FreeTcpPort();
|
var port = Utils.FreeTcpPort();
|
||||||
_Host = new WebHostBuilder()
|
_Host = new WebHostBuilder()
|
||||||
.Configure(app =>
|
.Configure(app =>
|
||||||
{
|
{
|
||||||
app.Run(req =>
|
app.Run(req =>
|
||||||
{
|
{
|
||||||
while(_Act == null)
|
while (_Act == null)
|
||||||
{
|
{
|
||||||
Thread.Sleep(10);
|
Thread.Sleep(10);
|
||||||
_Closed.Token.ThrowIfCancellationRequested();
|
_Closed.Token.ThrowIfCancellationRequested();
|
||||||
}
|
}
|
||||||
_Act(req);
|
_Act(req);
|
||||||
_Act = null;
|
_Act = null;
|
||||||
_Evt.TrySetResult(true);
|
_Evt.TrySetResult(true);
|
||||||
req.Response.StatusCode = 200;
|
req.Response.StatusCode = 200;
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.UseKestrel()
|
.UseKestrel()
|
||||||
.UseUrls("http://127.0.0.1:" + port)
|
.UseUrls("http://127.0.0.1:" + port)
|
||||||
.Build();
|
.Build();
|
||||||
_Host.Start();
|
_Host.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uri GetUri()
|
public Uri GetUri()
|
||||||
{
|
{
|
||||||
return new Uri(_Host.ServerFeatures.Get<IServerAddressesFeature>().Addresses.First());
|
return new Uri(_Host.ServerFeatures.Get<IServerAddressesFeature>().Addresses.First());
|
||||||
}
|
}
|
||||||
|
|
||||||
Action<HttpContext> _Act;
|
Action<HttpContext> _Act;
|
||||||
public void ProcessNextRequest(Action<HttpContext> act)
|
public void ProcessNextRequest(Action<HttpContext> act)
|
||||||
{
|
{
|
||||||
var source = new TaskCompletionSource<bool>();
|
var source = new TaskCompletionSource<bool>();
|
||||||
CancellationTokenSource cancellation = new CancellationTokenSource(20000);
|
CancellationTokenSource cancellation = new CancellationTokenSource(20000);
|
||||||
cancellation.Token.Register(() => source.TrySetCanceled());
|
cancellation.Token.Register(() => source.TrySetCanceled());
|
||||||
source = new TaskCompletionSource<bool>();
|
source = new TaskCompletionSource<bool>();
|
||||||
_Evt = source;
|
_Evt = source;
|
||||||
_Act = act;
|
_Act = act;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_Evt.Task.GetAwaiter().GetResult();
|
_Evt.Task.GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
catch(TaskCanceledException)
|
catch (TaskCanceledException)
|
||||||
{
|
{
|
||||||
throw new Xunit.Sdk.XunitException("Callback to the webserver was expected, check if the callback url is accessible from internet");
|
throw new Xunit.Sdk.XunitException("Callback to the webserver was expected, check if the callback url is accessible from internet");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_Closed.Cancel();
|
_Closed.Cancel();
|
||||||
_Host.Dispose();
|
_Host.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,84 +6,84 @@ using Xunit.Abstractions;
|
|||||||
|
|
||||||
namespace BTCPayServer.Tests.Logging
|
namespace BTCPayServer.Tests.Logging
|
||||||
{
|
{
|
||||||
public interface ILog
|
public interface ILog
|
||||||
{
|
{
|
||||||
void LogInformation(string msg);
|
void LogInformation(string msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class XUnitLogProvider : ILoggerProvider
|
public class XUnitLogProvider : ILoggerProvider
|
||||||
{
|
{
|
||||||
ITestOutputHelper _Helper;
|
ITestOutputHelper _Helper;
|
||||||
public XUnitLogProvider(ITestOutputHelper helper)
|
public XUnitLogProvider(ITestOutputHelper helper)
|
||||||
{
|
{
|
||||||
_Helper = helper;
|
_Helper = helper;
|
||||||
}
|
}
|
||||||
public ILogger CreateLogger(string categoryName)
|
public ILogger CreateLogger(string categoryName)
|
||||||
{
|
{
|
||||||
return new XUnitLog(_Helper) { Name = categoryName };
|
return new XUnitLog(_Helper) { Name = categoryName };
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public class XUnitLog : ILog, ILogger, IDisposable
|
|
||||||
{
|
|
||||||
ITestOutputHelper _Helper;
|
|
||||||
public XUnitLog(ITestOutputHelper helper)
|
|
||||||
{
|
|
||||||
_Helper = helper;
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
public string Name
|
||||||
{
|
{
|
||||||
return this;
|
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)
|
public bool IsEnabled(LogLevel logLevel)
|
||||||
{
|
{
|
||||||
StringBuilder builder = new StringBuilder();
|
return true;
|
||||||
builder.Append(formatter(state, exception));
|
}
|
||||||
if(exception != null)
|
|
||||||
{
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.Append(exception.ToString());
|
|
||||||
}
|
|
||||||
LogInformation(builder.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LogInformation(string msg)
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||||
{
|
{
|
||||||
if(msg != null)
|
StringBuilder builder = new StringBuilder();
|
||||||
_Helper.WriteLine(Name + ": " + msg);
|
builder.Append(formatter(state, exception));
|
||||||
}
|
if (exception != null)
|
||||||
}
|
{
|
||||||
public class Logs
|
builder.AppendLine();
|
||||||
{
|
builder.Append(exception.ToString());
|
||||||
public static ILog Tester
|
}
|
||||||
{
|
LogInformation(builder.ToString());
|
||||||
get; set;
|
}
|
||||||
}
|
|
||||||
public static XUnitLogProvider LogProvider
|
public void LogInformation(string msg)
|
||||||
{
|
{
|
||||||
get;
|
if (msg != null)
|
||||||
set;
|
_Helper.WriteLine(Name + ": " + msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public class Logs
|
||||||
|
{
|
||||||
|
public static ILog Tester
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
public static XUnitLogProvider LogProvider
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,38 +6,38 @@ using Microsoft.AspNetCore.Mvc.Routing;
|
|||||||
|
|
||||||
namespace BTCPayServer.Tests.Mocks
|
namespace BTCPayServer.Tests.Mocks
|
||||||
{
|
{
|
||||||
public class UrlHelperMock : IUrlHelper
|
public class UrlHelperMock : IUrlHelper
|
||||||
{
|
{
|
||||||
Uri _BaseUrl;
|
Uri _BaseUrl;
|
||||||
public UrlHelperMock(Uri baseUrl)
|
public UrlHelperMock(Uri baseUrl)
|
||||||
{
|
{
|
||||||
_BaseUrl = baseUrl;
|
_BaseUrl = baseUrl;
|
||||||
}
|
}
|
||||||
public ActionContext ActionContext => throw new NotImplementedException();
|
public ActionContext ActionContext => throw new NotImplementedException();
|
||||||
|
|
||||||
public string Action(UrlActionContext actionContext)
|
public string Action(UrlActionContext actionContext)
|
||||||
{
|
{
|
||||||
return $"{_BaseUrl}mock";
|
return $"{_BaseUrl}mock";
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Content(string contentPath)
|
public string Content(string contentPath)
|
||||||
{
|
{
|
||||||
return $"{_BaseUrl}{contentPath}";
|
return $"{_BaseUrl}{contentPath}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsLocalUrl(string url)
|
public bool IsLocalUrl(string url)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Link(string routeName, object values)
|
public string Link(string routeName, object values)
|
||||||
{
|
{
|
||||||
return _BaseUrl.AbsoluteUri;
|
return _BaseUrl.AbsoluteUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string RouteUrl(UrlRouteContext routeContext)
|
public string RouteUrl(UrlRouteContext routeContext)
|
||||||
{
|
{
|
||||||
return _BaseUrl.AbsoluteUri;
|
return _BaseUrl.AbsoluteUri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,105 +8,105 @@ using Xunit;
|
|||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
public class ProcessLauncher
|
public class ProcessLauncher
|
||||||
{
|
{
|
||||||
string _CurrentDirectory;
|
string _CurrentDirectory;
|
||||||
public string CurrentDirectory
|
public string CurrentDirectory
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _CurrentDirectory;
|
return _CurrentDirectory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public ProcessLauncher()
|
public ProcessLauncher()
|
||||||
{
|
{
|
||||||
_CurrentDirectory = Directory.GetCurrentDirectory();
|
_CurrentDirectory = Directory.GetCurrentDirectory();
|
||||||
}
|
}
|
||||||
public bool GoTo(string[] directories, bool createIfNotExists = false)
|
public bool GoTo(string[] directories, bool createIfNotExists = false)
|
||||||
{
|
{
|
||||||
var original = _CurrentDirectory;
|
var original = _CurrentDirectory;
|
||||||
foreach(var dir in directories)
|
foreach (var dir in directories)
|
||||||
{
|
{
|
||||||
var newDirectory = Path.Combine(_CurrentDirectory, dir);
|
var newDirectory = Path.Combine(_CurrentDirectory, dir);
|
||||||
if(!Directory.Exists(newDirectory))
|
if (!Directory.Exists(newDirectory))
|
||||||
{
|
{
|
||||||
if(createIfNotExists)
|
if (createIfNotExists)
|
||||||
Directory.CreateDirectory(newDirectory);
|
Directory.CreateDirectory(newDirectory);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_CurrentDirectory = original;
|
_CurrentDirectory = original;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_CurrentDirectory = newDirectory;
|
_CurrentDirectory = newDirectory;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Stack<string> _Directories = new Stack<string>();
|
Stack<string> _Directories = new Stack<string>();
|
||||||
public void PushDirectory()
|
public void PushDirectory()
|
||||||
{
|
{
|
||||||
_Directories.Push(_CurrentDirectory);
|
_Directories.Push(_CurrentDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PopDirectory()
|
public void PopDirectory()
|
||||||
{
|
{
|
||||||
_CurrentDirectory = _Directories.Pop();
|
_CurrentDirectory = _Directories.Pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GoTo(string directory, bool createIfNotExists = false)
|
public bool GoTo(string directory, bool createIfNotExists = false)
|
||||||
{
|
{
|
||||||
return GoTo(new[] { directory }, createIfNotExists);
|
return GoTo(new[] { directory }, createIfNotExists);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Run(string processName, string args)
|
public void Run(string processName, string args)
|
||||||
{
|
{
|
||||||
Start(processName, args).WaitForExit();
|
Start(processName, args).WaitForExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Process Start(string processName, string args)
|
public Process Start(string processName, string args)
|
||||||
{
|
{
|
||||||
Logs.Tester.LogInformation($"Running [{processName} {args}] from {_CurrentDirectory}");
|
Logs.Tester.LogInformation($"Running [{processName} {args}] from {_CurrentDirectory}");
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
var process = new Process()
|
var process = new Process()
|
||||||
{
|
{
|
||||||
StartInfo = new ProcessStartInfo
|
StartInfo = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
WorkingDirectory = _CurrentDirectory,
|
WorkingDirectory = _CurrentDirectory,
|
||||||
FileName = processName,
|
FileName = processName,
|
||||||
Arguments = args,
|
Arguments = args,
|
||||||
RedirectStandardOutput = true,
|
RedirectStandardOutput = true,
|
||||||
RedirectStandardError = true
|
RedirectStandardError = true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
process.OutputDataReceived += (s, e) =>
|
process.OutputDataReceived += (s, e) =>
|
||||||
{
|
{
|
||||||
Logs.Tester.LogInformation(e.Data);
|
Logs.Tester.LogInformation(e.Data);
|
||||||
};
|
};
|
||||||
process.ErrorDataReceived += (s, e) =>
|
process.ErrorDataReceived += (s, e) =>
|
||||||
{
|
{
|
||||||
Logs.Tester.LogInformation(e.Data);
|
Logs.Tester.LogInformation(e.Data);
|
||||||
};
|
};
|
||||||
process.Start();
|
process.Start();
|
||||||
process.BeginErrorReadLine();
|
process.BeginErrorReadLine();
|
||||||
process.BeginOutputReadLine();
|
process.BeginOutputReadLine();
|
||||||
}
|
}
|
||||||
catch(Exception ex) { throw new Exception($"You need to install {processName} for this test (info : {ex.Message})"); }
|
catch (Exception ex) { throw new Exception($"You need to install {processName} for this test (info : {ex.Message})"); }
|
||||||
return process;
|
return process;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AssertExists(string file)
|
public void AssertExists(string file)
|
||||||
{
|
{
|
||||||
var path = Path.Combine(_CurrentDirectory, file);
|
var path = Path.Combine(_CurrentDirectory, file);
|
||||||
Assert.True(File.Exists(path), $"The file {path} should exist");
|
Assert.True(File.Exists(path), $"The file {path} should exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Exists(string file)
|
public bool Exists(string file)
|
||||||
{
|
{
|
||||||
var path = Path.Combine(_CurrentDirectory, file);
|
var path = Path.Combine(_CurrentDirectory, file);
|
||||||
return File.Exists(path);
|
return File.Exists(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"BTCPayServer.Tests": {
|
"BTCPayServer.Tests": {
|
||||||
"commandName": "Project"
|
"commandName": "Project"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -19,218 +19,218 @@ using System.Threading;
|
|||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
public class ServerTester : IDisposable
|
public class ServerTester : IDisposable
|
||||||
{
|
{
|
||||||
public static ServerTester Create([CallerMemberNameAttribute]string scope = null)
|
public static ServerTester Create([CallerMemberNameAttribute]string scope = null)
|
||||||
{
|
{
|
||||||
return new ServerTester(scope);
|
return new ServerTester(scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
string _Directory;
|
string _Directory;
|
||||||
public ServerTester(string scope)
|
public ServerTester(string scope)
|
||||||
{
|
{
|
||||||
_Directory = scope;
|
_Directory = scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Dockerized
|
public bool Dockerized
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
if(Directory.Exists(_Directory))
|
if (Directory.Exists(_Directory))
|
||||||
Utils.DeleteDirectory(_Directory);
|
Utils.DeleteDirectory(_Directory);
|
||||||
if(!Directory.Exists(_Directory))
|
if (!Directory.Exists(_Directory))
|
||||||
Directory.CreateDirectory(_Directory);
|
Directory.CreateDirectory(_Directory);
|
||||||
|
|
||||||
|
|
||||||
FakeCallback = bool.Parse(GetEnvironment("TESTS_FAKECALLBACK", "true"));
|
FakeCallback = bool.Parse(GetEnvironment("TESTS_FAKECALLBACK", "true"));
|
||||||
ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_RPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), Network);
|
ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_RPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), Network);
|
||||||
ExplorerClient = new ExplorerClient(Network, new Uri(GetEnvironment("TESTS_NBXPLORERURL", "http://127.0.0.1:32838/")));
|
ExplorerClient = new ExplorerClient(Network, new Uri(GetEnvironment("TESTS_NBXPLORERURL", "http://127.0.0.1:32838/")));
|
||||||
PayTester = new BTCPayServerTester(Path.Combine(_Directory, "pay"))
|
PayTester = new BTCPayServerTester(Path.Combine(_Directory, "pay"))
|
||||||
{
|
{
|
||||||
NBXplorerUri = ExplorerClient.Address,
|
NBXplorerUri = ExplorerClient.Address,
|
||||||
Postgres = GetEnvironment("TESTS_POSTGRES", "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver")
|
Postgres = GetEnvironment("TESTS_POSTGRES", "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver")
|
||||||
};
|
};
|
||||||
PayTester.Port = int.Parse(GetEnvironment("TESTS_PORT", Utils.FreeTcpPort().ToString()));
|
PayTester.Port = int.Parse(GetEnvironment("TESTS_PORT", Utils.FreeTcpPort().ToString()));
|
||||||
PayTester.HostName = GetEnvironment("TESTS_HOSTNAME", "127.0.0.1");
|
PayTester.HostName = GetEnvironment("TESTS_HOSTNAME", "127.0.0.1");
|
||||||
PayTester.Start();
|
PayTester.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetEnvironment(string variable, string defaultValue)
|
private string GetEnvironment(string variable, string defaultValue)
|
||||||
{
|
{
|
||||||
var var = Environment.GetEnvironmentVariable(variable);
|
var var = Environment.GetEnvironmentVariable(variable);
|
||||||
return String.IsNullOrEmpty(var) ? defaultValue : var;
|
return String.IsNullOrEmpty(var) ? defaultValue : var;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestAccount NewAccount()
|
public TestAccount NewAccount()
|
||||||
{
|
{
|
||||||
return new TestAccount(this);
|
return new TestAccount(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool FakeCallback
|
public bool FakeCallback
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public RPCClient ExplorerNode
|
public RPCClient ExplorerNode
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExplorerClient ExplorerClient
|
public ExplorerClient ExplorerClient
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpClient _Http = new HttpClient();
|
HttpClient _Http = new HttpClient();
|
||||||
|
|
||||||
class MockHttpRequest : HttpRequest
|
class MockHttpRequest : HttpRequest
|
||||||
{
|
{
|
||||||
Uri serverUri;
|
Uri serverUri;
|
||||||
public MockHttpRequest(Uri serverUri)
|
public MockHttpRequest(Uri serverUri)
|
||||||
{
|
{
|
||||||
this.serverUri = serverUri;
|
this.serverUri = serverUri;
|
||||||
}
|
}
|
||||||
public override HttpContext HttpContext => throw new NotImplementedException();
|
public override HttpContext HttpContext => throw new NotImplementedException();
|
||||||
|
|
||||||
public override string Method
|
public override string Method
|
||||||
{
|
{
|
||||||
get => throw new NotImplementedException();
|
get => throw new NotImplementedException();
|
||||||
set => throw new NotImplementedException();
|
set => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
public override string Scheme
|
public override string Scheme
|
||||||
{
|
{
|
||||||
get => serverUri.Scheme;
|
get => serverUri.Scheme;
|
||||||
set => throw new NotImplementedException();
|
set => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
public override bool IsHttps
|
public override bool IsHttps
|
||||||
{
|
{
|
||||||
get => throw new NotImplementedException();
|
get => throw new NotImplementedException();
|
||||||
set => throw new NotImplementedException();
|
set => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
public override HostString Host
|
public override HostString Host
|
||||||
{
|
{
|
||||||
get => new HostString(serverUri.Host, serverUri.Port);
|
get => new HostString(serverUri.Host, serverUri.Port);
|
||||||
set => throw new NotImplementedException();
|
set => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
public override PathString PathBase
|
public override PathString PathBase
|
||||||
{
|
{
|
||||||
get => "";
|
get => "";
|
||||||
set => throw new NotImplementedException();
|
set => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
public override PathString Path
|
public override PathString Path
|
||||||
{
|
{
|
||||||
get => throw new NotImplementedException();
|
get => throw new NotImplementedException();
|
||||||
set => throw new NotImplementedException();
|
set => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
public override QueryString QueryString
|
public override QueryString QueryString
|
||||||
{
|
{
|
||||||
get => throw new NotImplementedException();
|
get => throw new NotImplementedException();
|
||||||
set => throw new NotImplementedException();
|
set => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
public override IQueryCollection Query
|
public override IQueryCollection Query
|
||||||
{
|
{
|
||||||
get => throw new NotImplementedException();
|
get => throw new NotImplementedException();
|
||||||
set => throw new NotImplementedException();
|
set => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
public override string Protocol
|
public override string Protocol
|
||||||
{
|
{
|
||||||
get => throw new NotImplementedException();
|
get => throw new NotImplementedException();
|
||||||
set => throw new NotImplementedException();
|
set => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IHeaderDictionary Headers => throw new NotImplementedException();
|
public override IHeaderDictionary Headers => throw new NotImplementedException();
|
||||||
|
|
||||||
public override IRequestCookieCollection Cookies
|
public override IRequestCookieCollection Cookies
|
||||||
{
|
{
|
||||||
get => throw new NotImplementedException();
|
get => throw new NotImplementedException();
|
||||||
set => throw new NotImplementedException();
|
set => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
public override long? ContentLength
|
public override long? ContentLength
|
||||||
{
|
{
|
||||||
get => throw new NotImplementedException();
|
get => throw new NotImplementedException();
|
||||||
set => throw new NotImplementedException();
|
set => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
public override string ContentType
|
public override string ContentType
|
||||||
{
|
{
|
||||||
get => throw new NotImplementedException();
|
get => throw new NotImplementedException();
|
||||||
set => throw new NotImplementedException();
|
set => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
public override Stream Body
|
public override Stream Body
|
||||||
{
|
{
|
||||||
get => throw new NotImplementedException();
|
get => throw new NotImplementedException();
|
||||||
set => throw new NotImplementedException();
|
set => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool HasFormContentType => throw new NotImplementedException();
|
public override bool HasFormContentType => throw new NotImplementedException();
|
||||||
|
|
||||||
public override IFormCollection Form
|
public override IFormCollection Form
|
||||||
{
|
{
|
||||||
get => throw new NotImplementedException();
|
get => throw new NotImplementedException();
|
||||||
set => throw new NotImplementedException();
|
set => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken = default(CancellationToken))
|
public override Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Simulating callback from NBXplorer. NBXplorer can't reach the host during tests as it is not running on localhost.
|
/// Simulating callback from NBXplorer. NBXplorer can't reach the host during tests as it is not running on localhost.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address"></param>
|
/// <param name="address"></param>
|
||||||
public void SimulateCallback(BitcoinAddress address = null)
|
public void SimulateCallback(BitcoinAddress address = null)
|
||||||
{
|
{
|
||||||
if(!FakeCallback) //The callback of NBXplorer should work
|
if (!FakeCallback) //The callback of NBXplorer should work
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var req = new MockHttpRequest(PayTester.ServerUri);
|
var req = new MockHttpRequest(PayTester.ServerUri);
|
||||||
var controller = PayTester.GetController<CallbackController>();
|
var controller = PayTester.GetController<CallbackController>();
|
||||||
if(address != null)
|
if (address != null)
|
||||||
{
|
{
|
||||||
|
|
||||||
var match = new TransactionMatch();
|
var match = new TransactionMatch();
|
||||||
match.Outputs.Add(new KeyPathInformation() { ScriptPubKey = address.ScriptPubKey });
|
match.Outputs.Add(new KeyPathInformation() { ScriptPubKey = address.ScriptPubKey });
|
||||||
var content = new StringContent(new NBXplorer.Serializer(Network).ToString(match), new UTF8Encoding(false), "application/json");
|
var content = new StringContent(new NBXplorer.Serializer(Network).ToString(match), new UTF8Encoding(false), "application/json");
|
||||||
var uri = controller.GetCallbackUriAsync(req).GetAwaiter().GetResult();
|
var uri = controller.GetCallbackUriAsync(req).GetAwaiter().GetResult();
|
||||||
|
|
||||||
HttpRequestMessage message = new HttpRequestMessage();
|
HttpRequestMessage message = new HttpRequestMessage();
|
||||||
message.Method = HttpMethod.Post;
|
message.Method = HttpMethod.Post;
|
||||||
message.RequestUri = uri;
|
message.RequestUri = uri;
|
||||||
message.Content = content;
|
message.Content = content;
|
||||||
|
|
||||||
_Http.SendAsync(message).GetAwaiter().GetResult();
|
_Http.SendAsync(message).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
var uri = controller.GetCallbackBlockUriAsync(req).GetAwaiter().GetResult();
|
var uri = controller.GetCallbackBlockUriAsync(req).GetAwaiter().GetResult();
|
||||||
HttpRequestMessage message = new HttpRequestMessage();
|
HttpRequestMessage message = new HttpRequestMessage();
|
||||||
message.Method = HttpMethod.Post;
|
message.Method = HttpMethod.Post;
|
||||||
message.RequestUri = uri;
|
message.RequestUri = uri;
|
||||||
_Http.SendAsync(message).GetAwaiter().GetResult();
|
_Http.SendAsync(message).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public BTCPayServerTester PayTester
|
public BTCPayServerTester PayTester
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Network Network
|
public Network Network
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
} = Network.RegTest;
|
} = Network.RegTest;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if(PayTester != null)
|
if (PayTester != null)
|
||||||
PayTester.Dispose();
|
PayTester.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,78 +15,78 @@ namespace BTCPayServer.Tests
|
|||||||
{
|
{
|
||||||
public class TestAccount
|
public class TestAccount
|
||||||
{
|
{
|
||||||
ServerTester parent;
|
ServerTester parent;
|
||||||
public TestAccount(ServerTester parent)
|
public TestAccount(ServerTester parent)
|
||||||
{
|
{
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
BitPay = new Bitpay(new Key(), parent.PayTester.ServerUri);
|
BitPay = new Bitpay(new Key(), parent.PayTester.ServerUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GrantAccess()
|
public void GrantAccess()
|
||||||
{
|
{
|
||||||
GrantAccessAsync().GetAwaiter().GetResult();
|
GrantAccessAsync().GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Register()
|
public void Register()
|
||||||
{
|
{
|
||||||
RegisterAsync().GetAwaiter().GetResult();
|
RegisterAsync().GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BitcoinExtKey ExtKey
|
public BitcoinExtKey ExtKey
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task GrantAccessAsync()
|
public async Task GrantAccessAsync()
|
||||||
{
|
{
|
||||||
await RegisterAsync();
|
await RegisterAsync();
|
||||||
var store = await CreateStoreAsync();
|
var store = await CreateStoreAsync();
|
||||||
var pairingCode = BitPay.RequestClientAuthorization("test", Facade.Merchant);
|
var pairingCode = BitPay.RequestClientAuthorization("test", Facade.Merchant);
|
||||||
Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString()));
|
Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString()));
|
||||||
await store.Pair(pairingCode.ToString(), StoreId);
|
await store.Pair(pairingCode.ToString(), StoreId);
|
||||||
}
|
}
|
||||||
public StoresController CreateStore()
|
public StoresController CreateStore()
|
||||||
{
|
{
|
||||||
return CreateStoreAsync().GetAwaiter().GetResult();
|
return CreateStoreAsync().GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
public async Task<StoresController> CreateStoreAsync()
|
public async Task<StoresController> CreateStoreAsync()
|
||||||
{
|
{
|
||||||
ExtKey = new ExtKey().GetWif(parent.Network);
|
ExtKey = new ExtKey().GetWif(parent.Network);
|
||||||
var store = parent.PayTester.GetController<StoresController>(UserId);
|
var store = parent.PayTester.GetController<StoresController>(UserId);
|
||||||
await store.CreateStore(new CreateStoreViewModel() { Name = "Test Store" });
|
await store.CreateStore(new CreateStoreViewModel() { Name = "Test Store" });
|
||||||
StoreId = store.CreatedStoreId;
|
StoreId = store.CreatedStoreId;
|
||||||
await store.UpdateStore(StoreId, new StoreViewModel()
|
await store.UpdateStore(StoreId, new StoreViewModel()
|
||||||
{
|
{
|
||||||
DerivationScheme = ExtKey.Neuter().ToString() + "-[legacy]",
|
DerivationScheme = ExtKey.Neuter().ToString() + "-[legacy]",
|
||||||
SpeedPolicy = SpeedPolicy.MediumSpeed
|
SpeedPolicy = SpeedPolicy.MediumSpeed
|
||||||
}, "Save");
|
}, "Save");
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RegisterAsync()
|
private async Task RegisterAsync()
|
||||||
{
|
{
|
||||||
var account = parent.PayTester.GetController<AccountController>();
|
var account = parent.PayTester.GetController<AccountController>();
|
||||||
await account.Register(new RegisterViewModel()
|
await account.Register(new RegisterViewModel()
|
||||||
{
|
{
|
||||||
Email = Guid.NewGuid() + "@toto.com",
|
Email = Guid.NewGuid() + "@toto.com",
|
||||||
ConfirmPassword = "Kitten0@",
|
ConfirmPassword = "Kitten0@",
|
||||||
Password = "Kitten0@",
|
Password = "Kitten0@",
|
||||||
});
|
});
|
||||||
UserId = account.RegisteredUserId;
|
UserId = account.RegisteredUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bitpay BitPay
|
public Bitpay BitPay
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string UserId
|
public string UserId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string StoreId
|
public string StoreId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,369 +25,369 @@ using Microsoft.Extensions.Caching.Memory;
|
|||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
public class UnitTest1
|
public class UnitTest1
|
||||||
{
|
{
|
||||||
public UnitTest1(ITestOutputHelper helper)
|
public UnitTest1(ITestOutputHelper helper)
|
||||||
{
|
{
|
||||||
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
|
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
|
||||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanCalculateCryptoDue()
|
public void CanCalculateCryptoDue()
|
||||||
{
|
{
|
||||||
var entity = new InvoiceEntity();
|
var entity = new InvoiceEntity();
|
||||||
entity.TxFee = Money.Coins(0.1m);
|
entity.TxFee = Money.Coins(0.1m);
|
||||||
entity.Rate = 5000;
|
entity.Rate = 5000;
|
||||||
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||||
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
||||||
|
|
||||||
Assert.Equal(Money.Coins(1.1m), entity.GetCryptoDue());
|
Assert.Equal(Money.Coins(1.1m), entity.GetCryptoDue());
|
||||||
Assert.Equal(Money.Coins(1.1m), entity.GetTotalCryptoDue());
|
Assert.Equal(Money.Coins(1.1m), entity.GetTotalCryptoDue());
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.5m), new Key()) });
|
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.5m), new Key()) });
|
||||||
|
|
||||||
//Since we need to spend one more txout, it should be 1.1 - 0,5 + 0.1
|
//Since we need to spend one more txout, it should be 1.1 - 0,5 + 0.1
|
||||||
Assert.Equal(Money.Coins(0.7m), entity.GetCryptoDue());
|
Assert.Equal(Money.Coins(0.7m), entity.GetCryptoDue());
|
||||||
Assert.Equal(Money.Coins(1.2m), entity.GetTotalCryptoDue());
|
Assert.Equal(Money.Coins(1.2m), entity.GetTotalCryptoDue());
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()) });
|
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()) });
|
||||||
Assert.Equal(Money.Coins(0.6m), entity.GetCryptoDue());
|
Assert.Equal(Money.Coins(0.6m), entity.GetCryptoDue());
|
||||||
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.6m), new Key()) });
|
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.6m), new Key()) });
|
||||||
|
|
||||||
Assert.Equal(Money.Zero, entity.GetCryptoDue());
|
Assert.Equal(Money.Zero, entity.GetCryptoDue());
|
||||||
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()) });
|
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()) });
|
||||||
|
|
||||||
Assert.Equal(Money.Zero, entity.GetCryptoDue());
|
Assert.Equal(Money.Zero, entity.GetCryptoDue());
|
||||||
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanPayUsingBIP70()
|
public void CanPayUsingBIP70()
|
||||||
{
|
{
|
||||||
using(var tester = ServerTester.Create())
|
using (var tester = ServerTester.Create())
|
||||||
{
|
{
|
||||||
tester.Start();
|
tester.Start();
|
||||||
var user = tester.NewAccount();
|
var user = tester.NewAccount();
|
||||||
user.GrantAccess();
|
user.GrantAccess();
|
||||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||||
{
|
{
|
||||||
Buyer = new Buyer() { email = "test@fwf.com" },
|
Buyer = new Buyer() { email = "test@fwf.com" },
|
||||||
Price = 5000.0,
|
Price = 5000.0,
|
||||||
Currency = "USD",
|
Currency = "USD",
|
||||||
PosData = "posData",
|
PosData = "posData",
|
||||||
OrderId = "orderId",
|
OrderId = "orderId",
|
||||||
//RedirectURL = redirect + "redirect",
|
//RedirectURL = redirect + "redirect",
|
||||||
//NotificationURL = CallbackUri + "/notification",
|
//NotificationURL = CallbackUri + "/notification",
|
||||||
ItemDesc = "Some description",
|
ItemDesc = "Some description",
|
||||||
FullNotifications = true
|
FullNotifications = true
|
||||||
}, Facade.Merchant);
|
}, Facade.Merchant);
|
||||||
|
|
||||||
Assert.False(invoice.Refundable);
|
Assert.False(invoice.Refundable);
|
||||||
|
|
||||||
var url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP72);
|
var url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP72);
|
||||||
var request = url.GetPaymentRequest();
|
var request = url.GetPaymentRequest();
|
||||||
var payment = request.CreatePayment();
|
var payment = request.CreatePayment();
|
||||||
|
|
||||||
Transaction tx = new Transaction();
|
Transaction tx = new Transaction();
|
||||||
tx.Outputs.AddRange(request.Details.Outputs.Select(o => new TxOut(o.Amount, o.Script)));
|
tx.Outputs.AddRange(request.Details.Outputs.Select(o => new TxOut(o.Amount, o.Script)));
|
||||||
var cashCow = tester.ExplorerNode;
|
var cashCow = tester.ExplorerNode;
|
||||||
tx = cashCow.FundRawTransaction(tx).Transaction;
|
tx = cashCow.FundRawTransaction(tx).Transaction;
|
||||||
tx = cashCow.SignRawTransaction(tx);
|
tx = cashCow.SignRawTransaction(tx);
|
||||||
|
|
||||||
payment.Transactions.Add(tx);
|
payment.Transactions.Add(tx);
|
||||||
|
|
||||||
payment.RefundTo.Add(new PaymentOutput(Money.Coins(1.0m), new Key().ScriptPubKey));
|
payment.RefundTo.Add(new PaymentOutput(Money.Coins(1.0m), new Key().ScriptPubKey));
|
||||||
var ack = payment.SubmitPayment();
|
var ack = payment.SubmitPayment();
|
||||||
Assert.NotNull(ack);
|
Assert.NotNull(ack);
|
||||||
|
|
||||||
Eventually(() =>
|
Eventually(() =>
|
||||||
{
|
{
|
||||||
tester.SimulateCallback(url.Address);
|
tester.SimulateCallback(url.Address);
|
||||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||||
Assert.Equal("paid", localInvoice.Status);
|
Assert.Equal("paid", localInvoice.Status);
|
||||||
Assert.True(localInvoice.Refundable);
|
Assert.True(localInvoice.Refundable);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanUseServerInitiatedPairingCode()
|
public void CanUseServerInitiatedPairingCode()
|
||||||
{
|
{
|
||||||
using(var tester = ServerTester.Create())
|
using (var tester = ServerTester.Create())
|
||||||
{
|
{
|
||||||
tester.Start();
|
tester.Start();
|
||||||
var acc = tester.NewAccount();
|
var acc = tester.NewAccount();
|
||||||
acc.Register();
|
acc.Register();
|
||||||
acc.CreateStore();
|
acc.CreateStore();
|
||||||
|
|
||||||
var controller = tester.PayTester.GetController<StoresController>(acc.UserId);
|
var controller = tester.PayTester.GetController<StoresController>(acc.UserId);
|
||||||
var token = (RedirectToActionResult)controller.CreateToken(acc.StoreId, new Models.StoreViewModels.CreateTokenViewModel()
|
var token = (RedirectToActionResult)controller.CreateToken(acc.StoreId, new Models.StoreViewModels.CreateTokenViewModel()
|
||||||
{
|
{
|
||||||
Facade = Facade.Merchant.ToString(),
|
Facade = Facade.Merchant.ToString(),
|
||||||
Label = "bla",
|
Label = "bla",
|
||||||
PublicKey = null
|
PublicKey = null
|
||||||
}).GetAwaiter().GetResult();
|
}).GetAwaiter().GetResult();
|
||||||
|
|
||||||
var pairingCode = (string)token.RouteValues["pairingCode"];
|
var pairingCode = (string)token.RouteValues["pairingCode"];
|
||||||
|
|
||||||
acc.BitPay.AuthorizeClient(new PairingCode(pairingCode)).GetAwaiter().GetResult();
|
acc.BitPay.AuthorizeClient(new PairingCode(pairingCode)).GetAwaiter().GetResult();
|
||||||
Assert.True(acc.BitPay.TestAccess(Facade.Merchant));
|
Assert.True(acc.BitPay.TestAccess(Facade.Merchant));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanSendIPN()
|
public void CanSendIPN()
|
||||||
{
|
{
|
||||||
using(var callbackServer = new CustomServer())
|
using (var callbackServer = new CustomServer())
|
||||||
{
|
{
|
||||||
using(var tester = ServerTester.Create())
|
using (var tester = ServerTester.Create())
|
||||||
{
|
{
|
||||||
tester.Start();
|
tester.Start();
|
||||||
var acc = tester.NewAccount();
|
var acc = tester.NewAccount();
|
||||||
acc.GrantAccess();
|
acc.GrantAccess();
|
||||||
var invoice = acc.BitPay.CreateInvoice(new Invoice()
|
var invoice = acc.BitPay.CreateInvoice(new Invoice()
|
||||||
{
|
{
|
||||||
Price = 5.0,
|
Price = 5.0,
|
||||||
Currency = "USD",
|
Currency = "USD",
|
||||||
PosData = "posData",
|
PosData = "posData",
|
||||||
OrderId = "orderId",
|
OrderId = "orderId",
|
||||||
NotificationURL = callbackServer.GetUri().AbsoluteUri,
|
NotificationURL = callbackServer.GetUri().AbsoluteUri,
|
||||||
ItemDesc = "Some description",
|
ItemDesc = "Some description",
|
||||||
FullNotifications = true
|
FullNotifications = true
|
||||||
});
|
});
|
||||||
BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21);
|
BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21);
|
||||||
tester.ExplorerNode.SendToAddress(url.Address, url.Amount);
|
tester.ExplorerNode.SendToAddress(url.Address, url.Amount);
|
||||||
Thread.Sleep(5000);
|
Thread.Sleep(5000);
|
||||||
tester.SimulateCallback(url.Address);
|
tester.SimulateCallback(url.Address);
|
||||||
callbackServer.ProcessNextRequest((ctx) =>
|
callbackServer.ProcessNextRequest((ctx) =>
|
||||||
{
|
{
|
||||||
var ipn = new StreamReader(ctx.Request.Body).ReadToEnd();
|
var ipn = new StreamReader(ctx.Request.Body).ReadToEnd();
|
||||||
JsonConvert.DeserializeObject<InvoicePaymentNotification>(ipn); //can deserialize
|
JsonConvert.DeserializeObject<InvoicePaymentNotification>(ipn); //can deserialize
|
||||||
});
|
});
|
||||||
var invoice2 = acc.BitPay.GetInvoice(invoice.Id);
|
var invoice2 = acc.BitPay.GetInvoice(invoice.Id);
|
||||||
Assert.NotNull(invoice2);
|
Assert.NotNull(invoice2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CantPairTwiceWithSamePubkey()
|
public void CantPairTwiceWithSamePubkey()
|
||||||
{
|
{
|
||||||
using(var tester = ServerTester.Create())
|
using (var tester = ServerTester.Create())
|
||||||
{
|
{
|
||||||
tester.Start();
|
tester.Start();
|
||||||
var acc = tester.NewAccount();
|
var acc = tester.NewAccount();
|
||||||
acc.Register();
|
acc.Register();
|
||||||
var store = acc.CreateStore();
|
var store = acc.CreateStore();
|
||||||
var pairingCode = acc.BitPay.RequestClientAuthorization("test", Facade.Merchant);
|
var pairingCode = acc.BitPay.RequestClientAuthorization("test", Facade.Merchant);
|
||||||
Assert.IsType<RedirectToActionResult>(store.Pair(pairingCode.ToString(), acc.StoreId).GetAwaiter().GetResult());
|
Assert.IsType<RedirectToActionResult>(store.Pair(pairingCode.ToString(), acc.StoreId).GetAwaiter().GetResult());
|
||||||
|
|
||||||
pairingCode = acc.BitPay.RequestClientAuthorization("test1", Facade.Merchant);
|
pairingCode = acc.BitPay.RequestClientAuthorization("test1", Facade.Merchant);
|
||||||
var store2 = acc.CreateStore();
|
var store2 = acc.CreateStore();
|
||||||
store2.Pair(pairingCode.ToString(), store2.CreatedStoreId).GetAwaiter().GetResult();
|
store2.Pair(pairingCode.ToString(), store2.CreatedStoreId).GetAwaiter().GetResult();
|
||||||
Assert.Contains(nameof(PairingResult.ReusedKey), store2.StatusMessage);
|
Assert.Contains(nameof(PairingResult.ReusedKey), store2.StatusMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void InvoiceFlowThroughDifferentStatesCorrectly()
|
public void InvoiceFlowThroughDifferentStatesCorrectly()
|
||||||
{
|
{
|
||||||
using(var tester = ServerTester.Create())
|
using (var tester = ServerTester.Create())
|
||||||
{
|
{
|
||||||
tester.Start();
|
tester.Start();
|
||||||
var user = tester.NewAccount();
|
var user = tester.NewAccount();
|
||||||
Assert.False(user.BitPay.TestAccess(Facade.Merchant));
|
Assert.False(user.BitPay.TestAccess(Facade.Merchant));
|
||||||
user.GrantAccess();
|
user.GrantAccess();
|
||||||
Assert.True(user.BitPay.TestAccess(Facade.Merchant));
|
Assert.True(user.BitPay.TestAccess(Facade.Merchant));
|
||||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||||
{
|
{
|
||||||
Price = 5000.0,
|
Price = 5000.0,
|
||||||
Currency = "USD",
|
Currency = "USD",
|
||||||
PosData = "posData",
|
PosData = "posData",
|
||||||
OrderId = "orderId",
|
OrderId = "orderId",
|
||||||
//RedirectURL = redirect + "redirect",
|
//RedirectURL = redirect + "redirect",
|
||||||
//NotificationURL = CallbackUri + "/notification",
|
//NotificationURL = CallbackUri + "/notification",
|
||||||
ItemDesc = "Some description",
|
ItemDesc = "Some description",
|
||||||
FullNotifications = true
|
FullNotifications = true
|
||||||
}, Facade.Merchant);
|
}, Facade.Merchant);
|
||||||
var repo = tester.PayTester.GetService<InvoiceRepository>();
|
var repo = tester.PayTester.GetService<InvoiceRepository>();
|
||||||
var ctx = tester.PayTester.GetService<ApplicationDbContextFactory>().CreateContext();
|
var ctx = tester.PayTester.GetService<ApplicationDbContextFactory>().CreateContext();
|
||||||
var textSearchResult = tester.PayTester.Runtime.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
var textSearchResult = tester.PayTester.Runtime.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||||
{
|
{
|
||||||
StoreId = user.StoreId,
|
StoreId = user.StoreId,
|
||||||
TextSearch = invoice.OrderId
|
TextSearch = invoice.OrderId
|
||||||
}).GetAwaiter().GetResult();
|
}).GetAwaiter().GetResult();
|
||||||
Assert.Equal(1, textSearchResult.Length);
|
Assert.Equal(1, textSearchResult.Length);
|
||||||
textSearchResult = tester.PayTester.Runtime.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
textSearchResult = tester.PayTester.Runtime.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||||
{
|
{
|
||||||
StoreId = user.StoreId,
|
StoreId = user.StoreId,
|
||||||
TextSearch = invoice.Id
|
TextSearch = invoice.Id
|
||||||
}).GetAwaiter().GetResult();
|
}).GetAwaiter().GetResult();
|
||||||
|
|
||||||
Assert.Equal(1, textSearchResult.Length);
|
Assert.Equal(1, textSearchResult.Length);
|
||||||
|
|
||||||
invoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
invoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||||
Assert.Equal(Money.Coins(0), invoice.BtcPaid);
|
Assert.Equal(Money.Coins(0), invoice.BtcPaid);
|
||||||
Assert.Equal("new", invoice.Status);
|
Assert.Equal("new", invoice.Status);
|
||||||
Assert.Equal(false, (bool)((JValue)invoice.ExceptionStatus).Value);
|
Assert.Equal(false, (bool)((JValue)invoice.ExceptionStatus).Value);
|
||||||
|
|
||||||
Assert.Equal(1, user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime).Length);
|
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(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)).Length);
|
||||||
Assert.Equal(1, user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime - TimeSpan.FromDays(5), invoice.InvoiceTime.DateTime).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);
|
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;
|
var cashCow = tester.ExplorerNode;
|
||||||
|
|
||||||
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||||
var iii = ctx.AddressInvoices.ToArray();
|
var iii = ctx.AddressInvoices.ToArray();
|
||||||
Assert.True(IsMapped(invoice, ctx));
|
Assert.True(IsMapped(invoice, ctx));
|
||||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||||
|
|
||||||
var invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult();
|
var invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult();
|
||||||
Assert.Equal(1, invoiceEntity.HistoricalAddresses.Length);
|
Assert.Equal(1, invoiceEntity.HistoricalAddresses.Length);
|
||||||
Assert.Null(invoiceEntity.HistoricalAddresses[0].UnAssigned);
|
Assert.Null(invoiceEntity.HistoricalAddresses[0].UnAssigned);
|
||||||
|
|
||||||
Money secondPayment = Money.Zero;
|
Money secondPayment = Money.Zero;
|
||||||
|
|
||||||
Eventually(() =>
|
Eventually(() =>
|
||||||
{
|
{
|
||||||
tester.SimulateCallback(invoiceAddress);
|
tester.SimulateCallback(invoiceAddress);
|
||||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||||
Assert.Equal("new", localInvoice.Status);
|
Assert.Equal("new", localInvoice.Status);
|
||||||
Assert.Equal(firstPayment, localInvoice.BtcPaid);
|
Assert.Equal(firstPayment, localInvoice.BtcPaid);
|
||||||
txFee = localInvoice.BtcDue - invoice.BtcDue;
|
txFee = localInvoice.BtcDue - invoice.BtcDue;
|
||||||
Assert.Equal("paidPartial", localInvoice.ExceptionStatus);
|
Assert.Equal("paidPartial", localInvoice.ExceptionStatus);
|
||||||
Assert.NotEqual(localInvoice.BitcoinAddress, invoice.BitcoinAddress); //New address
|
Assert.NotEqual(localInvoice.BitcoinAddress, invoice.BitcoinAddress); //New address
|
||||||
Assert.True(IsMapped(invoice, ctx));
|
Assert.True(IsMapped(invoice, ctx));
|
||||||
Assert.True(IsMapped(localInvoice, ctx));
|
Assert.True(IsMapped(localInvoice, ctx));
|
||||||
|
|
||||||
invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult();
|
invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult();
|
||||||
var historical1 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.Address == invoice.BitcoinAddress.ToString());
|
var historical1 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.Address == invoice.BitcoinAddress.ToString());
|
||||||
Assert.NotNull(historical1.UnAssigned);
|
Assert.NotNull(historical1.UnAssigned);
|
||||||
var historical2 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.Address == localInvoice.BitcoinAddress.ToString());
|
var historical2 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.Address == localInvoice.BitcoinAddress.ToString());
|
||||||
Assert.Null(historical2.UnAssigned);
|
Assert.Null(historical2.UnAssigned);
|
||||||
invoiceAddress = BitcoinAddress.Create(localInvoice.BitcoinAddress, cashCow.Network);
|
invoiceAddress = BitcoinAddress.Create(localInvoice.BitcoinAddress, cashCow.Network);
|
||||||
secondPayment = localInvoice.BtcDue;
|
secondPayment = localInvoice.BtcDue;
|
||||||
});
|
});
|
||||||
|
|
||||||
cashCow.SendToAddress(invoiceAddress, secondPayment);
|
cashCow.SendToAddress(invoiceAddress, secondPayment);
|
||||||
|
|
||||||
Eventually(() =>
|
Eventually(() =>
|
||||||
{
|
{
|
||||||
tester.SimulateCallback(invoiceAddress);
|
tester.SimulateCallback(invoiceAddress);
|
||||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||||
Assert.Equal("paid", localInvoice.Status);
|
Assert.Equal("paid", localInvoice.Status);
|
||||||
Assert.Equal(firstPayment + secondPayment, localInvoice.BtcPaid);
|
Assert.Equal(firstPayment + secondPayment, localInvoice.BtcPaid);
|
||||||
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
||||||
Assert.Equal(localInvoice.BitcoinAddress, invoiceAddress.ToString()); //no new address generated
|
Assert.Equal(localInvoice.BitcoinAddress, invoiceAddress.ToString()); //no new address generated
|
||||||
Assert.True(IsMapped(localInvoice, ctx));
|
Assert.True(IsMapped(localInvoice, ctx));
|
||||||
Assert.Equal(false, (bool)((JValue)localInvoice.ExceptionStatus).Value);
|
Assert.Equal(false, (bool)((JValue)localInvoice.ExceptionStatus).Value);
|
||||||
});
|
});
|
||||||
|
|
||||||
cashCow.Generate(1); //The user has medium speed settings, so 1 conf is enough to be confirmed
|
cashCow.Generate(1); //The user has medium speed settings, so 1 conf is enough to be confirmed
|
||||||
|
|
||||||
Eventually(() =>
|
Eventually(() =>
|
||||||
{
|
{
|
||||||
tester.SimulateCallback();
|
tester.SimulateCallback();
|
||||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||||
Assert.Equal("confirmed", localInvoice.Status);
|
Assert.Equal("confirmed", localInvoice.Status);
|
||||||
});
|
});
|
||||||
|
|
||||||
cashCow.Generate(5); //Now should be complete
|
cashCow.Generate(5); //Now should be complete
|
||||||
|
|
||||||
Eventually(() =>
|
Eventually(() =>
|
||||||
{
|
{
|
||||||
tester.SimulateCallback();
|
tester.SimulateCallback();
|
||||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||||
Assert.Equal("complete", localInvoice.Status);
|
Assert.Equal("complete", localInvoice.Status);
|
||||||
});
|
});
|
||||||
|
|
||||||
invoice = user.BitPay.CreateInvoice(new Invoice()
|
invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||||
{
|
{
|
||||||
Price = 5000.0,
|
Price = 5000.0,
|
||||||
Currency = "USD",
|
Currency = "USD",
|
||||||
PosData = "posData",
|
PosData = "posData",
|
||||||
OrderId = "orderId",
|
OrderId = "orderId",
|
||||||
//RedirectURL = redirect + "redirect",
|
//RedirectURL = redirect + "redirect",
|
||||||
//NotificationURL = CallbackUri + "/notification",
|
//NotificationURL = CallbackUri + "/notification",
|
||||||
ItemDesc = "Some description",
|
ItemDesc = "Some description",
|
||||||
FullNotifications = true
|
FullNotifications = true
|
||||||
}, Facade.Merchant);
|
}, Facade.Merchant);
|
||||||
invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||||
|
|
||||||
cashCow.SendToAddress(invoiceAddress, invoice.BtcDue + Money.Coins(1));
|
cashCow.SendToAddress(invoiceAddress, invoice.BtcDue + Money.Coins(1));
|
||||||
|
|
||||||
Eventually(() =>
|
Eventually(() =>
|
||||||
{
|
{
|
||||||
tester.SimulateCallback(invoiceAddress);
|
tester.SimulateCallback(invoiceAddress);
|
||||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||||
Assert.Equal("paid", localInvoice.Status);
|
Assert.Equal("paid", localInvoice.Status);
|
||||||
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
||||||
Assert.Equal("paidOver", (string)((JValue)localInvoice.ExceptionStatus).Value);
|
Assert.Equal("paidOver", (string)((JValue)localInvoice.ExceptionStatus).Value);
|
||||||
});
|
});
|
||||||
|
|
||||||
cashCow.Generate(1);
|
cashCow.Generate(1);
|
||||||
|
|
||||||
Eventually(() =>
|
Eventually(() =>
|
||||||
{
|
{
|
||||||
tester.SimulateCallback();
|
tester.SimulateCallback();
|
||||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||||
Assert.Equal("confirmed", localInvoice.Status);
|
Assert.Equal("confirmed", localInvoice.Status);
|
||||||
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
||||||
Assert.Equal("paidOver", (string)((JValue)localInvoice.ExceptionStatus).Value);
|
Assert.Equal("paidOver", (string)((JValue)localInvoice.ExceptionStatus).Value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CheckRatesProvider()
|
public void CheckRatesProvider()
|
||||||
{
|
{
|
||||||
var coinAverage = new CoinAverageRateProvider();
|
var coinAverage = new CoinAverageRateProvider();
|
||||||
var jpy = coinAverage.GetRateAsync("JPY").GetAwaiter().GetResult();
|
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 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) }));
|
var cached = new CachedRateProvider(coinAverage, new MemoryCache(new MemoryCacheOptions() { ExpirationScanFrequency = TimeSpan.FromSeconds(1.0) }));
|
||||||
cached.CacheSpan = TimeSpan.FromSeconds(10);
|
cached.CacheSpan = TimeSpan.FromSeconds(10);
|
||||||
var a = cached.GetRateAsync("JPY").GetAwaiter().GetResult();
|
var a = cached.GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||||
var b = cached.GetRateAsync("JPY").GetAwaiter().GetResult();
|
var b = cached.GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||||
//Manually check that cache get hit after 10 sec
|
//Manually check that cache get hit after 10 sec
|
||||||
var c = cached.GetRateAsync("JPY").GetAwaiter().GetResult();
|
var c = cached.GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx)
|
private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx)
|
||||||
{
|
{
|
||||||
var h = BitcoinAddress.Create(invoice.BitcoinAddress).ScriptPubKey.Hash.ToString();
|
var h = BitcoinAddress.Create(invoice.BitcoinAddress).ScriptPubKey.Hash.ToString();
|
||||||
return ctx.AddressInvoices.FirstOrDefault(i => i.InvoiceDataId == invoice.Id && i.Address == h) != null;
|
return ctx.AddressInvoices.FirstOrDefault(i => i.InvoiceDataId == invoice.Id && i.Address == h) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Eventually(Action act)
|
private void Eventually(Action act)
|
||||||
{
|
{
|
||||||
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
||||||
while(true)
|
while (true)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
act();
|
act();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch(XunitException) when(!cts.Token.IsCancellationRequested)
|
catch (XunitException) when (!cts.Token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
cts.Token.WaitHandle.WaitOne(500);
|
cts.Token.WaitHandle.WaitOne(500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,43 +8,43 @@ using Xunit;
|
|||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
// Helper class for testing functionality and generating data needed during coding/debuging
|
// Helper class for testing functionality and generating data needed during coding/debuging
|
||||||
public class UnitTestPeusa
|
public class UnitTestPeusa
|
||||||
{
|
{
|
||||||
// Unit test that generates temorary checkout Bitpay page
|
// Unit test that generates temorary checkout Bitpay page
|
||||||
// https://forkbitpay.slack.com/archives/C7M093Z55/p1508293682000217
|
// https://forkbitpay.slack.com/archives/C7M093Z55/p1508293682000217
|
||||||
[Fact]
|
[Fact]
|
||||||
public void BitpayCheckout()
|
public void BitpayCheckout()
|
||||||
{
|
{
|
||||||
var key = new Key(Encoders.Hex.DecodeData("7b70a06f35562873e3dcb46005ed0fe78e1991ad906e56adaaafa40ba861e056"));
|
var key = new Key(Encoders.Hex.DecodeData("7b70a06f35562873e3dcb46005ed0fe78e1991ad906e56adaaafa40ba861e056"));
|
||||||
var url = new Uri("https://test.bitpay.com/");
|
var url = new Uri("https://test.bitpay.com/");
|
||||||
var btcpay = new Bitpay(key, url);
|
var btcpay = new Bitpay(key, url);
|
||||||
var invoice = btcpay.CreateInvoice(new Invoice()
|
var invoice = btcpay.CreateInvoice(new Invoice()
|
||||||
{
|
{
|
||||||
|
|
||||||
Price = 5.0,
|
Price = 5.0,
|
||||||
Currency = "USD",
|
Currency = "USD",
|
||||||
PosData = "posData",
|
PosData = "posData",
|
||||||
OrderId = "cdfd8a5f-6928-4c3b-ba9b-ddf438029e73",
|
OrderId = "cdfd8a5f-6928-4c3b-ba9b-ddf438029e73",
|
||||||
ItemDesc = "Hello from the otherside"
|
ItemDesc = "Hello from the otherside"
|
||||||
}, Facade.Merchant);
|
}, Facade.Merchant);
|
||||||
|
|
||||||
// go to invoice.Url
|
// go to invoice.Url
|
||||||
Console.WriteLine(invoice.Url);
|
Console.WriteLine(invoice.Url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generating Extended public key to use on http://localhost:14142/stores/{storeId}
|
// Generating Extended public key to use on http://localhost:14142/stores/{storeId}
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GeneratePubkey()
|
public void GeneratePubkey()
|
||||||
{
|
{
|
||||||
var network = Network.RegTest;
|
var network = Network.RegTest;
|
||||||
|
|
||||||
ExtKey masterKey = new ExtKey();
|
ExtKey masterKey = new ExtKey();
|
||||||
Console.WriteLine("Master key : " + masterKey.ToString(network));
|
Console.WriteLine("Master key : " + masterKey.ToString(network));
|
||||||
ExtPubKey masterPubKey = masterKey.Neuter();
|
ExtPubKey masterPubKey = masterKey.Neuter();
|
||||||
|
|
||||||
ExtPubKey pubkey = masterPubKey.Derive(0);
|
ExtPubKey pubkey = masterPubKey.Derive(0);
|
||||||
Console.WriteLine("PubKey " + 0 + " : " + pubkey.ToString(network));
|
Console.WriteLine("PubKey " + 0 + " : " + pubkey.ToString(network));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,54 +10,54 @@ namespace BTCPayServer.Tests
|
|||||||
{
|
{
|
||||||
public class Utils
|
public class Utils
|
||||||
{
|
{
|
||||||
public static int FreeTcpPort()
|
public static int FreeTcpPort()
|
||||||
{
|
{
|
||||||
TcpListener l = new TcpListener(IPAddress.Loopback, 0);
|
TcpListener l = new TcpListener(IPAddress.Loopback, 0);
|
||||||
l.Start();
|
l.Start();
|
||||||
int port = ((IPEndPoint)l.LocalEndpoint).Port;
|
int port = ((IPEndPoint)l.LocalEndpoint).Port;
|
||||||
l.Stop();
|
l.Stop();
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://stackoverflow.com/a/14933880/2061103
|
// http://stackoverflow.com/a/14933880/2061103
|
||||||
public static void DeleteDirectory(string destinationDir)
|
public static void DeleteDirectory(string destinationDir)
|
||||||
{
|
{
|
||||||
const int magicDust = 10;
|
const int magicDust = 10;
|
||||||
for(var gnomes = 1; gnomes <= magicDust; gnomes++)
|
for (var gnomes = 1; gnomes <= magicDust; gnomes++)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Directory.Delete(destinationDir, true);
|
Directory.Delete(destinationDir, true);
|
||||||
}
|
}
|
||||||
catch(DirectoryNotFoundException)
|
catch (DirectoryNotFoundException)
|
||||||
{
|
{
|
||||||
return; // good!
|
return; // good!
|
||||||
}
|
}
|
||||||
catch(IOException)
|
catch (IOException)
|
||||||
{
|
{
|
||||||
if(gnomes == magicDust)
|
if (gnomes == magicDust)
|
||||||
throw;
|
throw;
|
||||||
// System.IO.IOException: The directory is not empty
|
// System.IO.IOException: The directory is not empty
|
||||||
System.Diagnostics.Debug.WriteLine("Gnomes prevent deletion of {0}! Applying magic dust, attempt #{1}.", destinationDir, gnomes);
|
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
|
// see http://stackoverflow.com/questions/329355/cannot-delete-directory-with-directory-deletepath-true for more magic
|
||||||
Thread.Sleep(100);
|
Thread.Sleep(100);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
catch(UnauthorizedAccessException)
|
catch (UnauthorizedAccessException)
|
||||||
{
|
{
|
||||||
if(gnomes == magicDust)
|
if (gnomes == magicDust)
|
||||||
throw;
|
throw;
|
||||||
// Wait, maybe another software make us authorized a little later
|
// 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);
|
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
|
// see http://stackoverflow.com/questions/329355/cannot-delete-directory-with-directory-deletepath-true for more magic
|
||||||
Thread.Sleep(100);
|
Thread.Sleep(100);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// depending on your use case, consider throwing an exception here
|
// depending on your use case, consider throwing an exception here
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,27 +9,27 @@ namespace BTCPayServer.Authentication
|
|||||||
{
|
{
|
||||||
public class BitIdentity : IIdentity
|
public class BitIdentity : IIdentity
|
||||||
{
|
{
|
||||||
public BitIdentity(PubKey key)
|
public BitIdentity(PubKey key)
|
||||||
{
|
{
|
||||||
PubKey = key;
|
PubKey = key;
|
||||||
_Name = Encoders.Base58Check.EncodeData(Encoders.Hex.DecodeData("0f02" + key.Hash.ToString()));
|
_Name = Encoders.Base58Check.EncodeData(Encoders.Hex.DecodeData("0f02" + key.Hash.ToString()));
|
||||||
SIN = NBitpayClient.Extensions.BitIdExtensions.GetBitIDSIN(key);
|
SIN = NBitpayClient.Extensions.BitIdExtensions.GetBitIDSIN(key);
|
||||||
}
|
}
|
||||||
string _Name;
|
string _Name;
|
||||||
|
|
||||||
public string SIN
|
public string SIN
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
}
|
}
|
||||||
public PubKey PubKey
|
public PubKey PubKey
|
||||||
{
|
{
|
||||||
get;
|
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,43 +8,43 @@ namespace BTCPayServer.Authentication
|
|||||||
{
|
{
|
||||||
public class BitTokenEntity
|
public class BitTokenEntity
|
||||||
{
|
{
|
||||||
public string Facade
|
public string Facade
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string Value
|
public string Value
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string StoreId
|
public string StoreId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string Label
|
public string Label
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public DateTimeOffset PairingTime
|
public DateTimeOffset PairingTime
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string SIN
|
public string SIN
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BitTokenEntity Clone(Facade facade)
|
public BitTokenEntity Clone(Facade facade)
|
||||||
{
|
{
|
||||||
return new BitTokenEntity()
|
return new BitTokenEntity()
|
||||||
{
|
{
|
||||||
Label = Label,
|
Label = Label,
|
||||||
Facade = Facade,
|
Facade = Facade,
|
||||||
StoreId = StoreId,
|
StoreId = StoreId,
|
||||||
PairingTime = PairingTime,
|
PairingTime = PairingTime,
|
||||||
SIN = SIN,
|
SIN = SIN,
|
||||||
Value = Value
|
Value = Value
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,47 +4,47 @@ using System.Text;
|
|||||||
|
|
||||||
namespace BTCPayServer.Authentication
|
namespace BTCPayServer.Authentication
|
||||||
{
|
{
|
||||||
public class PairingCodeEntity
|
public class PairingCodeEntity
|
||||||
{
|
{
|
||||||
public string Id
|
public string Id
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string Facade
|
public string Facade
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string Label
|
public string Label
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string SIN
|
public string SIN
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public DateTimeOffset CreatedTime
|
public DateTimeOffset CreatedTime
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public DateTimeOffset Expiration
|
public DateTimeOffset Expiration
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string TokenValue
|
public string TokenValue
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsExpired()
|
public bool IsExpired()
|
||||||
{
|
{
|
||||||
return DateTimeOffset.UtcNow > Expiration;
|
return DateTimeOffset.UtcNow > Expiration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,196 +13,196 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace BTCPayServer.Authentication
|
namespace BTCPayServer.Authentication
|
||||||
{
|
{
|
||||||
public enum PairingResult
|
public enum PairingResult
|
||||||
{
|
{
|
||||||
Partial,
|
Partial,
|
||||||
Complete,
|
Complete,
|
||||||
ReusedKey,
|
ReusedKey,
|
||||||
Expired
|
Expired
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TokenRepository
|
public class TokenRepository
|
||||||
{
|
{
|
||||||
ApplicationDbContextFactory _Factory;
|
ApplicationDbContextFactory _Factory;
|
||||||
public TokenRepository(ApplicationDbContextFactory dbFactory)
|
public TokenRepository(ApplicationDbContextFactory dbFactory)
|
||||||
{
|
{
|
||||||
if(dbFactory == null)
|
if (dbFactory == null)
|
||||||
throw new ArgumentNullException(nameof(dbFactory));
|
throw new ArgumentNullException(nameof(dbFactory));
|
||||||
_Factory = dbFactory;
|
_Factory = dbFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<BitTokenEntity[]> GetTokens(string sin)
|
public async Task<BitTokenEntity[]> GetTokens(string sin)
|
||||||
{
|
{
|
||||||
using(var ctx = _Factory.CreateContext())
|
using (var ctx = _Factory.CreateContext())
|
||||||
{
|
{
|
||||||
return (await ctx.PairedSINData
|
return (await ctx.PairedSINData
|
||||||
.Where(p => p.SIN == sin)
|
.Where(p => p.SIN == sin)
|
||||||
.ToListAsync())
|
.ToListAsync())
|
||||||
.Select(p => CreateTokenEntity(p))
|
.Select(p => CreateTokenEntity(p))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BitTokenEntity CreateTokenEntity(PairedSINData data)
|
private BitTokenEntity CreateTokenEntity(PairedSINData data)
|
||||||
{
|
{
|
||||||
return new BitTokenEntity()
|
return new BitTokenEntity()
|
||||||
{
|
{
|
||||||
Label = data.Label,
|
Label = data.Label,
|
||||||
Facade = data.Facade,
|
Facade = data.Facade,
|
||||||
Value = data.Id,
|
Value = data.Id,
|
||||||
SIN = data.SIN,
|
SIN = data.SIN,
|
||||||
PairingTime = data.PairingTime,
|
PairingTime = data.PairingTime,
|
||||||
StoreId = data.StoreDataId
|
StoreId = data.StoreDataId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> CreatePairingCodeAsync()
|
public async Task<string> CreatePairingCodeAsync()
|
||||||
{
|
{
|
||||||
string pairingCodeId = null;
|
string pairingCodeId = null;
|
||||||
while(true)
|
while (true)
|
||||||
{
|
{
|
||||||
pairingCodeId = Encoders.Base58.EncodeData(RandomUtils.GetBytes(6));
|
pairingCodeId = Encoders.Base58.EncodeData(RandomUtils.GetBytes(6));
|
||||||
if(pairingCodeId.Length == 7) // woocommerce plugin check for exactly 7 digits
|
if (pairingCodeId.Length == 7) // woocommerce plugin check for exactly 7 digits
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
using(var ctx = _Factory.CreateContext())
|
using (var ctx = _Factory.CreateContext())
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
var expiration = DateTime.UtcNow + TimeSpan.FromMinutes(15);
|
var expiration = DateTime.UtcNow + TimeSpan.FromMinutes(15);
|
||||||
await ctx.PairingCodes.AddAsync(new PairingCodeData()
|
await ctx.PairingCodes.AddAsync(new PairingCodeData()
|
||||||
{
|
{
|
||||||
Id = pairingCodeId,
|
Id = pairingCodeId,
|
||||||
DateCreated = now,
|
DateCreated = now,
|
||||||
Expiration = expiration,
|
Expiration = expiration,
|
||||||
TokenValue = Encoders.Base58.EncodeData(RandomUtils.GetBytes(32))
|
TokenValue = Encoders.Base58.EncodeData(RandomUtils.GetBytes(32))
|
||||||
});
|
});
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
return pairingCodeId;
|
return pairingCodeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PairingCodeEntity> UpdatePairingCode(PairingCodeEntity pairingCodeEntity)
|
public async Task<PairingCodeEntity> UpdatePairingCode(PairingCodeEntity pairingCodeEntity)
|
||||||
{
|
{
|
||||||
using(var ctx = _Factory.CreateContext())
|
using (var ctx = _Factory.CreateContext())
|
||||||
{
|
{
|
||||||
var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeEntity.Id);
|
var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeEntity.Id);
|
||||||
pairingCode.Label = pairingCodeEntity.Label;
|
pairingCode.Label = pairingCodeEntity.Label;
|
||||||
pairingCode.Facade = pairingCodeEntity.Facade;
|
pairingCode.Facade = pairingCodeEntity.Facade;
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
return CreatePairingCodeEntity(pairingCode);
|
return CreatePairingCodeEntity(pairingCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PairingResult> PairWithStoreAsync(string pairingCodeId, string storeId)
|
public async Task<PairingResult> PairWithStoreAsync(string pairingCodeId, string storeId)
|
||||||
{
|
{
|
||||||
using(var ctx = _Factory.CreateContext())
|
using (var ctx = _Factory.CreateContext())
|
||||||
{
|
{
|
||||||
var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId);
|
var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId);
|
||||||
if(pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow)
|
if (pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow)
|
||||||
return PairingResult.Expired;
|
return PairingResult.Expired;
|
||||||
pairingCode.StoreDataId = storeId;
|
pairingCode.StoreDataId = storeId;
|
||||||
var result = await ActivateIfComplete(ctx, pairingCode);
|
var result = await ActivateIfComplete(ctx, pairingCode);
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PairingResult> PairWithSINAsync(string pairingCodeId, string sin)
|
public async Task<PairingResult> PairWithSINAsync(string pairingCodeId, string sin)
|
||||||
{
|
{
|
||||||
using(var ctx = _Factory.CreateContext())
|
using (var ctx = _Factory.CreateContext())
|
||||||
{
|
{
|
||||||
var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId);
|
var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId);
|
||||||
if(pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow)
|
if (pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow)
|
||||||
return PairingResult.Expired;
|
return PairingResult.Expired;
|
||||||
pairingCode.SIN = sin;
|
pairingCode.SIN = sin;
|
||||||
var result = await ActivateIfComplete(ctx, pairingCode);
|
var result = await ActivateIfComplete(ctx, pairingCode);
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task<PairingResult> ActivateIfComplete(ApplicationDbContext ctx, PairingCodeData pairingCode)
|
private async Task<PairingResult> ActivateIfComplete(ApplicationDbContext ctx, PairingCodeData pairingCode)
|
||||||
{
|
{
|
||||||
if(!string.IsNullOrEmpty(pairingCode.SIN) && !string.IsNullOrEmpty(pairingCode.StoreDataId))
|
if (!string.IsNullOrEmpty(pairingCode.SIN) && !string.IsNullOrEmpty(pairingCode.StoreDataId))
|
||||||
{
|
{
|
||||||
ctx.PairingCodes.Remove(pairingCode);
|
ctx.PairingCodes.Remove(pairingCode);
|
||||||
|
|
||||||
// Can have concurrency issues... but no harm can be done
|
// 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();
|
var alreadyUsed = await ctx.PairedSINData.Where(p => p.SIN == pairingCode.SIN && p.StoreDataId != pairingCode.StoreDataId).AnyAsync();
|
||||||
if(alreadyUsed)
|
if (alreadyUsed)
|
||||||
return PairingResult.ReusedKey;
|
return PairingResult.ReusedKey;
|
||||||
await ctx.PairedSINData.AddAsync(new PairedSINData()
|
await ctx.PairedSINData.AddAsync(new PairedSINData()
|
||||||
{
|
{
|
||||||
Id = pairingCode.TokenValue,
|
Id = pairingCode.TokenValue,
|
||||||
PairingTime = DateTime.UtcNow,
|
PairingTime = DateTime.UtcNow,
|
||||||
Facade = pairingCode.Facade,
|
Facade = pairingCode.Facade,
|
||||||
Label = pairingCode.Label,
|
Label = pairingCode.Label,
|
||||||
StoreDataId = pairingCode.StoreDataId,
|
StoreDataId = pairingCode.StoreDataId,
|
||||||
SIN = pairingCode.SIN
|
SIN = pairingCode.SIN
|
||||||
});
|
});
|
||||||
return PairingResult.Complete;
|
return PairingResult.Complete;
|
||||||
}
|
}
|
||||||
return PairingResult.Partial;
|
return PairingResult.Partial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<BitTokenEntity[]> GetTokensByStoreIdAsync(string storeId)
|
public async Task<BitTokenEntity[]> GetTokensByStoreIdAsync(string storeId)
|
||||||
{
|
{
|
||||||
using(var ctx = _Factory.CreateContext())
|
using (var ctx = _Factory.CreateContext())
|
||||||
{
|
{
|
||||||
return (await ctx.PairedSINData.Where(p => p.StoreDataId == storeId).ToListAsync())
|
return (await ctx.PairedSINData.Where(p => p.StoreDataId == storeId).ToListAsync())
|
||||||
.Select(c => CreateTokenEntity(c))
|
.Select(c => CreateTokenEntity(c))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PairingCodeEntity> GetPairingAsync(string pairingCode)
|
public async Task<PairingCodeEntity> GetPairingAsync(string pairingCode)
|
||||||
{
|
{
|
||||||
using(var ctx = _Factory.CreateContext())
|
using (var ctx = _Factory.CreateContext())
|
||||||
{
|
{
|
||||||
return CreatePairingCodeEntity(await ctx.PairingCodes.FindAsync(pairingCode));
|
return CreatePairingCodeEntity(await ctx.PairingCodes.FindAsync(pairingCode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PairingCodeEntity CreatePairingCodeEntity(PairingCodeData data)
|
private PairingCodeEntity CreatePairingCodeEntity(PairingCodeData data)
|
||||||
{
|
{
|
||||||
if(data == null)
|
if (data == null)
|
||||||
return null;
|
return null;
|
||||||
return new PairingCodeEntity()
|
return new PairingCodeEntity()
|
||||||
{
|
{
|
||||||
Facade = data.Facade,
|
Facade = data.Facade,
|
||||||
Id = data.Id,
|
Id = data.Id,
|
||||||
Label = data.Label,
|
Label = data.Label,
|
||||||
Expiration = data.Expiration,
|
Expiration = data.Expiration,
|
||||||
CreatedTime = data.DateCreated,
|
CreatedTime = data.DateCreated,
|
||||||
TokenValue = data.TokenValue,
|
TokenValue = data.TokenValue,
|
||||||
SIN = data.SIN
|
SIN = data.SIN
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<bool> DeleteToken(string tokenId)
|
public async Task<bool> DeleteToken(string tokenId)
|
||||||
{
|
{
|
||||||
using(var ctx = _Factory.CreateContext())
|
using (var ctx = _Factory.CreateContext())
|
||||||
{
|
{
|
||||||
var token = await ctx.PairedSINData.FindAsync(tokenId);
|
var token = await ctx.PairedSINData.FindAsync(tokenId);
|
||||||
if(token == null)
|
if (token == null)
|
||||||
return false;
|
return false;
|
||||||
ctx.PairedSINData.Remove(token);
|
ctx.PairedSINData.Remove(token);
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<BitTokenEntity> GetToken(string tokenId)
|
public async Task<BitTokenEntity> GetToken(string tokenId)
|
||||||
{
|
{
|
||||||
using(var ctx = _Factory.CreateContext())
|
using (var ctx = _Factory.CreateContext())
|
||||||
{
|
{
|
||||||
var token = await ctx.PairedSINData.FindAsync(tokenId);
|
var token = await ctx.PairedSINData.FindAsync(tokenId);
|
||||||
return CreateTokenEntity(token);
|
return CreateTokenEntity(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ using System.Text;
|
|||||||
|
|
||||||
namespace BTCPayServer
|
namespace BTCPayServer
|
||||||
{
|
{
|
||||||
public class BitpayHttpException : Exception
|
public class BitpayHttpException : Exception
|
||||||
{
|
{
|
||||||
public BitpayHttpException(int code, string message) : base(message)
|
public BitpayHttpException(int code, string message) : base(message)
|
||||||
{
|
{
|
||||||
StatusCode = code;
|
StatusCode = code;
|
||||||
}
|
}
|
||||||
public int StatusCode
|
public int StatusCode
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,61 +12,61 @@ using Microsoft.Extensions.Configuration;
|
|||||||
|
|
||||||
namespace BTCPayServer.Configuration
|
namespace BTCPayServer.Configuration
|
||||||
{
|
{
|
||||||
public class BTCPayServerOptions
|
public class BTCPayServerOptions
|
||||||
{
|
{
|
||||||
public Network Network
|
public Network Network
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public Uri Explorer
|
public Uri Explorer
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string CookieFile
|
public string CookieFile
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string ConfigurationFile
|
public string ConfigurationFile
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
private set;
|
private set;
|
||||||
}
|
}
|
||||||
public string DataDir
|
public string DataDir
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
private set;
|
private set;
|
||||||
}
|
}
|
||||||
public List<IPEndPoint> Listen
|
public List<IPEndPoint> Listen
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadArgs(IConfiguration conf)
|
public void LoadArgs(IConfiguration conf)
|
||||||
{
|
{
|
||||||
var networkInfo = DefaultConfiguration.GetNetwork(conf);
|
var networkInfo = DefaultConfiguration.GetNetwork(conf);
|
||||||
Network = networkInfo?.Network;
|
Network = networkInfo?.Network;
|
||||||
if(Network == null)
|
if (Network == null)
|
||||||
throw new ConfigException("Invalid network");
|
throw new ConfigException("Invalid network");
|
||||||
|
|
||||||
DataDir = conf.GetOrDefault<string>("datadir", networkInfo.DefaultDataDirectory);
|
DataDir = conf.GetOrDefault<string>("datadir", networkInfo.DefaultDataDirectory);
|
||||||
Logs.Configuration.LogInformation("Network: " + Network);
|
Logs.Configuration.LogInformation("Network: " + Network);
|
||||||
|
|
||||||
Explorer = conf.GetOrDefault<Uri>("explorer.url", networkInfo.DefaultExplorerUrl);
|
Explorer = conf.GetOrDefault<Uri>("explorer.url", networkInfo.DefaultExplorerUrl);
|
||||||
CookieFile = conf.GetOrDefault<string>("explorer.cookiefile", networkInfo.DefaultExplorerCookieFile);
|
CookieFile = conf.GetOrDefault<string>("explorer.cookiefile", networkInfo.DefaultExplorerCookieFile);
|
||||||
RequireHttps = conf.GetOrDefault<bool>("requirehttps", false);
|
RequireHttps = conf.GetOrDefault<bool>("requirehttps", false);
|
||||||
PostgresConnectionString = conf.GetOrDefault<string>("postgres", null);
|
PostgresConnectionString = conf.GetOrDefault<string>("postgres", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool RequireHttps
|
public bool RequireHttps
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string PostgresConnectionString
|
public string PostgresConnectionString
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,96 +18,96 @@ using BTCPayServer.Services.Wallets;
|
|||||||
|
|
||||||
namespace BTCPayServer.Configuration
|
namespace BTCPayServer.Configuration
|
||||||
{
|
{
|
||||||
public class BTCPayServerRuntime : IDisposable
|
public class BTCPayServerRuntime : IDisposable
|
||||||
{
|
{
|
||||||
public ExplorerClient Explorer
|
public ExplorerClient Explorer
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
private set;
|
private set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(BTCPayServerOptions opts)
|
public void Configure(BTCPayServerOptions opts)
|
||||||
{
|
{
|
||||||
ConfigureAsync(opts).GetAwaiter().GetResult();
|
ConfigureAsync(opts).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
public async Task ConfigureAsync(BTCPayServerOptions opts)
|
public async Task ConfigureAsync(BTCPayServerOptions opts)
|
||||||
{
|
{
|
||||||
Network = opts.Network;
|
Network = opts.Network;
|
||||||
Explorer = new ExplorerClient(opts.Network, opts.Explorer);
|
Explorer = new ExplorerClient(opts.Network, opts.Explorer);
|
||||||
|
|
||||||
if(!Explorer.SetCookieAuth(opts.CookieFile))
|
if (!Explorer.SetCookieAuth(opts.CookieFile))
|
||||||
Explorer.SetNoAuth();
|
Explorer.SetNoAuth();
|
||||||
|
|
||||||
CancellationTokenSource cts = new CancellationTokenSource(30000);
|
CancellationTokenSource cts = new CancellationTokenSource(30000);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Logs.Configuration.LogInformation("Trying to connect to explorer " + Explorer.Address.AbsoluteUri);
|
Logs.Configuration.LogInformation("Trying to connect to explorer " + Explorer.Address.AbsoluteUri);
|
||||||
await Explorer.WaitServerStartedAsync(cts.Token).ConfigureAwait(false);
|
await Explorer.WaitServerStartedAsync(cts.Token).ConfigureAwait(false);
|
||||||
Logs.Configuration.LogInformation("Connection successfull");
|
Logs.Configuration.LogInformation("Connection successfull");
|
||||||
}
|
}
|
||||||
catch(Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
throw new ConfigException($"Could not connect to NBXplorer, {ex.Message}");
|
throw new ConfigException($"Could not connect to NBXplorer, {ex.Message}");
|
||||||
}
|
}
|
||||||
DBreezeEngine db = new DBreezeEngine(CreateDBPath(opts, "TokensDB"));
|
DBreezeEngine db = new DBreezeEngine(CreateDBPath(opts, "TokensDB"));
|
||||||
_Resources.Add(db);
|
_Resources.Add(db);
|
||||||
|
|
||||||
db = new DBreezeEngine(CreateDBPath(opts, "InvoiceDB"));
|
db = new DBreezeEngine(CreateDBPath(opts, "InvoiceDB"));
|
||||||
_Resources.Add(db);
|
_Resources.Add(db);
|
||||||
|
|
||||||
ApplicationDbContextFactory dbContext = null;
|
ApplicationDbContextFactory dbContext = null;
|
||||||
if(opts.PostgresConnectionString == null)
|
if (opts.PostgresConnectionString == null)
|
||||||
{
|
{
|
||||||
var connStr = "Data Source=" + Path.Combine(opts.DataDir, "sqllite.db");
|
var connStr = "Data Source=" + Path.Combine(opts.DataDir, "sqllite.db");
|
||||||
Logs.Configuration.LogInformation($"SQLite DB used ({connStr})");
|
Logs.Configuration.LogInformation($"SQLite DB used ({connStr})");
|
||||||
dbContext = new ApplicationDbContextFactory(DatabaseType.Sqlite, connStr);
|
dbContext = new ApplicationDbContextFactory(DatabaseType.Sqlite, connStr);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logs.Configuration.LogInformation($"Postgres DB used ({opts.PostgresConnectionString})");
|
Logs.Configuration.LogInformation($"Postgres DB used ({opts.PostgresConnectionString})");
|
||||||
dbContext = new ApplicationDbContextFactory(DatabaseType.Postgres, opts.PostgresConnectionString);
|
dbContext = new ApplicationDbContextFactory(DatabaseType.Postgres, opts.PostgresConnectionString);
|
||||||
}
|
}
|
||||||
DBFactory = dbContext;
|
DBFactory = dbContext;
|
||||||
InvoiceRepository = new InvoiceRepository(dbContext, db, Network);
|
InvoiceRepository = new InvoiceRepository(dbContext, db, Network);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string CreateDBPath(BTCPayServerOptions opts, string name)
|
private static string CreateDBPath(BTCPayServerOptions opts, string name)
|
||||||
{
|
{
|
||||||
var dbpath = Path.Combine(opts.DataDir, name);
|
var dbpath = Path.Combine(opts.DataDir, name);
|
||||||
if(!Directory.Exists(dbpath))
|
if (!Directory.Exists(dbpath))
|
||||||
Directory.CreateDirectory(dbpath);
|
Directory.CreateDirectory(dbpath);
|
||||||
return dbpath;
|
return dbpath;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<IDisposable> _Resources = new List<IDisposable>();
|
List<IDisposable> _Resources = new List<IDisposable>();
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
lock(_Resources)
|
lock (_Resources)
|
||||||
{
|
{
|
||||||
foreach(var r in _Resources)
|
foreach (var r in _Resources)
|
||||||
{
|
{
|
||||||
r.Dispose();
|
r.Dispose();
|
||||||
}
|
}
|
||||||
_Resources.Clear();
|
_Resources.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Network Network
|
public Network Network
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
private set;
|
private set;
|
||||||
}
|
}
|
||||||
public InvoiceRepository InvoiceRepository
|
public InvoiceRepository InvoiceRepository
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public ApplicationDbContextFactory DBFactory
|
public ApplicationDbContextFactory DBFactory
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Configuration
|
namespace BTCPayServer.Configuration
|
||||||
{
|
{
|
||||||
public class ConfigException : Exception
|
public class ConfigException : Exception
|
||||||
{
|
{
|
||||||
public ConfigException(string message) : base(message)
|
public ConfigException(string message) : base(message)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,44 +9,44 @@ using Microsoft.Extensions.Primitives;
|
|||||||
|
|
||||||
namespace BTCPayServer.Configuration
|
namespace BTCPayServer.Configuration
|
||||||
{
|
{
|
||||||
public static class ConfigurationExtensions
|
public static class ConfigurationExtensions
|
||||||
{
|
{
|
||||||
public static T GetOrDefault<T>(this IConfiguration configuration, string key, T defaultValue)
|
public static T GetOrDefault<T>(this IConfiguration configuration, string key, T defaultValue)
|
||||||
{
|
{
|
||||||
var str = configuration[key] ?? configuration[key.Replace(".", string.Empty)];
|
var str = configuration[key] ?? configuration[key.Replace(".", string.Empty)];
|
||||||
if(str == null)
|
if (str == null)
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
if(typeof(T) == typeof(bool))
|
if (typeof(T) == typeof(bool))
|
||||||
{
|
{
|
||||||
var trueValues = new[] { "1", "true" };
|
var trueValues = new[] { "1", "true" };
|
||||||
var falseValues = new[] { "0", "false" };
|
var falseValues = new[] { "0", "false" };
|
||||||
if(trueValues.Contains(str, StringComparer.OrdinalIgnoreCase))
|
if (trueValues.Contains(str, StringComparer.OrdinalIgnoreCase))
|
||||||
return (T)(object)true;
|
return (T)(object)true;
|
||||||
if(falseValues.Contains(str, StringComparer.OrdinalIgnoreCase))
|
if (falseValues.Contains(str, StringComparer.OrdinalIgnoreCase))
|
||||||
return (T)(object)false;
|
return (T)(object)false;
|
||||||
throw new FormatException();
|
throw new FormatException();
|
||||||
}
|
}
|
||||||
else if(typeof(T) == typeof(Uri))
|
else if (typeof(T) == typeof(Uri))
|
||||||
return (T)(object)new Uri(str, UriKind.Absolute);
|
return (T)(object)new Uri(str, UriKind.Absolute);
|
||||||
else if(typeof(T) == typeof(string))
|
else if (typeof(T) == typeof(string))
|
||||||
return (T)(object)str;
|
return (T)(object)str;
|
||||||
else if(typeof(T) == typeof(IPEndPoint))
|
else if (typeof(T) == typeof(IPEndPoint))
|
||||||
{
|
{
|
||||||
var separator = str.LastIndexOf(":");
|
var separator = str.LastIndexOf(":");
|
||||||
if(separator == -1)
|
if (separator == -1)
|
||||||
throw new FormatException();
|
throw new FormatException();
|
||||||
var ip = str.Substring(0, separator);
|
var ip = str.Substring(0, separator);
|
||||||
var port = str.Substring(separator + 1);
|
var port = str.Substring(separator + 1);
|
||||||
return (T)(object)new IPEndPoint(IPAddress.Parse(ip), int.Parse(port));
|
return (T)(object)new IPEndPoint(IPAddress.Parse(ip), int.Parse(port));
|
||||||
}
|
}
|
||||||
else if(typeof(T) == typeof(int))
|
else if (typeof(T) == typeof(int))
|
||||||
{
|
{
|
||||||
return (T)(object)int.Parse(str, CultureInfo.InvariantCulture);
|
return (T)(object)int.Parse(str, CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Configuration value does not support time " + typeof(T).Name);
|
throw new NotSupportedException("Configuration value does not support time " + typeof(T).Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,88 +13,88 @@ using CommandLine;
|
|||||||
|
|
||||||
namespace BTCPayServer.Configuration
|
namespace BTCPayServer.Configuration
|
||||||
{
|
{
|
||||||
public class DefaultConfiguration : StandardConfiguration.DefaultConfiguration
|
public class DefaultConfiguration : StandardConfiguration.DefaultConfiguration
|
||||||
{
|
{
|
||||||
protected override CommandLineApplication CreateCommandLineApplicationCore()
|
protected override CommandLineApplication CreateCommandLineApplicationCore()
|
||||||
{
|
{
|
||||||
CommandLineApplication app = new CommandLineApplication(true)
|
CommandLineApplication app = new CommandLineApplication(true)
|
||||||
{
|
{
|
||||||
FullName = "NBXplorer\r\nLightweight block explorer for tracking HD wallets",
|
FullName = "NBXplorer\r\nLightweight block explorer for tracking HD wallets",
|
||||||
Name = "NBXplorer"
|
Name = "NBXplorer"
|
||||||
};
|
};
|
||||||
app.HelpOption("-? | -h | --help");
|
app.HelpOption("-? | -h | --help");
|
||||||
app.Option("-n | --network", $"Set the network among ({NetworkInformation.ToStringAll()}) (default: {Network.Main.ToString()})", CommandOptionType.SingleValue);
|
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("--testnet | -testnet", $"Use testnet", CommandOptionType.BoolValue);
|
||||||
app.Option("--regtest | -regtest", $"Use regtest", 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("--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("--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("--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("--explorercookiefile", $"Path to the cookie file (default: Default setting of NBXplorer for the network)", CommandOptionType.SingleValue);
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string EnvironmentVariablePrefix => "BTCPAY_";
|
public override string EnvironmentVariablePrefix => "BTCPAY_";
|
||||||
|
|
||||||
protected override string GetDefaultDataDir(IConfiguration conf)
|
protected override string GetDefaultDataDir(IConfiguration conf)
|
||||||
{
|
{
|
||||||
return GetNetwork(conf).DefaultDataDirectory;
|
return GetNetwork(conf).DefaultDataDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string GetDefaultConfigurationFile(IConfiguration conf)
|
protected override string GetDefaultConfigurationFile(IConfiguration conf)
|
||||||
{
|
{
|
||||||
var network = GetNetwork(conf);
|
var network = GetNetwork(conf);
|
||||||
var dataDir = conf["datadir"];
|
var dataDir = conf["datadir"];
|
||||||
if(dataDir == null)
|
if (dataDir == null)
|
||||||
return network.DefaultConfigurationFile;
|
return network.DefaultConfigurationFile;
|
||||||
var fileName = Path.GetFileName(network.DefaultConfigurationFile);
|
var fileName = Path.GetFileName(network.DefaultConfigurationFile);
|
||||||
return Path.Combine(dataDir, fileName);
|
return Path.Combine(dataDir, fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NetworkInformation GetNetwork(IConfiguration conf)
|
public static NetworkInformation GetNetwork(IConfiguration conf)
|
||||||
{
|
{
|
||||||
var network = conf.GetOrDefault<string>("network", null);
|
var network = conf.GetOrDefault<string>("network", null);
|
||||||
if(network != null)
|
if (network != null)
|
||||||
{
|
{
|
||||||
var info = NetworkInformation.GetNetworkByName(network);
|
var info = NetworkInformation.GetNetworkByName(network);
|
||||||
if(info == null)
|
if (info == null)
|
||||||
throw new ConfigException($"Invalid network name {network}");
|
throw new ConfigException($"Invalid network name {network}");
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
var net = conf.GetOrDefault<bool>("regtest", false) ? Network.RegTest :
|
var net = conf.GetOrDefault<bool>("regtest", false) ? Network.RegTest :
|
||||||
conf.GetOrDefault<bool>("testnet", false) ? Network.TestNet : Network.Main;
|
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)
|
protected override string GetDefaultConfigurationFileTemplate(IConfiguration conf)
|
||||||
{
|
{
|
||||||
var network = GetNetwork(conf);
|
var network = GetNetwork(conf);
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
builder.AppendLine("### Global settings ###");
|
builder.AppendLine("### Global settings ###");
|
||||||
builder.AppendLine("#testnet=0");
|
builder.AppendLine("#testnet=0");
|
||||||
builder.AppendLine("#regtest=0");
|
builder.AppendLine("#regtest=0");
|
||||||
builder.AppendLine();
|
builder.AppendLine();
|
||||||
builder.AppendLine("### Server settings ###");
|
builder.AppendLine("### Server settings ###");
|
||||||
builder.AppendLine("#requirehttps=0");
|
builder.AppendLine("#requirehttps=0");
|
||||||
builder.AppendLine("#port=" + network.DefaultPort);
|
builder.AppendLine("#port=" + network.DefaultPort);
|
||||||
builder.AppendLine("#bind=127.0.0.1");
|
builder.AppendLine("#bind=127.0.0.1");
|
||||||
builder.AppendLine();
|
builder.AppendLine();
|
||||||
builder.AppendLine("### Database ###");
|
builder.AppendLine("### Database ###");
|
||||||
builder.AppendLine("#postgres=User ID=root;Password=myPassword;Host=localhost;Port=5432;Database=myDataBase;");
|
builder.AppendLine("#postgres=User ID=root;Password=myPassword;Host=localhost;Port=5432;Database=myDataBase;");
|
||||||
builder.AppendLine();
|
builder.AppendLine();
|
||||||
builder.AppendLine("### NBXplorer settings ###");
|
builder.AppendLine("### NBXplorer settings ###");
|
||||||
builder.AppendLine("#explorer.url=" + network.DefaultExplorerUrl.AbsoluteUri);
|
builder.AppendLine("#explorer.url=" + network.DefaultExplorerUrl.AbsoluteUri);
|
||||||
builder.AppendLine("#explorer.cookiefile=" + network.DefaultExplorerCookieFile);
|
builder.AppendLine("#explorer.cookiefile=" + network.DefaultExplorerCookieFile);
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected override IPEndPoint GetDefaultEndpoint(IConfiguration conf)
|
protected override IPEndPoint GetDefaultEndpoint(IConfiguration conf)
|
||||||
{
|
{
|
||||||
return new IPEndPoint(IPAddress.Parse("127.0.0.1"), GetNetwork(conf).DefaultPort);
|
return new IPEndPoint(IPAddress.Parse("127.0.0.1"), GetNetwork(conf).DefaultPort);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,96 +8,96 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Configuration
|
namespace BTCPayServer.Configuration
|
||||||
{
|
{
|
||||||
public class NetworkInformation
|
public class NetworkInformation
|
||||||
{
|
{
|
||||||
static NetworkInformation()
|
static NetworkInformation()
|
||||||
{
|
{
|
||||||
_Networks = new Dictionary<string, NetworkInformation>();
|
_Networks = new Dictionary<string, NetworkInformation>();
|
||||||
foreach(var network in Network.GetNetworks())
|
foreach (var network in Network.GetNetworks())
|
||||||
{
|
{
|
||||||
NetworkInformation info = new NetworkInformation();
|
NetworkInformation info = new NetworkInformation();
|
||||||
info.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", network.Name);
|
info.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", network.Name);
|
||||||
info.DefaultConfigurationFile = Path.Combine(info.DefaultDataDirectory, "settings.config");
|
info.DefaultConfigurationFile = Path.Combine(info.DefaultDataDirectory, "settings.config");
|
||||||
info.DefaultExplorerCookieFile = Path.Combine(StandardConfiguration.DefaultDataDirectory.GetDirectory("NBXplorer", network.Name, false), ".cookie");
|
info.DefaultExplorerCookieFile = Path.Combine(StandardConfiguration.DefaultDataDirectory.GetDirectory("NBXplorer", network.Name, false), ".cookie");
|
||||||
info.Network = network;
|
info.Network = network;
|
||||||
info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24446", UriKind.Absolute);
|
info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24446", UriKind.Absolute);
|
||||||
info.DefaultPort = 23002;
|
info.DefaultPort = 23002;
|
||||||
_Networks.Add(network.Name, info);
|
_Networks.Add(network.Name, info);
|
||||||
if(network == Network.Main)
|
if (network == Network.Main)
|
||||||
{
|
{
|
||||||
info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24444", UriKind.Absolute);
|
info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24444", UriKind.Absolute);
|
||||||
Main = info;
|
Main = info;
|
||||||
info.DefaultPort = 23000;
|
info.DefaultPort = 23000;
|
||||||
}
|
}
|
||||||
if(network == Network.TestNet)
|
if (network == Network.TestNet)
|
||||||
{
|
{
|
||||||
info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24445", UriKind.Absolute);
|
info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24445", UriKind.Absolute);
|
||||||
info.DefaultPort = 23001;
|
info.DefaultPort = 23001;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Dictionary<string, NetworkInformation> _Networks;
|
static Dictionary<string, NetworkInformation> _Networks;
|
||||||
public static NetworkInformation GetNetworkByName(string name)
|
public static NetworkInformation GetNetworkByName(string name)
|
||||||
{
|
{
|
||||||
var value = _Networks.TryGet(name);
|
var value = _Networks.TryGet(name);
|
||||||
if(value != null)
|
if (value != null)
|
||||||
return value;
|
return value;
|
||||||
|
|
||||||
//Maybe alias ?
|
//Maybe alias ?
|
||||||
var network = Network.GetNetwork(name);
|
var network = Network.GetNetwork(name);
|
||||||
if(network != null)
|
if (network != null)
|
||||||
{
|
{
|
||||||
value = _Networks.TryGet(network.Name);
|
value = _Networks.TryGet(network.Name);
|
||||||
if(value != null)
|
if (value != null)
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NetworkInformation Main
|
public static NetworkInformation Main
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public Network Network
|
public Network Network
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string DefaultConfigurationFile
|
public string DefaultConfigurationFile
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string DefaultDataDirectory
|
public string DefaultDataDirectory
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public Uri DefaultExplorerUrl
|
public Uri DefaultExplorerUrl
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
internal set;
|
internal set;
|
||||||
}
|
}
|
||||||
public int DefaultPort
|
public int DefaultPort
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
private set;
|
private set;
|
||||||
}
|
}
|
||||||
public string DefaultExplorerCookieFile
|
public string DefaultExplorerCookieFile
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
internal set;
|
internal set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return Network.ToString();
|
return Network.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ToStringAll()
|
public static string ToStringAll()
|
||||||
{
|
{
|
||||||
return string.Join(", ", _Networks.Select(n => n.Key).ToArray());
|
return string.Join(", ", _Networks.Select(n => n.Key).ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,79 +12,79 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
public class AccessTokenController : Controller
|
public class AccessTokenController : Controller
|
||||||
{
|
{
|
||||||
TokenRepository _TokenRepository;
|
TokenRepository _TokenRepository;
|
||||||
public AccessTokenController(TokenRepository tokenRepository)
|
public AccessTokenController(TokenRepository tokenRepository)
|
||||||
{
|
{
|
||||||
_TokenRepository = tokenRepository ?? throw new ArgumentNullException(nameof(tokenRepository));
|
_TokenRepository = tokenRepository ?? throw new ArgumentNullException(nameof(tokenRepository));
|
||||||
}
|
}
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("tokens")]
|
[Route("tokens")]
|
||||||
public async Task<GetTokensResponse> Tokens()
|
public async Task<GetTokensResponse> Tokens()
|
||||||
{
|
{
|
||||||
var tokens = await _TokenRepository.GetTokens(this.GetBitIdentity().SIN);
|
var tokens = await _TokenRepository.GetTokens(this.GetBitIdentity().SIN);
|
||||||
return new GetTokensResponse(tokens);
|
return new GetTokensResponse(tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("tokens")]
|
[Route("tokens")]
|
||||||
public async Task<DataWrapper<List<PairingCodeResponse>>> Tokens([FromBody] TokenRequest request)
|
public async Task<DataWrapper<List<PairingCodeResponse>>> Tokens([FromBody] TokenRequest request)
|
||||||
{
|
{
|
||||||
PairingCodeEntity pairingEntity = null;
|
PairingCodeEntity pairingEntity = null;
|
||||||
if(string.IsNullOrEmpty(request.PairingCode))
|
if (string.IsNullOrEmpty(request.PairingCode))
|
||||||
{
|
{
|
||||||
if(string.IsNullOrEmpty(request.Id) || !NBitpayClient.Extensions.BitIdExtensions.ValidateSIN(request.Id))
|
if (string.IsNullOrEmpty(request.Id) || !NBitpayClient.Extensions.BitIdExtensions.ValidateSIN(request.Id))
|
||||||
throw new BitpayHttpException(400, "'id' property is required");
|
throw new BitpayHttpException(400, "'id' property is required");
|
||||||
if(string.IsNullOrEmpty(request.Facade))
|
if (string.IsNullOrEmpty(request.Facade))
|
||||||
throw new BitpayHttpException(400, "'facade' property is required");
|
throw new BitpayHttpException(400, "'facade' property is required");
|
||||||
|
|
||||||
var pairingCode = await _TokenRepository.CreatePairingCodeAsync();
|
var pairingCode = await _TokenRepository.CreatePairingCodeAsync();
|
||||||
await _TokenRepository.PairWithSINAsync(pairingCode, request.Id);
|
await _TokenRepository.PairWithSINAsync(pairingCode, request.Id);
|
||||||
pairingEntity = await _TokenRepository.UpdatePairingCode(new PairingCodeEntity()
|
pairingEntity = await _TokenRepository.UpdatePairingCode(new PairingCodeEntity()
|
||||||
{
|
{
|
||||||
Id = pairingCode,
|
Id = pairingCode,
|
||||||
Facade = request.Facade,
|
Facade = request.Facade,
|
||||||
Label = request.Label
|
Label = request.Label
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var sin = this.GetBitIdentity(false)?.SIN ?? request.Id;
|
var sin = this.GetBitIdentity(false)?.SIN ?? request.Id;
|
||||||
if(string.IsNullOrEmpty(request.Id) || !NBitpayClient.Extensions.BitIdExtensions.ValidateSIN(request.Id))
|
if (string.IsNullOrEmpty(request.Id) || !NBitpayClient.Extensions.BitIdExtensions.ValidateSIN(request.Id))
|
||||||
throw new BitpayHttpException(400, "'id' property is required, alternatively, use BitId");
|
throw new BitpayHttpException(400, "'id' property is required, alternatively, use BitId");
|
||||||
|
|
||||||
pairingEntity = await _TokenRepository.GetPairingAsync(request.PairingCode);
|
pairingEntity = await _TokenRepository.GetPairingAsync(request.PairingCode);
|
||||||
if(pairingEntity == null)
|
if (pairingEntity == null)
|
||||||
throw new BitpayHttpException(404, "The specified pairingCode is not found");
|
throw new BitpayHttpException(404, "The specified pairingCode is not found");
|
||||||
pairingEntity.SIN = sin;
|
pairingEntity.SIN = sin;
|
||||||
|
|
||||||
if(string.IsNullOrEmpty(pairingEntity.Label) && !string.IsNullOrEmpty(request.Label))
|
if (string.IsNullOrEmpty(pairingEntity.Label) && !string.IsNullOrEmpty(request.Label))
|
||||||
{
|
{
|
||||||
pairingEntity.Label = request.Label;
|
pairingEntity.Label = request.Label;
|
||||||
await _TokenRepository.UpdatePairingCode(pairingEntity);
|
await _TokenRepository.UpdatePairingCode(pairingEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await _TokenRepository.PairWithSINAsync(request.PairingCode, sin);
|
var result = await _TokenRepository.PairWithSINAsync(request.PairingCode, sin);
|
||||||
if(result != PairingResult.Complete && result != PairingResult.Partial)
|
if (result != PairingResult.Complete && result != PairingResult.Partial)
|
||||||
throw new BitpayHttpException(400, $"Error while pairing ({result})");
|
throw new BitpayHttpException(400, $"Error while pairing ({result})");
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var pairingCodes = new List<PairingCodeResponse>
|
}
|
||||||
{
|
|
||||||
new PairingCodeResponse()
|
var pairingCodes = new List<PairingCodeResponse>
|
||||||
{
|
{
|
||||||
PairingCode = pairingEntity.Id,
|
new PairingCodeResponse()
|
||||||
PairingExpiration = pairingEntity.Expiration,
|
{
|
||||||
DateCreated = pairingEntity.CreatedTime,
|
PairingCode = pairingEntity.Id,
|
||||||
Facade = pairingEntity.Facade,
|
PairingExpiration = pairingEntity.Expiration,
|
||||||
Token = pairingEntity.TokenValue,
|
DateCreated = pairingEntity.CreatedTime,
|
||||||
Label = pairingEntity.Label
|
Facade = pairingEntity.Facade,
|
||||||
}
|
Token = pairingEntity.TokenValue,
|
||||||
};
|
Label = pairingEntity.Label
|
||||||
return DataWrapper.Create(pairingCodes);
|
}
|
||||||
}
|
};
|
||||||
}
|
return DataWrapper.Create(pairingCodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,503 +18,503 @@ using BTCPayServer.Services.Stores;
|
|||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route("[controller]/[action]")]
|
[Route("[controller]/[action]")]
|
||||||
public class AccountController : Controller
|
public class AccountController : Controller
|
||||||
{
|
{
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||||
private readonly IEmailSender _emailSender;
|
private readonly IEmailSender _emailSender;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
StoreRepository storeRepository;
|
StoreRepository storeRepository;
|
||||||
RoleManager<IdentityRole> _RoleManager;
|
RoleManager<IdentityRole> _RoleManager;
|
||||||
SettingsRepository _SettingsRepository;
|
SettingsRepository _SettingsRepository;
|
||||||
|
|
||||||
public AccountController(
|
public AccountController(
|
||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
RoleManager<IdentityRole> roleManager,
|
RoleManager<IdentityRole> roleManager,
|
||||||
StoreRepository storeRepository,
|
StoreRepository storeRepository,
|
||||||
SignInManager<ApplicationUser> signInManager,
|
SignInManager<ApplicationUser> signInManager,
|
||||||
IEmailSender emailSender,
|
IEmailSender emailSender,
|
||||||
SettingsRepository settingsRepository,
|
SettingsRepository settingsRepository,
|
||||||
ILogger<AccountController> logger)
|
ILogger<AccountController> logger)
|
||||||
{
|
{
|
||||||
this.storeRepository = storeRepository;
|
this.storeRepository = storeRepository;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_signInManager = signInManager;
|
_signInManager = signInManager;
|
||||||
_emailSender = emailSender;
|
_emailSender = emailSender;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_RoleManager = roleManager;
|
_RoleManager = roleManager;
|
||||||
_SettingsRepository = settingsRepository;
|
_SettingsRepository = settingsRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
[TempData]
|
[TempData]
|
||||||
public string ErrorMessage
|
public string ErrorMessage
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public async Task<IActionResult> Login(string returnUrl = null)
|
public async Task<IActionResult> Login(string returnUrl = null)
|
||||||
{
|
{
|
||||||
// Clear the existing external cookie to ensure a clean login process
|
// Clear the existing external cookie to ensure a clean login process
|
||||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
||||||
|
|
||||||
ViewData["ReturnUrl"] = returnUrl;
|
ViewData["ReturnUrl"] = returnUrl;
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
|
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
|
||||||
{
|
{
|
||||||
ViewData["ReturnUrl"] = returnUrl;
|
ViewData["ReturnUrl"] = returnUrl;
|
||||||
if(ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
// Require the user to have a confirmed email before they can log on.
|
// Require the user to have a confirmed email before they can log on.
|
||||||
var user = await _userManager.FindByEmailAsync(model.Email);
|
var user = await _userManager.FindByEmailAsync(model.Email);
|
||||||
if(user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
if(user.RequiresEmailConfirmation && !await _userManager.IsEmailConfirmedAsync(user))
|
if (user.RequiresEmailConfirmation && !await _userManager.IsEmailConfirmedAsync(user))
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(string.Empty,
|
ModelState.AddModelError(string.Empty,
|
||||||
"You must have a confirmed email to log in.");
|
"You must have a confirmed email to log in.");
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// This doesn't count login failures towards account lockout
|
// This doesn't count login failures towards account lockout
|
||||||
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
|
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
|
||||||
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
|
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
|
||||||
if(result.Succeeded)
|
if (result.Succeeded)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("User logged in.");
|
_logger.LogInformation("User logged in.");
|
||||||
return RedirectToLocal(returnUrl);
|
return RedirectToLocal(returnUrl);
|
||||||
}
|
}
|
||||||
if(result.RequiresTwoFactor)
|
if (result.RequiresTwoFactor)
|
||||||
{
|
{
|
||||||
return RedirectToAction(nameof(LoginWith2fa), new
|
return RedirectToAction(nameof(LoginWith2fa), new
|
||||||
{
|
{
|
||||||
returnUrl,
|
returnUrl,
|
||||||
model.RememberMe
|
model.RememberMe
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if(result.IsLockedOut)
|
if (result.IsLockedOut)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("User account locked out.");
|
_logger.LogWarning("User account locked out.");
|
||||||
return RedirectToAction(nameof(Lockout));
|
return RedirectToAction(nameof(Lockout));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
|
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we got this far, something failed, redisplay form
|
// If we got this far, something failed, redisplay form
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public async Task<IActionResult> LoginWith2fa(bool rememberMe, string returnUrl = null)
|
public async Task<IActionResult> LoginWith2fa(bool rememberMe, string returnUrl = null)
|
||||||
{
|
{
|
||||||
// Ensure the user has gone through the username & password screen first
|
// Ensure the user has gone through the username & password screen first
|
||||||
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
||||||
|
|
||||||
if(user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
throw new ApplicationException($"Unable to load two-factor authentication user.");
|
throw new ApplicationException($"Unable to load two-factor authentication user.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var model = new LoginWith2faViewModel { RememberMe = rememberMe };
|
var model = new LoginWith2faViewModel { RememberMe = rememberMe };
|
||||||
ViewData["ReturnUrl"] = returnUrl;
|
ViewData["ReturnUrl"] = returnUrl;
|
||||||
|
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public async Task<IActionResult> LoginWith2fa(LoginWith2faViewModel model, bool rememberMe, string returnUrl = null)
|
public async Task<IActionResult> LoginWith2fa(LoginWith2faViewModel model, bool rememberMe, string returnUrl = null)
|
||||||
{
|
{
|
||||||
if(!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
{
|
{
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
||||||
if(user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
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)
|
if (result.Succeeded)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("User with ID {UserId} logged in with 2fa.", user.Id);
|
_logger.LogInformation("User with ID {UserId} logged in with 2fa.", user.Id);
|
||||||
return RedirectToLocal(returnUrl);
|
return RedirectToLocal(returnUrl);
|
||||||
}
|
}
|
||||||
else if(result.IsLockedOut)
|
else if (result.IsLockedOut)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("User with ID {UserId} account locked out.", user.Id);
|
_logger.LogWarning("User with ID {UserId} account locked out.", user.Id);
|
||||||
return RedirectToAction(nameof(Lockout));
|
return RedirectToAction(nameof(Lockout));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Invalid authenticator code entered for user with ID {UserId}.", user.Id);
|
_logger.LogWarning("Invalid authenticator code entered for user with ID {UserId}.", user.Id);
|
||||||
ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
|
ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public async Task<IActionResult> LoginWithRecoveryCode(string returnUrl = null)
|
public async Task<IActionResult> LoginWithRecoveryCode(string returnUrl = null)
|
||||||
{
|
{
|
||||||
// Ensure the user has gone through the username & password screen first
|
// Ensure the user has gone through the username & password screen first
|
||||||
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
||||||
if(user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
throw new ApplicationException($"Unable to load two-factor authentication user.");
|
throw new ApplicationException($"Unable to load two-factor authentication user.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewData["ReturnUrl"] = returnUrl;
|
ViewData["ReturnUrl"] = returnUrl;
|
||||||
|
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public async Task<IActionResult> LoginWithRecoveryCode(LoginWithRecoveryCodeViewModel model, string returnUrl = null)
|
public async Task<IActionResult> LoginWithRecoveryCode(LoginWithRecoveryCodeViewModel model, string returnUrl = null)
|
||||||
{
|
{
|
||||||
if(!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
{
|
{
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
||||||
if(user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
throw new ApplicationException($"Unable to load two-factor authentication user.");
|
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)
|
if (result.Succeeded)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("User with ID {UserId} logged in with a recovery code.", user.Id);
|
_logger.LogInformation("User with ID {UserId} logged in with a recovery code.", user.Id);
|
||||||
return RedirectToLocal(returnUrl);
|
return RedirectToLocal(returnUrl);
|
||||||
}
|
}
|
||||||
if(result.IsLockedOut)
|
if (result.IsLockedOut)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("User with ID {UserId} account locked out.", user.Id);
|
_logger.LogWarning("User with ID {UserId} account locked out.", user.Id);
|
||||||
return RedirectToAction(nameof(Lockout));
|
return RedirectToAction(nameof(Lockout));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Invalid recovery code entered for user with ID {UserId}", user.Id);
|
_logger.LogWarning("Invalid recovery code entered for user with ID {UserId}", user.Id);
|
||||||
ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
|
ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public IActionResult Lockout()
|
public IActionResult Lockout()
|
||||||
{
|
{
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public IActionResult Register(string returnUrl = null)
|
public IActionResult Register(string returnUrl = null)
|
||||||
{
|
{
|
||||||
ViewData["ReturnUrl"] = returnUrl;
|
ViewData["ReturnUrl"] = returnUrl;
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
|
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
|
||||||
{
|
{
|
||||||
ViewData["ReturnUrl"] = returnUrl;
|
ViewData["ReturnUrl"] = returnUrl;
|
||||||
if(ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
var policies = await _SettingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
|
var policies = await _SettingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
|
||||||
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, RequiresEmailConfirmation = policies.RequiresConfirmedEmail };
|
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, RequiresEmailConfirmation = policies.RequiresConfirmedEmail };
|
||||||
var result = await _userManager.CreateAsync(user, model.Password);
|
var result = await _userManager.CreateAsync(user, model.Password);
|
||||||
if(result.Succeeded)
|
if (result.Succeeded)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("User created a new account with password.");
|
_logger.LogInformation("User created a new account with password.");
|
||||||
|
|
||||||
var admin = await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin);
|
var admin = await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin);
|
||||||
if(admin.Count == 0)
|
if (admin.Count == 0)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Admin created.");
|
_logger.LogInformation("Admin created.");
|
||||||
await _RoleManager.CreateAsync(new IdentityRole(Roles.ServerAdmin));
|
await _RoleManager.CreateAsync(new IdentityRole(Roles.ServerAdmin));
|
||||||
await _userManager.AddToRoleAsync(user, Roles.ServerAdmin);
|
await _userManager.AddToRoleAsync(user, Roles.ServerAdmin);
|
||||||
}
|
}
|
||||||
|
|
||||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
||||||
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
|
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
|
||||||
RegisteredUserId = user.Id;
|
RegisteredUserId = user.Id;
|
||||||
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
|
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
|
||||||
_logger.LogInformation("User created a new account with password.");
|
_logger.LogInformation("User created a new account with password.");
|
||||||
if(!policies.RequiresConfirmedEmail)
|
if (!policies.RequiresConfirmedEmail)
|
||||||
{
|
{
|
||||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||||
return RedirectToLocal(returnUrl);
|
return RedirectToLocal(returnUrl);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TempData["StatusMessage"] = "Account created, please confirm your email";
|
TempData["StatusMessage"] = "Account created, please confirm your email";
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AddErrors(result);
|
AddErrors(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we got this far, something failed, redisplay form
|
// If we got this far, something failed, redisplay form
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test property
|
/// Test property
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string RegisteredUserId
|
public string RegisteredUserId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> Logout()
|
public async Task<IActionResult> Logout()
|
||||||
{
|
{
|
||||||
await _signInManager.SignOutAsync();
|
await _signInManager.SignOutAsync();
|
||||||
_logger.LogInformation("User logged out.");
|
_logger.LogInformation("User logged out.");
|
||||||
return RedirectToAction(nameof(HomeController.Index), "Home");
|
return RedirectToAction(nameof(HomeController.Index), "Home");
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public IActionResult ExternalLogin(string provider, string returnUrl = null)
|
public IActionResult ExternalLogin(string provider, string returnUrl = null)
|
||||||
{
|
{
|
||||||
// Request a redirect to the external login provider.
|
// Request a redirect to the external login provider.
|
||||||
var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Account", new
|
var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Account", new
|
||||||
{
|
{
|
||||||
returnUrl
|
returnUrl
|
||||||
});
|
});
|
||||||
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
|
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
|
||||||
return Challenge(properties, provider);
|
return Challenge(properties, provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
|
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
|
||||||
{
|
{
|
||||||
if(remoteError != null)
|
if (remoteError != null)
|
||||||
{
|
{
|
||||||
ErrorMessage = $"Error from external provider: {remoteError}";
|
ErrorMessage = $"Error from external provider: {remoteError}";
|
||||||
return RedirectToAction(nameof(Login));
|
return RedirectToAction(nameof(Login));
|
||||||
}
|
}
|
||||||
var info = await _signInManager.GetExternalLoginInfoAsync();
|
var info = await _signInManager.GetExternalLoginInfoAsync();
|
||||||
if(info == null)
|
if (info == null)
|
||||||
{
|
{
|
||||||
return RedirectToAction(nameof(Login));
|
return RedirectToAction(nameof(Login));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign in the user with this external login provider if the user already has a 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);
|
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
|
||||||
if(result.Succeeded)
|
if (result.Succeeded)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("User logged in with {Name} provider.", info.LoginProvider);
|
_logger.LogInformation("User logged in with {Name} provider.", info.LoginProvider);
|
||||||
return RedirectToLocal(returnUrl);
|
return RedirectToLocal(returnUrl);
|
||||||
}
|
}
|
||||||
if(result.IsLockedOut)
|
if (result.IsLockedOut)
|
||||||
{
|
{
|
||||||
return RedirectToAction(nameof(Lockout));
|
return RedirectToAction(nameof(Lockout));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If the user does not have an account, then ask the user to create an account.
|
// If the user does not have an account, then ask the user to create an account.
|
||||||
ViewData["ReturnUrl"] = returnUrl;
|
ViewData["ReturnUrl"] = returnUrl;
|
||||||
ViewData["LoginProvider"] = info.LoginProvider;
|
ViewData["LoginProvider"] = info.LoginProvider;
|
||||||
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
|
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
|
||||||
return View("ExternalLogin", new ExternalLoginViewModel { Email = email });
|
return View("ExternalLogin", new ExternalLoginViewModel { Email = email });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public async Task<IActionResult> ExternalLoginConfirmation(ExternalLoginViewModel model, string returnUrl = null)
|
public async Task<IActionResult> ExternalLoginConfirmation(ExternalLoginViewModel model, string returnUrl = null)
|
||||||
{
|
{
|
||||||
if(ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
// Get the information about the user from the external login provider
|
// Get the information about the user from the external login provider
|
||||||
var info = await _signInManager.GetExternalLoginInfoAsync();
|
var info = await _signInManager.GetExternalLoginInfoAsync();
|
||||||
if(info == null)
|
if (info == null)
|
||||||
{
|
{
|
||||||
throw new ApplicationException("Error loading external login information during confirmation.");
|
throw new ApplicationException("Error loading external login information during confirmation.");
|
||||||
}
|
}
|
||||||
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
|
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
|
||||||
var result = await _userManager.CreateAsync(user);
|
var result = await _userManager.CreateAsync(user);
|
||||||
if(result.Succeeded)
|
if (result.Succeeded)
|
||||||
{
|
{
|
||||||
result = await _userManager.AddLoginAsync(user, info);
|
result = await _userManager.AddLoginAsync(user, info);
|
||||||
if(result.Succeeded)
|
if (result.Succeeded)
|
||||||
{
|
{
|
||||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||||
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
|
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
|
||||||
return RedirectToLocal(returnUrl);
|
return RedirectToLocal(returnUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AddErrors(result);
|
AddErrors(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewData["ReturnUrl"] = returnUrl;
|
ViewData["ReturnUrl"] = returnUrl;
|
||||||
return View(nameof(ExternalLogin), model);
|
return View(nameof(ExternalLogin), model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public async Task<IActionResult> ConfirmEmail(string userId, string code)
|
public async Task<IActionResult> ConfirmEmail(string userId, string code)
|
||||||
{
|
{
|
||||||
if(userId == null || code == null)
|
if (userId == null || code == null)
|
||||||
{
|
{
|
||||||
return RedirectToAction(nameof(HomeController.Index), "Home");
|
return RedirectToAction(nameof(HomeController.Index), "Home");
|
||||||
}
|
}
|
||||||
var user = await _userManager.FindByIdAsync(userId);
|
var user = await _userManager.FindByIdAsync(userId);
|
||||||
if(user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
throw new ApplicationException($"Unable to load user with ID '{userId}'.");
|
throw new ApplicationException($"Unable to load user with ID '{userId}'.");
|
||||||
}
|
}
|
||||||
var result = await _userManager.ConfirmEmailAsync(user, code);
|
var result = await _userManager.ConfirmEmailAsync(user, code);
|
||||||
return View(result.Succeeded ? "ConfirmEmail" : "Error");
|
return View(result.Succeeded ? "ConfirmEmail" : "Error");
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public IActionResult ForgotPassword()
|
public IActionResult ForgotPassword()
|
||||||
{
|
{
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
|
public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
|
||||||
{
|
{
|
||||||
if(ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
var user = await _userManager.FindByEmailAsync(model.Email);
|
var user = await _userManager.FindByEmailAsync(model.Email);
|
||||||
if(user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
|
if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
|
||||||
{
|
{
|
||||||
// Don't reveal that the user does not exist or is not confirmed
|
// Don't reveal that the user does not exist or is not confirmed
|
||||||
return RedirectToAction(nameof(ForgotPasswordConfirmation));
|
return RedirectToAction(nameof(ForgotPasswordConfirmation));
|
||||||
}
|
}
|
||||||
|
|
||||||
// For more information on how to enable account confirmation and password reset please
|
// For more information on how to enable account confirmation and password reset please
|
||||||
// visit https://go.microsoft.com/fwlink/?LinkID=532713
|
// visit https://go.microsoft.com/fwlink/?LinkID=532713
|
||||||
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
|
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
|
||||||
var callbackUrl = Url.ResetPasswordCallbackLink(user.Id, code, Request.Scheme);
|
var callbackUrl = Url.ResetPasswordCallbackLink(user.Id, code, Request.Scheme);
|
||||||
await _emailSender.SendEmailAsync(model.Email, "Reset Password",
|
await _emailSender.SendEmailAsync(model.Email, "Reset Password",
|
||||||
$"Please reset your password by clicking here: <a href='{callbackUrl}'>link</a>");
|
$"Please reset your password by clicking here: <a href='{callbackUrl}'>link</a>");
|
||||||
return RedirectToAction(nameof(ForgotPasswordConfirmation));
|
return RedirectToAction(nameof(ForgotPasswordConfirmation));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we got this far, something failed, redisplay form
|
// If we got this far, something failed, redisplay form
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public IActionResult ForgotPasswordConfirmation()
|
public IActionResult ForgotPasswordConfirmation()
|
||||||
{
|
{
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public IActionResult ResetPassword(string code = null)
|
public IActionResult ResetPassword(string code = null)
|
||||||
{
|
{
|
||||||
if(code == null)
|
if (code == null)
|
||||||
{
|
{
|
||||||
throw new ApplicationException("A code must be supplied for password reset.");
|
throw new ApplicationException("A code must be supplied for password reset.");
|
||||||
}
|
}
|
||||||
var model = new ResetPasswordViewModel { Code = code };
|
var model = new ResetPasswordViewModel { Code = code };
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public async Task<IActionResult> ResetPassword(ResetPasswordViewModel model)
|
public async Task<IActionResult> ResetPassword(ResetPasswordViewModel model)
|
||||||
{
|
{
|
||||||
if(!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
{
|
{
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
var user = await _userManager.FindByEmailAsync(model.Email);
|
var user = await _userManager.FindByEmailAsync(model.Email);
|
||||||
if(user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
// Don't reveal that the user does not exist
|
// Don't reveal that the user does not exist
|
||||||
return RedirectToAction(nameof(ResetPasswordConfirmation));
|
return RedirectToAction(nameof(ResetPasswordConfirmation));
|
||||||
}
|
}
|
||||||
var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password);
|
var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password);
|
||||||
if(result.Succeeded)
|
if (result.Succeeded)
|
||||||
{
|
{
|
||||||
return RedirectToAction(nameof(ResetPasswordConfirmation));
|
return RedirectToAction(nameof(ResetPasswordConfirmation));
|
||||||
}
|
}
|
||||||
AddErrors(result);
|
AddErrors(result);
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public IActionResult ResetPasswordConfirmation()
|
public IActionResult ResetPasswordConfirmation()
|
||||||
{
|
{
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult AccessDenied()
|
public IActionResult AccessDenied()
|
||||||
{
|
{
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Helpers
|
#region Helpers
|
||||||
|
|
||||||
private void AddErrors(IdentityResult result)
|
private void AddErrors(IdentityResult result)
|
||||||
{
|
{
|
||||||
foreach(var error in result.Errors)
|
foreach (var error in result.Errors)
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(string.Empty, error.Description);
|
ModelState.AddModelError(string.Empty, error.Description);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IActionResult RedirectToLocal(string returnUrl)
|
private IActionResult RedirectToLocal(string returnUrl)
|
||||||
{
|
{
|
||||||
if(Url.IsLocalUrl(returnUrl))
|
if (Url.IsLocalUrl(returnUrl))
|
||||||
{
|
{
|
||||||
return Redirect(returnUrl);
|
return Redirect(returnUrl);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return RedirectToAction(nameof(HomeController.Index), "Home");
|
return RedirectToAction(nameof(HomeController.Index), "Home");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,99 +18,99 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
public class CallbackController : Controller
|
public class CallbackController : Controller
|
||||||
{
|
{
|
||||||
public class CallbackSettings
|
public class CallbackSettings
|
||||||
{
|
{
|
||||||
public string Token
|
public string Token
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SettingsRepository _Settings;
|
SettingsRepository _Settings;
|
||||||
Network _Network;
|
Network _Network;
|
||||||
InvoiceWatcher _Watcher;
|
InvoiceWatcher _Watcher;
|
||||||
ExplorerClient _Explorer;
|
ExplorerClient _Explorer;
|
||||||
|
|
||||||
public CallbackController(SettingsRepository repo,
|
public CallbackController(SettingsRepository repo,
|
||||||
ExplorerClient explorer,
|
ExplorerClient explorer,
|
||||||
InvoiceWatcher watcher,
|
InvoiceWatcher watcher,
|
||||||
Network network)
|
Network network)
|
||||||
{
|
{
|
||||||
_Settings = repo;
|
_Settings = repo;
|
||||||
_Network = network;
|
_Network = network;
|
||||||
_Watcher = watcher;
|
_Watcher = watcher;
|
||||||
_Explorer = explorer;
|
_Explorer = explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("callbacks/transactions")]
|
[Route("callbacks/transactions")]
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task NewTransaction(string token)
|
public async Task NewTransaction(string token)
|
||||||
{
|
{
|
||||||
await AssertToken(token);
|
await AssertToken(token);
|
||||||
Logs.PayServer.LogInformation("New transaction callback");
|
Logs.PayServer.LogInformation("New transaction callback");
|
||||||
//We don't want to register all the json converter at MVC level, so we parse here
|
//We don't want to register all the json converter at MVC level, so we parse here
|
||||||
var serializer = new NBXplorer.Serializer(_Network);
|
var serializer = new NBXplorer.Serializer(_Network);
|
||||||
var content = await new StreamReader(Request.Body, new UTF8Encoding(false), false, 1024, true).ReadToEndAsync();
|
var content = await new StreamReader(Request.Body, new UTF8Encoding(false), false, 1024, true).ReadToEndAsync();
|
||||||
var match = serializer.ToObject<TransactionMatch>(content);
|
var match = serializer.ToObject<TransactionMatch>(content);
|
||||||
|
|
||||||
foreach(var output in match.Outputs)
|
foreach (var output in match.Outputs)
|
||||||
{
|
{
|
||||||
await _Watcher.NotifyReceived(output.ScriptPubKey);
|
await _Watcher.NotifyReceived(output.ScriptPubKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("callbacks/blocks")]
|
[Route("callbacks/blocks")]
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task NewBlock(string token)
|
public async Task NewBlock(string token)
|
||||||
{
|
{
|
||||||
await AssertToken(token);
|
await AssertToken(token);
|
||||||
Logs.PayServer.LogInformation("New block callback");
|
Logs.PayServer.LogInformation("New block callback");
|
||||||
await _Watcher.NotifyBlock();
|
await _Watcher.NotifyBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AssertToken(string token)
|
private async Task AssertToken(string token)
|
||||||
{
|
{
|
||||||
var callback = await _Settings.GetSettingAsync<CallbackSettings>();
|
var callback = await _Settings.GetSettingAsync<CallbackSettings>();
|
||||||
if(await GetToken() != token)
|
if (await GetToken() != token)
|
||||||
throw new BTCPayServer.BitpayHttpException(400, "invalid-callback-token");
|
throw new BTCPayServer.BitpayHttpException(400, "invalid-callback-token");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Uri> GetCallbackUriAsync(HttpRequest request)
|
public async Task<Uri> GetCallbackUriAsync(HttpRequest request)
|
||||||
{
|
{
|
||||||
string token = await GetToken();
|
string token = await GetToken();
|
||||||
return new Uri(request.GetAbsoluteRoot() + "/callbacks/transactions?token=" + token);
|
return new Uri(request.GetAbsoluteRoot() + "/callbacks/transactions?token=" + token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RegisterCallbackUriAsync(DerivationStrategyBase derivationScheme, HttpRequest request)
|
public async Task RegisterCallbackUriAsync(DerivationStrategyBase derivationScheme, HttpRequest request)
|
||||||
{
|
{
|
||||||
var uri = await GetCallbackUriAsync(request);
|
var uri = await GetCallbackUriAsync(request);
|
||||||
await _Explorer.SubscribeToWalletAsync(uri, derivationScheme);
|
await _Explorer.SubscribeToWalletAsync(uri, derivationScheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> GetToken()
|
private async Task<string> GetToken()
|
||||||
{
|
{
|
||||||
var callback = await _Settings.GetSettingAsync<CallbackSettings>();
|
var callback = await _Settings.GetSettingAsync<CallbackSettings>();
|
||||||
if(callback == null)
|
if (callback == null)
|
||||||
{
|
{
|
||||||
callback = new CallbackSettings() { Token = Guid.NewGuid().ToString() };
|
callback = new CallbackSettings() { Token = Guid.NewGuid().ToString() };
|
||||||
await _Settings.UpdateSetting(callback);
|
await _Settings.UpdateSetting(callback);
|
||||||
}
|
}
|
||||||
var token = callback.Token;
|
var token = callback.Token;
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Uri> GetCallbackBlockUriAsync(HttpRequest request)
|
public async Task<Uri> GetCallbackBlockUriAsync(HttpRequest request)
|
||||||
{
|
{
|
||||||
string token = await GetToken();
|
string token = await GetToken();
|
||||||
return new Uri(request.GetAbsoluteRoot() + "/callbacks/blocks?token=" + token);
|
return new Uri(request.GetAbsoluteRoot() + "/callbacks/blocks?token=" + token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Uri> RegisterCallbackBlockUriAsync(HttpRequest request)
|
public async Task<Uri> RegisterCallbackBlockUriAsync(HttpRequest request)
|
||||||
{
|
{
|
||||||
var uri = await GetCallbackBlockUriAsync(request);
|
var uri = await GetCallbackBlockUriAsync(request);
|
||||||
await _Explorer.SubscribeToBlocksAsync(uri);
|
await _Explorer.SubscribeToBlocksAsync(uri);
|
||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,123 +16,123 @@ using BTCPayServer.Services.Stores;
|
|||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
[EnableCors("BitpayAPI")]
|
[EnableCors("BitpayAPI")]
|
||||||
[BitpayAPIConstraint]
|
[BitpayAPIConstraint]
|
||||||
public class InvoiceControllerAPI : Controller
|
public class InvoiceControllerAPI : Controller
|
||||||
{
|
{
|
||||||
private InvoiceController _InvoiceController;
|
private InvoiceController _InvoiceController;
|
||||||
private InvoiceRepository _InvoiceRepository;
|
private InvoiceRepository _InvoiceRepository;
|
||||||
private TokenRepository _TokenRepository;
|
private TokenRepository _TokenRepository;
|
||||||
private StoreRepository _StoreRepository;
|
private StoreRepository _StoreRepository;
|
||||||
|
|
||||||
public InvoiceControllerAPI(InvoiceController invoiceController,
|
public InvoiceControllerAPI(InvoiceController invoiceController,
|
||||||
InvoiceRepository invoceRepository,
|
InvoiceRepository invoceRepository,
|
||||||
TokenRepository tokenRepository,
|
TokenRepository tokenRepository,
|
||||||
StoreRepository storeRepository)
|
StoreRepository storeRepository)
|
||||||
{
|
{
|
||||||
this._InvoiceController = invoiceController;
|
this._InvoiceController = invoiceController;
|
||||||
this._InvoiceRepository = invoceRepository;
|
this._InvoiceRepository = invoceRepository;
|
||||||
this._TokenRepository = tokenRepository;
|
this._TokenRepository = tokenRepository;
|
||||||
this._StoreRepository = storeRepository;
|
this._StoreRepository = storeRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("invoices")]
|
[Route("invoices")]
|
||||||
[MediaTypeConstraint("application/json")]
|
[MediaTypeConstraint("application/json")]
|
||||||
public async Task<DataWrapper<InvoiceResponse>> CreateInvoice([FromBody] Invoice invoice)
|
public async Task<DataWrapper<InvoiceResponse>> CreateInvoice([FromBody] Invoice invoice)
|
||||||
{
|
{
|
||||||
var bitToken = await CheckTokenPermissionAsync(Facade.Merchant, invoice.Token);
|
var bitToken = await CheckTokenPermissionAsync(Facade.Merchant, invoice.Token);
|
||||||
var store = await FindStore(bitToken);
|
var store = await FindStore(bitToken);
|
||||||
return await _InvoiceController.CreateInvoiceCore(invoice, store, HttpContext.Request.GetAbsoluteRoot());
|
return await _InvoiceController.CreateInvoiceCore(invoice, store, HttpContext.Request.GetAbsoluteRoot());
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("invoices/{id}")]
|
[Route("invoices/{id}")]
|
||||||
public async Task<DataWrapper<InvoiceResponse>> GetInvoice(string id, string token)
|
public async Task<DataWrapper<InvoiceResponse>> GetInvoice(string id, string token)
|
||||||
{
|
{
|
||||||
var bitToken = await CheckTokenPermissionAsync(Facade.Merchant, token);
|
var bitToken = await CheckTokenPermissionAsync(Facade.Merchant, token);
|
||||||
var store = await FindStore(bitToken);
|
var store = await FindStore(bitToken);
|
||||||
var invoice = await _InvoiceRepository.GetInvoice(store.Id, id);
|
var invoice = await _InvoiceRepository.GetInvoice(store.Id, id);
|
||||||
if(invoice == null)
|
if (invoice == null)
|
||||||
throw new BitpayHttpException(404, "Object not found");
|
throw new BitpayHttpException(404, "Object not found");
|
||||||
|
|
||||||
var resp = invoice.EntityToDTO();
|
var resp = invoice.EntityToDTO();
|
||||||
return new DataWrapper<InvoiceResponse>(resp);
|
return new DataWrapper<InvoiceResponse>(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("invoices")]
|
[Route("invoices")]
|
||||||
public async Task<DataWrapper<InvoiceResponse[]>> GetInvoices(
|
public async Task<DataWrapper<InvoiceResponse[]>> GetInvoices(
|
||||||
string token,
|
string token,
|
||||||
DateTimeOffset? dateStart = null,
|
DateTimeOffset? dateStart = null,
|
||||||
DateTimeOffset? dateEnd = null,
|
DateTimeOffset? dateEnd = null,
|
||||||
string orderId = null,
|
string orderId = null,
|
||||||
string itemCode = null,
|
string itemCode = null,
|
||||||
string status = null,
|
string status = null,
|
||||||
int? limit = null,
|
int? limit = null,
|
||||||
int? offset = null)
|
int? offset = null)
|
||||||
{
|
{
|
||||||
if(dateEnd != null)
|
if (dateEnd != null)
|
||||||
dateEnd = dateEnd.Value + TimeSpan.FromDays(1); //Should include the end day
|
dateEnd = dateEnd.Value + TimeSpan.FromDays(1); //Should include the end day
|
||||||
var bitToken = await CheckTokenPermissionAsync(Facade.Merchant, token);
|
var bitToken = await CheckTokenPermissionAsync(Facade.Merchant, token);
|
||||||
var store = await FindStore(bitToken);
|
var store = await FindStore(bitToken);
|
||||||
var query = new InvoiceQuery()
|
var query = new InvoiceQuery()
|
||||||
{
|
{
|
||||||
Count = limit,
|
Count = limit,
|
||||||
Skip = offset,
|
Skip = offset,
|
||||||
EndDate = dateEnd,
|
EndDate = dateEnd,
|
||||||
StartDate = dateStart,
|
StartDate = dateStart,
|
||||||
OrderId = orderId,
|
OrderId = orderId,
|
||||||
ItemCode = itemCode,
|
ItemCode = itemCode,
|
||||||
Status = status,
|
Status = status,
|
||||||
StoreId = store.Id
|
StoreId = store.Id
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var entities = (await _InvoiceRepository.GetInvoices(query))
|
var entities = (await _InvoiceRepository.GetInvoices(query))
|
||||||
.Select((o) => o.EntityToDTO()).ToArray();
|
.Select((o) => o.EntityToDTO()).ToArray();
|
||||||
|
|
||||||
return DataWrapper.Create(entities);
|
return DataWrapper.Create(entities);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<BitTokenEntity> CheckTokenPermissionAsync(Facade facade, string expectedToken)
|
private async Task<BitTokenEntity> CheckTokenPermissionAsync(Facade facade, string expectedToken)
|
||||||
{
|
{
|
||||||
if(facade == null)
|
if (facade == null)
|
||||||
throw new ArgumentNullException(nameof(facade));
|
throw new ArgumentNullException(nameof(facade));
|
||||||
|
|
||||||
var actualTokens = (await _TokenRepository.GetTokens(this.GetBitIdentity().SIN)).ToArray();
|
var actualTokens = (await _TokenRepository.GetTokens(this.GetBitIdentity().SIN)).ToArray();
|
||||||
actualTokens = actualTokens.SelectMany(t => GetCompatibleTokens(t)).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.Facade).Concat(new[] { "user" }).FirstOrDefault()}` facade");
|
|
||||||
}
|
|
||||||
return actualToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<BitTokenEntity> GetCompatibleTokens(BitTokenEntity token)
|
var actualToken = actualTokens.FirstOrDefault(a => a.Value.Equals(expectedToken, StringComparison.Ordinal));
|
||||||
{
|
if (expectedToken == null || actualToken == null)
|
||||||
if(token.Facade == Facade.Merchant.ToString())
|
{
|
||||||
{
|
Logs.PayServer.LogDebug($"No token found for facade {facade} for SIN {this.GetBitIdentity().SIN}");
|
||||||
yield return token.Clone(Facade.User);
|
throw new BitpayHttpException(401, $"This endpoint does not support the `{actualTokens.Select(a => a.Facade).Concat(new[] { "user" }).FirstOrDefault()}` facade");
|
||||||
yield return token.Clone(Facade.PointOfSale);
|
}
|
||||||
}
|
return actualToken;
|
||||||
if(token.Facade == Facade.PointOfSale.ToString())
|
}
|
||||||
{
|
|
||||||
yield return token.Clone(Facade.User);
|
|
||||||
}
|
|
||||||
yield return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<StoreData> FindStore(BitTokenEntity bitToken)
|
private IEnumerable<BitTokenEntity> GetCompatibleTokens(BitTokenEntity token)
|
||||||
{
|
{
|
||||||
var store = await _StoreRepository.FindStore(bitToken.StoreId);
|
if (token.Facade == Facade.Merchant.ToString())
|
||||||
if(store == null)
|
{
|
||||||
throw new BitpayHttpException(401, "Unknown store");
|
yield return token.Clone(Facade.User);
|
||||||
return store;
|
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
|
public partial class InvoiceController
|
||||||
{
|
{
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("i/{invoiceId}")]
|
[Route("i/{invoiceId}")]
|
||||||
[AcceptMediaTypeConstraint("application/bitcoin-paymentrequest")]
|
[AcceptMediaTypeConstraint("application/bitcoin-paymentrequest")]
|
||||||
public async Task<IActionResult> GetInvoiceRequest(string invoiceId)
|
public async Task<IActionResult> GetInvoiceRequest(string invoiceId)
|
||||||
{
|
{
|
||||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||||
if(invoice == null || invoice.IsExpired())
|
if (invoice == null || invoice.IsExpired())
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
var dto = invoice.EntityToDTO();
|
var dto = invoice.EntityToDTO();
|
||||||
PaymentRequest request = new PaymentRequest
|
PaymentRequest request = new PaymentRequest
|
||||||
{
|
{
|
||||||
DetailsVersion = 1
|
DetailsVersion = 1
|
||||||
};
|
};
|
||||||
request.Details.Expires = invoice.ExpirationTime;
|
request.Details.Expires = invoice.ExpirationTime;
|
||||||
request.Details.Memo = invoice.ProductInformation.ItemDesc;
|
request.Details.Memo = invoice.ProductInformation.ItemDesc;
|
||||||
request.Details.Network = _Network;
|
request.Details.Network = _Network;
|
||||||
request.Details.Outputs.Add(new PaymentOutput() { Amount = dto.BTCDue, Script = BitcoinAddress.Create(dto.BitcoinAddress, _Network).ScriptPubKey });
|
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.MerchantData = Encoding.UTF8.GetBytes(invoice.Id);
|
||||||
request.Details.Time = DateTimeOffset.UtcNow;
|
request.Details.Time = DateTimeOffset.UtcNow;
|
||||||
request.Details.PaymentUrl = new Uri(invoice.ServerUrl.WithTrailingSlash() + ($"i/{invoice.Id}"), UriKind.Absolute);
|
request.Details.PaymentUrl = new Uri(invoice.ServerUrl.WithTrailingSlash() + ($"i/{invoice.Id}"), UriKind.Absolute);
|
||||||
|
|
||||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||||
if(store == null)
|
if (store == null)
|
||||||
throw new BitpayHttpException(401, "Unknown store");
|
throw new BitpayHttpException(401, "Unknown store");
|
||||||
|
|
||||||
if(store.StoreCertificate != null)
|
if (store.StoreCertificate != null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
request.Sign(store.StoreCertificate, PKIType.X509SHA256);
|
request.Sign(store.StoreCertificate, PKIType.X509SHA256);
|
||||||
}
|
}
|
||||||
catch(Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logs.PayServer.LogWarning(ex, "Error while signing payment request");
|
Logs.PayServer.LogWarning(ex, "Error while signing payment request");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PaymentRequestActionResult(request);
|
return new PaymentRequestActionResult(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("i/{invoiceId}", Order = 99)]
|
[Route("i/{invoiceId}", Order = 99)]
|
||||||
[MediaTypeConstraint("application/bitcoin-payment")]
|
[MediaTypeConstraint("application/bitcoin-payment")]
|
||||||
public async Task<IActionResult> PostPayment(string invoiceId)
|
public async Task<IActionResult> PostPayment(string invoiceId)
|
||||||
{
|
{
|
||||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||||
if(invoice == null || invoice.IsExpired())
|
if (invoice == null || invoice.IsExpired())
|
||||||
return NotFound();
|
return NotFound();
|
||||||
var payment = PaymentMessage.Load(Request.Body);
|
var payment = PaymentMessage.Load(Request.Body);
|
||||||
var unused = _Wallet.BroadcastTransactionsAsync(payment.Transactions);
|
var unused = _Wallet.BroadcastTransactionsAsync(payment.Transactions);
|
||||||
await _InvoiceRepository.AddRefundsAsync(invoiceId, payment.RefundTo.Select(p => new TxOut(p.Amount, p.Script)).ToArray());
|
await _InvoiceRepository.AddRefundsAsync(invoiceId, payment.RefundTo.Select(p => new TxOut(p.Amount, p.Script)).ToArray());
|
||||||
return new PaymentAckActionResult(payment.CreateACK(invoiceId + " is currently processing, thanks for your purchase..."));
|
return new PaymentAckActionResult(payment.CreateACK(invoiceId + " is currently processing, thanks for your purchase..."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,299 +18,299 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
public partial class InvoiceController
|
public partial class InvoiceController
|
||||||
{
|
{
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("invoices/{invoiceId}")]
|
[Route("invoices/{invoiceId}")]
|
||||||
public async Task<IActionResult> Invoice(string invoiceId, string command)
|
public async Task<IActionResult> Invoice(string invoiceId, string command)
|
||||||
{
|
{
|
||||||
if(command == "refresh")
|
if (command == "refresh")
|
||||||
{
|
{
|
||||||
await _Watcher.WatchAsync(invoiceId, true);
|
await _Watcher.WatchAsync(invoiceId, true);
|
||||||
}
|
}
|
||||||
StatusMessage = "Invoice is state is being refreshed, please refresh the page soon...";
|
StatusMessage = "Invoice is state is being refreshed, please refresh the page soon...";
|
||||||
return RedirectToAction(nameof(Invoice), new
|
return RedirectToAction(nameof(Invoice), new
|
||||||
{
|
{
|
||||||
invoiceId = invoiceId
|
invoiceId = invoiceId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("invoices/{invoiceId}")]
|
[Route("invoices/{invoiceId}")]
|
||||||
public async Task<IActionResult> Invoice(string invoiceId)
|
public async Task<IActionResult> Invoice(string invoiceId)
|
||||||
{
|
{
|
||||||
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||||
{
|
{
|
||||||
UserId = GetUserId(),
|
UserId = GetUserId(),
|
||||||
InvoiceId = invoiceId
|
InvoiceId = invoiceId
|
||||||
})).FirstOrDefault();
|
})).FirstOrDefault();
|
||||||
if(invoice == null)
|
if (invoice == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
var dto = invoice.EntityToDTO();
|
var dto = invoice.EntityToDTO();
|
||||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||||
InvoiceDetailsModel model = new InvoiceDetailsModel()
|
InvoiceDetailsModel model = new InvoiceDetailsModel()
|
||||||
{
|
{
|
||||||
StoreName = store.StoreName,
|
StoreName = store.StoreName,
|
||||||
StoreLink = Url.Action(nameof(StoresController.UpdateStore), "Stores", new { storeId = store.Id }),
|
StoreLink = Url.Action(nameof(StoresController.UpdateStore), "Stores", new { storeId = store.Id }),
|
||||||
Id = invoice.Id,
|
Id = invoice.Id,
|
||||||
Status = invoice.Status,
|
Status = invoice.Status,
|
||||||
RefundEmail = invoice.RefundMail,
|
RefundEmail = invoice.RefundMail,
|
||||||
CreatedDate = invoice.InvoiceTime,
|
CreatedDate = invoice.InvoiceTime,
|
||||||
ExpirationDate = invoice.ExpirationTime,
|
ExpirationDate = invoice.ExpirationTime,
|
||||||
OrderId = invoice.OrderId,
|
OrderId = invoice.OrderId,
|
||||||
BuyerInformation = invoice.BuyerInformation,
|
BuyerInformation = invoice.BuyerInformation,
|
||||||
Rate = invoice.Rate,
|
Rate = invoice.Rate,
|
||||||
Fiat = dto.Price + " " + dto.Currency,
|
Fiat = dto.Price + " " + dto.Currency,
|
||||||
BTC = invoice.GetTotalCryptoDue().ToString() + " BTC",
|
BTC = invoice.GetTotalCryptoDue().ToString() + " BTC",
|
||||||
BTCDue = invoice.GetCryptoDue().ToString() + " BTC",
|
BTCDue = invoice.GetCryptoDue().ToString() + " BTC",
|
||||||
BTCPaid = invoice.GetTotalPaid().ToString() + " BTC",
|
BTCPaid = invoice.GetTotalPaid().ToString() + " BTC",
|
||||||
NetworkFee = invoice.GetNetworkFee().ToString() + " BTC",
|
NetworkFee = invoice.GetNetworkFee().ToString() + " BTC",
|
||||||
NotificationUrl = invoice.NotificationURL,
|
NotificationUrl = invoice.NotificationURL,
|
||||||
ProductInformation = invoice.ProductInformation,
|
ProductInformation = invoice.ProductInformation,
|
||||||
BitcoinAddress = invoice.DepositAddress,
|
BitcoinAddress = invoice.DepositAddress,
|
||||||
PaymentUrl = dto.PaymentUrls.BIP72
|
PaymentUrl = dto.PaymentUrls.BIP72
|
||||||
};
|
};
|
||||||
|
|
||||||
var payments = invoice
|
var payments = invoice
|
||||||
.Payments
|
.Payments
|
||||||
.Select(async payment =>
|
.Select(async payment =>
|
||||||
{
|
{
|
||||||
var m = new InvoiceDetailsModel.Payment();
|
var m = new InvoiceDetailsModel.Payment();
|
||||||
m.DepositAddress = payment.Output.ScriptPubKey.GetDestinationAddress(_Network);
|
m.DepositAddress = payment.Output.ScriptPubKey.GetDestinationAddress(_Network);
|
||||||
m.Confirmations = (await _Explorer.GetTransactionAsync(payment.Outpoint.Hash))?.Confirmations ?? 0;
|
m.Confirmations = (await _Explorer.GetTransactionAsync(payment.Outpoint.Hash))?.Confirmations ?? 0;
|
||||||
m.TransactionId = payment.Outpoint.Hash.ToString();
|
m.TransactionId = payment.Outpoint.Hash.ToString();
|
||||||
m.ReceivedTime = payment.ReceivedTime;
|
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}";
|
m.TransactionLink = _Network == Network.Main ? $"https://www.smartbit.com.au/tx/{m.TransactionId}" : $"https://testnet.smartbit.com.au/tx/{m.TransactionId}";
|
||||||
return m;
|
return m;
|
||||||
})
|
})
|
||||||
.ToArray();
|
.ToArray();
|
||||||
await Task.WhenAll(payments);
|
await Task.WhenAll(payments);
|
||||||
model.Payments = payments.Select(p => p.GetAwaiter().GetResult()).ToList();
|
model.Payments = payments.Select(p => p.GetAwaiter().GetResult()).ToList();
|
||||||
model.StatusMessage = StatusMessage;
|
model.StatusMessage = StatusMessage;
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static Dictionary<string, CultureInfo> _CurrencyProviders = new Dictionary<string, CultureInfo>();
|
static Dictionary<string, CultureInfo> _CurrencyProviders = new Dictionary<string, CultureInfo>();
|
||||||
private IFormatProvider GetCurrencyProvider(string currency)
|
private IFormatProvider GetCurrencyProvider(string currency)
|
||||||
{
|
{
|
||||||
lock(_CurrencyProviders)
|
lock (_CurrencyProviders)
|
||||||
{
|
{
|
||||||
if(_CurrencyProviders.Count == 0)
|
if (_CurrencyProviders.Count == 0)
|
||||||
{
|
{
|
||||||
foreach(var culture in CultureInfo.GetCultures(CultureTypes.AllCultures).Where(c => !c.IsNeutralCulture))
|
foreach (var culture in CultureInfo.GetCultures(CultureTypes.AllCultures).Where(c => !c.IsNeutralCulture))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_CurrencyProviders.TryAdd(new RegionInfo(culture.LCID).ISOCurrencySymbol, culture);
|
_CurrencyProviders.TryAdd(new RegionInfo(culture.LCID).ISOCurrencySymbol, culture);
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _CurrencyProviders.TryGet(currency);
|
return _CurrencyProviders.TryGet(currency);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("i/{invoiceId}")]
|
[Route("i/{invoiceId}")]
|
||||||
[Route("invoice")]
|
[Route("invoice")]
|
||||||
[AcceptMediaTypeConstraint("application/bitcoin-paymentrequest", false)]
|
[AcceptMediaTypeConstraint("application/bitcoin-paymentrequest", false)]
|
||||||
[XFrameOptionsAttribute(null)]
|
[XFrameOptionsAttribute(null)]
|
||||||
public async Task<IActionResult> Checkout(string invoiceId, string id = null)
|
public async Task<IActionResult> Checkout(string invoiceId, string id = null)
|
||||||
{
|
{
|
||||||
//Keep compatibility with Bitpay
|
//Keep compatibility with Bitpay
|
||||||
invoiceId = invoiceId ?? id;
|
invoiceId = invoiceId ?? id;
|
||||||
id = invoiceId;
|
id = invoiceId;
|
||||||
////
|
////
|
||||||
|
|
||||||
var model = await GetInvoiceModel(invoiceId);
|
var model = await GetInvoiceModel(invoiceId);
|
||||||
if (model == null)
|
if (model == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
return View(nameof(Checkout), model);
|
return View(nameof(Checkout), model);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<PaymentModel> GetInvoiceModel(string invoiceId)
|
private async Task<PaymentModel> GetInvoiceModel(string invoiceId)
|
||||||
{
|
{
|
||||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||||
if (invoice == null)
|
if (invoice == null)
|
||||||
return null;
|
return null;
|
||||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||||
var dto = invoice.EntityToDTO();
|
var dto = invoice.EntityToDTO();
|
||||||
|
|
||||||
var model = new PaymentModel()
|
var model = new PaymentModel()
|
||||||
{
|
{
|
||||||
ServerUrl = HttpContext.Request.GetAbsoluteRoot(),
|
ServerUrl = HttpContext.Request.GetAbsoluteRoot(),
|
||||||
OrderId = invoice.OrderId,
|
OrderId = invoice.OrderId,
|
||||||
InvoiceId = invoice.Id,
|
InvoiceId = invoice.Id,
|
||||||
BtcAddress = invoice.DepositAddress.ToString(),
|
BtcAddress = invoice.DepositAddress.ToString(),
|
||||||
BtcAmount = (invoice.GetTotalCryptoDue() - invoice.TxFee).ToString(),
|
BtcAmount = (invoice.GetTotalCryptoDue() - invoice.TxFee).ToString(),
|
||||||
BtcTotalDue = invoice.GetTotalCryptoDue().ToString(),
|
BtcTotalDue = invoice.GetTotalCryptoDue().ToString(),
|
||||||
BtcDue = invoice.GetCryptoDue().ToString(),
|
BtcDue = invoice.GetCryptoDue().ToString(),
|
||||||
CustomerEmail = invoice.RefundMail,
|
CustomerEmail = invoice.RefundMail,
|
||||||
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
|
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
|
||||||
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
|
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
|
||||||
ItemDesc = invoice.ProductInformation.ItemDesc,
|
ItemDesc = invoice.ProductInformation.ItemDesc,
|
||||||
Rate = invoice.Rate.ToString("C", GetCurrencyProvider(invoice.ProductInformation.Currency)),
|
Rate = invoice.Rate.ToString("C", GetCurrencyProvider(invoice.ProductInformation.Currency)),
|
||||||
MerchantRefLink = invoice.RedirectURL ?? "/",
|
MerchantRefLink = invoice.RedirectURL ?? "/",
|
||||||
StoreName = store.StoreName,
|
StoreName = store.StoreName,
|
||||||
TxFees = invoice.TxFee.ToString(),
|
TxFees = invoice.TxFee.ToString(),
|
||||||
InvoiceBitcoinUrl = dto.PaymentUrls.BIP72,
|
InvoiceBitcoinUrl = dto.PaymentUrls.BIP72,
|
||||||
TxCount = invoice.GetTxCount(),
|
TxCount = invoice.GetTxCount(),
|
||||||
BtcPaid = invoice.GetTotalPaid().ToString(),
|
BtcPaid = invoice.GetTotalPaid().ToString(),
|
||||||
Status = invoice.Status
|
Status = invoice.Status
|
||||||
};
|
};
|
||||||
|
|
||||||
var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds);
|
var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds);
|
||||||
model.TimeLeft = PrettyPrint(expiration);
|
model.TimeLeft = PrettyPrint(expiration);
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string PrettyPrint(TimeSpan expiration)
|
private string PrettyPrint(TimeSpan expiration)
|
||||||
{
|
{
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
if(expiration.Days >= 1)
|
if (expiration.Days >= 1)
|
||||||
builder.Append(expiration.Days.ToString());
|
builder.Append(expiration.Days.ToString());
|
||||||
if(expiration.Hours >= 1)
|
if (expiration.Hours >= 1)
|
||||||
builder.Append(expiration.Hours.ToString("00"));
|
builder.Append(expiration.Hours.ToString("00"));
|
||||||
builder.Append($"{expiration.Minutes.ToString("00")}:{expiration.Seconds.ToString("00")}");
|
builder.Append($"{expiration.Minutes.ToString("00")}:{expiration.Seconds.ToString("00")}");
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("i/{invoiceId}/status")]
|
[Route("i/{invoiceId}/status")]
|
||||||
public async Task<IActionResult> GetStatus(string invoiceId)
|
public async Task<IActionResult> GetStatus(string invoiceId)
|
||||||
{
|
{
|
||||||
var model = await GetInvoiceModel(invoiceId);
|
var model = await GetInvoiceModel(invoiceId);
|
||||||
if(model == null)
|
if (model == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
return Json(model);
|
return Json(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("i/{invoiceId}/UpdateCustomer")]
|
[Route("i/{invoiceId}/UpdateCustomer")]
|
||||||
public async Task<IActionResult> UpdateCustomer(string invoiceId, [FromBody]UpdateCustomerModel data)
|
public async Task<IActionResult> UpdateCustomer(string invoiceId, [FromBody]UpdateCustomerModel data)
|
||||||
{
|
{
|
||||||
if(!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
{
|
{
|
||||||
return BadRequest(ModelState);
|
return BadRequest(ModelState);
|
||||||
}
|
}
|
||||||
await _InvoiceRepository.UpdateInvoice(invoiceId, data).ConfigureAwait(false);
|
await _InvoiceRepository.UpdateInvoice(invoiceId, data).ConfigureAwait(false);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("invoices")]
|
[Route("invoices")]
|
||||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||||
[BitpayAPIConstraint(false)]
|
[BitpayAPIConstraint(false)]
|
||||||
public async Task<IActionResult> ListInvoices(string searchTerm = null, int skip = 0, int count = 20)
|
public async Task<IActionResult> ListInvoices(string searchTerm = null, int skip = 0, int count = 20)
|
||||||
{
|
{
|
||||||
var model = new InvoicesModel();
|
var model = new InvoicesModel();
|
||||||
foreach(var invoice in await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
foreach (var invoice in await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||||
{
|
{
|
||||||
TextSearch = searchTerm,
|
TextSearch = searchTerm,
|
||||||
Count = count,
|
Count = count,
|
||||||
Skip = skip,
|
Skip = skip,
|
||||||
UserId = GetUserId()
|
UserId = GetUserId()
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
model.SearchTerm = searchTerm;
|
model.SearchTerm = searchTerm;
|
||||||
model.Invoices.Add(new InvoiceModel()
|
model.Invoices.Add(new InvoiceModel()
|
||||||
{
|
{
|
||||||
Status = invoice.Status,
|
Status = invoice.Status,
|
||||||
Date = invoice.InvoiceTime,
|
Date = invoice.InvoiceTime,
|
||||||
InvoiceId = invoice.Id,
|
InvoiceId = invoice.Id,
|
||||||
AmountCurrency = $"{invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture)} {invoice.ProductInformation.Currency}"
|
AmountCurrency = $"{invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture)} {invoice.ProductInformation.Currency}"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
model.Skip = skip;
|
model.Skip = skip;
|
||||||
model.Count = count;
|
model.Count = count;
|
||||||
model.StatusMessage = StatusMessage;
|
model.StatusMessage = StatusMessage;
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("invoices/create")]
|
[Route("invoices/create")]
|
||||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||||
[BitpayAPIConstraint(false)]
|
[BitpayAPIConstraint(false)]
|
||||||
public async Task<IActionResult> CreateInvoice()
|
public async Task<IActionResult> CreateInvoice()
|
||||||
{
|
{
|
||||||
var stores = await GetStores(GetUserId());
|
var stores = await GetStores(GetUserId());
|
||||||
if(stores.Count() == 0)
|
if (stores.Count() == 0)
|
||||||
{
|
{
|
||||||
StatusMessage = "Error: You need to create at least one store before creating a transaction";
|
StatusMessage = "Error: You need to create at least one store before creating a transaction";
|
||||||
return RedirectToAction(nameof(StoresController.ListStores), "Stores");
|
return RedirectToAction(nameof(StoresController.ListStores), "Stores");
|
||||||
}
|
}
|
||||||
return View(new CreateInvoiceModel() { Stores = stores });
|
return View(new CreateInvoiceModel() { Stores = stores });
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("invoices/create")]
|
[Route("invoices/create")]
|
||||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||||
[BitpayAPIConstraint(false)]
|
[BitpayAPIConstraint(false)]
|
||||||
public async Task<IActionResult> CreateInvoice(CreateInvoiceModel model)
|
public async Task<IActionResult> CreateInvoice(CreateInvoiceModel model)
|
||||||
{
|
{
|
||||||
if(!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
{
|
{
|
||||||
model.Stores = await GetStores(GetUserId(), model.StoreId);
|
model.Stores = await GetStores(GetUserId(), model.StoreId);
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
var store = await _StoreRepository.FindStore(model.StoreId, GetUserId());
|
var store = await _StoreRepository.FindStore(model.StoreId, GetUserId());
|
||||||
if(string.IsNullOrEmpty(store.DerivationStrategy))
|
if (string.IsNullOrEmpty(store.DerivationStrategy))
|
||||||
{
|
{
|
||||||
StatusMessage = "Error: You need to configure the derivation scheme in order to create an invoice";
|
StatusMessage = "Error: You need to configure the derivation scheme in order to create an invoice";
|
||||||
return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new
|
return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new
|
||||||
{
|
{
|
||||||
storeId = store.Id
|
storeId = store.Id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
var result = await CreateInvoiceCore(new Invoice()
|
var result = await CreateInvoiceCore(new Invoice()
|
||||||
{
|
{
|
||||||
Price = model.Amount.Value,
|
Price = model.Amount.Value,
|
||||||
Currency = "USD",
|
Currency = "USD",
|
||||||
PosData = model.PosData,
|
PosData = model.PosData,
|
||||||
OrderId = model.OrderId,
|
OrderId = model.OrderId,
|
||||||
//RedirectURL = redirect + "redirect",
|
//RedirectURL = redirect + "redirect",
|
||||||
NotificationURL = model.NotificationUrl,
|
NotificationURL = model.NotificationUrl,
|
||||||
ItemDesc = model.ItemDesc,
|
ItemDesc = model.ItemDesc,
|
||||||
FullNotifications = true,
|
FullNotifications = true,
|
||||||
BuyerEmail = model.BuyerEmail,
|
BuyerEmail = model.BuyerEmail,
|
||||||
}, store, HttpContext.Request.GetAbsoluteRoot());
|
}, store, HttpContext.Request.GetAbsoluteRoot());
|
||||||
|
|
||||||
StatusMessage = $"Invoice {result.Data.Id} just created!";
|
StatusMessage = $"Invoice {result.Data.Id} just created!";
|
||||||
return RedirectToAction(nameof(ListInvoices));
|
return RedirectToAction(nameof(ListInvoices));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<SelectList> GetStores(string userId, string storeId = null)
|
private async Task<SelectList> GetStores(string userId, string storeId = null)
|
||||||
{
|
{
|
||||||
return new SelectList(await _StoreRepository.GetStoresByUserId(userId), nameof(StoreData.Id), nameof(StoreData.StoreName), storeId);
|
return new SelectList(await _StoreRepository.GetStoresByUserId(userId), nameof(StoreData.Id), nameof(StoreData.StoreName), storeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||||
[BitpayAPIConstraint(false)]
|
[BitpayAPIConstraint(false)]
|
||||||
public IActionResult SearchInvoice(InvoicesModel invoices)
|
public IActionResult SearchInvoice(InvoicesModel invoices)
|
||||||
{
|
{
|
||||||
return RedirectToAction(nameof(ListInvoices), new
|
return RedirectToAction(nameof(ListInvoices), new
|
||||||
{
|
{
|
||||||
searchTerm = invoices.SearchTerm,
|
searchTerm = invoices.SearchTerm,
|
||||||
skip = invoices.Skip,
|
skip = invoices.Skip,
|
||||||
count = invoices.Count,
|
count = invoices.Count,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[TempData]
|
[TempData]
|
||||||
public string StatusMessage
|
public string StatusMessage
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetUserId()
|
private string GetUserId()
|
||||||
{
|
{
|
||||||
return _UserManager.GetUserId(User);
|
return _UserManager.GetUserId(User);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,119 +41,119 @@ using NBXplorer;
|
|||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
public partial class InvoiceController : Controller
|
public partial class InvoiceController : Controller
|
||||||
{
|
{
|
||||||
InvoiceRepository _InvoiceRepository;
|
InvoiceRepository _InvoiceRepository;
|
||||||
BTCPayWallet _Wallet;
|
BTCPayWallet _Wallet;
|
||||||
IRateProvider _RateProvider;
|
IRateProvider _RateProvider;
|
||||||
private InvoiceWatcher _Watcher;
|
private InvoiceWatcher _Watcher;
|
||||||
StoreRepository _StoreRepository;
|
StoreRepository _StoreRepository;
|
||||||
Network _Network;
|
Network _Network;
|
||||||
UserManager<ApplicationUser> _UserManager;
|
UserManager<ApplicationUser> _UserManager;
|
||||||
IFeeProvider _FeeProvider;
|
IFeeProvider _FeeProvider;
|
||||||
ExplorerClient _Explorer;
|
ExplorerClient _Explorer;
|
||||||
|
|
||||||
public InvoiceController(
|
public InvoiceController(
|
||||||
Network network,
|
Network network,
|
||||||
InvoiceRepository invoiceRepository,
|
InvoiceRepository invoiceRepository,
|
||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
BTCPayWallet wallet,
|
BTCPayWallet wallet,
|
||||||
IRateProvider rateProvider,
|
IRateProvider rateProvider,
|
||||||
StoreRepository storeRepository,
|
StoreRepository storeRepository,
|
||||||
InvoiceWatcher watcher,
|
InvoiceWatcher watcher,
|
||||||
ExplorerClient explorerClient,
|
ExplorerClient explorerClient,
|
||||||
IFeeProvider feeProvider)
|
IFeeProvider feeProvider)
|
||||||
{
|
{
|
||||||
_Explorer = explorerClient ?? throw new ArgumentNullException(nameof(explorerClient));
|
_Explorer = explorerClient ?? throw new ArgumentNullException(nameof(explorerClient));
|
||||||
_StoreRepository = storeRepository ?? throw new ArgumentNullException(nameof(storeRepository));
|
_StoreRepository = storeRepository ?? throw new ArgumentNullException(nameof(storeRepository));
|
||||||
_Network = network ?? throw new ArgumentNullException(nameof(network));
|
_Network = network ?? throw new ArgumentNullException(nameof(network));
|
||||||
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
|
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
|
||||||
_Wallet = wallet ?? throw new ArgumentNullException(nameof(wallet));
|
_Wallet = wallet ?? throw new ArgumentNullException(nameof(wallet));
|
||||||
_RateProvider = rateProvider ?? throw new ArgumentNullException(nameof(rateProvider));
|
_RateProvider = rateProvider ?? throw new ArgumentNullException(nameof(rateProvider));
|
||||||
_Watcher = watcher ?? throw new ArgumentNullException(nameof(watcher));
|
_Watcher = watcher ?? throw new ArgumentNullException(nameof(watcher));
|
||||||
_UserManager = userManager;
|
_UserManager = userManager;
|
||||||
_FeeProvider = feeProvider ?? throw new ArgumentNullException(nameof(feeProvider));
|
_FeeProvider = feeProvider ?? throw new ArgumentNullException(nameof(feeProvider));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl, double expiryMinutes = 15, double monitoringMinutes = 60)
|
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl, double expiryMinutes = 15, double monitoringMinutes = 60)
|
||||||
{
|
{
|
||||||
//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
|
//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 derivationStrategy = store.DerivationStrategy;
|
||||||
var entity = new InvoiceEntity
|
var entity = new InvoiceEntity
|
||||||
{
|
{
|
||||||
InvoiceTime = DateTimeOffset.UtcNow,
|
InvoiceTime = DateTimeOffset.UtcNow,
|
||||||
DerivationStrategy = derivationStrategy ?? throw new BitpayHttpException(400, "This store has not configured the derivation strategy")
|
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;
|
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 ?
|
if (notificationUri == null || (notificationUri.Scheme != "http" && notificationUri.Scheme != "https")) //TODO: Filer non routable addresses ?
|
||||||
notificationUri = null;
|
notificationUri = null;
|
||||||
EmailAddressAttribute emailValidator = new EmailAddressAttribute();
|
EmailAddressAttribute emailValidator = new EmailAddressAttribute();
|
||||||
entity.ExpirationTime = entity.InvoiceTime.AddMinutes(expiryMinutes);
|
entity.ExpirationTime = entity.InvoiceTime.AddMinutes(expiryMinutes);
|
||||||
entity.MonitoringExpiration = entity.InvoiceTime.AddMinutes(monitoringMinutes);
|
entity.MonitoringExpiration = entity.InvoiceTime.AddMinutes(monitoringMinutes);
|
||||||
entity.OrderId = invoice.OrderId;
|
entity.OrderId = invoice.OrderId;
|
||||||
entity.ServerUrl = serverUrl;
|
entity.ServerUrl = serverUrl;
|
||||||
entity.FullNotifications = invoice.FullNotifications;
|
entity.FullNotifications = invoice.FullNotifications;
|
||||||
entity.NotificationURL = notificationUri?.AbsoluteUri;
|
entity.NotificationURL = notificationUri?.AbsoluteUri;
|
||||||
entity.BuyerInformation = Map<Invoice, BuyerInformation>(invoice);
|
entity.BuyerInformation = Map<Invoice, BuyerInformation>(invoice);
|
||||||
//Another way of passing buyer info to support
|
//Another way of passing buyer info to support
|
||||||
FillBuyerInfo(invoice.Buyer, entity.BuyerInformation);
|
FillBuyerInfo(invoice.Buyer, entity.BuyerInformation);
|
||||||
if(entity?.BuyerInformation?.BuyerEmail != null)
|
if (entity?.BuyerInformation?.BuyerEmail != null)
|
||||||
{
|
{
|
||||||
if(!EmailValidator.IsEmail(entity.BuyerInformation.BuyerEmail))
|
if (!EmailValidator.IsEmail(entity.BuyerInformation.BuyerEmail))
|
||||||
throw new BitpayHttpException(400, "Invalid email");
|
throw new BitpayHttpException(400, "Invalid email");
|
||||||
entity.RefundMail = entity.BuyerInformation.BuyerEmail;
|
entity.RefundMail = entity.BuyerInformation.BuyerEmail;
|
||||||
}
|
}
|
||||||
entity.ProductInformation = Map<Invoice, ProductInformation>(invoice);
|
entity.ProductInformation = Map<Invoice, ProductInformation>(invoice);
|
||||||
entity.RedirectURL = invoice.RedirectURL ?? store.StoreWebsite;
|
entity.RedirectURL = invoice.RedirectURL ?? store.StoreWebsite;
|
||||||
entity.Status = "new";
|
entity.Status = "new";
|
||||||
entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);
|
entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);
|
||||||
entity.TxFee = store.GetStoreBlob(_Network).NetworkFeeDisabled ? Money.Zero : (await _FeeProvider.GetFeeRateAsync()).GetFee(100); // assume price for 100 bytes
|
entity.TxFee = store.GetStoreBlob(_Network).NetworkFeeDisabled ? Money.Zero : (await _FeeProvider.GetFeeRateAsync()).GetFee(100); // assume price for 100 bytes
|
||||||
entity.Rate = (double)await _RateProvider.GetRateAsync(invoice.Currency);
|
entity.Rate = (double)await _RateProvider.GetRateAsync(invoice.Currency);
|
||||||
entity.PosData = invoice.PosData;
|
entity.PosData = invoice.PosData;
|
||||||
entity.DepositAddress = await _Wallet.ReserveAddressAsync(ParseDerivationStrategy(derivationStrategy));
|
entity.DepositAddress = await _Wallet.ReserveAddressAsync(ParseDerivationStrategy(derivationStrategy));
|
||||||
|
|
||||||
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity);
|
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity);
|
||||||
await _Watcher.WatchAsync(entity.Id);
|
await _Watcher.WatchAsync(entity.Id);
|
||||||
var resp = entity.EntityToDTO();
|
var resp = entity.EntityToDTO();
|
||||||
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
|
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
|
||||||
}
|
}
|
||||||
|
|
||||||
private SpeedPolicy ParseSpeedPolicy(string transactionSpeed, SpeedPolicy defaultPolicy)
|
private SpeedPolicy ParseSpeedPolicy(string transactionSpeed, SpeedPolicy defaultPolicy)
|
||||||
{
|
{
|
||||||
if(transactionSpeed == null)
|
if (transactionSpeed == null)
|
||||||
return defaultPolicy;
|
return defaultPolicy;
|
||||||
var mappings = new Dictionary<string, SpeedPolicy>();
|
var mappings = new Dictionary<string, SpeedPolicy>();
|
||||||
mappings.Add("low", SpeedPolicy.LowSpeed);
|
mappings.Add("low", SpeedPolicy.LowSpeed);
|
||||||
mappings.Add("medium", SpeedPolicy.MediumSpeed);
|
mappings.Add("medium", SpeedPolicy.MediumSpeed);
|
||||||
mappings.Add("high", SpeedPolicy.HighSpeed);
|
mappings.Add("high", SpeedPolicy.HighSpeed);
|
||||||
if(!mappings.TryGetValue(transactionSpeed, out SpeedPolicy policy))
|
if (!mappings.TryGetValue(transactionSpeed, out SpeedPolicy policy))
|
||||||
policy = defaultPolicy;
|
policy = defaultPolicy;
|
||||||
return policy;
|
return policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FillBuyerInfo(Buyer buyer, BuyerInformation buyerInformation)
|
private void FillBuyerInfo(Buyer buyer, BuyerInformation buyerInformation)
|
||||||
{
|
{
|
||||||
if(buyer == null)
|
if (buyer == null)
|
||||||
return;
|
return;
|
||||||
buyerInformation.BuyerAddress1 = buyerInformation.BuyerAddress1 ?? buyer.Address1;
|
buyerInformation.BuyerAddress1 = buyerInformation.BuyerAddress1 ?? buyer.Address1;
|
||||||
buyerInformation.BuyerAddress2 = buyerInformation.BuyerAddress2 ?? buyer.Address2;
|
buyerInformation.BuyerAddress2 = buyerInformation.BuyerAddress2 ?? buyer.Address2;
|
||||||
buyerInformation.BuyerCity = buyerInformation.BuyerCity ?? buyer.City;
|
buyerInformation.BuyerCity = buyerInformation.BuyerCity ?? buyer.City;
|
||||||
buyerInformation.BuyerCountry = buyerInformation.BuyerCountry ?? buyer.country;
|
buyerInformation.BuyerCountry = buyerInformation.BuyerCountry ?? buyer.country;
|
||||||
buyerInformation.BuyerEmail = buyerInformation.BuyerEmail ?? buyer.email;
|
buyerInformation.BuyerEmail = buyerInformation.BuyerEmail ?? buyer.email;
|
||||||
buyerInformation.BuyerName = buyerInformation.BuyerName ?? buyer.Name;
|
buyerInformation.BuyerName = buyerInformation.BuyerName ?? buyer.Name;
|
||||||
buyerInformation.BuyerPhone = buyerInformation.BuyerPhone ?? buyer.phone;
|
buyerInformation.BuyerPhone = buyerInformation.BuyerPhone ?? buyer.phone;
|
||||||
buyerInformation.BuyerState = buyerInformation.BuyerState ?? buyer.State;
|
buyerInformation.BuyerState = buyerInformation.BuyerState ?? buyer.State;
|
||||||
buyerInformation.BuyerZip = buyerInformation.BuyerZip ?? buyer.zip;
|
buyerInformation.BuyerZip = buyerInformation.BuyerZip ?? buyer.zip;
|
||||||
}
|
}
|
||||||
|
|
||||||
private DerivationStrategyBase ParseDerivationStrategy(string derivationStrategy)
|
private DerivationStrategyBase ParseDerivationStrategy(string derivationStrategy)
|
||||||
{
|
{
|
||||||
return new DerivationStrategyFactory(_Network).Parse(derivationStrategy);
|
return new DerivationStrategyFactory(_Network).Parse(derivationStrategy);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TDest Map<TFrom, TDest>(TFrom data)
|
private TDest Map<TFrom, TDest>(TFrom data)
|
||||||
{
|
{
|
||||||
return JsonConvert.DeserializeObject<TDest>(JsonConvert.SerializeObject(data));
|
return JsonConvert.DeserializeObject<TDest>(JsonConvert.SerializeObject(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,34 +7,34 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
public class PaymentRequestActionResult : IActionResult
|
public class PaymentRequestActionResult : IActionResult
|
||||||
{
|
{
|
||||||
PaymentRequest req;
|
PaymentRequest req;
|
||||||
public PaymentRequestActionResult(PaymentRequest req)
|
public PaymentRequestActionResult(PaymentRequest req)
|
||||||
{
|
{
|
||||||
this.req = req;
|
this.req = req;
|
||||||
}
|
}
|
||||||
public Task ExecuteResultAsync(ActionContext context)
|
public Task ExecuteResultAsync(ActionContext context)
|
||||||
{
|
{
|
||||||
context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary";
|
context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary";
|
||||||
context.HttpContext.Response.ContentType = "application/bitcoin-paymentrequest";
|
context.HttpContext.Response.ContentType = "application/bitcoin-paymentrequest";
|
||||||
req.WriteTo(context.HttpContext.Response.Body);
|
req.WriteTo(context.HttpContext.Response.Body);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public class PaymentAckActionResult : IActionResult
|
public class PaymentAckActionResult : IActionResult
|
||||||
{
|
{
|
||||||
PaymentACK req;
|
PaymentACK req;
|
||||||
public PaymentAckActionResult(PaymentACK req)
|
public PaymentAckActionResult(PaymentACK req)
|
||||||
{
|
{
|
||||||
this.req = req;
|
this.req = req;
|
||||||
}
|
}
|
||||||
public Task ExecuteResultAsync(ActionContext context)
|
public Task ExecuteResultAsync(ActionContext context)
|
||||||
{
|
{
|
||||||
context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary";
|
context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary";
|
||||||
context.HttpContext.Response.ContentType = "application/bitcoin-paymentack";
|
context.HttpContext.Response.ContentType = "application/bitcoin-paymentack";
|
||||||
req.WriteTo(context.HttpContext.Response.Body);
|
req.WriteTo(context.HttpContext.Response.Body);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,31 +10,31 @@ using BTCPayServer.Services.Rates;
|
|||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
public class RateController : Controller
|
public class RateController : Controller
|
||||||
{
|
{
|
||||||
IRateProvider _RateProvider;
|
IRateProvider _RateProvider;
|
||||||
CurrencyNameTable _CurrencyNameTable;
|
CurrencyNameTable _CurrencyNameTable;
|
||||||
public RateController(IRateProvider rateProvider, CurrencyNameTable currencyNameTable)
|
public RateController(IRateProvider rateProvider, CurrencyNameTable currencyNameTable)
|
||||||
{
|
{
|
||||||
_RateProvider = rateProvider ?? throw new ArgumentNullException(nameof(rateProvider));
|
_RateProvider = rateProvider ?? throw new ArgumentNullException(nameof(rateProvider));
|
||||||
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("rates")]
|
[Route("rates")]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[BitpayAPIConstraint]
|
[BitpayAPIConstraint]
|
||||||
public async Task<DataWrapper<NBitpayClient.Rate[]>> GetRates()
|
public async Task<DataWrapper<NBitpayClient.Rate[]>> GetRates()
|
||||||
{
|
{
|
||||||
var allRates = (await _RateProvider.GetRatesAsync());
|
var allRates = (await _RateProvider.GetRatesAsync());
|
||||||
return new DataWrapper<NBitpayClient.Rate[]>
|
return new DataWrapper<NBitpayClient.Rate[]>
|
||||||
(allRates.Select(r =>
|
(allRates.Select(r =>
|
||||||
new NBitpayClient.Rate()
|
new NBitpayClient.Rate()
|
||||||
{
|
{
|
||||||
Code = r.Currency,
|
Code = r.Currency,
|
||||||
Name = _CurrencyNameTable.GetCurrencyData(r.Currency)?.Name,
|
Name = _CurrencyNameTable.GetCurrencyData(r.Currency)?.Name,
|
||||||
Value = r.Value
|
Value = r.Value
|
||||||
}).Where(n => n.Name != null).ToArray());
|
}).Where(n => n.Name != null).ToArray());
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,82 +16,82 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
[Authorize(Roles = Roles.ServerAdmin)]
|
[Authorize(Roles = Roles.ServerAdmin)]
|
||||||
public class ServerController : Controller
|
public class ServerController : Controller
|
||||||
{
|
{
|
||||||
private UserManager<ApplicationUser> _UserManager;
|
private UserManager<ApplicationUser> _UserManager;
|
||||||
SettingsRepository _SettingsRepository;
|
SettingsRepository _SettingsRepository;
|
||||||
|
|
||||||
public ServerController(UserManager<ApplicationUser> userManager, SettingsRepository settingsRepository)
|
public ServerController(UserManager<ApplicationUser> userManager, SettingsRepository settingsRepository)
|
||||||
{
|
{
|
||||||
_UserManager = userManager;
|
_UserManager = userManager;
|
||||||
_SettingsRepository = settingsRepository;
|
_SettingsRepository = settingsRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("server/users")]
|
[Route("server/users")]
|
||||||
public IActionResult ListUsers()
|
public IActionResult ListUsers()
|
||||||
{
|
{
|
||||||
var users = new UsersViewModel();
|
var users = new UsersViewModel();
|
||||||
users.Users
|
users.Users
|
||||||
= _UserManager.Users.Select(u => new UsersViewModel.UserViewModel()
|
= _UserManager.Users.Select(u => new UsersViewModel.UserViewModel()
|
||||||
{
|
{
|
||||||
Name = u.UserName,
|
Name = u.UserName,
|
||||||
Email = u.Email
|
Email = u.Email
|
||||||
}).ToList();
|
}).ToList();
|
||||||
return View(users);
|
return View(users);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("server/emails")]
|
[Route("server/emails")]
|
||||||
public async Task<IActionResult> Emails()
|
public async Task<IActionResult> Emails()
|
||||||
{
|
{
|
||||||
var data = (await _SettingsRepository.GetSettingAsync<EmailSettings>()) ?? new EmailSettings();
|
var data = (await _SettingsRepository.GetSettingAsync<EmailSettings>()) ?? new EmailSettings();
|
||||||
return View(new EmailsViewModel() { Settings = data });
|
return View(new EmailsViewModel() { Settings = data });
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("server/policies")]
|
[Route("server/policies")]
|
||||||
public async Task<IActionResult> Policies()
|
public async Task<IActionResult> Policies()
|
||||||
{
|
{
|
||||||
var data = (await _SettingsRepository.GetSettingAsync<PoliciesSettings>()) ?? new PoliciesSettings();
|
var data = (await _SettingsRepository.GetSettingAsync<PoliciesSettings>()) ?? new PoliciesSettings();
|
||||||
return View(data);
|
return View(data);
|
||||||
}
|
}
|
||||||
[Route("server/policies")]
|
[Route("server/policies")]
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> Policies(PoliciesSettings settings)
|
public async Task<IActionResult> Policies(PoliciesSettings settings)
|
||||||
{
|
{
|
||||||
await _SettingsRepository.UpdateSetting(settings);
|
await _SettingsRepository.UpdateSetting(settings);
|
||||||
TempData["StatusMessage"] = "Policies upadated successfully";
|
TempData["StatusMessage"] = "Policies upadated successfully";
|
||||||
return View(settings);
|
return View(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("server/emails")]
|
[Route("server/emails")]
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> Emails(EmailsViewModel model, string command)
|
public async Task<IActionResult> Emails(EmailsViewModel model, string command)
|
||||||
{
|
{
|
||||||
if(command == "Test")
|
if (command == "Test")
|
||||||
{
|
{
|
||||||
if(!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
return View(model);
|
return View(model);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = model.Settings.CreateSmtpClient();
|
var client = model.Settings.CreateSmtpClient();
|
||||||
await client.SendMailAsync(model.Settings.From, model.TestEmail, "BTCPay test", "BTCPay test");
|
await client.SendMailAsync(model.Settings.From, model.TestEmail, "BTCPay test", "BTCPay test");
|
||||||
model.StatusMessage = "Email sent to " + model.TestEmail + ", please, verify you received it";
|
model.StatusMessage = "Email sent to " + model.TestEmail + ", please, verify you received it";
|
||||||
}
|
}
|
||||||
catch(Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
model.StatusMessage = "Error: " + ex.Message;
|
model.StatusMessage = "Error: " + ex.Message;
|
||||||
}
|
}
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ModelState.Remove(nameof(model.TestEmail));
|
ModelState.Remove(nameof(model.TestEmail));
|
||||||
if(!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
return View(model);
|
return View(model);
|
||||||
await _SettingsRepository.UpdateSetting(model.Settings);
|
await _SettingsRepository.UpdateSetting(model.Settings);
|
||||||
model.StatusMessage = "Email settings saved";
|
model.StatusMessage = "Email settings saved";
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,399 +20,399 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
[Route("stores")]
|
[Route("stores")]
|
||||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||||
[Authorize(Policy = "CanAccessStore")]
|
[Authorize(Policy = "CanAccessStore")]
|
||||||
[AutoValidateAntiforgeryToken]
|
[AutoValidateAntiforgeryToken]
|
||||||
public class StoresController : Controller
|
public class StoresController : Controller
|
||||||
{
|
{
|
||||||
public StoresController(
|
public StoresController(
|
||||||
StoreRepository repo,
|
StoreRepository repo,
|
||||||
TokenRepository tokenRepo,
|
TokenRepository tokenRepo,
|
||||||
CallbackController callbackController,
|
CallbackController callbackController,
|
||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
AccessTokenController tokenController,
|
AccessTokenController tokenController,
|
||||||
BTCPayWallet wallet,
|
BTCPayWallet wallet,
|
||||||
Network network,
|
Network network,
|
||||||
IHostingEnvironment env)
|
IHostingEnvironment env)
|
||||||
{
|
{
|
||||||
_Repo = repo;
|
_Repo = repo;
|
||||||
_TokenRepository = tokenRepo;
|
_TokenRepository = tokenRepo;
|
||||||
_UserManager = userManager;
|
_UserManager = userManager;
|
||||||
_TokenController = tokenController;
|
_TokenController = tokenController;
|
||||||
_Wallet = wallet;
|
_Wallet = wallet;
|
||||||
_Env = env;
|
_Env = env;
|
||||||
_Network = network;
|
_Network = network;
|
||||||
_CallbackController = callbackController;
|
_CallbackController = callbackController;
|
||||||
}
|
}
|
||||||
Network _Network;
|
Network _Network;
|
||||||
CallbackController _CallbackController;
|
CallbackController _CallbackController;
|
||||||
BTCPayWallet _Wallet;
|
BTCPayWallet _Wallet;
|
||||||
AccessTokenController _TokenController;
|
AccessTokenController _TokenController;
|
||||||
StoreRepository _Repo;
|
StoreRepository _Repo;
|
||||||
TokenRepository _TokenRepository;
|
TokenRepository _TokenRepository;
|
||||||
UserManager<ApplicationUser> _UserManager;
|
UserManager<ApplicationUser> _UserManager;
|
||||||
IHostingEnvironment _Env;
|
IHostingEnvironment _Env;
|
||||||
|
|
||||||
[TempData]
|
[TempData]
|
||||||
public string StatusMessage
|
public string StatusMessage
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("create")]
|
[Route("create")]
|
||||||
public IActionResult CreateStore()
|
public IActionResult CreateStore()
|
||||||
{
|
{
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("create")]
|
[Route("create")]
|
||||||
public async Task<IActionResult> CreateStore(CreateStoreViewModel vm)
|
public async Task<IActionResult> CreateStore(CreateStoreViewModel vm)
|
||||||
{
|
{
|
||||||
if(!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
{
|
{
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
var store = await _Repo.CreateStore(GetUserId(), vm.Name);
|
var store = await _Repo.CreateStore(GetUserId(), vm.Name);
|
||||||
CreatedStoreId = store.Id;
|
CreatedStoreId = store.Id;
|
||||||
StatusMessage = "Store successfully created";
|
StatusMessage = "Store successfully created";
|
||||||
return RedirectToAction(nameof(ListStores));
|
return RedirectToAction(nameof(ListStores));
|
||||||
}
|
}
|
||||||
|
|
||||||
public string CreatedStoreId
|
public string CreatedStoreId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> ListStores()
|
public async Task<IActionResult> ListStores()
|
||||||
{
|
{
|
||||||
StoresViewModel result = new StoresViewModel();
|
StoresViewModel result = new StoresViewModel();
|
||||||
result.StatusMessage = StatusMessage;
|
result.StatusMessage = StatusMessage;
|
||||||
var stores = await _Repo.GetStoresByUserId(GetUserId());
|
var stores = await _Repo.GetStoresByUserId(GetUserId());
|
||||||
var balances = stores.Select(async s => string.IsNullOrEmpty(s.DerivationStrategy) ? Money.Zero : await _Wallet.GetBalance(ParseDerivationStrategy(s.DerivationStrategy))).ToArray();
|
var balances = stores.Select(async s => string.IsNullOrEmpty(s.DerivationStrategy) ? Money.Zero : await _Wallet.GetBalance(ParseDerivationStrategy(s.DerivationStrategy))).ToArray();
|
||||||
|
|
||||||
for(int i = 0; i < stores.Length; i++)
|
for (int i = 0; i < stores.Length; i++)
|
||||||
{
|
{
|
||||||
var store = stores[i];
|
var store = stores[i];
|
||||||
result.Stores.Add(new StoresViewModel.StoreViewModel()
|
result.Stores.Add(new StoresViewModel.StoreViewModel()
|
||||||
{
|
{
|
||||||
Id = store.Id,
|
Id = store.Id,
|
||||||
Name = store.StoreName,
|
Name = store.StoreName,
|
||||||
WebSite = store.StoreWebsite,
|
WebSite = store.StoreWebsite,
|
||||||
Balance = await balances[i]
|
Balance = await balances[i]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return View(result);
|
return View(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("{storeId}/delete")]
|
[Route("{storeId}/delete")]
|
||||||
public async Task<IActionResult> DeleteStore(string storeId)
|
public async Task<IActionResult> DeleteStore(string storeId)
|
||||||
{
|
{
|
||||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||||
if(store == null)
|
if (store == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
return View("Confirm", new ConfirmModel()
|
return View("Confirm", new ConfirmModel()
|
||||||
{
|
{
|
||||||
Title = "Delete store " + store.StoreName,
|
Title = "Delete store " + store.StoreName,
|
||||||
Description = "This store will still be accessible to users sharing it",
|
Description = "This store will still be accessible to users sharing it",
|
||||||
Action = "Delete"
|
Action = "Delete"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("{storeId}/delete")]
|
[Route("{storeId}/delete")]
|
||||||
public async Task<IActionResult> DeleteStorePost(string storeId)
|
public async Task<IActionResult> DeleteStorePost(string storeId)
|
||||||
{
|
{
|
||||||
var userId = GetUserId();
|
var userId = GetUserId();
|
||||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||||
if(store == null)
|
if (store == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
await _Repo.RemoveStore(storeId, userId);
|
await _Repo.RemoveStore(storeId, userId);
|
||||||
StatusMessage = "Store removed successfully";
|
StatusMessage = "Store removed successfully";
|
||||||
return RedirectToAction(nameof(ListStores));
|
return RedirectToAction(nameof(ListStores));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("{storeId}")]
|
[Route("{storeId}")]
|
||||||
public async Task<IActionResult> UpdateStore(string storeId)
|
public async Task<IActionResult> UpdateStore(string storeId)
|
||||||
{
|
{
|
||||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||||
if(store == null)
|
if (store == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
var vm = new StoreViewModel();
|
var vm = new StoreViewModel();
|
||||||
vm.StoreName = store.StoreName;
|
vm.StoreName = store.StoreName;
|
||||||
vm.StoreWebsite = store.StoreWebsite;
|
vm.StoreWebsite = store.StoreWebsite;
|
||||||
vm.NetworkFee = !store.GetStoreBlob(_Network).NetworkFeeDisabled;
|
vm.NetworkFee = !store.GetStoreBlob(_Network).NetworkFeeDisabled;
|
||||||
vm.SpeedPolicy = store.SpeedPolicy;
|
vm.SpeedPolicy = store.SpeedPolicy;
|
||||||
vm.DerivationScheme = store.DerivationStrategy;
|
vm.DerivationScheme = store.DerivationStrategy;
|
||||||
vm.StatusMessage = StatusMessage;
|
vm.StatusMessage = StatusMessage;
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("{storeId}")]
|
[Route("{storeId}")]
|
||||||
public async Task<IActionResult> UpdateStore(string storeId, StoreViewModel model, string command)
|
public async Task<IActionResult> UpdateStore(string storeId, StoreViewModel model, string command)
|
||||||
{
|
{
|
||||||
if(!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
{
|
{
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||||
if(store == null)
|
if (store == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
if(command == "Save")
|
if (command == "Save")
|
||||||
{
|
{
|
||||||
bool needUpdate = false;
|
bool needUpdate = false;
|
||||||
if(store.SpeedPolicy != model.SpeedPolicy)
|
if (store.SpeedPolicy != model.SpeedPolicy)
|
||||||
{
|
{
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
store.SpeedPolicy = model.SpeedPolicy;
|
store.SpeedPolicy = model.SpeedPolicy;
|
||||||
}
|
}
|
||||||
if(store.StoreName != model.StoreName)
|
if (store.StoreName != model.StoreName)
|
||||||
{
|
{
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
store.StoreName = model.StoreName;
|
store.StoreName = model.StoreName;
|
||||||
}
|
}
|
||||||
if(store.StoreWebsite != model.StoreWebsite)
|
if (store.StoreWebsite != model.StoreWebsite)
|
||||||
{
|
{
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
store.StoreWebsite = model.StoreWebsite;
|
store.StoreWebsite = model.StoreWebsite;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(store.DerivationStrategy != model.DerivationScheme)
|
if (store.DerivationStrategy != model.DerivationScheme)
|
||||||
{
|
{
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var strategy = ParseDerivationStrategy(model.DerivationScheme);
|
var strategy = ParseDerivationStrategy(model.DerivationScheme);
|
||||||
await _Wallet.TrackAsync(strategy);
|
await _Wallet.TrackAsync(strategy);
|
||||||
await _CallbackController.RegisterCallbackUriAsync(strategy, Request);
|
await _CallbackController.RegisterCallbackUriAsync(strategy, Request);
|
||||||
store.DerivationStrategy = model.DerivationScheme;
|
store.DerivationStrategy = model.DerivationScheme;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(nameof(model.DerivationScheme), "Invalid Derivation Scheme");
|
ModelState.AddModelError(nameof(model.DerivationScheme), "Invalid Derivation Scheme");
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(store.GetStoreBlob(_Network).NetworkFeeDisabled != !model.NetworkFee)
|
if (store.GetStoreBlob(_Network).NetworkFeeDisabled != !model.NetworkFee)
|
||||||
{
|
{
|
||||||
var blob = store.GetStoreBlob(_Network);
|
var blob = store.GetStoreBlob(_Network);
|
||||||
blob.NetworkFeeDisabled = !model.NetworkFee;
|
blob.NetworkFeeDisabled = !model.NetworkFee;
|
||||||
store.SetStoreBlob(blob, _Network);
|
store.SetStoreBlob(blob, _Network);
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(needUpdate)
|
if (needUpdate)
|
||||||
{
|
{
|
||||||
await _Repo.UpdateStore(store);
|
await _Repo.UpdateStore(store);
|
||||||
StatusMessage = "Store successfully updated";
|
StatusMessage = "Store successfully updated";
|
||||||
}
|
}
|
||||||
|
|
||||||
return RedirectToAction(nameof(UpdateStore), new
|
return RedirectToAction(nameof(UpdateStore), new
|
||||||
{
|
{
|
||||||
storeId = storeId
|
storeId = storeId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var facto = new DerivationStrategyFactory(_Network);
|
var facto = new DerivationStrategyFactory(_Network);
|
||||||
var scheme = facto.Parse(model.DerivationScheme);
|
var scheme = facto.Parse(model.DerivationScheme);
|
||||||
var line = scheme.GetLineFor(DerivationFeature.Deposit);
|
var line = scheme.GetLineFor(DerivationFeature.Deposit);
|
||||||
|
|
||||||
for(int i = 0; i < 10; i++)
|
for (int i = 0; i < 10; i++)
|
||||||
{
|
{
|
||||||
var address = line.Derive((uint)i);
|
var address = line.Derive((uint)i);
|
||||||
model.AddressSamples.Add((line.Path.Derive((uint)i).ToString(), address.ScriptPubKey.GetDestinationAddress(_Network).ToString()));
|
model.AddressSamples.Add((line.Path.Derive((uint)i).ToString(), address.ScriptPubKey.GetDestinationAddress(_Network).ToString()));
|
||||||
}
|
}
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DerivationStrategyBase ParseDerivationStrategy(string derivationScheme)
|
private DerivationStrategyBase ParseDerivationStrategy(string derivationScheme)
|
||||||
{
|
{
|
||||||
return new DerivationStrategyFactory(_Network).Parse(derivationScheme);
|
return new DerivationStrategyFactory(_Network).Parse(derivationScheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("{storeId}/Tokens")]
|
[Route("{storeId}/Tokens")]
|
||||||
public async Task<IActionResult> ListTokens(string storeId)
|
public async Task<IActionResult> ListTokens(string storeId)
|
||||||
{
|
{
|
||||||
var model = new TokensViewModel();
|
var model = new TokensViewModel();
|
||||||
var tokens = await _TokenRepository.GetTokensByStoreIdAsync(storeId);
|
var tokens = await _TokenRepository.GetTokensByStoreIdAsync(storeId);
|
||||||
model.StatusMessage = StatusMessage;
|
model.StatusMessage = StatusMessage;
|
||||||
model.Tokens = tokens.Select(t => new TokenViewModel()
|
model.Tokens = tokens.Select(t => new TokenViewModel()
|
||||||
{
|
{
|
||||||
Facade = t.Facade,
|
Facade = t.Facade,
|
||||||
Label = t.Label,
|
Label = t.Label,
|
||||||
SIN = t.SIN,
|
SIN = t.SIN,
|
||||||
Id = t.Value
|
Id = t.Value
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("/api-tokens")]
|
[Route("/api-tokens")]
|
||||||
[Route("{storeId}/Tokens/Create")]
|
[Route("{storeId}/Tokens/Create")]
|
||||||
public async Task<IActionResult> CreateToken(string storeId, CreateTokenViewModel model)
|
public async Task<IActionResult> CreateToken(string storeId, CreateTokenViewModel model)
|
||||||
{
|
{
|
||||||
if(!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
{
|
{
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
model.Label = model.Label ?? String.Empty;
|
model.Label = model.Label ?? String.Empty;
|
||||||
if(storeId == null) // Permissions are not checked by Policy if the storeId is not passed by url
|
if (storeId == null) // Permissions are not checked by Policy if the storeId is not passed by url
|
||||||
{
|
{
|
||||||
storeId = model.StoreId;
|
storeId = model.StoreId;
|
||||||
var userId = GetUserId();
|
var userId = GetUserId();
|
||||||
if(userId == null)
|
if (userId == null)
|
||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
var store = await _Repo.FindStore(storeId, userId);
|
var store = await _Repo.FindStore(storeId, userId);
|
||||||
if(store == null)
|
if (store == null)
|
||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokenRequest = new TokenRequest()
|
var tokenRequest = new TokenRequest()
|
||||||
{
|
{
|
||||||
Facade = model.Facade,
|
Facade = model.Facade,
|
||||||
Label = model.Label,
|
Label = model.Label,
|
||||||
Id = model.PublicKey == null ? null : NBitpayClient.Extensions.BitIdExtensions.GetBitIDSIN(new PubKey(model.PublicKey))
|
Id = model.PublicKey == null ? null : NBitpayClient.Extensions.BitIdExtensions.GetBitIDSIN(new PubKey(model.PublicKey))
|
||||||
};
|
};
|
||||||
|
|
||||||
string pairingCode = null;
|
string pairingCode = null;
|
||||||
if(model.PublicKey == null)
|
if (model.PublicKey == null)
|
||||||
{
|
{
|
||||||
tokenRequest.PairingCode = await _TokenRepository.CreatePairingCodeAsync();
|
tokenRequest.PairingCode = await _TokenRepository.CreatePairingCodeAsync();
|
||||||
await _TokenRepository.UpdatePairingCode(new PairingCodeEntity()
|
await _TokenRepository.UpdatePairingCode(new PairingCodeEntity()
|
||||||
{
|
{
|
||||||
Id = tokenRequest.PairingCode,
|
Id = tokenRequest.PairingCode,
|
||||||
Facade = model.Facade,
|
Facade = model.Facade,
|
||||||
Label = model.Label,
|
Label = model.Label,
|
||||||
});
|
});
|
||||||
await _TokenRepository.PairWithStoreAsync(tokenRequest.PairingCode, storeId);
|
await _TokenRepository.PairWithStoreAsync(tokenRequest.PairingCode, storeId);
|
||||||
pairingCode = tokenRequest.PairingCode;
|
pairingCode = tokenRequest.PairingCode;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pairingCode = ((DataWrapper<List<PairingCodeResponse>>)await _TokenController.Tokens(tokenRequest)).Data[0].PairingCode;
|
pairingCode = ((DataWrapper<List<PairingCodeResponse>>)await _TokenController.Tokens(tokenRequest)).Data[0].PairingCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
return RedirectToAction(nameof(RequestPairing), new
|
return RedirectToAction(nameof(RequestPairing), new
|
||||||
{
|
{
|
||||||
pairingCode = pairingCode,
|
pairingCode = pairingCode,
|
||||||
selectedStore = storeId
|
selectedStore = storeId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("/api-tokens")]
|
[Route("/api-tokens")]
|
||||||
[Route("{storeId}/Tokens/Create")]
|
[Route("{storeId}/Tokens/Create")]
|
||||||
public async Task<IActionResult> CreateToken(string storeId)
|
public async Task<IActionResult> CreateToken(string storeId)
|
||||||
{
|
{
|
||||||
var userId = GetUserId();
|
var userId = GetUserId();
|
||||||
if(string.IsNullOrWhiteSpace(userId))
|
if (string.IsNullOrWhiteSpace(userId))
|
||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
var model = new CreateTokenViewModel();
|
var model = new CreateTokenViewModel();
|
||||||
model.Facade = "merchant";
|
model.Facade = "merchant";
|
||||||
ViewBag.HidePublicKey = storeId == null;
|
ViewBag.HidePublicKey = storeId == null;
|
||||||
ViewBag.ShowStores = storeId == null;
|
ViewBag.ShowStores = storeId == null;
|
||||||
ViewBag.ShowMenu = storeId != null;
|
ViewBag.ShowMenu = storeId != null;
|
||||||
model.StoreId = storeId;
|
model.StoreId = storeId;
|
||||||
if(storeId == null)
|
if (storeId == null)
|
||||||
{
|
{
|
||||||
model.Stores = new SelectList(await _Repo.GetStoresByUserId(userId), nameof(StoreData.Id), nameof(StoreData.StoreName), storeId);
|
model.Stores = new SelectList(await _Repo.GetStoresByUserId(userId), nameof(StoreData.Id), nameof(StoreData.StoreName), storeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("{storeId}/Tokens/Delete")]
|
[Route("{storeId}/Tokens/Delete")]
|
||||||
public async Task<IActionResult> DeleteToken(string storeId, string tokenId)
|
public async Task<IActionResult> DeleteToken(string storeId, string tokenId)
|
||||||
{
|
{
|
||||||
var token = await _TokenRepository.GetToken(tokenId);
|
var token = await _TokenRepository.GetToken(tokenId);
|
||||||
if(token == null ||
|
if (token == null ||
|
||||||
token.StoreId != storeId ||
|
token.StoreId != storeId ||
|
||||||
!await _TokenRepository.DeleteToken(tokenId))
|
!await _TokenRepository.DeleteToken(tokenId))
|
||||||
StatusMessage = "Failure to revoke this token";
|
StatusMessage = "Failure to revoke this token";
|
||||||
else
|
else
|
||||||
StatusMessage = "Token revoked";
|
StatusMessage = "Token revoked";
|
||||||
return RedirectToAction(nameof(ListTokens));
|
return RedirectToAction(nameof(ListTokens));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("/api-access-request")]
|
[Route("/api-access-request")]
|
||||||
public async Task<IActionResult> RequestPairing(string pairingCode, string selectedStore = null)
|
public async Task<IActionResult> RequestPairing(string pairingCode, string selectedStore = null)
|
||||||
{
|
{
|
||||||
var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
|
var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
|
||||||
if(pairing == null)
|
if (pairing == null)
|
||||||
{
|
{
|
||||||
StatusMessage = "Unknown pairing code";
|
StatusMessage = "Unknown pairing code";
|
||||||
return RedirectToAction(nameof(ListStores));
|
return RedirectToAction(nameof(ListStores));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var stores = await _Repo.GetStoresByUserId(GetUserId());
|
var stores = await _Repo.GetStoresByUserId(GetUserId());
|
||||||
return View(new PairingModel()
|
return View(new PairingModel()
|
||||||
{
|
{
|
||||||
Id = pairing.Id,
|
Id = pairing.Id,
|
||||||
Facade = pairing.Facade,
|
Facade = pairing.Facade,
|
||||||
Label = pairing.Label,
|
Label = pairing.Label,
|
||||||
SIN = pairing.SIN ?? "Server-Initiated Pairing",
|
SIN = pairing.SIN ?? "Server-Initiated Pairing",
|
||||||
SelectedStore = selectedStore ?? stores.FirstOrDefault()?.Id,
|
SelectedStore = selectedStore ?? stores.FirstOrDefault()?.Id,
|
||||||
Stores = stores.Select(s => new PairingModel.StoreViewModel()
|
Stores = stores.Select(s => new PairingModel.StoreViewModel()
|
||||||
{
|
{
|
||||||
Id = s.Id,
|
Id = s.Id,
|
||||||
Name = string.IsNullOrEmpty(s.StoreName) ? s.Id : s.StoreName
|
Name = string.IsNullOrEmpty(s.StoreName) ? s.Id : s.StoreName
|
||||||
}).ToArray()
|
}).ToArray()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("api-access-request")]
|
[Route("api-access-request")]
|
||||||
public async Task<IActionResult> Pair(string pairingCode, string selectedStore)
|
public async Task<IActionResult> Pair(string pairingCode, string selectedStore)
|
||||||
{
|
{
|
||||||
if(pairingCode == null)
|
if (pairingCode == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
var store = await _Repo.FindStore(selectedStore, GetUserId());
|
var store = await _Repo.FindStore(selectedStore, GetUserId());
|
||||||
var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
|
var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
|
||||||
if(store == null || pairing == null)
|
if (store == null || pairing == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
var pairingResult = await _TokenRepository.PairWithStoreAsync(pairingCode, store.Id);
|
var pairingResult = await _TokenRepository.PairWithStoreAsync(pairingCode, store.Id);
|
||||||
if(pairingResult == PairingResult.Complete || pairingResult == PairingResult.Partial)
|
if (pairingResult == PairingResult.Complete || pairingResult == PairingResult.Partial)
|
||||||
{
|
{
|
||||||
StatusMessage = "Pairing is successfull";
|
StatusMessage = "Pairing is successfull";
|
||||||
if(pairingResult == PairingResult.Partial)
|
if (pairingResult == PairingResult.Partial)
|
||||||
StatusMessage = "Server initiated pairing code: " + pairingCode;
|
StatusMessage = "Server initiated pairing code: " + pairingCode;
|
||||||
return RedirectToAction(nameof(ListTokens), new
|
return RedirectToAction(nameof(ListTokens), new
|
||||||
{
|
{
|
||||||
storeId = store.Id
|
storeId = store.Id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
StatusMessage = $"Pairing failed ({pairingResult})";
|
StatusMessage = $"Pairing failed ({pairingResult})";
|
||||||
return RedirectToAction(nameof(ListTokens), new
|
return RedirectToAction(nameof(ListTokens), new
|
||||||
{
|
{
|
||||||
storeId = store.Id
|
storeId = store.Id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetUserId()
|
private string GetUserId()
|
||||||
{
|
{
|
||||||
return _UserManager.GetUserId(User);
|
return _UserManager.GetUserId(User);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,26 +5,26 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
public class AddressInvoiceData
|
public class AddressInvoiceData
|
||||||
{
|
{
|
||||||
public string Address
|
public string Address
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InvoiceData InvoiceData
|
public InvoiceData InvoiceData
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string InvoiceDataId
|
public string InvoiceDataId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTimeOffset? CreatedTime
|
public DateTimeOffset? CreatedTime
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,126 +10,126 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
|
|||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
|
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
|
||||||
{
|
{
|
||||||
public ApplicationDbContext()
|
public ApplicationDbContext()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
|
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
|
||||||
: base(options)
|
: base(options)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public DbSet<InvoiceData> Invoices
|
public DbSet<InvoiceData> Invoices
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DbSet<HistoricalAddressInvoiceData> HistoricalAddressInvoices
|
public DbSet<HistoricalAddressInvoiceData> HistoricalAddressInvoices
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DbSet<PendingInvoiceData> PendingInvoices
|
public DbSet<PendingInvoiceData> PendingInvoices
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public DbSet<RefundAddressesData> RefundAddresses
|
public DbSet<RefundAddressesData> RefundAddresses
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DbSet<PaymentData> Payments
|
public DbSet<PaymentData> Payments
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DbSet<StoreData> Stores
|
public DbSet<StoreData> Stores
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DbSet<UserStore> UserStore
|
public DbSet<UserStore> UserStore
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DbSet<AddressInvoiceData> AddressInvoices
|
public DbSet<AddressInvoiceData> AddressInvoices
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DbSet<SettingData> Settings
|
public DbSet<SettingData> Settings
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public DbSet<PairingCodeData> PairingCodes
|
public DbSet<PairingCodeData> PairingCodes
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DbSet<PairedSINData> PairedSINData
|
public DbSet<PairedSINData> PairedSINData
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
var isConfigured = optionsBuilder.Options.Extensions.OfType<RelationalOptionsExtension>().Any();
|
var isConfigured = optionsBuilder.Options.Extensions.OfType<RelationalOptionsExtension>().Any();
|
||||||
if(!isConfigured)
|
if (!isConfigured)
|
||||||
optionsBuilder.UseSqlite("Data Source=temp.db");
|
optionsBuilder.UseSqlite("Data Source=temp.db");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder builder)
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
{
|
{
|
||||||
base.OnModelCreating(builder);
|
base.OnModelCreating(builder);
|
||||||
builder.Entity<InvoiceData>()
|
builder.Entity<InvoiceData>()
|
||||||
.HasIndex(o => o.StoreDataId);
|
.HasIndex(o => o.StoreDataId);
|
||||||
|
|
||||||
builder.Entity<PaymentData>()
|
builder.Entity<PaymentData>()
|
||||||
.HasIndex(o => o.InvoiceDataId);
|
.HasIndex(o => o.InvoiceDataId);
|
||||||
|
|
||||||
builder.Entity<RefundAddressesData>()
|
builder.Entity<RefundAddressesData>()
|
||||||
.HasIndex(o => o.InvoiceDataId);
|
.HasIndex(o => o.InvoiceDataId);
|
||||||
|
|
||||||
builder.Entity<UserStore>()
|
builder.Entity<UserStore>()
|
||||||
.HasKey(t => new
|
.HasKey(t => new
|
||||||
{
|
{
|
||||||
t.ApplicationUserId,
|
t.ApplicationUserId,
|
||||||
t.StoreDataId
|
t.StoreDataId
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Entity<UserStore>()
|
builder.Entity<UserStore>()
|
||||||
.HasOne(pt => pt.ApplicationUser)
|
.HasOne(pt => pt.ApplicationUser)
|
||||||
.WithMany(p => p.UserStores)
|
.WithMany(p => p.UserStores)
|
||||||
.HasForeignKey(pt => pt.ApplicationUserId);
|
.HasForeignKey(pt => pt.ApplicationUserId);
|
||||||
|
|
||||||
builder.Entity<UserStore>()
|
builder.Entity<UserStore>()
|
||||||
.HasOne(pt => pt.StoreData)
|
.HasOne(pt => pt.StoreData)
|
||||||
.WithMany(t => t.UserStores)
|
.WithMany(t => t.UserStores)
|
||||||
.HasForeignKey(pt => pt.StoreDataId);
|
.HasForeignKey(pt => pt.StoreDataId);
|
||||||
|
|
||||||
builder.Entity<AddressInvoiceData>()
|
builder.Entity<AddressInvoiceData>()
|
||||||
.HasKey(o => o.Address);
|
.HasKey(o => o.Address);
|
||||||
|
|
||||||
builder.Entity<PairingCodeData>()
|
builder.Entity<PairingCodeData>()
|
||||||
.HasKey(o => o.Id);
|
.HasKey(o => o.Id);
|
||||||
|
|
||||||
builder.Entity<PairedSINData>(b =>
|
builder.Entity<PairedSINData>(b =>
|
||||||
{
|
{
|
||||||
b.HasIndex(o => o.SIN);
|
b.HasIndex(o => o.SIN);
|
||||||
b.HasIndex(o => o.StoreDataId);
|
b.HasIndex(o => o.StoreDataId);
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Entity<HistoricalAddressInvoiceData>()
|
builder.Entity<HistoricalAddressInvoiceData>()
|
||||||
.HasKey(o => new
|
.HasKey(o => new
|
||||||
{
|
{
|
||||||
o.InvoiceDataId,
|
o.InvoiceDataId,
|
||||||
o.Address
|
o.Address
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,42 +9,42 @@ using Hangfire.PostgreSql;
|
|||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
public enum DatabaseType
|
public enum DatabaseType
|
||||||
{
|
{
|
||||||
Sqlite,
|
Sqlite,
|
||||||
Postgres
|
Postgres
|
||||||
}
|
}
|
||||||
public class ApplicationDbContextFactory
|
public class ApplicationDbContextFactory
|
||||||
{
|
{
|
||||||
string _ConnectionString;
|
string _ConnectionString;
|
||||||
DatabaseType _Type;
|
DatabaseType _Type;
|
||||||
public ApplicationDbContextFactory(DatabaseType type, string connectionString)
|
public ApplicationDbContextFactory(DatabaseType type, string connectionString)
|
||||||
{
|
{
|
||||||
_ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
_ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
||||||
_Type = type;
|
_Type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ApplicationDbContext CreateContext()
|
public ApplicationDbContext CreateContext()
|
||||||
{
|
{
|
||||||
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
|
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
|
||||||
ConfigureBuilder(builder);
|
ConfigureBuilder(builder);
|
||||||
return new ApplicationDbContext(builder.Options);
|
return new ApplicationDbContext(builder.Options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConfigureBuilder(DbContextOptionsBuilder builder)
|
public void ConfigureBuilder(DbContextOptionsBuilder builder)
|
||||||
{
|
{
|
||||||
if(_Type == DatabaseType.Sqlite)
|
if (_Type == DatabaseType.Sqlite)
|
||||||
builder.UseSqlite(_ConnectionString);
|
builder.UseSqlite(_ConnectionString);
|
||||||
else if(_Type == DatabaseType.Postgres)
|
else if (_Type == DatabaseType.Postgres)
|
||||||
builder.UseNpgsql(_ConnectionString);
|
builder.UseNpgsql(_ConnectionString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConfigureHangfireBuilder(IGlobalConfiguration builder)
|
public void ConfigureHangfireBuilder(IGlobalConfiguration builder)
|
||||||
{
|
{
|
||||||
if(_Type == DatabaseType.Sqlite)
|
if (_Type == DatabaseType.Sqlite)
|
||||||
builder.UseMemoryStorage(); //Sql provider does not support multiple workers
|
builder.UseMemoryStorage(); //Sql provider does not support multiple workers
|
||||||
else if(_Type == DatabaseType.Postgres)
|
else if (_Type == DatabaseType.Postgres)
|
||||||
builder.UsePostgreSqlStorage(_ConnectionString);
|
builder.UsePostgreSqlStorage(_ConnectionString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,24 +7,24 @@ namespace BTCPayServer.Data
|
|||||||
{
|
{
|
||||||
public class HistoricalAddressInvoiceData
|
public class HistoricalAddressInvoiceData
|
||||||
{
|
{
|
||||||
public string InvoiceDataId
|
public string InvoiceDataId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Address
|
public string Address
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTimeOffset Assigned
|
public DateTimeOffset Assigned
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTimeOffset? UnAssigned
|
public DateTimeOffset? UnAssigned
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,70 +6,70 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
public class InvoiceData
|
public class InvoiceData
|
||||||
{
|
{
|
||||||
public string StoreDataId
|
public string StoreDataId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public StoreData StoreData
|
public StoreData StoreData
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Id
|
public string Id
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTimeOffset Created
|
public DateTimeOffset Created
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PaymentData> Payments
|
public List<PaymentData> Payments
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<RefundAddressesData> RefundAddresses
|
public List<RefundAddressesData> RefundAddresses
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<HistoricalAddressInvoiceData> HistoricalAddressInvoices
|
public List<HistoricalAddressInvoiceData> HistoricalAddressInvoices
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] Blob
|
public byte[] Blob
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string ItemCode
|
public string ItemCode
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string OrderId
|
public string OrderId
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string Status
|
public string Status
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string ExceptionStatus
|
public string ExceptionStatus
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string CustomerEmail
|
public string CustomerEmail
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,33 +7,33 @@ namespace BTCPayServer.Data
|
|||||||
{
|
{
|
||||||
public class PairedSINData
|
public class PairedSINData
|
||||||
{
|
{
|
||||||
public string Id
|
public string Id
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Facade
|
public string Facade
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string StoreDataId
|
public string StoreDataId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string Label
|
public string Label
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public DateTimeOffset PairingTime
|
public DateTimeOffset PairingTime
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string SIN
|
public string SIN
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,44 +7,44 @@ namespace BTCPayServer.Data
|
|||||||
{
|
{
|
||||||
public class PairingCodeData
|
public class PairingCodeData
|
||||||
{
|
{
|
||||||
public string Id
|
public string Id
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Facade
|
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
public string StoreDataId
|
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
public DateTimeOffset Expiration
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Label
|
public string Facade
|
||||||
{
|
{
|
||||||
get;
|
get; set;
|
||||||
set;
|
}
|
||||||
}
|
public string StoreDataId
|
||||||
public string SIN
|
{
|
||||||
{
|
get; set;
|
||||||
get;
|
}
|
||||||
set;
|
public DateTimeOffset Expiration
|
||||||
}
|
{
|
||||||
public DateTime DateCreated
|
get;
|
||||||
{
|
set;
|
||||||
get;
|
}
|
||||||
set;
|
|
||||||
}
|
public string Label
|
||||||
public string TokenValue
|
{
|
||||||
{
|
get;
|
||||||
get;
|
set;
|
||||||
set;
|
}
|
||||||
}
|
public string SIN
|
||||||
}
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
public DateTime DateCreated
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
public string TokenValue
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,25 +5,25 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
public class PaymentData
|
public class PaymentData
|
||||||
{
|
{
|
||||||
public string Id
|
public string Id
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string InvoiceDataId
|
public string InvoiceDataId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public InvoiceData InvoiceData
|
public InvoiceData InvoiceData
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] Blob
|
public byte[] Blob
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ namespace BTCPayServer.Data
|
|||||||
{
|
{
|
||||||
public class PendingInvoiceData
|
public class PendingInvoiceData
|
||||||
{
|
{
|
||||||
public string Id
|
public string Id
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,21 +7,21 @@ namespace BTCPayServer.Data
|
|||||||
{
|
{
|
||||||
public class RefundAddressesData
|
public class RefundAddressesData
|
||||||
{
|
{
|
||||||
public string Id
|
public string Id
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string InvoiceDataId
|
public string InvoiceDataId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public InvoiceData InvoiceData
|
public InvoiceData InvoiceData
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public byte[] Blob
|
public byte[] Blob
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ namespace BTCPayServer.Data
|
|||||||
{
|
{
|
||||||
public class SettingData
|
public class SettingData
|
||||||
{
|
{
|
||||||
public string Id
|
public string Id
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Value
|
public string Value
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,69 +13,69 @@ namespace BTCPayServer.Data
|
|||||||
{
|
{
|
||||||
public class StoreData
|
public class StoreData
|
||||||
{
|
{
|
||||||
public string Id
|
public string Id
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<UserStore> UserStores
|
public List<UserStore> UserStores
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string DerivationStrategy
|
public string DerivationStrategy
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string StoreName
|
public string StoreName
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SpeedPolicy SpeedPolicy
|
public SpeedPolicy SpeedPolicy
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string StoreWebsite
|
public string StoreWebsite
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] StoreCertificate
|
public byte[] StoreCertificate
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
public string Role
|
public string Role
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public byte[] StoreBlob
|
public byte[] StoreBlob
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StoreBlob GetStoreBlob(Network network)
|
public StoreBlob GetStoreBlob(Network network)
|
||||||
{
|
{
|
||||||
return StoreBlob == null ? new StoreBlob() : new Serializer(network).ToObject<StoreBlob>(Encoding.UTF8.GetString(StoreBlob));
|
return StoreBlob == null ? new StoreBlob() : new Serializer(network).ToObject<StoreBlob>(Encoding.UTF8.GetString(StoreBlob));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetStoreBlob(StoreBlob storeBlob, Network network)
|
public void SetStoreBlob(StoreBlob storeBlob, Network network)
|
||||||
{
|
{
|
||||||
StoreBlob = Encoding.UTF8.GetBytes(new Serializer(network).ToString(storeBlob));
|
StoreBlob = Encoding.UTF8.GetBytes(new Serializer(network).ToString(storeBlob));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class StoreBlob
|
public class StoreBlob
|
||||||
{
|
{
|
||||||
public bool NetworkFeeDisabled
|
public bool NetworkFeeDisabled
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,29 +6,29 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
public class UserStore
|
public class UserStore
|
||||||
{
|
{
|
||||||
public string ApplicationUserId
|
public string ApplicationUserId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public ApplicationUser ApplicationUser
|
public ApplicationUser ApplicationUser
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string StoreDataId
|
public string StoreDataId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public StoreData StoreData
|
public StoreData StoreData
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string Role
|
public string Role
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,54 +14,54 @@ using System.Text.Encodings.Web;
|
|||||||
|
|
||||||
namespace BTCPayServer
|
namespace BTCPayServer
|
||||||
{
|
{
|
||||||
public static class Extensions
|
public static class Extensions
|
||||||
{
|
{
|
||||||
public static string WithTrailingSlash(this string str)
|
public static string WithTrailingSlash(this string str)
|
||||||
{
|
{
|
||||||
if(str.EndsWith("/"))
|
if (str.EndsWith("/"))
|
||||||
return str;
|
return str;
|
||||||
return str + "/";
|
return str + "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetAbsoluteRoot(this HttpRequest request)
|
public static string GetAbsoluteRoot(this HttpRequest request)
|
||||||
{
|
{
|
||||||
return string.Concat(
|
return string.Concat(
|
||||||
request.Scheme,
|
request.Scheme,
|
||||||
"://",
|
"://",
|
||||||
request.Host.ToUriComponent(),
|
request.Host.ToUriComponent(),
|
||||||
request.PathBase.ToUriComponent());
|
request.PathBase.ToUriComponent());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection ConfigureBTCPayServer(this IServiceCollection services, IConfiguration conf)
|
public static IServiceCollection ConfigureBTCPayServer(this IServiceCollection services, IConfiguration conf)
|
||||||
{
|
{
|
||||||
services.Configure<BTCPayServerOptions>(o =>
|
services.Configure<BTCPayServerOptions>(o =>
|
||||||
{
|
{
|
||||||
o.LoadArgs(conf);
|
o.LoadArgs(conf);
|
||||||
});
|
});
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static BitIdentity GetBitIdentity(this Controller controller, bool throws = true)
|
public static BitIdentity GetBitIdentity(this Controller controller, bool throws = true)
|
||||||
{
|
{
|
||||||
if(!(controller.User.Identity is BitIdentity))
|
if (!(controller.User.Identity is BitIdentity))
|
||||||
return throws ? throw new UnauthorizedAccessException("no-bitid") : (BitIdentity)null;
|
return throws ? throw new UnauthorizedAccessException("no-bitid") : (BitIdentity)null;
|
||||||
return (BitIdentity)controller.User.Identity;
|
return (BitIdentity)controller.User.Identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JsonSerializerSettings jsonSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
|
private static JsonSerializerSettings jsonSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
|
||||||
public static string ToJson(this object o)
|
public static string ToJson(this object o)
|
||||||
{
|
{
|
||||||
var res = JsonConvert.SerializeObject(o, Formatting.None, jsonSettings);
|
var res = JsonConvert.SerializeObject(o, Formatting.None, jsonSettings);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HtmlString ToSrvModel(this object o)
|
public static HtmlString ToSrvModel(this object o)
|
||||||
{
|
{
|
||||||
var encodedJson = JavaScriptEncoder.Default.Encode(o.ToJson());
|
var encodedJson = JavaScriptEncoder.Default.Encode(o.ToJson());
|
||||||
return new HtmlString("var srvModel = JSON.parse('" + encodedJson + "');");
|
return new HtmlString("var srvModel = JSON.parse('" + encodedJson + "');");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,72 +7,72 @@ using System.Text;
|
|||||||
|
|
||||||
namespace BTCPayServer.Filters
|
namespace BTCPayServer.Filters
|
||||||
{
|
{
|
||||||
public class MediaTypeConstraintAttribute : Attribute, IActionConstraint
|
public class MediaTypeConstraintAttribute : Attribute, IActionConstraint
|
||||||
{
|
{
|
||||||
public MediaTypeConstraintAttribute(string mediaType)
|
public MediaTypeConstraintAttribute(string mediaType)
|
||||||
{
|
{
|
||||||
MediaType = mediaType ?? throw new ArgumentNullException(nameof(mediaType));
|
MediaType = mediaType ?? throw new ArgumentNullException(nameof(mediaType));
|
||||||
}
|
}
|
||||||
|
|
||||||
public string MediaType
|
public string MediaType
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Order => 100;
|
public int Order => 100;
|
||||||
|
|
||||||
public bool Accept(ActionConstraintContext context)
|
public bool Accept(ActionConstraintContext context)
|
||||||
{
|
{
|
||||||
var match = context.RouteContext.HttpContext.Request.ContentType?.StartsWith(MediaType, StringComparison.Ordinal);
|
var match = context.RouteContext.HttpContext.Request.ContentType?.StartsWith(MediaType, StringComparison.Ordinal);
|
||||||
return match.HasValue && match.Value;
|
return match.HasValue && match.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BitpayAPIConstraintAttribute : Attribute, IActionConstraint
|
public class BitpayAPIConstraintAttribute : Attribute, IActionConstraint
|
||||||
{
|
{
|
||||||
public BitpayAPIConstraintAttribute(bool isBitpayAPI = true)
|
public BitpayAPIConstraintAttribute(bool isBitpayAPI = true)
|
||||||
{
|
{
|
||||||
IsBitpayAPI = isBitpayAPI;
|
IsBitpayAPI = isBitpayAPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsBitpayAPI
|
public bool IsBitpayAPI
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public int Order => 100;
|
public int Order => 100;
|
||||||
|
|
||||||
public bool Accept(ActionConstraintContext context)
|
public bool Accept(ActionConstraintContext context)
|
||||||
{
|
{
|
||||||
var hasVersion = context.RouteContext.HttpContext.Request.Headers["x-accept-version"].Where(h => h == "2.0.0").Any();
|
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();
|
var hasIdentity = context.RouteContext.HttpContext.Request.Headers["x-identity"].Any();
|
||||||
return (hasVersion || hasIdentity) == IsBitpayAPI;
|
return (hasVersion || hasIdentity) == IsBitpayAPI;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AcceptMediaTypeConstraintAttribute : Attribute, IActionConstraint
|
public class AcceptMediaTypeConstraintAttribute : Attribute, IActionConstraint
|
||||||
{
|
{
|
||||||
public AcceptMediaTypeConstraintAttribute(string mediaType, bool expectedValue = true)
|
public AcceptMediaTypeConstraintAttribute(string mediaType, bool expectedValue = true)
|
||||||
{
|
{
|
||||||
MediaType = mediaType ?? throw new ArgumentNullException(nameof(mediaType));
|
MediaType = mediaType ?? throw new ArgumentNullException(nameof(mediaType));
|
||||||
ExpectedValue = expectedValue;
|
ExpectedValue = expectedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ExpectedValue
|
public bool ExpectedValue
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string MediaType
|
public string MediaType
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Order => 100;
|
public int Order => 100;
|
||||||
|
|
||||||
public bool Accept(ActionConstraintContext context)
|
public bool Accept(ActionConstraintContext context)
|
||||||
{
|
{
|
||||||
var hasHeader = context.RouteContext.HttpContext.Request.Headers["Accept"].Any(m => m.StartsWith(MediaType, StringComparison.Ordinal));
|
var hasHeader = context.RouteContext.HttpContext.Request.Headers["Accept"].Any(m => m.StartsWith(MediaType, StringComparison.Ordinal));
|
||||||
return hasHeader == ExpectedValue;
|
return hasHeader == ExpectedValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,28 +6,28 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Filters
|
namespace BTCPayServer.Filters
|
||||||
{
|
{
|
||||||
public class XFrameOptionsAttribute : Attribute, IActionFilter
|
public class XFrameOptionsAttribute : Attribute, IActionFilter
|
||||||
{
|
{
|
||||||
public XFrameOptionsAttribute(string value)
|
public XFrameOptionsAttribute(string value)
|
||||||
{
|
{
|
||||||
Value = value;
|
Value = value;
|
||||||
}
|
}
|
||||||
public string Value
|
public string Value
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public void OnActionExecuted(ActionExecutedContext context)
|
public void OnActionExecuted(ActionExecutedContext context)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnActionExecuting(ActionExecutingContext context)
|
}
|
||||||
{
|
|
||||||
var existing = context.HttpContext.Response.Headers["x-frame-options"].FirstOrDefault();
|
public void OnActionExecuting(ActionExecutingContext context)
|
||||||
if(existing != null && Value == null)
|
{
|
||||||
context.HttpContext.Response.Headers.Remove("x-frame-options");
|
var existing = context.HttpContext.Response.Headers["x-frame-options"].FirstOrDefault();
|
||||||
else
|
if (existing != null && Value == null)
|
||||||
context.HttpContext.Response.Headers["x-frame-options"] = Value;
|
context.HttpContext.Response.Headers.Remove("x-frame-options");
|
||||||
}
|
else
|
||||||
}
|
context.HttpContext.Response.Headers["x-frame-options"] = Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,178 +37,178 @@ using Microsoft.Extensions.Caching.Memory;
|
|||||||
|
|
||||||
namespace BTCPayServer.Hosting
|
namespace BTCPayServer.Hosting
|
||||||
{
|
{
|
||||||
public static class BTCPayServerServices
|
public static class BTCPayServerServices
|
||||||
{
|
{
|
||||||
public class OwnStoreAuthorizationRequirement : IAuthorizationRequirement
|
public class OwnStoreAuthorizationRequirement : IAuthorizationRequirement
|
||||||
{
|
{
|
||||||
public OwnStoreAuthorizationRequirement()
|
public OwnStoreAuthorizationRequirement()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public OwnStoreAuthorizationRequirement(string role)
|
public OwnStoreAuthorizationRequirement(string role)
|
||||||
{
|
{
|
||||||
Role = role;
|
Role = role;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Role
|
public string Role
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class OwnStoreHandler : AuthorizationHandler<OwnStoreAuthorizationRequirement>
|
public class OwnStoreHandler : AuthorizationHandler<OwnStoreAuthorizationRequirement>
|
||||||
{
|
{
|
||||||
StoreRepository _StoreRepository;
|
StoreRepository _StoreRepository;
|
||||||
UserManager<ApplicationUser> _UserManager;
|
UserManager<ApplicationUser> _UserManager;
|
||||||
public OwnStoreHandler(StoreRepository storeRepository, UserManager<ApplicationUser> userManager)
|
public OwnStoreHandler(StoreRepository storeRepository, UserManager<ApplicationUser> userManager)
|
||||||
{
|
{
|
||||||
_StoreRepository = storeRepository;
|
_StoreRepository = storeRepository;
|
||||||
_UserManager = userManager;
|
_UserManager = userManager;
|
||||||
}
|
}
|
||||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OwnStoreAuthorizationRequirement requirement)
|
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OwnStoreAuthorizationRequirement requirement)
|
||||||
{
|
{
|
||||||
object storeId = null;
|
object storeId = null;
|
||||||
if(!((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).RouteData.Values.TryGetValue("storeId", out storeId))
|
if (!((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).RouteData.Values.TryGetValue("storeId", out storeId))
|
||||||
context.Succeed(requirement);
|
context.Succeed(requirement);
|
||||||
else if(storeId != null)
|
else if (storeId != null)
|
||||||
{
|
{
|
||||||
var user = _UserManager.GetUserId(((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).HttpContext.User);
|
var user = _UserManager.GetUserId(((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).HttpContext.User);
|
||||||
if(user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
var store = await _StoreRepository.FindStore((string)storeId, user);
|
var store = await _StoreRepository.FindStore((string)storeId, user);
|
||||||
if(store != null)
|
if (store != null)
|
||||||
if(requirement.Role == null || requirement.Role == store.Role)
|
if (requirement.Role == null || requirement.Role == store.Role)
|
||||||
context.Succeed(requirement);
|
context.Succeed(requirement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class BTCPayServerConfigureOptions : IConfigureOptions<MvcOptions>
|
class BTCPayServerConfigureOptions : IConfigureOptions<MvcOptions>
|
||||||
{
|
{
|
||||||
BTCPayServerOptions _Options;
|
BTCPayServerOptions _Options;
|
||||||
public BTCPayServerConfigureOptions(BTCPayServerOptions options)
|
public BTCPayServerConfigureOptions(BTCPayServerOptions options)
|
||||||
{
|
{
|
||||||
_Options = options;
|
_Options = options;
|
||||||
}
|
}
|
||||||
public void Configure(MvcOptions options)
|
public void Configure(MvcOptions options)
|
||||||
{
|
{
|
||||||
if(_Options.RequireHttps)
|
if (_Options.RequireHttps)
|
||||||
options.Filters.Add(new RequireHttpsAttribute());
|
options.Filters.Add(new RequireHttpsAttribute());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static IServiceCollection AddBTCPayServer(this IServiceCollection services)
|
public static IServiceCollection AddBTCPayServer(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddDbContext<ApplicationDbContext>((provider, o) =>
|
services.AddDbContext<ApplicationDbContext>((provider, o) =>
|
||||||
{
|
{
|
||||||
var factory = provider.GetRequiredService<ApplicationDbContextFactory>();
|
var factory = provider.GetRequiredService<ApplicationDbContextFactory>();
|
||||||
factory.ConfigureBuilder(o);
|
factory.ConfigureBuilder(o);
|
||||||
});
|
});
|
||||||
services.TryAddSingleton<SettingsRepository>();
|
services.TryAddSingleton<SettingsRepository>();
|
||||||
services.TryAddSingleton<InvoicePaymentNotification>();
|
services.TryAddSingleton<InvoicePaymentNotification>();
|
||||||
services.TryAddSingleton<BTCPayServerOptions>(o => o.GetRequiredService<IOptions<BTCPayServerOptions>>().Value);
|
services.TryAddSingleton<BTCPayServerOptions>(o => o.GetRequiredService<IOptions<BTCPayServerOptions>>().Value);
|
||||||
services.TryAddSingleton<IConfigureOptions<MvcOptions>, BTCPayServerConfigureOptions>();
|
services.TryAddSingleton<IConfigureOptions<MvcOptions>, BTCPayServerConfigureOptions>();
|
||||||
services.TryAddSingleton(o =>
|
services.TryAddSingleton(o =>
|
||||||
{
|
{
|
||||||
var runtime = new BTCPayServerRuntime();
|
var runtime = new BTCPayServerRuntime();
|
||||||
runtime.Configure(o.GetRequiredService<BTCPayServerOptions>());
|
runtime.Configure(o.GetRequiredService<BTCPayServerOptions>());
|
||||||
return runtime;
|
return runtime;
|
||||||
});
|
});
|
||||||
services.AddSingleton<BTCPayServerEnvironment>();
|
services.AddSingleton<BTCPayServerEnvironment>();
|
||||||
services.TryAddSingleton<TokenRepository>();
|
services.TryAddSingleton<TokenRepository>();
|
||||||
services.TryAddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().InvoiceRepository);
|
services.TryAddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().InvoiceRepository);
|
||||||
services.TryAddSingleton<Network>(o => o.GetRequiredService<BTCPayServerOptions>().Network);
|
services.TryAddSingleton<Network>(o => o.GetRequiredService<BTCPayServerOptions>().Network);
|
||||||
services.TryAddSingleton<ApplicationDbContextFactory>(o => o.GetRequiredService<BTCPayServerRuntime>().DBFactory);
|
services.TryAddSingleton<ApplicationDbContextFactory>(o => o.GetRequiredService<BTCPayServerRuntime>().DBFactory);
|
||||||
services.TryAddSingleton<StoreRepository>();
|
services.TryAddSingleton<StoreRepository>();
|
||||||
services.TryAddSingleton<BTCPayWallet>();
|
services.TryAddSingleton<BTCPayWallet>();
|
||||||
services.TryAddSingleton<CurrencyNameTable>();
|
services.TryAddSingleton<CurrencyNameTable>();
|
||||||
services.TryAddSingleton<IFeeProvider>(o => new NBXplorerFeeProvider()
|
services.TryAddSingleton<IFeeProvider>(o => new NBXplorerFeeProvider()
|
||||||
{
|
{
|
||||||
Fallback = new FeeRate(100, 1),
|
Fallback = new FeeRate(100, 1),
|
||||||
BlockTarget = 20,
|
BlockTarget = 20,
|
||||||
ExplorerClient = o.GetRequiredService<ExplorerClient>()
|
ExplorerClient = o.GetRequiredService<ExplorerClient>()
|
||||||
});
|
});
|
||||||
services.TryAddSingleton<ExplorerClient>(o =>
|
services.TryAddSingleton<ExplorerClient>(o =>
|
||||||
{
|
{
|
||||||
var runtime = o.GetRequiredService<BTCPayServerRuntime>();
|
var runtime = o.GetRequiredService<BTCPayServerRuntime>();
|
||||||
return runtime.Explorer;
|
return runtime.Explorer;
|
||||||
});
|
});
|
||||||
services.TryAddSingleton<Bitpay>(o =>
|
services.TryAddSingleton<Bitpay>(o =>
|
||||||
{
|
{
|
||||||
if(o.GetRequiredService<BTCPayServerOptions>().Network == Network.Main)
|
if (o.GetRequiredService<BTCPayServerOptions>().Network == Network.Main)
|
||||||
return new Bitpay(new Key(), new Uri("https://bitpay.com/"));
|
return new Bitpay(new Key(), new Uri("https://bitpay.com/"));
|
||||||
else
|
else
|
||||||
return new Bitpay(new Key(), new Uri("https://test.bitpay.com/"));
|
return new Bitpay(new Key(), new Uri("https://test.bitpay.com/"));
|
||||||
});
|
});
|
||||||
services.TryAddSingleton<IRateProvider>(o =>
|
services.TryAddSingleton<IRateProvider>(o =>
|
||||||
{
|
{
|
||||||
return new CachedRateProvider(new CoinAverageRateProvider(), o.GetRequiredService<IMemoryCache>()) { CacheSpan = TimeSpan.FromMinutes(1.0) };
|
return new CachedRateProvider(new CoinAverageRateProvider(), o.GetRequiredService<IMemoryCache>()) { CacheSpan = TimeSpan.FromMinutes(1.0) };
|
||||||
});
|
});
|
||||||
services.TryAddSingleton<InvoiceWatcher>();
|
services.TryAddSingleton<InvoiceWatcher>();
|
||||||
services.TryAddSingleton<InvoiceNotificationManager>();
|
services.TryAddSingleton<InvoiceNotificationManager>();
|
||||||
services.TryAddSingleton<IHostedService>(o => o.GetRequiredService<InvoiceWatcher>());
|
services.TryAddSingleton<IHostedService>(o => o.GetRequiredService<InvoiceWatcher>());
|
||||||
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
|
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
|
||||||
services.TryAddSingleton<IAuthorizationHandler, OwnStoreHandler>();
|
services.TryAddSingleton<IAuthorizationHandler, OwnStoreHandler>();
|
||||||
services.AddTransient<AccessTokenController>();
|
services.AddTransient<AccessTokenController>();
|
||||||
services.AddTransient<CallbackController>();
|
services.AddTransient<CallbackController>();
|
||||||
services.AddTransient<InvoiceController>();
|
services.AddTransient<InvoiceController>();
|
||||||
// Add application services.
|
// Add application services.
|
||||||
services.AddTransient<IEmailSender, EmailSender>();
|
services.AddTransient<IEmailSender, EmailSender>();
|
||||||
|
|
||||||
services.AddAuthorization(o =>
|
services.AddAuthorization(o =>
|
||||||
{
|
{
|
||||||
o.AddPolicy("CanAccessStore", builder =>
|
o.AddPolicy("CanAccessStore", builder =>
|
||||||
{
|
{
|
||||||
builder.AddRequirements(new OwnStoreAuthorizationRequirement());
|
builder.AddRequirements(new OwnStoreAuthorizationRequirement());
|
||||||
});
|
});
|
||||||
|
|
||||||
o.AddPolicy("OwnStore", builder =>
|
o.AddPolicy("OwnStore", builder =>
|
||||||
{
|
{
|
||||||
builder.AddRequirements(new OwnStoreAuthorizationRequirement("Owner"));
|
builder.AddRequirements(new OwnStoreAuthorizationRequirement("Owner"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IApplicationBuilder UsePayServer(this IApplicationBuilder app)
|
public static IApplicationBuilder UsePayServer(this IApplicationBuilder app)
|
||||||
{
|
{
|
||||||
if(app.ApplicationServices.GetRequiredService<BTCPayServerOptions>().RequireHttps)
|
if (app.ApplicationServices.GetRequiredService<BTCPayServerOptions>().RequireHttps)
|
||||||
{
|
{
|
||||||
var options = new RewriteOptions().AddRedirectToHttps();
|
var options = new RewriteOptions().AddRedirectToHttps();
|
||||||
app.UseRewriter(options);
|
app.UseRewriter(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
using(var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
|
using (var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
|
||||||
{
|
{
|
||||||
//Wait the DB is ready
|
//Wait the DB is ready
|
||||||
Retry(() =>
|
Retry(() =>
|
||||||
{
|
{
|
||||||
scope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate();
|
scope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
app.UseMiddleware<BTCPayMiddleware>();
|
app.UseMiddleware<BTCPayMiddleware>();
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Retry(Action act)
|
static void Retry(Action act)
|
||||||
{
|
{
|
||||||
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
||||||
while(true)
|
while (true)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
act();
|
act();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
if(cts.IsCancellationRequested)
|
if (cts.IsCancellationRequested)
|
||||||
throw;
|
throw;
|
||||||
Thread.Sleep(1000);
|
Thread.Sleep(1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,94 +25,94 @@ using BTCPayServer.Controllers;
|
|||||||
|
|
||||||
namespace BTCPayServer.Hosting
|
namespace BTCPayServer.Hosting
|
||||||
{
|
{
|
||||||
public class BTCPayMiddleware
|
public class BTCPayMiddleware
|
||||||
{
|
{
|
||||||
TokenRepository _TokenRepository;
|
TokenRepository _TokenRepository;
|
||||||
RequestDelegate _Next;
|
RequestDelegate _Next;
|
||||||
CallbackController _CallbackController;
|
CallbackController _CallbackController;
|
||||||
public BTCPayMiddleware(RequestDelegate next,
|
public BTCPayMiddleware(RequestDelegate next,
|
||||||
TokenRepository tokenRepo,
|
TokenRepository tokenRepo,
|
||||||
CallbackController callbackController)
|
CallbackController callbackController)
|
||||||
{
|
{
|
||||||
_TokenRepository = tokenRepo ?? throw new ArgumentNullException(nameof(tokenRepo));
|
_TokenRepository = tokenRepo ?? throw new ArgumentNullException(nameof(tokenRepo));
|
||||||
_Next = next ?? throw new ArgumentNullException(nameof(next));
|
_Next = next ?? throw new ArgumentNullException(nameof(next));
|
||||||
_CallbackController = callbackController;
|
_CallbackController = callbackController;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool _Registered;
|
bool _Registered;
|
||||||
public async Task Invoke(HttpContext httpContext)
|
public async Task Invoke(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
if(!_Registered)
|
if (!_Registered)
|
||||||
{
|
{
|
||||||
var callback = await _CallbackController.RegisterCallbackBlockUriAsync(httpContext.Request);
|
var callback = await _CallbackController.RegisterCallbackBlockUriAsync(httpContext.Request);
|
||||||
Logs.PayServer.LogInformation($"Registering block callback to " + callback);
|
Logs.PayServer.LogInformation($"Registering block callback to " + callback);
|
||||||
_Registered = true;
|
_Registered = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
httpContext.Request.Headers.TryGetValue("x-signature", out StringValues values);
|
httpContext.Request.Headers.TryGetValue("x-signature", out StringValues values);
|
||||||
var sig = values.FirstOrDefault();
|
var sig = values.FirstOrDefault();
|
||||||
httpContext.Request.Headers.TryGetValue("x-identity", out values);
|
httpContext.Request.Headers.TryGetValue("x-identity", out values);
|
||||||
var id = values.FirstOrDefault();
|
var id = values.FirstOrDefault();
|
||||||
if(!string.IsNullOrEmpty(sig) && !string.IsNullOrEmpty(id))
|
if (!string.IsNullOrEmpty(sig) && !string.IsNullOrEmpty(id))
|
||||||
{
|
{
|
||||||
httpContext.Request.EnableRewind();
|
httpContext.Request.EnableRewind();
|
||||||
|
|
||||||
string body = string.Empty;
|
string body = string.Empty;
|
||||||
if(httpContext.Request.ContentLength != 0 && httpContext.Request.Body != null)
|
if (httpContext.Request.ContentLength != 0 && httpContext.Request.Body != null)
|
||||||
{
|
{
|
||||||
using(StreamReader reader = new StreamReader(httpContext.Request.Body, Encoding.UTF8, true, 1024, true))
|
using (StreamReader reader = new StreamReader(httpContext.Request.Body, Encoding.UTF8, true, 1024, true))
|
||||||
{
|
{
|
||||||
body = reader.ReadToEnd();
|
body = reader.ReadToEnd();
|
||||||
}
|
}
|
||||||
httpContext.Request.Body.Position = 0;
|
httpContext.Request.Body.Position = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = httpContext.Request.GetEncodedUrl();
|
var url = httpContext.Request.GetEncodedUrl();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var key = new PubKey(id);
|
var key = new PubKey(id);
|
||||||
if(BitIdExtensions.CheckBitIDSignature(key, sig, url, body))
|
if (BitIdExtensions.CheckBitIDSignature(key, sig, url, body))
|
||||||
{
|
{
|
||||||
var bitid = new BitIdentity(key);
|
var bitid = new BitIdentity(key);
|
||||||
httpContext.User = new GenericPrincipal(bitid, new string[0]);
|
httpContext.User = new GenericPrincipal(bitid, new string[0]);
|
||||||
Logs.PayServer.LogDebug($"BitId signature check success for SIN {bitid.SIN}");
|
Logs.PayServer.LogDebug($"BitId signature check success for SIN {bitid.SIN}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(FormatException) { }
|
catch (FormatException) { }
|
||||||
if(!(httpContext.User.Identity is BitIdentity))
|
if (!(httpContext.User.Identity is BitIdentity))
|
||||||
Logs.PayServer.LogDebug("BitId signature check failed");
|
Logs.PayServer.LogDebug("BitId signature check failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _Next(httpContext);
|
await _Next(httpContext);
|
||||||
}
|
}
|
||||||
catch(UnauthorizedAccessException ex)
|
catch (UnauthorizedAccessException ex)
|
||||||
{
|
{
|
||||||
await HandleBitpayHttpException(httpContext, new BitpayHttpException(401, ex.Message));
|
await HandleBitpayHttpException(httpContext, new BitpayHttpException(401, ex.Message));
|
||||||
}
|
}
|
||||||
catch(BitpayHttpException ex)
|
catch (BitpayHttpException ex)
|
||||||
{
|
{
|
||||||
await HandleBitpayHttpException(httpContext, ex);
|
await HandleBitpayHttpException(httpContext, ex);
|
||||||
}
|
}
|
||||||
catch(Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logs.PayServer.LogCritical(new EventId(), ex, "Unhandled exception in BTCPayMiddleware");
|
Logs.PayServer.LogCritical(new EventId(), ex, "Unhandled exception in BTCPayMiddleware");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task HandleBitpayHttpException(HttpContext httpContext, BitpayHttpException ex)
|
private static async Task HandleBitpayHttpException(HttpContext httpContext, BitpayHttpException ex)
|
||||||
{
|
{
|
||||||
httpContext.Response.StatusCode = ex.StatusCode;
|
httpContext.Response.StatusCode = ex.StatusCode;
|
||||||
using(var writer = new StreamWriter(httpContext.Response.Body, new UTF8Encoding(false), 1024, true))
|
using (var writer = new StreamWriter(httpContext.Response.Body, new UTF8Encoding(false), 1024, true))
|
||||||
{
|
{
|
||||||
httpContext.Response.ContentType = "application/json";
|
httpContext.Response.ContentType = "application/json";
|
||||||
var result = JsonConvert.SerializeObject(new BitpayErrorsModel(ex));
|
var result = JsonConvert.SerializeObject(new BitpayErrorsModel(ex));
|
||||||
writer.Write(result);
|
writer.Write(result);
|
||||||
await writer.FlushAsync();
|
await writer.FlushAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,117 +39,117 @@ using Microsoft.AspNetCore.Mvc.Cors.Internal;
|
|||||||
|
|
||||||
namespace BTCPayServer.Hosting
|
namespace BTCPayServer.Hosting
|
||||||
{
|
{
|
||||||
public class Startup
|
public class Startup
|
||||||
{
|
{
|
||||||
class NeedRole : IDashboardAuthorizationFilter
|
class NeedRole : IDashboardAuthorizationFilter
|
||||||
{
|
{
|
||||||
string _Role;
|
string _Role;
|
||||||
public NeedRole(string role)
|
public NeedRole(string role)
|
||||||
{
|
{
|
||||||
_Role = role;
|
_Role = role;
|
||||||
}
|
}
|
||||||
public bool Authorize([NotNull] DashboardContext context)
|
public bool Authorize([NotNull] DashboardContext context)
|
||||||
{
|
{
|
||||||
return context.GetHttpContext().User.IsInRole(_Role);
|
return context.GetHttpContext().User.IsInRole(_Role);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public Startup(IConfiguration conf, IHostingEnvironment env)
|
public Startup(IConfiguration conf, IHostingEnvironment env)
|
||||||
{
|
{
|
||||||
Configuration = conf;
|
Configuration = conf;
|
||||||
_Env = env;
|
_Env = env;
|
||||||
}
|
}
|
||||||
IHostingEnvironment _Env;
|
IHostingEnvironment _Env;
|
||||||
public IConfiguration Configuration
|
public IConfiguration Configuration
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
public void ConfigureServices(IServiceCollection services)
|
||||||
AddHangfireFix(services);
|
{
|
||||||
services.AddBTCPayServer();
|
services.ConfigureBTCPayServer(Configuration);
|
||||||
services.AddMvc(o =>
|
services.AddMemoryCache();
|
||||||
{
|
services.AddIdentity<ApplicationUser, IdentityRole>()
|
||||||
o.Filters.Add(new XFrameOptionsAttribute("DENY"));
|
.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||||
});
|
.AddDefaultTokenProviders();
|
||||||
}
|
|
||||||
|
|
||||||
// Big hack, tests fails if only call AddHangfire because Hangfire fail at initializing at the second test run
|
// Big hack, tests fails because Hangfire fail at initializing at the second test run
|
||||||
private void AddHangfireFix(IServiceCollection services)
|
AddHangfireFix(services);
|
||||||
{
|
services.AddBTCPayServer();
|
||||||
Action<IGlobalConfiguration> configuration = o =>
|
services.AddMvc(o =>
|
||||||
{
|
{
|
||||||
var scope = AspNetCoreJobActivator.Current.BeginScope(null);
|
o.Filters.Add(new XFrameOptionsAttribute("DENY"));
|
||||||
var options = (ApplicationDbContextFactory)scope.Resolve(typeof(ApplicationDbContextFactory));
|
});
|
||||||
options.ConfigureHangfireBuilder(o);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
ServiceCollectionDescriptorExtensions.TryAddSingleton<Action<IGlobalConfiguration>>(services, (IServiceProvider serviceProvider) => new Action<IGlobalConfiguration>((config) =>
|
// Big hack, tests fails if only call AddHangfire because Hangfire fail at initializing at the second test run
|
||||||
{
|
private void AddHangfireFix(IServiceCollection services)
|
||||||
ILoggerFactory service = ServiceProviderServiceExtensions.GetService<ILoggerFactory>(serviceProvider);
|
{
|
||||||
if(service != null)
|
Action<IGlobalConfiguration> configuration = o =>
|
||||||
{
|
{
|
||||||
Hangfire.GlobalConfigurationExtensions.UseLogProvider<AspNetCoreLogProvider>(config, new AspNetCoreLogProvider(service));
|
var scope = AspNetCoreJobActivator.Current.BeginScope(null);
|
||||||
}
|
var options = (ApplicationDbContextFactory)scope.Resolve(typeof(ApplicationDbContextFactory));
|
||||||
IServiceScopeFactory service2 = ServiceProviderServiceExtensions.GetService<IServiceScopeFactory>(serviceProvider);
|
options.ConfigureHangfireBuilder(o);
|
||||||
if(service2 != null)
|
};
|
||||||
{
|
|
||||||
Hangfire.GlobalConfigurationExtensions.UseActivator<AspNetCoreJobActivator>(config, new AspNetCoreJobActivator(service2));
|
|
||||||
}
|
|
||||||
configuration(config);
|
|
||||||
}));
|
|
||||||
|
|
||||||
services.AddHangfire(configuration);
|
ServiceCollectionDescriptorExtensions.TryAddSingleton<Action<IGlobalConfiguration>>(services, (IServiceProvider serviceProvider) => new Action<IGlobalConfiguration>((config) =>
|
||||||
services.AddCors(o =>
|
{
|
||||||
{
|
ILoggerFactory service = ServiceProviderServiceExtensions.GetService<ILoggerFactory>(serviceProvider);
|
||||||
o.AddPolicy("BitpayAPI", b =>
|
if (service != null)
|
||||||
{
|
{
|
||||||
b.AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin();
|
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.Configure<IOptions<ApplicationInsightsServiceOptions>>(o =>
|
services.AddHangfire(configuration);
|
||||||
{
|
services.AddCors(o =>
|
||||||
o.Value.DeveloperMode = _Env.IsDevelopment();
|
{
|
||||||
});
|
o.AddPolicy("BitpayAPI", b =>
|
||||||
}
|
{
|
||||||
|
b.AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
public void Configure(
|
services.Configure<IOptions<ApplicationInsightsServiceOptions>>(o =>
|
||||||
IApplicationBuilder app,
|
{
|
||||||
IHostingEnvironment env,
|
o.Value.DeveloperMode = _Env.IsDevelopment();
|
||||||
IServiceProvider prov,
|
});
|
||||||
ILoggerFactory loggerFactory)
|
}
|
||||||
{
|
|
||||||
if(env.IsDevelopment())
|
public void Configure(
|
||||||
{
|
IApplicationBuilder app,
|
||||||
app.UseDeveloperExceptionPage();
|
IHostingEnvironment env,
|
||||||
app.UseBrowserLink();
|
IServiceProvider prov,
|
||||||
}
|
ILoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
app.UseBrowserLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Logs.Configure(loggerFactory);
|
Logs.Configure(loggerFactory);
|
||||||
|
|
||||||
//App insight do not that by itself...
|
//App insight do not that by itself...
|
||||||
loggerFactory.AddApplicationInsights(prov, LogLevel.Information);
|
loggerFactory.AddApplicationInsights(prov, LogLevel.Information);
|
||||||
|
|
||||||
app.UsePayServer();
|
app.UsePayServer();
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseHangfireServer();
|
app.UseHangfireServer();
|
||||||
app.UseHangfireDashboard("/hangfire", new DashboardOptions() { Authorization = new[] { new NeedRole(Roles.ServerAdmin) } });
|
app.UseHangfireDashboard("/hangfire", new DashboardOptions() { Authorization = new[] { new NeedRole(Roles.ServerAdmin) } });
|
||||||
app.UseMvc(routes =>
|
app.UseMvc(routes =>
|
||||||
{
|
{
|
||||||
routes.MapRoute(
|
routes.MapRoute(
|
||||||
name: "default",
|
name: "default",
|
||||||
template: "{controller=Home}/{action=Index}/{id?}");
|
template: "{controller=Home}/{action=Index}/{id?}");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,391 +10,391 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Logging
|
namespace BTCPayServer.Logging
|
||||||
{
|
{
|
||||||
public class CustomConsoleLogProvider : ILoggerProvider
|
public class CustomConsoleLogProvider : ILoggerProvider
|
||||||
{
|
{
|
||||||
ConsoleLoggerProcessor _Processor = new ConsoleLoggerProcessor();
|
ConsoleLoggerProcessor _Processor = new ConsoleLoggerProcessor();
|
||||||
public ILogger CreateLogger(string categoryName)
|
public ILogger CreateLogger(string categoryName)
|
||||||
{
|
{
|
||||||
return new CustomConsoleLogger(categoryName, (a,b) => true, false, _Processor);
|
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
|
/// <summary>
|
||||||
private readonly ConsoleColor? DefaultConsoleColor = null;
|
/// 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;
|
// ConsoleColor does not have a value to specify the 'Default' color
|
||||||
private Func<string, LogLevel, bool> _filter;
|
private readonly ConsoleColor? DefaultConsoleColor = null;
|
||||||
|
|
||||||
[ThreadStatic]
|
private readonly ConsoleLoggerProcessor _queueProcessor;
|
||||||
private static StringBuilder _logBuilder;
|
private Func<string, LogLevel, bool> _filter;
|
||||||
|
|
||||||
static CustomConsoleLogger()
|
[ThreadStatic]
|
||||||
{
|
private static StringBuilder _logBuilder;
|
||||||
var logLevelString = GetLogLevelString(LogLevel.Information);
|
|
||||||
_messagePadding = new string(' ', logLevelString.Length + _loglevelPadding.Length);
|
|
||||||
_newLineWithMessagePadding = Environment.NewLine + _messagePadding;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CustomConsoleLogger(string name, Func<string, LogLevel, bool> filter, bool includeScopes, ConsoleLoggerProcessor loggerProcessor)
|
static CustomConsoleLogger()
|
||||||
{
|
{
|
||||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
var logLevelString = GetLogLevelString(LogLevel.Information);
|
||||||
Filter = filter ?? ((category, logLevel) => true);
|
_messagePadding = new string(' ', logLevelString.Length + _loglevelPadding.Length);
|
||||||
IncludeScopes = includeScopes;
|
_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))
|
_queueProcessor = loggerProcessor;
|
||||||
{
|
|
||||||
Console = new WindowsLogConsole();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console = new AnsiLogConsole(new AnsiSystemConsole());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IConsole Console
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
get
|
Console = new WindowsLogConsole();
|
||||||
{
|
}
|
||||||
return _queueProcessor.Console;
|
else
|
||||||
}
|
{
|
||||||
set
|
Console = new AnsiLogConsole(new AnsiSystemConsole());
|
||||||
{
|
}
|
||||||
_queueProcessor.Console = value ?? throw new ArgumentNullException(nameof(value));
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Func<string, LogLevel, bool> Filter
|
public IConsole Console
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _filter;
|
return _queueProcessor.Console;
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_filter = value ?? throw new ArgumentNullException(nameof(value));
|
_queueProcessor.Console = value ?? throw new ArgumentNullException(nameof(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IncludeScopes
|
public Func<string, LogLevel, bool> Filter
|
||||||
{
|
{
|
||||||
get; set;
|
get
|
||||||
}
|
{
|
||||||
|
return _filter;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_filter = value ?? throw new ArgumentNullException(nameof(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string Name
|
public bool IncludeScopes
|
||||||
{
|
{
|
||||||
get;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
public string Name
|
||||||
{
|
{
|
||||||
if(!IsEnabled(logLevel))
|
get;
|
||||||
{
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(formatter == null)
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(formatter));
|
if (!IsEnabled(logLevel))
|
||||||
}
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var message = formatter(state, exception);
|
if (formatter == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(formatter));
|
||||||
|
}
|
||||||
|
|
||||||
if(!string.IsNullOrEmpty(message) || exception != null)
|
var message = formatter(state, exception);
|
||||||
{
|
|
||||||
WriteMessage(logLevel, Name, eventId.Id, message, exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void WriteMessage(LogLevel logLevel, string logName, int eventId, string message, Exception exception)
|
if (!string.IsNullOrEmpty(message) || exception != null)
|
||||||
{
|
{
|
||||||
var logBuilder = _logBuilder;
|
WriteMessage(logLevel, Name, eventId.Id, message, exception);
|
||||||
_logBuilder = null;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(logBuilder == null)
|
public virtual void WriteMessage(LogLevel logLevel, string logName, int eventId, string message, Exception exception)
|
||||||
{
|
{
|
||||||
logBuilder = new StringBuilder();
|
var logBuilder = _logBuilder;
|
||||||
}
|
_logBuilder = null;
|
||||||
|
|
||||||
var logLevelColors = default(ConsoleColors);
|
if (logBuilder == null)
|
||||||
var logLevelString = string.Empty;
|
{
|
||||||
|
logBuilder = new StringBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
// Example:
|
var logLevelColors = default(ConsoleColors);
|
||||||
// INFO: ConsoleApp.Program[10]
|
var logLevelString = string.Empty;
|
||||||
// Request received
|
|
||||||
|
|
||||||
logLevelColors = GetLogLevelConsoleColors(logLevel);
|
// Example:
|
||||||
logLevelString = GetLogLevelString(logLevel);
|
// INFO: ConsoleApp.Program[10]
|
||||||
// category and event id
|
// Request received
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!string.IsNullOrEmpty(message))
|
logLevelColors = GetLogLevelConsoleColors(logLevel);
|
||||||
{
|
logLevelString = GetLogLevelString(logLevel);
|
||||||
// message
|
// category and event id
|
||||||
//logBuilder.Append(_messagePadding);
|
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;
|
if (!string.IsNullOrEmpty(message))
|
||||||
logBuilder.AppendLine(message);
|
{
|
||||||
logBuilder.Replace(Environment.NewLine, _newLineWithMessagePadding, len, message.Length);
|
// message
|
||||||
}
|
//logBuilder.Append(_messagePadding);
|
||||||
|
|
||||||
// Example:
|
var len = logBuilder.Length;
|
||||||
// System.InvalidOperationException
|
logBuilder.AppendLine(message);
|
||||||
// at Namespace.Class.Function() in File:line X
|
logBuilder.Replace(Environment.NewLine, _newLineWithMessagePadding, len, message.Length);
|
||||||
if(exception != null)
|
}
|
||||||
{
|
|
||||||
// exception message
|
|
||||||
logBuilder.AppendLine(exception.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if(logBuilder.Length > 0)
|
// Example:
|
||||||
{
|
// System.InvalidOperationException
|
||||||
var hasLevel = !string.IsNullOrEmpty(logLevelString);
|
// at Namespace.Class.Function() in File:line X
|
||||||
// Queue log message
|
if (exception != null)
|
||||||
_queueProcessor.EnqueueMessage(new LogMessageEntry()
|
{
|
||||||
{
|
// exception message
|
||||||
Message = logBuilder.ToString(),
|
logBuilder.AppendLine(exception.ToString());
|
||||||
MessageColor = DefaultConsoleColor,
|
}
|
||||||
LevelString = hasLevel ? logLevelString : null,
|
|
||||||
LevelBackground = hasLevel ? logLevelColors.Background : null,
|
|
||||||
LevelForeground = hasLevel ? logLevelColors.Foreground : null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
logBuilder.Clear();
|
if (logBuilder.Length > 0)
|
||||||
if(logBuilder.Capacity > 1024)
|
{
|
||||||
{
|
var hasLevel = !string.IsNullOrEmpty(logLevelString);
|
||||||
logBuilder.Capacity = 1024;
|
// Queue log message
|
||||||
}
|
_queueProcessor.EnqueueMessage(new LogMessageEntry()
|
||||||
_logBuilder = logBuilder;
|
{
|
||||||
}
|
Message = logBuilder.ToString(),
|
||||||
|
MessageColor = DefaultConsoleColor,
|
||||||
|
LevelString = hasLevel ? logLevelString : null,
|
||||||
|
LevelBackground = hasLevel ? logLevelColors.Background : null,
|
||||||
|
LevelForeground = hasLevel ? logLevelColors.Foreground : null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsEnabled(LogLevel logLevel)
|
logBuilder.Clear();
|
||||||
{
|
if (logBuilder.Capacity > 1024)
|
||||||
return Filter(Name, logLevel);
|
{
|
||||||
}
|
logBuilder.Capacity = 1024;
|
||||||
|
}
|
||||||
|
_logBuilder = logBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
public IDisposable BeginScope<TState>(TState state)
|
public bool IsEnabled(LogLevel logLevel)
|
||||||
{
|
{
|
||||||
if(state == null)
|
return Filter(Name, logLevel);
|
||||||
{
|
}
|
||||||
throw new ArgumentNullException(nameof(state));
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
return ConsoleLogScope.Push(Name, state);
|
||||||
{
|
}
|
||||||
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 ConsoleColors GetLogLevelConsoleColors(LogLevel logLevel)
|
private static string GetLogLevelString(LogLevel logLevel)
|
||||||
{
|
{
|
||||||
// We must explicitly set the background color if we are setting the foreground color,
|
switch (logLevel)
|
||||||
// since just setting one can look bad on the users console.
|
{
|
||||||
switch(logLevel)
|
case LogLevel.Trace:
|
||||||
{
|
return "trce";
|
||||||
case LogLevel.Critical:
|
case LogLevel.Debug:
|
||||||
return new ConsoleColors(ConsoleColor.White, ConsoleColor.Red);
|
return "dbug";
|
||||||
case LogLevel.Error:
|
case LogLevel.Information:
|
||||||
return new ConsoleColors(ConsoleColor.Black, ConsoleColor.Red);
|
return "info";
|
||||||
case LogLevel.Warning:
|
case LogLevel.Warning:
|
||||||
return new ConsoleColors(ConsoleColor.Yellow, ConsoleColor.Black);
|
return "warn";
|
||||||
case LogLevel.Information:
|
case LogLevel.Error:
|
||||||
return new ConsoleColors(ConsoleColor.DarkGreen, ConsoleColor.Black);
|
return "fail";
|
||||||
case LogLevel.Debug:
|
case LogLevel.Critical:
|
||||||
return new ConsoleColors(ConsoleColor.Gray, ConsoleColor.Black);
|
return "crit";
|
||||||
case LogLevel.Trace:
|
default:
|
||||||
return new ConsoleColors(ConsoleColor.Gray, ConsoleColor.Black);
|
throw new ArgumentOutOfRangeException(nameof(logLevel));
|
||||||
default:
|
}
|
||||||
return new ConsoleColors(DefaultConsoleColor, DefaultConsoleColor);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GetScopeInformation(StringBuilder builder)
|
private ConsoleColors GetLogLevelConsoleColors(LogLevel logLevel)
|
||||||
{
|
{
|
||||||
var current = ConsoleLogScope.Current;
|
// We must explicitly set the background color if we are setting the foreground color,
|
||||||
string scopeLog = string.Empty;
|
// since just setting one can look bad on the users console.
|
||||||
var length = builder.Length;
|
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)
|
private void GetScopeInformation(StringBuilder builder)
|
||||||
{
|
{
|
||||||
if(length == builder.Length)
|
var current = ConsoleLogScope.Current;
|
||||||
{
|
string scopeLog = string.Empty;
|
||||||
scopeLog = $"=> {current}";
|
var length = builder.Length;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
scopeLog = $"=> {current} ";
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.Insert(length, scopeLog);
|
while (current != null)
|
||||||
current = current.Parent;
|
{
|
||||||
}
|
if (length == builder.Length)
|
||||||
if(builder.Length > length)
|
{
|
||||||
{
|
scopeLog = $"=> {current}";
|
||||||
builder.Insert(length, _messagePadding);
|
}
|
||||||
builder.AppendLine();
|
else
|
||||||
}
|
{
|
||||||
}
|
scopeLog = $"=> {current} ";
|
||||||
|
}
|
||||||
|
|
||||||
private struct ConsoleColors
|
builder.Insert(length, scopeLog);
|
||||||
{
|
current = current.Parent;
|
||||||
public ConsoleColors(ConsoleColor? foreground, ConsoleColor? background)
|
}
|
||||||
{
|
if (builder.Length > length)
|
||||||
Foreground = foreground;
|
{
|
||||||
Background = background;
|
builder.Insert(length, _messagePadding);
|
||||||
}
|
builder.AppendLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ConsoleColor? Foreground
|
private struct ConsoleColors
|
||||||
{
|
{
|
||||||
get;
|
public ConsoleColors(ConsoleColor? foreground, ConsoleColor? background)
|
||||||
}
|
{
|
||||||
|
Foreground = foreground;
|
||||||
|
Background = background;
|
||||||
|
}
|
||||||
|
|
||||||
public ConsoleColor? Background
|
public ConsoleColor? Foreground
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private class AnsiSystemConsole : IAnsiSystemConsole
|
public ConsoleColor? Background
|
||||||
{
|
{
|
||||||
public void Write(string message)
|
get;
|
||||||
{
|
}
|
||||||
System.Console.Write(message);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteLine(string message)
|
private class AnsiSystemConsole : IAnsiSystemConsole
|
||||||
{
|
{
|
||||||
System.Console.WriteLine(message);
|
public void Write(string message)
|
||||||
}
|
{
|
||||||
}
|
System.Console.Write(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ConsoleLoggerProcessor : IDisposable
|
public void WriteLine(string message)
|
||||||
{
|
{
|
||||||
private const int _maxQueuedMessages = 1024;
|
System.Console.WriteLine(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly BlockingCollection<LogMessageEntry> _messageQueue = new BlockingCollection<LogMessageEntry>(_maxQueuedMessages);
|
public class ConsoleLoggerProcessor : IDisposable
|
||||||
private readonly Task _outputTask;
|
{
|
||||||
|
private const int _maxQueuedMessages = 1024;
|
||||||
|
|
||||||
public IConsole Console;
|
private readonly BlockingCollection<LogMessageEntry> _messageQueue = new BlockingCollection<LogMessageEntry>(_maxQueuedMessages);
|
||||||
|
private readonly Task _outputTask;
|
||||||
|
|
||||||
public ConsoleLoggerProcessor()
|
public IConsole Console;
|
||||||
{
|
|
||||||
// Start Console message queue processor
|
|
||||||
_outputTask = Task.Factory.StartNew(
|
|
||||||
ProcessLogQueue,
|
|
||||||
this,
|
|
||||||
TaskCreationOptions.LongRunning);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void EnqueueMessage(LogMessageEntry message)
|
public ConsoleLoggerProcessor()
|
||||||
{
|
{
|
||||||
if(!_messageQueue.IsAddingCompleted)
|
// Start Console message queue processor
|
||||||
{
|
_outputTask = Task.Factory.StartNew(
|
||||||
try
|
ProcessLogQueue,
|
||||||
{
|
this,
|
||||||
_messageQueue.Add(message);
|
TaskCreationOptions.LongRunning);
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
catch(InvalidOperationException) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adding is completed so just log the message
|
public virtual void EnqueueMessage(LogMessageEntry message)
|
||||||
WriteMessage(message);
|
{
|
||||||
}
|
if (!_messageQueue.IsAddingCompleted)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_messageQueue.Add(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException) { }
|
||||||
|
}
|
||||||
|
|
||||||
// for testing
|
// Adding is completed so just log the message
|
||||||
internal virtual void WriteMessage(LogMessageEntry message)
|
WriteMessage(message);
|
||||||
{
|
}
|
||||||
if(message.LevelString != null)
|
|
||||||
{
|
|
||||||
Console.Write(message.LevelString, message.LevelBackground, message.LevelForeground);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.Write(message.Message, message.MessageColor, message.MessageColor);
|
// for testing
|
||||||
Console.Flush();
|
internal virtual void WriteMessage(LogMessageEntry message)
|
||||||
}
|
{
|
||||||
|
if (message.LevelString != null)
|
||||||
|
{
|
||||||
|
Console.Write(message.LevelString, message.LevelBackground, message.LevelForeground);
|
||||||
|
}
|
||||||
|
|
||||||
private void ProcessLogQueue()
|
Console.Write(message.Message, message.MessageColor, message.MessageColor);
|
||||||
{
|
Console.Flush();
|
||||||
foreach(var message in _messageQueue.GetConsumingEnumerable())
|
}
|
||||||
{
|
|
||||||
WriteMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ProcessLogQueue(object state)
|
private void ProcessLogQueue()
|
||||||
{
|
{
|
||||||
var consoleLogger = (ConsoleLoggerProcessor)state;
|
foreach (var message in _messageQueue.GetConsumingEnumerable())
|
||||||
|
{
|
||||||
|
WriteMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
consoleLogger.ProcessLogQueue();
|
private static void ProcessLogQueue(object state)
|
||||||
}
|
{
|
||||||
|
var consoleLogger = (ConsoleLoggerProcessor)state;
|
||||||
|
|
||||||
public void Dispose()
|
consoleLogger.ProcessLogQueue();
|
||||||
{
|
}
|
||||||
_messageQueue.CompleteAdding();
|
|
||||||
|
|
||||||
try
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_outputTask.Wait(1500); // with timeout in-case Console is locked by user input
|
_messageQueue.CompleteAdding();
|
||||||
}
|
|
||||||
catch(TaskCanceledException) { }
|
|
||||||
catch(AggregateException ex) when(ex.InnerExceptions.Count == 1 && ex.InnerExceptions[0] is TaskCanceledException) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct LogMessageEntry
|
try
|
||||||
{
|
{
|
||||||
public string LevelString;
|
_outputTask.Wait(1500); // with timeout in-case Console is locked by user input
|
||||||
public ConsoleColor? LevelBackground;
|
}
|
||||||
public ConsoleColor? LevelForeground;
|
catch (TaskCanceledException) { }
|
||||||
public ConsoleColor? MessageColor;
|
catch (AggregateException ex) when (ex.InnerExceptions.Count == 1 && ex.InnerExceptions[0] is TaskCanceledException) { }
|
||||||
public string Message;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct LogMessageEntry
|
||||||
|
{
|
||||||
|
public string LevelString;
|
||||||
|
public ConsoleColor? LevelBackground;
|
||||||
|
public ConsoleColor? LevelForeground;
|
||||||
|
public ConsoleColor? MessageColor;
|
||||||
|
public string Message;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,48 +7,48 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Logging
|
namespace BTCPayServer.Logging
|
||||||
{
|
{
|
||||||
public class Logs
|
public class Logs
|
||||||
{
|
{
|
||||||
static Logs()
|
static Logs()
|
||||||
{
|
{
|
||||||
Configure(new FuncLoggerFactory(n => NullLogger.Instance));
|
Configure(new FuncLoggerFactory(n => NullLogger.Instance));
|
||||||
}
|
}
|
||||||
public static void Configure(ILoggerFactory factory)
|
public static void Configure(ILoggerFactory factory)
|
||||||
{
|
{
|
||||||
Configuration = factory.CreateLogger("Configuration");
|
Configuration = factory.CreateLogger("Configuration");
|
||||||
PayServer = factory.CreateLogger("PayServer");
|
PayServer = factory.CreateLogger("PayServer");
|
||||||
}
|
}
|
||||||
public static ILogger Configuration
|
public static ILogger Configuration
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public static ILogger PayServer
|
public static ILogger PayServer
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public const int ColumnLength = 16;
|
public const int ColumnLength = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FuncLoggerFactory : ILoggerFactory
|
public class FuncLoggerFactory : ILoggerFactory
|
||||||
{
|
{
|
||||||
private Func<string, ILogger> createLogger;
|
private Func<string, ILogger> createLogger;
|
||||||
public FuncLoggerFactory(Func<string, ILogger> createLogger)
|
public FuncLoggerFactory(Func<string, ILogger> createLogger)
|
||||||
{
|
{
|
||||||
this.createLogger = createLogger;
|
this.createLogger = createLogger;
|
||||||
}
|
}
|
||||||
public void AddProvider(ILoggerProvider provider)
|
public void AddProvider(ILoggerProvider provider)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ILogger CreateLogger(string categoryName)
|
public ILogger CreateLogger(string categoryName)
|
||||||
{
|
{
|
||||||
return createLogger(categoryName);
|
return createLogger(categoryName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,51 +4,51 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace BTCPayServer.Migrations
|
namespace BTCPayServer.Migrations
|
||||||
{
|
{
|
||||||
public partial class PendingInvoices : Migration
|
public partial class PendingInvoices : Migration
|
||||||
{
|
{
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
if(SupportDropColumn(migrationBuilder.ActiveProvider))
|
if (SupportDropColumn(migrationBuilder.ActiveProvider))
|
||||||
{
|
{
|
||||||
migrationBuilder.DropColumn(
|
migrationBuilder.DropColumn(
|
||||||
name: "Name",
|
name: "Name",
|
||||||
table: "PairingCodes");
|
table: "PairingCodes");
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
migrationBuilder.DropColumn(
|
||||||
name: "Name",
|
name: "Name",
|
||||||
table: "PairedSINData");
|
table: "PairedSINData");
|
||||||
}
|
}
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "PendingInvoices",
|
name: "PendingInvoices",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<string>(type: "TEXT", nullable: false)
|
Id = table.Column<string>(type: "TEXT", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_PendingInvoices", x => x.Id);
|
table.PrimaryKey("PK_PendingInvoices", x => x.Id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool SupportDropColumn(string activeProvider)
|
private bool SupportDropColumn(string activeProvider)
|
||||||
{
|
{
|
||||||
return activeProvider != "Microsoft.EntityFrameworkCore.Sqlite";
|
return activeProvider != "Microsoft.EntityFrameworkCore.Sqlite";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "PendingInvoices");
|
name: "PendingInvoices");
|
||||||
|
|
||||||
migrationBuilder.AddColumn<string>(
|
migrationBuilder.AddColumn<string>(
|
||||||
name: "Name",
|
name: "Name",
|
||||||
table: "PairingCodes",
|
table: "PairingCodes",
|
||||||
nullable: true);
|
nullable: true);
|
||||||
|
|
||||||
migrationBuilder.AddColumn<string>(
|
migrationBuilder.AddColumn<string>(
|
||||||
name: "Name",
|
name: "Name",
|
||||||
table: "PairedSINData",
|
table: "PairedSINData",
|
||||||
nullable: true);
|
nullable: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ namespace BTCPayServer.Models.AccountViewModels
|
|||||||
{
|
{
|
||||||
public class LoginWithRecoveryCodeViewModel
|
public class LoginWithRecoveryCodeViewModel
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
[DataType(DataType.Text)]
|
[DataType(DataType.Text)]
|
||||||
[Display(Name = "Recovery Code")]
|
[Display(Name = "Recovery Code")]
|
||||||
public string RecoveryCode { get; set; }
|
public string RecoveryCode { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,15 +10,15 @@ namespace BTCPayServer.Models
|
|||||||
// Add profile data for application users by adding properties to the ApplicationUser class
|
// Add profile data for application users by adding properties to the ApplicationUser class
|
||||||
public class ApplicationUser : IdentityUser
|
public class ApplicationUser : IdentityUser
|
||||||
{
|
{
|
||||||
public List<UserStore> UserStores
|
public List<UserStore> UserStores
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool RequiresEmailConfirmation
|
public bool RequiresEmailConfirmation
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,35 +5,35 @@ using System.Text;
|
|||||||
|
|
||||||
namespace BTCPayServer.Models
|
namespace BTCPayServer.Models
|
||||||
{
|
{
|
||||||
public class BitpayErrorsModel
|
public class BitpayErrorsModel
|
||||||
{
|
{
|
||||||
public BitpayErrorsModel()
|
public BitpayErrorsModel()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
public BitpayErrorsModel(BitpayHttpException ex)
|
public BitpayErrorsModel(BitpayHttpException ex)
|
||||||
{
|
{
|
||||||
Error = ex.Message;
|
Error = ex.Message;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty("errors", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
[JsonProperty("errors", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||||
public BitpayErrorModel[] Errors
|
public BitpayErrorModel[] Errors
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
[JsonProperty("error", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
[JsonProperty("error", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||||
public string Error
|
public string Error
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BitpayErrorModel
|
public class BitpayErrorModel
|
||||||
{
|
{
|
||||||
[JsonProperty("error")]
|
[JsonProperty("error")]
|
||||||
public string Error
|
public string Error
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,17 @@ namespace BTCPayServer.Models
|
|||||||
{
|
{
|
||||||
public class ConfirmModel
|
public class ConfirmModel
|
||||||
{
|
{
|
||||||
public string Title
|
public string Title
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string Description
|
public string Description
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string Action
|
public string Action
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,33 +5,33 @@ using System.Text;
|
|||||||
|
|
||||||
namespace BTCPayServer.Models
|
namespace BTCPayServer.Models
|
||||||
{
|
{
|
||||||
public class DataWrapper
|
public class DataWrapper
|
||||||
{
|
{
|
||||||
public static DataWrapper<T> Create<T>(T obj)
|
public static DataWrapper<T> Create<T>(T obj)
|
||||||
{
|
{
|
||||||
return new DataWrapper<T>(obj);
|
return new DataWrapper<T>(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public class DataWrapper<T>
|
public class DataWrapper<T>
|
||||||
{
|
{
|
||||||
public DataWrapper()
|
public DataWrapper()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
public DataWrapper(T data)
|
public DataWrapper(T data)
|
||||||
{
|
{
|
||||||
Data = data;
|
Data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty("facade", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("facade", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public string Facade
|
public string Facade
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
[JsonProperty("data")]
|
[JsonProperty("data")]
|
||||||
public T Data
|
public T Data
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,43 +12,43 @@ using System.IO;
|
|||||||
|
|
||||||
namespace BTCPayServer.Models
|
namespace BTCPayServer.Models
|
||||||
{
|
{
|
||||||
//{"data":[{"pos":"FfZ6WCa8TunAvPCpQZXkdBsoH4Yo18FyPaJ5X5qjrVVY"},{"pos/invoice":"H1pwwh2tMeSCri9rh5VvHWEHokGdf2EGtghfZkUEbeZv"},{"merchant":"89zEBr9orAc6wgybAABp8ioGcjYeFrUaZgMzjxNuqYty"},{"merchant/invoice":"8e7ijDxGfJsWXWgJuKXjjNgxnX1xpsBM8cTZCFnU7ehj"}]}
|
//{"data":[{"pos":"FfZ6WCa8TunAvPCpQZXkdBsoH4Yo18FyPaJ5X5qjrVVY"},{"pos/invoice":"H1pwwh2tMeSCri9rh5VvHWEHokGdf2EGtghfZkUEbeZv"},{"merchant":"89zEBr9orAc6wgybAABp8ioGcjYeFrUaZgMzjxNuqYty"},{"merchant/invoice":"8e7ijDxGfJsWXWgJuKXjjNgxnX1xpsBM8cTZCFnU7ehj"}]}
|
||||||
public class GetTokensResponse : IActionResult
|
public class GetTokensResponse : IActionResult
|
||||||
{
|
{
|
||||||
BitTokenEntity[] _Tokens;
|
BitTokenEntity[] _Tokens;
|
||||||
public GetTokensResponse(BitTokenEntity[] tokens)
|
public GetTokensResponse(BitTokenEntity[] tokens)
|
||||||
{
|
{
|
||||||
if(tokens == null)
|
if (tokens == null)
|
||||||
throw new ArgumentNullException(nameof(tokens));
|
throw new ArgumentNullException(nameof(tokens));
|
||||||
this._Tokens = tokens;
|
this._Tokens = tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "data")]
|
[JsonProperty(PropertyName = "data")]
|
||||||
//{"pos":"FfZ6WCa8TunAvPCpQZXkdBsoH4Yo18FyPaJ5X5qjrVVY"}
|
//{"pos":"FfZ6WCa8TunAvPCpQZXkdBsoH4Yo18FyPaJ5X5qjrVVY"}
|
||||||
public JArray Data
|
public JArray Data
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ExecuteResultAsync(ActionContext context)
|
public async Task ExecuteResultAsync(ActionContext context)
|
||||||
{
|
{
|
||||||
JObject jobj = new JObject();
|
JObject jobj = new JObject();
|
||||||
JArray jarray = new JArray();
|
JArray jarray = new JArray();
|
||||||
jobj.Add("data", jarray);
|
jobj.Add("data", jarray);
|
||||||
foreach(var token in _Tokens)
|
foreach (var token in _Tokens)
|
||||||
{
|
{
|
||||||
JObject item = new JObject();
|
JObject item = new JObject();
|
||||||
jarray.Add(item);
|
jarray.Add(item);
|
||||||
JProperty jProp = new JProperty(token.Facade);
|
JProperty jProp = new JProperty(token.Facade);
|
||||||
item.Add(jProp);
|
item.Add(jProp);
|
||||||
jProp.Value = token.Value;
|
jProp.Value = token.Value;
|
||||||
}
|
}
|
||||||
context.HttpContext.Response.Headers.Add("Content-Type", new Microsoft.Extensions.Primitives.StringValues("application/json"));
|
context.HttpContext.Response.Headers.Add("Content-Type", new Microsoft.Extensions.Primitives.StringValues("application/json"));
|
||||||
var str = JsonConvert.SerializeObject(jobj);
|
var str = JsonConvert.SerializeObject(jobj);
|
||||||
using(var writer = new StreamWriter(context.HttpContext.Response.Body, new UTF8Encoding(false), 1024 * 10, true))
|
using (var writer = new StreamWriter(context.HttpContext.Response.Body, new UTF8Encoding(false), 1024 * 10, true))
|
||||||
{
|
{
|
||||||
await writer.WriteAsync(str);
|
await writer.WriteAsync(str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,220 +6,220 @@ using System.Text;
|
|||||||
|
|
||||||
namespace BTCPayServer.Models
|
namespace BTCPayServer.Models
|
||||||
{
|
{
|
||||||
class DateTimeJsonConverter : JsonConverter
|
class DateTimeJsonConverter : JsonConverter
|
||||||
{
|
{
|
||||||
public override bool CanConvert(Type objectType)
|
public override bool CanConvert(Type objectType)
|
||||||
{
|
{
|
||||||
return objectType == typeof(DateTimeOffset);
|
return objectType == typeof(DateTimeOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||||
{
|
{
|
||||||
var v = (long)reader.Value;
|
var v = (long)reader.Value;
|
||||||
Check(v);
|
Check(v);
|
||||||
return unixRef + TimeSpan.FromMilliseconds((long)v);
|
return unixRef + TimeSpan.FromMilliseconds((long)v);
|
||||||
}
|
}
|
||||||
|
|
||||||
static DateTimeOffset unixRef = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
static DateTimeOffset unixRef = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||||
{
|
{
|
||||||
var date = ((DateTimeOffset)value).ToUniversalTime();
|
var date = ((DateTimeOffset)value).ToUniversalTime();
|
||||||
long v = (long)(date - unixRef).TotalMilliseconds;
|
long v = (long)(date - unixRef).TotalMilliseconds;
|
||||||
Check(v);
|
Check(v);
|
||||||
writer.WriteValue(v);
|
writer.WriteValue(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Check(long v)
|
private static void Check(long v)
|
||||||
{
|
{
|
||||||
if(v < 0)
|
if (v < 0)
|
||||||
throw new FormatException("Invalid datetime (less than 1/1/1970)");
|
throw new FormatException("Invalid datetime (less than 1/1/1970)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//{"facade":"pos/invoice","data":{,}}
|
//{"facade":"pos/invoice","data":{,}}
|
||||||
public class InvoiceResponse
|
public class InvoiceResponse
|
||||||
{
|
{
|
||||||
//"url":"https://test.bitpay.com/invoice?id=9saCHtp1zyPcNoi3rDdBu8"
|
//"url":"https://test.bitpay.com/invoice?id=9saCHtp1zyPcNoi3rDdBu8"
|
||||||
[JsonProperty("url")]
|
[JsonProperty("url")]
|
||||||
public string Url
|
public string Url
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
//"posData":"posData"
|
//"posData":"posData"
|
||||||
[JsonProperty("posData")]
|
[JsonProperty("posData")]
|
||||||
public string PosData
|
public string PosData
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
//status":"new"
|
//status":"new"
|
||||||
[JsonProperty("status")]
|
[JsonProperty("status")]
|
||||||
public string Status
|
public string Status
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
//"btcPrice":"0.001157"
|
//"btcPrice":"0.001157"
|
||||||
[JsonProperty("btcPrice")]
|
[JsonProperty("btcPrice")]
|
||||||
public string BTCPrice
|
public string BTCPrice
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
//"btcDue":"0.001160"
|
//"btcDue":"0.001160"
|
||||||
[JsonProperty("btcDue")]
|
[JsonProperty("btcDue")]
|
||||||
public string BTCDue
|
public string BTCDue
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
//"price":5
|
//"price":5
|
||||||
[JsonProperty("price")]
|
[JsonProperty("price")]
|
||||||
public double Price
|
public double Price
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
//"currency":"USD"
|
//"currency":"USD"
|
||||||
[JsonProperty("currency")]
|
[JsonProperty("currency")]
|
||||||
public string Currency
|
public string Currency
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
//"exRates":{"USD":4320.02}
|
//"exRates":{"USD":4320.02}
|
||||||
[JsonProperty("exRates")]
|
[JsonProperty("exRates")]
|
||||||
public Dictionary<string, double> ExRates
|
public Dictionary<string, double> ExRates
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
//"buyerTotalBtcAmount":"0.001160"
|
//"buyerTotalBtcAmount":"0.001160"
|
||||||
[JsonProperty("buyerTotalBtcAmount")]
|
[JsonProperty("buyerTotalBtcAmount")]
|
||||||
public string BuyerTotalBtcAmount
|
public string BuyerTotalBtcAmount
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
//"itemDesc":"Some description"
|
//"itemDesc":"Some description"
|
||||||
[JsonProperty("itemDesc")]
|
[JsonProperty("itemDesc")]
|
||||||
public string ItemDesc
|
public string ItemDesc
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
//"orderId":"orderId"
|
//"orderId":"orderId"
|
||||||
[JsonProperty("orderId")]
|
[JsonProperty("orderId")]
|
||||||
public string OrderId
|
public string OrderId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
//"guid":"e238ce2a-06da-47e9-aefd-2588d4aa5f8d"
|
//"guid":"e238ce2a-06da-47e9-aefd-2588d4aa5f8d"
|
||||||
[JsonProperty("guid")]
|
[JsonProperty("guid")]
|
||||||
public string Guid
|
public string Guid
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
//"id":"9saCHtp1zyPcNoi3rDdBu8"
|
//"id":"9saCHtp1zyPcNoi3rDdBu8"
|
||||||
[JsonProperty("id")]
|
[JsonProperty("id")]
|
||||||
public string Id
|
public string Id
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonConverter(typeof(DateTimeJsonConverter))]
|
[JsonConverter(typeof(DateTimeJsonConverter))]
|
||||||
[JsonProperty("invoiceTime")]
|
[JsonProperty("invoiceTime")]
|
||||||
public DateTimeOffset InvoiceTime
|
public DateTimeOffset InvoiceTime
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonConverter(typeof(DateTimeJsonConverter))]
|
[JsonConverter(typeof(DateTimeJsonConverter))]
|
||||||
[JsonProperty("expirationTime")]
|
[JsonProperty("expirationTime")]
|
||||||
public DateTimeOffset ExpirationTime
|
public DateTimeOffset ExpirationTime
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonConverter(typeof(DateTimeJsonConverter))]
|
[JsonConverter(typeof(DateTimeJsonConverter))]
|
||||||
[JsonProperty("currentTime")]
|
[JsonProperty("currentTime")]
|
||||||
public DateTimeOffset CurrentTime
|
public DateTimeOffset CurrentTime
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
//"lowFeeDetected":false
|
//"lowFeeDetected":false
|
||||||
[JsonProperty("lowFeeDetected")]
|
[JsonProperty("lowFeeDetected")]
|
||||||
public bool LowFeeDetected
|
public bool LowFeeDetected
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
//"btcPaid":"0.000000"
|
//"btcPaid":"0.000000"
|
||||||
[JsonProperty("btcPaid")]
|
[JsonProperty("btcPaid")]
|
||||||
public string BTCPaid
|
public string BTCPaid
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
//"rate":4320.02
|
//"rate":4320.02
|
||||||
[JsonProperty("rate")]
|
[JsonProperty("rate")]
|
||||||
public double Rate
|
public double Rate
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
//"exceptionStatus":false
|
//"exceptionStatus":false
|
||||||
//Can be `paidPartial`, `paidOver`, or false
|
//Can be `paidPartial`, `paidOver`, or false
|
||||||
[JsonProperty("exceptionStatus")]
|
[JsonProperty("exceptionStatus")]
|
||||||
public JToken ExceptionStatus
|
public JToken ExceptionStatus
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
//"paymentUrls":{"BIP21":"bitcoin:muFQCEbfRJohcds3bkfv1sRFj8uVTfv2wv?amount=0.001160","BIP72":"bitcoin:muFQCEbfRJohcds3bkfv1sRFj8uVTfv2wv?amount=0.001160&r=https://test.bitpay.com/i/9saCHtp1zyPcNoi3rDdBu8","BIP72b":"bitcoin:?r=https://test.bitpay.com/i/9saCHtp1zyPcNoi3rDdBu8","BIP73":"https://test.bitpay.com/i/9saCHtp1zyPcNoi3rDdBu8"}
|
//"paymentUrls":{"BIP21":"bitcoin:muFQCEbfRJohcds3bkfv1sRFj8uVTfv2wv?amount=0.001160","BIP72":"bitcoin:muFQCEbfRJohcds3bkfv1sRFj8uVTfv2wv?amount=0.001160&r=https://test.bitpay.com/i/9saCHtp1zyPcNoi3rDdBu8","BIP72b":"bitcoin:?r=https://test.bitpay.com/i/9saCHtp1zyPcNoi3rDdBu8","BIP73":"https://test.bitpay.com/i/9saCHtp1zyPcNoi3rDdBu8"}
|
||||||
[JsonProperty("paymentUrls")]
|
[JsonProperty("paymentUrls")]
|
||||||
public NBitpayClient.InvoicePaymentUrls PaymentUrls
|
public NBitpayClient.InvoicePaymentUrls PaymentUrls
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
//"refundAddressRequestPending":false
|
//"refundAddressRequestPending":false
|
||||||
[JsonProperty("refundAddressRequestPending")]
|
[JsonProperty("refundAddressRequestPending")]
|
||||||
public bool RefundAddressRequestPending
|
public bool RefundAddressRequestPending
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
//"buyerPaidBtcMinerFee":"0.000003"
|
//"buyerPaidBtcMinerFee":"0.000003"
|
||||||
[JsonProperty("buyerPaidBtcMinerFee")]
|
[JsonProperty("buyerPaidBtcMinerFee")]
|
||||||
public string BuyerPaidBtcMinerFee
|
public string BuyerPaidBtcMinerFee
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
//"bitcoinAddress":"muFQCEbfRJohcds3bkfv1sRFj8uVTfv2wv"
|
//"bitcoinAddress":"muFQCEbfRJohcds3bkfv1sRFj8uVTfv2wv"
|
||||||
[JsonProperty("bitcoinAddress")]
|
[JsonProperty("bitcoinAddress")]
|
||||||
public string BitcoinAddress
|
public string BitcoinAddress
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
//"token":"9jF3TU7A8inKHDRQXFrKcRnMkLXWGQ2yKf7pnjMKGHEfpwTNV35HytrD9FXDBy25Li"
|
//"token":"9jF3TU7A8inKHDRQXFrKcRnMkLXWGQ2yKf7pnjMKGHEfpwTNV35HytrD9FXDBy25Li"
|
||||||
[JsonProperty("token")]
|
[JsonProperty("token")]
|
||||||
public string Token
|
public string Token
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty("flags")]
|
[JsonProperty("flags")]
|
||||||
public Flags Flags
|
public Flags Flags
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public class Flags
|
public class Flags
|
||||||
{
|
{
|
||||||
[JsonProperty("refundable")]
|
[JsonProperty("refundable")]
|
||||||
public bool Refundable
|
public bool Refundable
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,52 +7,52 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Models.InvoicingModels
|
namespace BTCPayServer.Models.InvoicingModels
|
||||||
{
|
{
|
||||||
public class CreateInvoiceModel
|
public class CreateInvoiceModel
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
public double? Amount
|
public double? Amount
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public string StoreId
|
public string StoreId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string OrderId
|
public string OrderId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ItemDesc
|
public string ItemDesc
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string PosData
|
public string PosData
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[EmailAddress]
|
[EmailAddress]
|
||||||
public string BuyerEmail
|
public string BuyerEmail
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Url]
|
[Url]
|
||||||
public string NotificationUrl
|
public string NotificationUrl
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SelectList Stores
|
public SelectList Stores
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,138 +9,138 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||||||
{
|
{
|
||||||
public class InvoiceDetailsModel
|
public class InvoiceDetailsModel
|
||||||
{
|
{
|
||||||
public class Payment
|
public class Payment
|
||||||
{
|
{
|
||||||
public int Confirmations
|
public int Confirmations
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public BitcoinAddress DepositAddress
|
public BitcoinAddress DepositAddress
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string Amount
|
public string Amount
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string TransactionId
|
public string TransactionId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public DateTimeOffset ReceivedTime
|
public DateTimeOffset ReceivedTime
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
internal set;
|
internal set;
|
||||||
}
|
}
|
||||||
public string TransactionLink
|
public string TransactionLink
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string StatusMessage
|
public string StatusMessage
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public String Id
|
public String Id
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Payment> Payments
|
public List<Payment> Payments
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
} = new List<Payment>();
|
} = new List<Payment>();
|
||||||
|
|
||||||
public string Status
|
public string Status
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTimeOffset CreatedDate
|
public DateTimeOffset CreatedDate
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTimeOffset ExpirationDate
|
public DateTimeOffset ExpirationDate
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string OrderId
|
public string OrderId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string RefundEmail
|
public string RefundEmail
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public BuyerInformation BuyerInformation
|
public BuyerInformation BuyerInformation
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public object StoreName
|
public object StoreName
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
internal set;
|
internal set;
|
||||||
}
|
}
|
||||||
public string StoreLink
|
public string StoreLink
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public double Rate
|
public double Rate
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
internal set;
|
internal set;
|
||||||
}
|
}
|
||||||
public string NotificationUrl
|
public string NotificationUrl
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
internal set;
|
internal set;
|
||||||
}
|
}
|
||||||
public string Fiat
|
public string Fiat
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string BTC
|
public string BTC
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string BTCDue
|
public string BTCDue
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string BTCPaid
|
public string BTCPaid
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
internal set;
|
internal set;
|
||||||
}
|
}
|
||||||
public String NetworkFee
|
public String NetworkFee
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
internal set;
|
internal set;
|
||||||
}
|
}
|
||||||
public ProductInformation ProductInformation
|
public ProductInformation ProductInformation
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
internal set;
|
internal set;
|
||||||
}
|
}
|
||||||
public BitcoinAddress BitcoinAddress
|
public BitcoinAddress BitcoinAddress
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
internal set;
|
internal set;
|
||||||
}
|
}
|
||||||
public string PaymentUrl
|
public string PaymentUrl
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,53 +7,53 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||||||
{
|
{
|
||||||
public class InvoicesModel
|
public class InvoicesModel
|
||||||
{
|
{
|
||||||
public int Skip
|
public int Skip
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public int Count
|
public int Count
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string SearchTerm
|
public string SearchTerm
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<InvoiceModel> Invoices
|
public List<InvoiceModel> Invoices
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
} = new List<InvoiceModel>();
|
} = new List<InvoiceModel>();
|
||||||
public string StatusMessage
|
public string StatusMessage
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class InvoiceModel
|
public class InvoiceModel
|
||||||
{
|
{
|
||||||
public DateTimeOffset Date
|
public DateTimeOffset Date
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string InvoiceId
|
public string InvoiceId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Status
|
public string Status
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string AmountCurrency
|
public string AmountCurrency
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string StatusMessage
|
public string StatusMessage
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,31 +5,31 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Models.InvoicingModels
|
namespace BTCPayServer.Models.InvoicingModels
|
||||||
{
|
{
|
||||||
public class PaymentModel
|
public class PaymentModel
|
||||||
{
|
{
|
||||||
public string ServerUrl { get; set; }
|
public string ServerUrl { get; set; }
|
||||||
public string InvoiceId { get; set; }
|
public string InvoiceId { get; set; }
|
||||||
public string BtcAddress { get; set; }
|
public string BtcAddress { get; set; }
|
||||||
public string BtcDue { get; set; }
|
public string BtcDue { get; set; }
|
||||||
public string CustomerEmail { get; set; }
|
public string CustomerEmail { get; set; }
|
||||||
public int ExpirationSeconds { get; set; }
|
public int ExpirationSeconds { get; set; }
|
||||||
public string Status { get; set; }
|
public string Status { get; set; }
|
||||||
public string MerchantRefLink { get; set; }
|
public string MerchantRefLink { get; set; }
|
||||||
public int MaxTimeSeconds { get; set; }
|
public int MaxTimeSeconds { get; set; }
|
||||||
|
|
||||||
// These properties are not used in client side code
|
// These properties are not used in client side code
|
||||||
public string StoreName { get; set; }
|
public string StoreName { get; set; }
|
||||||
public string ItemDesc { get; set; }
|
public string ItemDesc { get; set; }
|
||||||
public string TimeLeft { get; set; }
|
public string TimeLeft { get; set; }
|
||||||
public string Rate { get; set; }
|
public string Rate { get; set; }
|
||||||
public string BtcAmount { get; set; }
|
public string BtcAmount { get; set; }
|
||||||
public string TxFees { get; set; }
|
public string TxFees { get; set; }
|
||||||
public string InvoiceBitcoinUrl { get; set; }
|
public string InvoiceBitcoinUrl { get; set; }
|
||||||
public string BtcTotalDue { get; set; }
|
public string BtcTotalDue { get; set; }
|
||||||
public int TxCount { get; set; }
|
public int TxCount { get; set; }
|
||||||
public string BtcPaid { get; set; }
|
public string BtcPaid { get; set; }
|
||||||
public string StoreEmail { get; set; }
|
public string StoreEmail { get; set; }
|
||||||
|
|
||||||
public string OrderId { get; set; }
|
public string OrderId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||||||
{
|
{
|
||||||
public class UpdateCustomerModel
|
public class UpdateCustomerModel
|
||||||
{
|
{
|
||||||
[EmailAddress]
|
[EmailAddress]
|
||||||
[Required]
|
[Required]
|
||||||
public string Email
|
public string Email
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,15 +9,15 @@ namespace BTCPayServer.Models.ManageViewModels
|
|||||||
{
|
{
|
||||||
public class EnableAuthenticatorViewModel
|
public class EnableAuthenticatorViewModel
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||||
[DataType(DataType.Text)]
|
[DataType(DataType.Text)]
|
||||||
[Display(Name = "Verification Code")]
|
[Display(Name = "Verification Code")]
|
||||||
public string Code { get; set; }
|
public string Code { get; set; }
|
||||||
|
|
||||||
[ReadOnly(true)]
|
[ReadOnly(true)]
|
||||||
public string SharedKey { get; set; }
|
public string SharedKey { get; set; }
|
||||||
|
|
||||||
public string AuthenticatorUri { get; set; }
|
public string AuthenticatorUri { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,25 +12,25 @@ namespace BTCPayServer.Models.ManageViewModels
|
|||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[EmailAddress]
|
[EmailAddress]
|
||||||
[MaxLength(50)]
|
[MaxLength(50)]
|
||||||
public string Email
|
public string Email
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsEmailConfirmed { get; set; }
|
public bool IsEmailConfirmed { get; set; }
|
||||||
|
|
||||||
[Phone]
|
[Phone]
|
||||||
[Display(Name = "Phone number")]
|
[Display(Name = "Phone number")]
|
||||||
[MaxLength(50)]
|
[MaxLength(50)]
|
||||||
public string PhoneNumber { get; set; }
|
public string PhoneNumber { get; set; }
|
||||||
|
|
||||||
public string StatusMessage
|
public string StatusMessage
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,20 +10,20 @@ namespace BTCPayServer.Models.ServerViewModels
|
|||||||
{
|
{
|
||||||
public class EmailsViewModel
|
public class EmailsViewModel
|
||||||
{
|
{
|
||||||
public string StatusMessage
|
public string StatusMessage
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public EmailSettings Settings
|
public EmailSettings Settings
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[EmailAddress]
|
[EmailAddress]
|
||||||
public string TestEmail
|
public string TestEmail
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,27 +7,27 @@ namespace BTCPayServer.Models.ServerViewModels
|
|||||||
{
|
{
|
||||||
public class UsersViewModel
|
public class UsersViewModel
|
||||||
{
|
{
|
||||||
public class UserViewModel
|
public class UserViewModel
|
||||||
{
|
{
|
||||||
public string Name
|
public string Name
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string Email
|
public string Email
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string StatusMessage
|
public string StatusMessage
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<UserViewModel> Users
|
public List<UserViewModel> Users
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
} = new List<UserViewModel>();
|
} = new List<UserViewModel>();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||||||
{
|
{
|
||||||
public class CreateStoreViewModel
|
public class CreateStoreViewModel
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
[MaxLength(50)]
|
[MaxLength(50)]
|
||||||
[MinLength(1)]
|
[MinLength(1)]
|
||||||
public string Name
|
public string Name
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,44 +8,44 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||||||
{
|
{
|
||||||
public class PairingModel
|
public class PairingModel
|
||||||
{
|
{
|
||||||
public class StoreViewModel
|
public class StoreViewModel
|
||||||
{
|
{
|
||||||
public string Name
|
public string Name
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string Id
|
public string Id
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public string Id
|
public string Id
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string Label
|
public string Label
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string Facade
|
public string Facade
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string SIN
|
public string SIN
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public StoreViewModel[] Stores
|
public StoreViewModel[] Stores
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Display(Name = "Pair to")]
|
[Display(Name = "Pair to")]
|
||||||
[Required]
|
[Required]
|
||||||
public string SelectedStore
|
public string SelectedStore
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,50 +10,50 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||||||
{
|
{
|
||||||
public class StoreViewModel
|
public class StoreViewModel
|
||||||
{
|
{
|
||||||
[Display(Name = "Store Name")]
|
[Display(Name = "Store Name")]
|
||||||
[Required]
|
[Required]
|
||||||
[MaxLength(50)]
|
[MaxLength(50)]
|
||||||
[MinLength(1)]
|
[MinLength(1)]
|
||||||
public string StoreName
|
public string StoreName
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Url]
|
[Url]
|
||||||
[Display(Name = "Store Website")]
|
[Display(Name = "Store Website")]
|
||||||
[MaxLength(500)]
|
[MaxLength(500)]
|
||||||
public string StoreWebsite
|
public string StoreWebsite
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[DerivationStrategyValidator]
|
[DerivationStrategyValidator]
|
||||||
public string DerivationScheme
|
public string DerivationScheme
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Display(Name = "Consider the invoice confirmed when the payment transaction...")]
|
[Display(Name = "Consider the invoice confirmed when the payment transaction...")]
|
||||||
public SpeedPolicy SpeedPolicy
|
public SpeedPolicy SpeedPolicy
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Display(Name = "Add network fee to invoice (vary with mining fees)")]
|
[Display(Name = "Add network fee to invoice (vary with mining fees)")]
|
||||||
public bool NetworkFee
|
public bool NetworkFee
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<(string KeyPath, string Address)> AddressSamples
|
public List<(string KeyPath, string Address)> AddressSamples
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
} = new List<(string KeyPath, string Address)>();
|
} = new List<(string KeyPath, string Address)>();
|
||||||
|
|
||||||
public string StatusMessage
|
public string StatusMessage
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,34 +8,34 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||||||
{
|
{
|
||||||
public class StoresViewModel
|
public class StoresViewModel
|
||||||
{
|
{
|
||||||
public string StatusMessage
|
public string StatusMessage
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public List<StoreViewModel> Stores
|
public List<StoreViewModel> Stores
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
} = new List<StoreViewModel>();
|
} = new List<StoreViewModel>();
|
||||||
public class StoreViewModel
|
public class StoreViewModel
|
||||||
{
|
{
|
||||||
public string Name
|
public string Name
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string WebSite
|
public string WebSite
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Id
|
public string Id
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public Money Balance
|
public Money Balance
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,65 +8,65 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Models.StoreViewModels
|
namespace BTCPayServer.Models.StoreViewModels
|
||||||
{
|
{
|
||||||
public class CreateTokenViewModel
|
public class CreateTokenViewModel
|
||||||
{
|
|
||||||
[PubKeyValidatorAttribute]
|
|
||||||
public string PublicKey
|
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Label
|
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Required]
|
|
||||||
public string Facade
|
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Required]
|
|
||||||
public string StoreId
|
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SelectList Stores
|
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public class TokenViewModel
|
|
||||||
{
|
|
||||||
public string Id
|
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
public string Label
|
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
public string SIN
|
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
public string Facade
|
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public class TokensViewModel
|
|
||||||
{
|
{
|
||||||
public TokenViewModel[] Tokens
|
[PubKeyValidatorAttribute]
|
||||||
{
|
public string PublicKey
|
||||||
get; set;
|
{
|
||||||
}
|
get; set;
|
||||||
public string StatusMessage
|
}
|
||||||
{
|
|
||||||
get;
|
public string Label
|
||||||
set;
|
{
|
||||||
}
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string Facade
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string StoreId
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SelectList Stores
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public class TokenViewModel
|
||||||
|
{
|
||||||
|
public string Id
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
public string Label
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
public string SIN
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
public string Facade
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public class TokensViewModel
|
||||||
|
{
|
||||||
|
public TokenViewModel[] Tokens
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
public string StatusMessage
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,83 +6,83 @@ using NBitcoin;
|
|||||||
|
|
||||||
namespace BTCPayServer.Models
|
namespace BTCPayServer.Models
|
||||||
{
|
{
|
||||||
public class TokenRequest
|
public class TokenRequest
|
||||||
{
|
{
|
||||||
[JsonProperty(PropertyName = "id")]
|
[JsonProperty(PropertyName = "id")]
|
||||||
public string Id
|
public string Id
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "guid")]
|
[JsonProperty(PropertyName = "guid")]
|
||||||
public string Guid
|
public string Guid
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
[JsonProperty(PropertyName = "facade")]
|
[JsonProperty(PropertyName = "facade")]
|
||||||
public string Facade
|
public string Facade
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
[JsonProperty(PropertyName = "count")]
|
[JsonProperty(PropertyName = "count")]
|
||||||
public int Count
|
public int Count
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
[JsonProperty(PropertyName = "label")]
|
[JsonProperty(PropertyName = "label")]
|
||||||
public string Label
|
public string Label
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "pairingCode")]
|
[JsonProperty(PropertyName = "pairingCode")]
|
||||||
public string PairingCode
|
public string PairingCode
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PairingCodeResponse
|
public class PairingCodeResponse
|
||||||
{
|
{
|
||||||
[JsonProperty(PropertyName = "pairingCode")]
|
[JsonProperty(PropertyName = "pairingCode")]
|
||||||
public string PairingCode
|
public string PairingCode
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "pairingExpiration")]
|
[JsonProperty(PropertyName = "pairingExpiration")]
|
||||||
[JsonConverter(typeof(DateTimeJsonConverter))]
|
[JsonConverter(typeof(DateTimeJsonConverter))]
|
||||||
public DateTimeOffset PairingExpiration
|
public DateTimeOffset PairingExpiration
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "dateCreated")]
|
[JsonProperty(PropertyName = "dateCreated")]
|
||||||
[JsonConverter(typeof(DateTimeJsonConverter))]
|
[JsonConverter(typeof(DateTimeJsonConverter))]
|
||||||
public DateTimeOffset DateCreated
|
public DateTimeOffset DateCreated
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "facade")]
|
[JsonProperty(PropertyName = "facade")]
|
||||||
public string Facade
|
public string Facade
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "token")]
|
[JsonProperty(PropertyName = "token")]
|
||||||
public string Token
|
public string Token
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "label")]
|
[JsonProperty(PropertyName = "label")]
|
||||||
public string Label
|
public string Label
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,60 +18,60 @@ using System.Threading;
|
|||||||
|
|
||||||
namespace BTCPayServer
|
namespace BTCPayServer
|
||||||
{
|
{
|
||||||
class Program
|
class Program
|
||||||
{
|
{
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
{
|
{
|
||||||
ServicePointManager.DefaultConnectionLimit = 100;
|
ServicePointManager.DefaultConnectionLimit = 100;
|
||||||
IWebHost host = null;
|
IWebHost host = null;
|
||||||
CustomConsoleLogProvider loggerProvider = new CustomConsoleLogProvider();
|
CustomConsoleLogProvider loggerProvider = new CustomConsoleLogProvider();
|
||||||
|
|
||||||
var loggerFactory = new LoggerFactory();
|
var loggerFactory = new LoggerFactory();
|
||||||
loggerFactory.AddProvider(loggerProvider);
|
loggerFactory.AddProvider(loggerProvider);
|
||||||
var logger = loggerFactory.CreateLogger("Configuration");
|
var logger = loggerFactory.CreateLogger("Configuration");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var conf = new DefaultConfiguration() { Logger = logger }.CreateConfiguration(args);
|
var conf = new DefaultConfiguration() { Logger = logger }.CreateConfiguration(args);
|
||||||
if(conf == null)
|
if (conf == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
host = new WebHostBuilder()
|
host = new WebHostBuilder()
|
||||||
.UseKestrel()
|
.UseKestrel()
|
||||||
.UseIISIntegration()
|
.UseIISIntegration()
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
.UseConfiguration(conf)
|
.UseConfiguration(conf)
|
||||||
.UseApplicationInsights()
|
.UseApplicationInsights()
|
||||||
.ConfigureLogging(l =>
|
.ConfigureLogging(l =>
|
||||||
{
|
{
|
||||||
l.AddFilter("Microsoft", LogLevel.Error);
|
l.AddFilter("Microsoft", LogLevel.Error);
|
||||||
l.AddProvider(new CustomConsoleLogProvider());
|
l.AddProvider(new CustomConsoleLogProvider());
|
||||||
})
|
})
|
||||||
.UseStartup<Startup>()
|
.UseStartup<Startup>()
|
||||||
.Build();
|
.Build();
|
||||||
host.StartAsync().GetAwaiter().GetResult();
|
host.StartAsync().GetAwaiter().GetResult();
|
||||||
var urls = host.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
|
var urls = host.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
|
||||||
foreach(var url in urls)
|
foreach (var url in urls)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Listening on " + url);
|
logger.LogInformation("Listening on " + url);
|
||||||
}
|
}
|
||||||
host.WaitForShutdown();
|
host.WaitForShutdown();
|
||||||
}
|
}
|
||||||
catch(ConfigException ex)
|
catch (ConfigException ex)
|
||||||
{
|
{
|
||||||
if(!string.IsNullOrEmpty(ex.Message))
|
if (!string.IsNullOrEmpty(ex.Message))
|
||||||
Logs.Configuration.LogError(ex.Message);
|
Logs.Configuration.LogError(ex.Message);
|
||||||
}
|
}
|
||||||
catch(Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
logger.LogError("Exception thrown while running the server");
|
logger.LogError("Exception thrown while running the server");
|
||||||
logger.LogError(exception.ToString());
|
logger.LogError(exception.ToString());
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if(host != null)
|
if (host != null)
|
||||||
host.Dispose();
|
host.Dispose();
|
||||||
loggerProvider.Dispose();
|
loggerProvider.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ namespace BTCPayServer
|
|||||||
{
|
{
|
||||||
public class Roles
|
public class Roles
|
||||||
{
|
{
|
||||||
public const string ServerAdmin = "ServerAdmin";
|
public const string ServerAdmin = "ServerAdmin";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,37 +10,37 @@ namespace BTCPayServer.Services
|
|||||||
{
|
{
|
||||||
public class BTCPayServerEnvironment
|
public class BTCPayServerEnvironment
|
||||||
{
|
{
|
||||||
public BTCPayServerEnvironment(IHostingEnvironment env)
|
public BTCPayServerEnvironment(IHostingEnvironment env)
|
||||||
{
|
{
|
||||||
Version = typeof(BTCPayServerEnvironment).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyFileVersionAttribute>().Version;
|
Version = typeof(BTCPayServerEnvironment).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyFileVersionAttribute>().Version;
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Build = "Debug";
|
Build = "Debug";
|
||||||
#else
|
#else
|
||||||
Build = "Release";
|
Build = "Release";
|
||||||
#endif
|
#endif
|
||||||
Environment = env;
|
Environment = env;
|
||||||
}
|
}
|
||||||
public IHostingEnvironment Environment
|
public IHostingEnvironment Environment
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string Version
|
public string Version
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string Build
|
public string Build
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
StringBuilder txt = new StringBuilder();
|
StringBuilder txt = new StringBuilder();
|
||||||
txt.Append($"@Copyright BTCPayServer v{Version}");
|
txt.Append($"@Copyright BTCPayServer v{Version}");
|
||||||
if(!Environment.IsProduction() || Build.Equals("Release", StringComparison.OrdinalIgnoreCase))
|
if (!Environment.IsProduction() || Build.Equals("Release", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
txt.Append($" Environment: {Environment.EnvironmentName} Build: {Build}");
|
txt.Append($" Environment: {Environment.EnvironmentName} Build: {Build}");
|
||||||
}
|
}
|
||||||
return txt.ToString();
|
return txt.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,21 +6,21 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Services.Fees
|
namespace BTCPayServer.Services.Fees
|
||||||
{
|
{
|
||||||
public class FixedFeeProvider : IFeeProvider
|
public class FixedFeeProvider : IFeeProvider
|
||||||
{
|
{
|
||||||
public FixedFeeProvider(FeeRate feeRate)
|
public FixedFeeProvider(FeeRate feeRate)
|
||||||
{
|
{
|
||||||
FeeRate = feeRate;
|
FeeRate = feeRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FeeRate FeeRate
|
public FeeRate FeeRate
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<FeeRate> GetFeeRateAsync()
|
public Task<FeeRate> GetFeeRateAsync()
|
||||||
{
|
{
|
||||||
return Task.FromResult(FeeRate);
|
return Task.FromResult(FeeRate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Services
|
namespace BTCPayServer.Services
|
||||||
{
|
{
|
||||||
public interface IFeeProvider
|
public interface IFeeProvider
|
||||||
{
|
{
|
||||||
Task<FeeRate> GetFeeRateAsync();
|
Task<FeeRate> GetFeeRateAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,30 +8,30 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Services.Fees
|
namespace BTCPayServer.Services.Fees
|
||||||
{
|
{
|
||||||
public class NBXplorerFeeProvider : IFeeProvider
|
public class NBXplorerFeeProvider : IFeeProvider
|
||||||
{
|
{
|
||||||
public ExplorerClient ExplorerClient
|
public ExplorerClient ExplorerClient
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public FeeRate Fallback
|
public FeeRate Fallback
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public int BlockTarget
|
public int BlockTarget
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public async Task<FeeRate> GetFeeRateAsync()
|
public async Task<FeeRate> GetFeeRateAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return (await ExplorerClient.GetFeeRateAsync(BlockTarget).ConfigureAwait(false)).FeeRate;
|
return (await ExplorerClient.GetFeeRateAsync(BlockTarget).ConfigureAwait(false)).FeeRate;
|
||||||
}
|
}
|
||||||
catch(NBXplorerException ex) when(ex.Error.HttpCode == 400 && ex.Error.Code == "fee-estimation-unavailable")
|
catch (NBXplorerException ex) when (ex.Error.HttpCode == 400 && ex.Error.Code == "fee-estimation-unavailable")
|
||||||
{
|
{
|
||||||
return Fallback;
|
return Fallback;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,328 +12,328 @@ using BTCPayServer.Data;
|
|||||||
|
|
||||||
namespace BTCPayServer.Services.Invoices
|
namespace BTCPayServer.Services.Invoices
|
||||||
{
|
{
|
||||||
public class BuyerInformation
|
public class BuyerInformation
|
||||||
{
|
{
|
||||||
[JsonProperty(PropertyName = "buyerName")]
|
[JsonProperty(PropertyName = "buyerName")]
|
||||||
public string BuyerName
|
public string BuyerName
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
[JsonProperty(PropertyName = "buyerEmail")]
|
[JsonProperty(PropertyName = "buyerEmail")]
|
||||||
public string BuyerEmail
|
public string BuyerEmail
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
[JsonProperty(PropertyName = "buyerCountry")]
|
[JsonProperty(PropertyName = "buyerCountry")]
|
||||||
public string BuyerCountry
|
public string BuyerCountry
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
[JsonProperty(PropertyName = "buyerZip")]
|
[JsonProperty(PropertyName = "buyerZip")]
|
||||||
public string BuyerZip
|
public string BuyerZip
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
[JsonProperty(PropertyName = "buyerState")]
|
[JsonProperty(PropertyName = "buyerState")]
|
||||||
public string BuyerState
|
public string BuyerState
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
[JsonProperty(PropertyName = "buyerCity")]
|
[JsonProperty(PropertyName = "buyerCity")]
|
||||||
public string BuyerCity
|
public string BuyerCity
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
[JsonProperty(PropertyName = "buyerAddress2")]
|
[JsonProperty(PropertyName = "buyerAddress2")]
|
||||||
public string BuyerAddress2
|
public string BuyerAddress2
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
[JsonProperty(PropertyName = "buyerAddress1")]
|
[JsonProperty(PropertyName = "buyerAddress1")]
|
||||||
public string BuyerAddress1
|
public string BuyerAddress1
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "buyerPhone")]
|
[JsonProperty(PropertyName = "buyerPhone")]
|
||||||
public string BuyerPhone
|
public string BuyerPhone
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ProductInformation
|
public class ProductInformation
|
||||||
{
|
{
|
||||||
[JsonProperty(PropertyName = "itemDesc")]
|
[JsonProperty(PropertyName = "itemDesc")]
|
||||||
public string ItemDesc
|
public string ItemDesc
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
[JsonProperty(PropertyName = "itemCode")]
|
[JsonProperty(PropertyName = "itemCode")]
|
||||||
public string ItemCode
|
public string ItemCode
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
[JsonProperty(PropertyName = "physical")]
|
[JsonProperty(PropertyName = "physical")]
|
||||||
public bool Physical
|
public bool Physical
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "price")]
|
[JsonProperty(PropertyName = "price")]
|
||||||
public double Price
|
public double Price
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "currency")]
|
[JsonProperty(PropertyName = "currency")]
|
||||||
public string Currency
|
public string Currency
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SpeedPolicy
|
public enum SpeedPolicy
|
||||||
{
|
{
|
||||||
HighSpeed = 0,
|
HighSpeed = 0,
|
||||||
MediumSpeed = 1,
|
MediumSpeed = 1,
|
||||||
LowSpeed = 2
|
LowSpeed = 2
|
||||||
}
|
}
|
||||||
public class InvoiceEntity
|
public class InvoiceEntity
|
||||||
{
|
{
|
||||||
public string Id
|
public string Id
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string StoreId
|
public string StoreId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetTxCount()
|
public int GetTxCount()
|
||||||
{
|
{
|
||||||
return Calculate().TxCount;
|
return Calculate().TxCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string OrderId
|
public string OrderId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Money GetTotalCryptoDue()
|
public Money GetTotalCryptoDue()
|
||||||
{
|
{
|
||||||
return Calculate().TotalDue;
|
return Calculate().TotalDue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private (Money TotalDue, Money Paid, int TxCount) Calculate()
|
private (Money TotalDue, Money Paid, int TxCount) Calculate()
|
||||||
{
|
{
|
||||||
var totalDue = Money.Coins((decimal)(ProductInformation.Price / Rate)) + TxFee;
|
var totalDue = Money.Coins((decimal)(ProductInformation.Price / Rate)) + TxFee;
|
||||||
var paid = Money.Zero;
|
var paid = Money.Zero;
|
||||||
int txCount = 1;
|
int txCount = 1;
|
||||||
var payments =
|
var payments =
|
||||||
Payments
|
Payments
|
||||||
.OrderByDescending(p => p.ReceivedTime)
|
.OrderByDescending(p => p.ReceivedTime)
|
||||||
.Select(_ =>
|
.Select(_ =>
|
||||||
{
|
{
|
||||||
paid += _.Output.Value;
|
paid += _.Output.Value;
|
||||||
return _;
|
return _;
|
||||||
})
|
})
|
||||||
.TakeWhile(_ =>
|
.TakeWhile(_ =>
|
||||||
{
|
{
|
||||||
var paidEnough = totalDue <= paid;
|
var paidEnough = totalDue <= paid;
|
||||||
if(!paidEnough)
|
if (!paidEnough)
|
||||||
{
|
{
|
||||||
txCount++;
|
txCount++;
|
||||||
totalDue += TxFee;
|
totalDue += TxFee;
|
||||||
}
|
}
|
||||||
return !paidEnough;
|
return !paidEnough;
|
||||||
})
|
})
|
||||||
.ToArray();
|
.ToArray();
|
||||||
return (totalDue, paid, txCount);
|
return (totalDue, paid, txCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Money GetTotalPaid()
|
public Money GetTotalPaid()
|
||||||
{
|
{
|
||||||
return Calculate().Paid;
|
return Calculate().Paid;
|
||||||
}
|
}
|
||||||
public Money GetCryptoDue()
|
public Money GetCryptoDue()
|
||||||
{
|
{
|
||||||
var o = Calculate();
|
var o = Calculate();
|
||||||
var v = o.TotalDue - o.Paid;
|
var v = o.TotalDue - o.Paid;
|
||||||
return v < Money.Zero ? Money.Zero : v;
|
return v < Money.Zero ? Money.Zero : v;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SpeedPolicy SpeedPolicy
|
public SpeedPolicy SpeedPolicy
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public double Rate
|
public double Rate
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public DateTimeOffset InvoiceTime
|
public DateTimeOffset InvoiceTime
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public DateTimeOffset ExpirationTime
|
public DateTimeOffset ExpirationTime
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public BitcoinAddress DepositAddress
|
public BitcoinAddress DepositAddress
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public ProductInformation ProductInformation
|
public ProductInformation ProductInformation
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public BuyerInformation BuyerInformation
|
public BuyerInformation BuyerInformation
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string PosData
|
public string PosData
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string DerivationStrategy
|
public string DerivationStrategy
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string Status
|
public string Status
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string ExceptionStatus
|
public string ExceptionStatus
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public List<PaymentEntity> Payments
|
public List<PaymentEntity> Payments
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public bool Refundable
|
public bool Refundable
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string RefundMail
|
public string RefundMail
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string RedirectURL
|
public string RedirectURL
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public Money TxFee
|
public Money TxFee
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public bool FullNotifications
|
public bool FullNotifications
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string NotificationURL
|
public string NotificationURL
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string ServerUrl
|
public string ServerUrl
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public DateTimeOffset? MonitoringExpiration
|
public DateTimeOffset? MonitoringExpiration
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public HistoricalAddressInvoiceData[] HistoricalAddresses
|
public HistoricalAddressInvoiceData[] HistoricalAddresses
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsExpired()
|
public bool IsExpired()
|
||||||
{
|
{
|
||||||
return DateTimeOffset.UtcNow > ExpirationTime;
|
return DateTimeOffset.UtcNow > ExpirationTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public InvoiceResponse EntityToDTO()
|
public InvoiceResponse EntityToDTO()
|
||||||
{
|
{
|
||||||
ServerUrl = ServerUrl ?? "";
|
ServerUrl = ServerUrl ?? "";
|
||||||
InvoiceResponse dto = new InvoiceResponse
|
InvoiceResponse dto = new InvoiceResponse
|
||||||
{
|
{
|
||||||
Id = Id,
|
Id = Id,
|
||||||
OrderId = OrderId,
|
OrderId = OrderId,
|
||||||
PosData = PosData,
|
PosData = PosData,
|
||||||
CurrentTime = DateTimeOffset.UtcNow,
|
CurrentTime = DateTimeOffset.UtcNow,
|
||||||
InvoiceTime = InvoiceTime,
|
InvoiceTime = InvoiceTime,
|
||||||
ExpirationTime = ExpirationTime,
|
ExpirationTime = ExpirationTime,
|
||||||
BTCPrice = Money.Coins((decimal)(1.0 / Rate)).ToString(),
|
BTCPrice = Money.Coins((decimal)(1.0 / Rate)).ToString(),
|
||||||
Status = Status,
|
Status = Status,
|
||||||
Url = ServerUrl.WithTrailingSlash() + "invoice?id=" + Id,
|
Url = ServerUrl.WithTrailingSlash() + "invoice?id=" + Id,
|
||||||
Currency = ProductInformation.Currency,
|
Currency = ProductInformation.Currency,
|
||||||
Flags = new Flags() { Refundable = Refundable }
|
Flags = new Flags() { Refundable = Refundable }
|
||||||
};
|
};
|
||||||
Populate(ProductInformation, dto);
|
Populate(ProductInformation, dto);
|
||||||
Populate(BuyerInformation, dto);
|
Populate(BuyerInformation, dto);
|
||||||
dto.ExRates = new Dictionary<string, double>
|
dto.ExRates = new Dictionary<string, double>
|
||||||
{
|
{
|
||||||
{ ProductInformation.Currency, Rate }
|
{ ProductInformation.Currency, Rate }
|
||||||
};
|
};
|
||||||
dto.PaymentUrls = new InvoicePaymentUrls()
|
dto.PaymentUrls = new InvoicePaymentUrls()
|
||||||
{
|
{
|
||||||
BIP72 = $"bitcoin:{DepositAddress}?amount={GetCryptoDue()}&r={ServerUrl.WithTrailingSlash() + ($"i/{Id}")}",
|
BIP72 = $"bitcoin:{DepositAddress}?amount={GetCryptoDue()}&r={ServerUrl.WithTrailingSlash() + ($"i/{Id}")}",
|
||||||
BIP72b = $"bitcoin:?r={ServerUrl.WithTrailingSlash() + ($"i/{Id}")}",
|
BIP72b = $"bitcoin:?r={ServerUrl.WithTrailingSlash() + ($"i/{Id}")}",
|
||||||
BIP73 = ServerUrl.WithTrailingSlash() + ($"i/{Id}"),
|
BIP73 = ServerUrl.WithTrailingSlash() + ($"i/{Id}"),
|
||||||
BIP21 = $"bitcoin:{DepositAddress}?amount={GetCryptoDue()}",
|
BIP21 = $"bitcoin:{DepositAddress}?amount={GetCryptoDue()}",
|
||||||
};
|
};
|
||||||
dto.BitcoinAddress = DepositAddress.ToString();
|
dto.BitcoinAddress = DepositAddress.ToString();
|
||||||
dto.Token = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16)); //No idea what it is useful for
|
dto.Token = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16)); //No idea what it is useful for
|
||||||
dto.Guid = Guid.NewGuid().ToString();
|
dto.Guid = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
var paid = Payments.Select(p => p.Output.Value).Sum();
|
var paid = Payments.Select(p => p.Output.Value).Sum();
|
||||||
dto.BTCPaid = paid.ToString();
|
dto.BTCPaid = paid.ToString();
|
||||||
dto.BTCDue = GetCryptoDue().ToString();
|
dto.BTCDue = GetCryptoDue().ToString();
|
||||||
|
|
||||||
dto.ExceptionStatus = ExceptionStatus == null ? new JValue(false) : new JValue(ExceptionStatus);
|
dto.ExceptionStatus = ExceptionStatus == null ? new JValue(false) : new JValue(ExceptionStatus);
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Populate<TFrom, TDest>(TFrom from, TDest dest)
|
private void Populate<TFrom, TDest>(TFrom from, TDest dest)
|
||||||
{
|
{
|
||||||
var str = JsonConvert.SerializeObject(from);
|
var str = JsonConvert.SerializeObject(from);
|
||||||
JsonConvert.PopulateObject(str, dest);
|
JsonConvert.PopulateObject(str, dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Money GetNetworkFee()
|
public Money GetNetworkFee()
|
||||||
{
|
{
|
||||||
var item = Calculate();
|
var item = Calculate();
|
||||||
return TxFee * item.TxCount;
|
return TxFee * item.TxCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PaymentEntity
|
public class PaymentEntity
|
||||||
{
|
{
|
||||||
public DateTimeOffset ReceivedTime
|
public DateTimeOffset ReceivedTime
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public OutPoint Outpoint
|
public OutPoint Outpoint
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public TxOut Output
|
public TxOut Output
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,109 +18,109 @@ using System.Collections.Concurrent;
|
|||||||
|
|
||||||
namespace BTCPayServer.Services.Invoices
|
namespace BTCPayServer.Services.Invoices
|
||||||
{
|
{
|
||||||
public class InvoiceNotificationManager
|
public class InvoiceNotificationManager
|
||||||
{
|
{
|
||||||
public static HttpClient _Client = new HttpClient();
|
public static HttpClient _Client = new HttpClient();
|
||||||
|
|
||||||
public class ScheduledJob
|
public class ScheduledJob
|
||||||
{
|
{
|
||||||
public int TryCount
|
public int TryCount
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InvoiceEntity Invoice
|
public InvoiceEntity Invoice
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ILogger Logger
|
public ILogger Logger
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
IBackgroundJobClient _JobClient;
|
IBackgroundJobClient _JobClient;
|
||||||
public InvoiceNotificationManager(
|
public InvoiceNotificationManager(
|
||||||
IBackgroundJobClient jobClient,
|
IBackgroundJobClient jobClient,
|
||||||
ILogger<InvoiceNotificationManager> logger)
|
ILogger<InvoiceNotificationManager> logger)
|
||||||
{
|
{
|
||||||
Logger = logger as ILogger ?? NullLogger.Instance;
|
Logger = logger as ILogger ?? NullLogger.Instance;
|
||||||
_JobClient = jobClient;
|
_JobClient = jobClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Notify(InvoiceEntity invoice)
|
public void Notify(InvoiceEntity invoice)
|
||||||
{
|
{
|
||||||
var invoiceStr = NBitcoin.JsonConverters.Serializer.ToString(new ScheduledJob() { TryCount = 0, Invoice = invoice });
|
var invoiceStr = NBitcoin.JsonConverters.Serializer.ToString(new ScheduledJob() { TryCount = 0, Invoice = invoice });
|
||||||
if(!string.IsNullOrEmpty(invoice.NotificationURL))
|
if (!string.IsNullOrEmpty(invoice.NotificationURL))
|
||||||
_JobClient.Schedule(() => NotifyHttp(invoiceStr), TimeSpan.Zero);
|
_JobClient.Schedule(() => NotifyHttp(invoiceStr), TimeSpan.Zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
ConcurrentDictionary<string, string> _Executing = new ConcurrentDictionary<string, string>();
|
ConcurrentDictionary<string, string> _Executing = new ConcurrentDictionary<string, string>();
|
||||||
public async Task NotifyHttp(string invoiceData)
|
public async Task NotifyHttp(string invoiceData)
|
||||||
{
|
{
|
||||||
var job = NBitcoin.JsonConverters.Serializer.ToObject<ScheduledJob>(invoiceData);
|
var job = NBitcoin.JsonConverters.Serializer.ToObject<ScheduledJob>(invoiceData);
|
||||||
var jobId = GetHttpJobId(job.Invoice);
|
var jobId = GetHttpJobId(job.Invoice);
|
||||||
|
|
||||||
if(!_Executing.TryAdd(jobId, jobId))
|
if (!_Executing.TryAdd(jobId, jobId))
|
||||||
return; //For some reason, Hangfire fire the job several time
|
return; //For some reason, Hangfire fire the job several time
|
||||||
|
|
||||||
Logger.LogInformation("Running " + jobId);
|
Logger.LogInformation("Running " + jobId);
|
||||||
bool reschedule = false;
|
bool reschedule = false;
|
||||||
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var request = new HttpRequestMessage();
|
var request = new HttpRequestMessage();
|
||||||
request.Method = HttpMethod.Post;
|
request.Method = HttpMethod.Post;
|
||||||
|
|
||||||
var dto = job.Invoice.EntityToDTO();
|
var dto = job.Invoice.EntityToDTO();
|
||||||
InvoicePaymentNotification notification = new InvoicePaymentNotification()
|
InvoicePaymentNotification notification = new InvoicePaymentNotification()
|
||||||
{
|
{
|
||||||
Id = dto.Id,
|
Id = dto.Id,
|
||||||
Url = dto.Url,
|
Url = dto.Url,
|
||||||
BTCDue = dto.BTCDue,
|
BTCDue = dto.BTCDue,
|
||||||
BTCPaid = dto.BTCPaid,
|
BTCPaid = dto.BTCPaid,
|
||||||
BTCPrice = dto.BTCPrice,
|
BTCPrice = dto.BTCPrice,
|
||||||
Currency = dto.Currency,
|
Currency = dto.Currency,
|
||||||
CurrentTime = dto.CurrentTime,
|
CurrentTime = dto.CurrentTime,
|
||||||
ExceptionStatus = dto.ExceptionStatus,
|
ExceptionStatus = dto.ExceptionStatus,
|
||||||
ExpirationTime = dto.ExpirationTime,
|
ExpirationTime = dto.ExpirationTime,
|
||||||
InvoiceTime = dto.InvoiceTime,
|
InvoiceTime = dto.InvoiceTime,
|
||||||
PosData = dto.PosData,
|
PosData = dto.PosData,
|
||||||
Price = dto.Price,
|
Price = dto.Price,
|
||||||
Rate = dto.Rate,
|
Rate = dto.Rate,
|
||||||
Status = dto.Status,
|
Status = dto.Status,
|
||||||
BuyerFields = job.Invoice.RefundMail == null ? null : new Newtonsoft.Json.Linq.JObject() { new JProperty("buyerEmail", job.Invoice.RefundMail) }
|
BuyerFields = job.Invoice.RefundMail == null ? null : new Newtonsoft.Json.Linq.JObject() { new JProperty("buyerEmail", job.Invoice.RefundMail) }
|
||||||
};
|
};
|
||||||
request.RequestUri = new Uri(job.Invoice.NotificationURL, UriKind.Absolute);
|
request.RequestUri = new Uri(job.Invoice.NotificationURL, UriKind.Absolute);
|
||||||
request.Content = new StringContent(JsonConvert.SerializeObject(notification), Encoding.UTF8, "application/json");
|
request.Content = new StringContent(JsonConvert.SerializeObject(notification), Encoding.UTF8, "application/json");
|
||||||
var response = await _Client.SendAsync(request, cts.Token);
|
var response = await _Client.SendAsync(request, cts.Token);
|
||||||
reschedule = response.StatusCode != System.Net.HttpStatusCode.OK;
|
reschedule = response.StatusCode != System.Net.HttpStatusCode.OK;
|
||||||
Logger.LogInformation("Job " + jobId + " returned " + response.StatusCode);
|
Logger.LogInformation("Job " + jobId + " returned " + response.StatusCode);
|
||||||
}
|
}
|
||||||
catch(Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
reschedule = true;
|
reschedule = true;
|
||||||
Logger.LogInformation("Job " + jobId + " threw exception " + ex.Message);
|
Logger.LogInformation("Job " + jobId + " threw exception " + ex.Message);
|
||||||
}
|
}
|
||||||
finally { cts.Dispose(); _Executing.TryRemove(jobId, out jobId); }
|
finally { cts.Dispose(); _Executing.TryRemove(jobId, out jobId); }
|
||||||
|
|
||||||
job.TryCount++;
|
job.TryCount++;
|
||||||
|
|
||||||
if(job.TryCount < MaxTry && reschedule)
|
if (job.TryCount < MaxTry && reschedule)
|
||||||
{
|
{
|
||||||
Logger.LogInformation("Rescheduling " + jobId + " in 10 minutes, remaining try " + (MaxTry - job.TryCount));
|
Logger.LogInformation("Rescheduling " + jobId + " in 10 minutes, remaining try " + (MaxTry - job.TryCount));
|
||||||
|
|
||||||
invoiceData = NBitcoin.JsonConverters.Serializer.ToString(job);
|
invoiceData = NBitcoin.JsonConverters.Serializer.ToString(job);
|
||||||
_JobClient.Schedule(() => NotifyHttp(invoiceData), TimeSpan.FromMinutes(10.0));
|
_JobClient.Schedule(() => NotifyHttp(invoiceData), TimeSpan.FromMinutes(10.0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int MaxTry = 6;
|
int MaxTry = 6;
|
||||||
|
|
||||||
private static string GetHttpJobId(InvoiceEntity invoice)
|
private static string GetHttpJobId(InvoiceEntity invoice)
|
||||||
{
|
{
|
||||||
return $"{invoice.Id}-{invoice.Status}-HTTP";
|
return $"{invoice.Id}-{invoice.Status}-HTTP";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,464 +19,464 @@ using BTCPayServer.Models.InvoicingModels;
|
|||||||
|
|
||||||
namespace BTCPayServer.Services.Invoices
|
namespace BTCPayServer.Services.Invoices
|
||||||
{
|
{
|
||||||
public class InvoiceRepository
|
public class InvoiceRepository
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
private readonly DBreezeEngine _Engine;
|
private readonly DBreezeEngine _Engine;
|
||||||
public DBreezeEngine Engine
|
public DBreezeEngine Engine
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _Engine;
|
return _Engine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Network _Network;
|
Network _Network;
|
||||||
public Network Network
|
public Network Network
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _Network;
|
return _Network;
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_Network = value;
|
_Network = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ApplicationDbContextFactory _ContextFactory;
|
private ApplicationDbContextFactory _ContextFactory;
|
||||||
public InvoiceRepository(ApplicationDbContextFactory contextFactory, DBreezeEngine engine, Network network)
|
public InvoiceRepository(ApplicationDbContextFactory contextFactory, DBreezeEngine engine, Network network)
|
||||||
{
|
{
|
||||||
_Engine = engine;
|
_Engine = engine;
|
||||||
_Network = network;
|
_Network = network;
|
||||||
_ContextFactory = contextFactory;
|
_ContextFactory = contextFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddPendingInvoice(string invoiceId)
|
public async Task AddPendingInvoice(string invoiceId)
|
||||||
{
|
{
|
||||||
using(var ctx = _ContextFactory.CreateContext())
|
using (var ctx = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
ctx.PendingInvoices.Add(new PendingInvoiceData() { Id = invoiceId });
|
ctx.PendingInvoices.Add(new PendingInvoiceData() { Id = invoiceId });
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> RemovePendingInvoice(string invoiceId)
|
public async Task<bool> RemovePendingInvoice(string invoiceId)
|
||||||
{
|
{
|
||||||
using(var ctx = _ContextFactory.CreateContext())
|
using (var ctx = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
ctx.PendingInvoices.Remove(new PendingInvoiceData() { Id = invoiceId });
|
ctx.PendingInvoices.Remove(new PendingInvoiceData() { Id = invoiceId });
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch(DbUpdateException) { return false; }
|
catch (DbUpdateException) { return false; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetInvoiceIdFromScriptPubKey(Script scriptPubKey)
|
public async Task<string> GetInvoiceIdFromScriptPubKey(Script scriptPubKey)
|
||||||
{
|
{
|
||||||
using(var db = _ContextFactory.CreateContext())
|
using (var db = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var result = await db.AddressInvoices.FindAsync(scriptPubKey.Hash.ToString());
|
var result = await db.AddressInvoices.FindAsync(scriptPubKey.Hash.ToString());
|
||||||
return result?.InvoiceDataId;
|
return result?.InvoiceDataId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string[]> GetPendingInvoices()
|
public async Task<string[]> GetPendingInvoices()
|
||||||
{
|
{
|
||||||
using(var ctx = _ContextFactory.CreateContext())
|
using (var ctx = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
return await ctx.PendingInvoices.Select(p => p.Id).ToArrayAsync();
|
return await ctx.PendingInvoices.Select(p => p.Id).ToArrayAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateInvoice(string invoiceId, UpdateCustomerModel data)
|
public async Task UpdateInvoice(string invoiceId, UpdateCustomerModel data)
|
||||||
{
|
{
|
||||||
using(var ctx = _ContextFactory.CreateContext())
|
using (var ctx = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var invoiceData = await ctx.Invoices.FindAsync(invoiceId).ConfigureAwait(false);
|
var invoiceData = await ctx.Invoices.FindAsync(invoiceId).ConfigureAwait(false);
|
||||||
if(invoiceData == null)
|
if (invoiceData == null)
|
||||||
return;
|
return;
|
||||||
if(invoiceData.CustomerEmail == null && data.Email != null)
|
if (invoiceData.CustomerEmail == null && data.Email != null)
|
||||||
{
|
{
|
||||||
invoiceData.CustomerEmail = data.Email;
|
invoiceData.CustomerEmail = data.Email;
|
||||||
}
|
}
|
||||||
await ctx.SaveChangesAsync().ConfigureAwait(false);
|
await ctx.SaveChangesAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice)
|
public async Task<InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice)
|
||||||
{
|
{
|
||||||
invoice = Clone(invoice);
|
invoice = Clone(invoice);
|
||||||
invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
|
invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
|
||||||
invoice.Payments = new List<PaymentEntity>();
|
invoice.Payments = new List<PaymentEntity>();
|
||||||
invoice.StoreId = storeId;
|
invoice.StoreId = storeId;
|
||||||
using(var context = _ContextFactory.CreateContext())
|
using (var context = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
await context.AddAsync(new InvoiceData()
|
await context.AddAsync(new InvoiceData()
|
||||||
{
|
{
|
||||||
StoreDataId = storeId,
|
StoreDataId = storeId,
|
||||||
Id = invoice.Id,
|
Id = invoice.Id,
|
||||||
Created = invoice.InvoiceTime,
|
Created = invoice.InvoiceTime,
|
||||||
Blob = ToBytes(invoice),
|
Blob = ToBytes(invoice),
|
||||||
OrderId = invoice.OrderId,
|
OrderId = invoice.OrderId,
|
||||||
Status = invoice.Status,
|
Status = invoice.Status,
|
||||||
ItemCode = invoice.ProductInformation.ItemCode,
|
ItemCode = invoice.ProductInformation.ItemCode,
|
||||||
CustomerEmail = invoice.RefundMail
|
CustomerEmail = invoice.RefundMail
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
context.AddressInvoices.Add(new AddressInvoiceData()
|
context.AddressInvoices.Add(new AddressInvoiceData()
|
||||||
{
|
{
|
||||||
Address = invoice.DepositAddress.ScriptPubKey.Hash.ToString(),
|
Address = invoice.DepositAddress.ScriptPubKey.Hash.ToString(),
|
||||||
InvoiceDataId = invoice.Id,
|
InvoiceDataId = invoice.Id,
|
||||||
CreatedTime = DateTimeOffset.UtcNow,
|
CreatedTime = DateTimeOffset.UtcNow,
|
||||||
});
|
});
|
||||||
|
|
||||||
context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
|
context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
|
||||||
{
|
{
|
||||||
InvoiceDataId = invoice.Id,
|
InvoiceDataId = invoice.Id,
|
||||||
Address = invoice.DepositAddress.ToString(),
|
Address = invoice.DepositAddress.ToString(),
|
||||||
Assigned = DateTimeOffset.UtcNow
|
Assigned = DateTimeOffset.UtcNow
|
||||||
});
|
});
|
||||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddToTextSearch(invoice.Id,
|
AddToTextSearch(invoice.Id,
|
||||||
invoice.Id,
|
invoice.Id,
|
||||||
invoice.DepositAddress.ToString(),
|
invoice.DepositAddress.ToString(),
|
||||||
invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture),
|
invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture),
|
||||||
invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture),
|
invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture),
|
||||||
invoice.GetTotalCryptoDue().ToString(),
|
invoice.GetTotalCryptoDue().ToString(),
|
||||||
invoice.OrderId,
|
invoice.OrderId,
|
||||||
ToString(invoice.BuyerInformation),
|
ToString(invoice.BuyerInformation),
|
||||||
ToString(invoice.ProductInformation),
|
ToString(invoice.ProductInformation),
|
||||||
invoice.StoreId
|
invoice.StoreId
|
||||||
);
|
);
|
||||||
|
|
||||||
return invoice;
|
return invoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> NewAddress(string invoiceId, BitcoinAddress bitcoinAddress)
|
public async Task<bool> NewAddress(string invoiceId, BitcoinAddress bitcoinAddress)
|
||||||
{
|
{
|
||||||
using(var context = _ContextFactory.CreateContext())
|
using (var context = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var invoice = await context.Invoices.FirstOrDefaultAsync(i => i.Id == invoiceId);
|
var invoice = await context.Invoices.FirstOrDefaultAsync(i => i.Id == invoiceId);
|
||||||
if(invoice == null)
|
if (invoice == null)
|
||||||
return false;
|
return false;
|
||||||
var invoiceEntity = ToObject<InvoiceEntity>(invoice.Blob);
|
var invoiceEntity = ToObject<InvoiceEntity>(invoice.Blob);
|
||||||
var old = invoiceEntity.DepositAddress;
|
var old = invoiceEntity.DepositAddress;
|
||||||
invoiceEntity.DepositAddress = bitcoinAddress;
|
invoiceEntity.DepositAddress = bitcoinAddress;
|
||||||
invoice.Blob = ToBytes(invoiceEntity);
|
invoice.Blob = ToBytes(invoiceEntity);
|
||||||
if(old != null)
|
if (old != null)
|
||||||
{
|
{
|
||||||
MarkUnassigned(invoiceId, old, context);
|
MarkUnassigned(invoiceId, old, context);
|
||||||
}
|
}
|
||||||
context.AddressInvoices.Add(new AddressInvoiceData() { Address = bitcoinAddress.ScriptPubKey.Hash.ToString(), InvoiceDataId = invoiceId, CreatedTime = DateTimeOffset.UtcNow });
|
context.AddressInvoices.Add(new AddressInvoiceData() { Address = bitcoinAddress.ScriptPubKey.Hash.ToString(), InvoiceDataId = invoiceId, CreatedTime = DateTimeOffset.UtcNow });
|
||||||
context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
|
context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
|
||||||
{
|
{
|
||||||
InvoiceDataId = invoiceId,
|
InvoiceDataId = invoiceId,
|
||||||
Address = bitcoinAddress.ToString(),
|
Address = bitcoinAddress.ToString(),
|
||||||
Assigned = DateTimeOffset.UtcNow
|
Assigned = DateTimeOffset.UtcNow
|
||||||
});
|
});
|
||||||
|
|
||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
AddToTextSearch(invoice.Id, bitcoinAddress.ToString());
|
AddToTextSearch(invoice.Id, bitcoinAddress.ToString());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void MarkUnassigned(string invoiceId, BitcoinAddress old, ApplicationDbContext context)
|
private static void MarkUnassigned(string invoiceId, BitcoinAddress old, ApplicationDbContext context)
|
||||||
{
|
{
|
||||||
var historical = new HistoricalAddressInvoiceData();
|
var historical = new HistoricalAddressInvoiceData();
|
||||||
historical.InvoiceDataId = invoiceId;
|
historical.InvoiceDataId = invoiceId;
|
||||||
historical.Address = old.ToString();
|
historical.Address = old.ToString();
|
||||||
historical.UnAssigned = DateTimeOffset.UtcNow;
|
historical.UnAssigned = DateTimeOffset.UtcNow;
|
||||||
context.Attach(historical);
|
context.Attach(historical);
|
||||||
context.Entry(historical).Property(o => o.UnAssigned).IsModified = true;
|
context.Entry(historical).Property(o => o.UnAssigned).IsModified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UnaffectAddress(string invoiceId)
|
public async Task UnaffectAddress(string invoiceId)
|
||||||
{
|
{
|
||||||
using(var context = _ContextFactory.CreateContext())
|
using (var context = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var invoiceData = await context.FindAsync<InvoiceData>(invoiceId).ConfigureAwait(false);
|
var invoiceData = await context.FindAsync<InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||||
if(invoiceData == null)
|
if (invoiceData == null)
|
||||||
return;
|
return;
|
||||||
var invoiceEntity = ToObject<InvoiceEntity>(invoiceData.Blob);
|
var invoiceEntity = ToObject<InvoiceEntity>(invoiceData.Blob);
|
||||||
if(invoiceEntity.DepositAddress == null)
|
if (invoiceEntity.DepositAddress == null)
|
||||||
return;
|
return;
|
||||||
MarkUnassigned(invoiceId, invoiceEntity.DepositAddress, context);
|
MarkUnassigned(invoiceId, invoiceEntity.DepositAddress, context);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
catch(DbUpdateException) { } //Possibly, it was unassigned before
|
catch (DbUpdateException) { } //Possibly, it was unassigned before
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string[] SearchInvoice(string searchTerms)
|
private string[] SearchInvoice(string searchTerms)
|
||||||
{
|
{
|
||||||
using(var tx = _Engine.GetTransaction())
|
using (var tx = _Engine.GetTransaction())
|
||||||
{
|
{
|
||||||
return tx.TextSearch("InvoiceSearch").Block(searchTerms)
|
return tx.TextSearch("InvoiceSearch").Block(searchTerms)
|
||||||
.GetDocumentIDs()
|
.GetDocumentIDs()
|
||||||
.Select(id => Encoders.Base58.EncodeData(id))
|
.Select(id => Encoders.Base58.EncodeData(id))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddToTextSearch(string invoiceId, params string[] terms)
|
void AddToTextSearch(string invoiceId, params string[] terms)
|
||||||
{
|
{
|
||||||
using(var tx = _Engine.GetTransaction())
|
using (var tx = _Engine.GetTransaction())
|
||||||
{
|
{
|
||||||
tx.TextInsert("InvoiceSearch", Encoders.Base58.DecodeData(invoiceId), string.Join(" ", terms.Where(t => !String.IsNullOrWhiteSpace(t))));
|
tx.TextInsert("InvoiceSearch", Encoders.Base58.DecodeData(invoiceId), string.Join(" ", terms.Where(t => !String.IsNullOrWhiteSpace(t))));
|
||||||
tx.Commit();
|
tx.Commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateInvoiceStatus(string invoiceId, string status, string exceptionStatus)
|
public async Task UpdateInvoiceStatus(string invoiceId, string status, string exceptionStatus)
|
||||||
{
|
{
|
||||||
using(var context = _ContextFactory.CreateContext())
|
using (var context = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var invoiceData = await context.FindAsync<InvoiceData>(invoiceId).ConfigureAwait(false);
|
var invoiceData = await context.FindAsync<InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||||
if(invoiceData == null)
|
if (invoiceData == null)
|
||||||
return;
|
return;
|
||||||
invoiceData.Status = status;
|
invoiceData.Status = status;
|
||||||
invoiceData.ExceptionStatus = exceptionStatus;
|
invoiceData.ExceptionStatus = exceptionStatus;
|
||||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<InvoiceEntity> GetInvoice(string storeId, string id, bool includeHistoricalAddresses = false)
|
public async Task<InvoiceEntity> GetInvoice(string storeId, string id, bool includeHistoricalAddresses = false)
|
||||||
{
|
{
|
||||||
using(var context = _ContextFactory.CreateContext())
|
using (var context = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
IQueryable<InvoiceData> query =
|
IQueryable<InvoiceData> query =
|
||||||
context
|
context
|
||||||
.Invoices
|
.Invoices
|
||||||
.Include(o => o.Payments)
|
.Include(o => o.Payments)
|
||||||
.Include(o => o.RefundAddresses);
|
.Include(o => o.RefundAddresses);
|
||||||
if(includeHistoricalAddresses)
|
if (includeHistoricalAddresses)
|
||||||
query = query.Include(o => o.HistoricalAddressInvoices);
|
query = query.Include(o => o.HistoricalAddressInvoices);
|
||||||
query = query.Where(i => i.Id == id);
|
query = query.Where(i => i.Id == id);
|
||||||
|
|
||||||
if(storeId != null)
|
if (storeId != null)
|
||||||
query = query.Where(i => i.StoreDataId == storeId);
|
query = query.Where(i => i.StoreDataId == storeId);
|
||||||
|
|
||||||
var invoice = await query.FirstOrDefaultAsync().ConfigureAwait(false);
|
var invoice = await query.FirstOrDefaultAsync().ConfigureAwait(false);
|
||||||
if(invoice == null)
|
if (invoice == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return ToEntity(invoice);
|
return ToEntity(invoice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private InvoiceEntity ToEntity(InvoiceData invoice)
|
private InvoiceEntity ToEntity(InvoiceData invoice)
|
||||||
{
|
{
|
||||||
var entity = ToObject<InvoiceEntity>(invoice.Blob);
|
var entity = ToObject<InvoiceEntity>(invoice.Blob);
|
||||||
entity.Payments = invoice.Payments.Select(p => ToObject<PaymentEntity>(p.Blob)).ToList();
|
entity.Payments = invoice.Payments.Select(p => ToObject<PaymentEntity>(p.Blob)).ToList();
|
||||||
entity.ExceptionStatus = invoice.ExceptionStatus;
|
entity.ExceptionStatus = invoice.ExceptionStatus;
|
||||||
entity.Status = invoice.Status;
|
entity.Status = invoice.Status;
|
||||||
entity.RefundMail = invoice.CustomerEmail;
|
entity.RefundMail = invoice.CustomerEmail;
|
||||||
entity.Refundable = invoice.RefundAddresses.Count != 0;
|
entity.Refundable = invoice.RefundAddresses.Count != 0;
|
||||||
if(invoice.HistoricalAddressInvoices != null)
|
if (invoice.HistoricalAddressInvoices != null)
|
||||||
{
|
{
|
||||||
entity.HistoricalAddresses = invoice.HistoricalAddressInvoices.ToArray();
|
entity.HistoricalAddresses = invoice.HistoricalAddressInvoices.ToArray();
|
||||||
}
|
}
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public async Task<InvoiceEntity[]> GetInvoices(InvoiceQuery queryObject)
|
public async Task<InvoiceEntity[]> GetInvoices(InvoiceQuery queryObject)
|
||||||
{
|
{
|
||||||
using(var context = _ContextFactory.CreateContext())
|
using (var context = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
IQueryable<InvoiceData> query = context
|
IQueryable<InvoiceData> query = context
|
||||||
.Invoices
|
.Invoices
|
||||||
.Include(o => o.Payments)
|
.Include(o => o.Payments)
|
||||||
.Include(o => o.RefundAddresses);
|
.Include(o => o.RefundAddresses);
|
||||||
|
|
||||||
if(!string.IsNullOrEmpty(queryObject.InvoiceId))
|
if (!string.IsNullOrEmpty(queryObject.InvoiceId))
|
||||||
{
|
{
|
||||||
query = query.Where(i => i.Id == queryObject.InvoiceId);
|
query = query.Where(i => i.Id == queryObject.InvoiceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!string.IsNullOrEmpty(queryObject.StoreId))
|
if (!string.IsNullOrEmpty(queryObject.StoreId))
|
||||||
{
|
{
|
||||||
query = query.Where(i => i.StoreDataId == queryObject.StoreId);
|
query = query.Where(i => i.StoreDataId == queryObject.StoreId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(queryObject.UserId != null)
|
if (queryObject.UserId != null)
|
||||||
{
|
{
|
||||||
query = query.Where(i => i.StoreData.UserStores.Any(u => u.ApplicationUserId == queryObject.UserId));
|
query = query.Where(i => i.StoreData.UserStores.Any(u => u.ApplicationUserId == queryObject.UserId));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!string.IsNullOrEmpty(queryObject.TextSearch))
|
if (!string.IsNullOrEmpty(queryObject.TextSearch))
|
||||||
{
|
{
|
||||||
var ids = new HashSet<string>(SearchInvoice(queryObject.TextSearch));
|
var ids = new HashSet<string>(SearchInvoice(queryObject.TextSearch));
|
||||||
if(ids.Count == 0)
|
if (ids.Count == 0)
|
||||||
return new InvoiceEntity[0];
|
return new InvoiceEntity[0];
|
||||||
query = query.Where(i => ids.Contains(i.Id));
|
query = query.Where(i => ids.Contains(i.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(queryObject.StartDate != null)
|
if (queryObject.StartDate != null)
|
||||||
query = query.Where(i => queryObject.StartDate.Value <= i.Created);
|
query = query.Where(i => queryObject.StartDate.Value <= i.Created);
|
||||||
|
|
||||||
if(queryObject.EndDate != null)
|
if (queryObject.EndDate != null)
|
||||||
query = query.Where(i => i.Created <= queryObject.EndDate.Value);
|
query = query.Where(i => i.Created <= queryObject.EndDate.Value);
|
||||||
|
|
||||||
if(queryObject.ItemCode != null)
|
if (queryObject.ItemCode != null)
|
||||||
query = query.Where(i => i.ItemCode == queryObject.ItemCode);
|
query = query.Where(i => i.ItemCode == queryObject.ItemCode);
|
||||||
|
|
||||||
if(queryObject.OrderId != null)
|
if (queryObject.OrderId != null)
|
||||||
query = query.Where(i => i.OrderId == queryObject.OrderId);
|
query = query.Where(i => i.OrderId == queryObject.OrderId);
|
||||||
|
|
||||||
if(queryObject.Status != null)
|
if (queryObject.Status != null)
|
||||||
query = query.Where(i => i.Status == queryObject.Status);
|
query = query.Where(i => i.Status == queryObject.Status);
|
||||||
|
|
||||||
query = query.OrderByDescending(q => q.Created);
|
query = query.OrderByDescending(q => q.Created);
|
||||||
|
|
||||||
if(queryObject.Skip != null)
|
if (queryObject.Skip != null)
|
||||||
query = query.Skip(queryObject.Skip.Value);
|
query = query.Skip(queryObject.Skip.Value);
|
||||||
|
|
||||||
if(queryObject.Count != null)
|
if (queryObject.Count != null)
|
||||||
query = query.Take(queryObject.Count.Value);
|
query = query.Take(queryObject.Count.Value);
|
||||||
|
|
||||||
var data = await query.ToArrayAsync().ConfigureAwait(false);
|
var data = await query.ToArrayAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
return data.Select(ToEntity).ToArray();
|
return data.Select(ToEntity).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddRefundsAsync(string invoiceId, TxOut[] outputs)
|
public async Task AddRefundsAsync(string invoiceId, TxOut[] outputs)
|
||||||
{
|
{
|
||||||
if(outputs.Length == 0)
|
if (outputs.Length == 0)
|
||||||
return;
|
return;
|
||||||
outputs = outputs.Take(10).ToArray();
|
outputs = outputs.Take(10).ToArray();
|
||||||
using(var context = _ContextFactory.CreateContext())
|
using (var context = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
int i = 0;
|
int i = 0;
|
||||||
foreach(var output in outputs)
|
foreach (var output in outputs)
|
||||||
{
|
{
|
||||||
await context.RefundAddresses.AddAsync(new RefundAddressesData()
|
await context.RefundAddresses.AddAsync(new RefundAddressesData()
|
||||||
{
|
{
|
||||||
Id = invoiceId + "-" + i,
|
Id = invoiceId + "-" + i,
|
||||||
InvoiceDataId = invoiceId,
|
InvoiceDataId = invoiceId,
|
||||||
Blob = ToBytes(output)
|
Blob = ToBytes(output)
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var addresses = outputs.Select(o => o.ScriptPubKey.GetDestinationAddress(_Network)).Where(a => a != null).ToArray();
|
var addresses = outputs.Select(o => o.ScriptPubKey.GetDestinationAddress(_Network)).Where(a => a != null).ToArray();
|
||||||
AddToTextSearch(invoiceId, addresses.Select(a => a.ToString()).ToArray());
|
AddToTextSearch(invoiceId, addresses.Select(a => a.ToString()).ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PaymentEntity> AddPayment(string invoiceId, Coin receivedCoin)
|
public async Task<PaymentEntity> AddPayment(string invoiceId, Coin receivedCoin)
|
||||||
{
|
{
|
||||||
using(var context = _ContextFactory.CreateContext())
|
using (var context = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
PaymentEntity entity = new PaymentEntity
|
PaymentEntity entity = new PaymentEntity
|
||||||
{
|
{
|
||||||
Outpoint = receivedCoin.Outpoint,
|
Outpoint = receivedCoin.Outpoint,
|
||||||
Output = receivedCoin.TxOut,
|
Output = receivedCoin.TxOut,
|
||||||
ReceivedTime = DateTime.UtcNow
|
ReceivedTime = DateTime.UtcNow
|
||||||
};
|
};
|
||||||
|
|
||||||
PaymentData data = new PaymentData
|
PaymentData data = new PaymentData
|
||||||
{
|
{
|
||||||
Id = receivedCoin.Outpoint.ToString(),
|
Id = receivedCoin.Outpoint.ToString(),
|
||||||
Blob = ToBytes(entity),
|
Blob = ToBytes(entity),
|
||||||
InvoiceDataId = invoiceId
|
InvoiceDataId = invoiceId
|
||||||
};
|
};
|
||||||
|
|
||||||
await context.Payments.AddAsync(data).ConfigureAwait(false);
|
await context.Payments.AddAsync(data).ConfigureAwait(false);
|
||||||
|
|
||||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private T ToObject<T>(byte[] value)
|
private T ToObject<T>(byte[] value)
|
||||||
{
|
{
|
||||||
return NBitcoin.JsonConverters.Serializer.ToObject<T>(ZipUtils.Unzip(value), Network);
|
return NBitcoin.JsonConverters.Serializer.ToObject<T>(ZipUtils.Unzip(value), Network);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] ToBytes<T>(T obj)
|
private byte[] ToBytes<T>(T obj)
|
||||||
{
|
{
|
||||||
return ZipUtils.Zip(NBitcoin.JsonConverters.Serializer.ToString(obj));
|
return ZipUtils.Zip(NBitcoin.JsonConverters.Serializer.ToString(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
private T Clone<T>(T invoice)
|
private T Clone<T>(T invoice)
|
||||||
{
|
{
|
||||||
return NBitcoin.JsonConverters.Serializer.ToObject<T>(ToString(invoice), Network);
|
return NBitcoin.JsonConverters.Serializer.ToObject<T>(ToString(invoice), Network);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ToString<T>(T data)
|
private string ToString<T>(T data)
|
||||||
{
|
{
|
||||||
return NBitcoin.JsonConverters.Serializer.ToString(data, Network);
|
return NBitcoin.JsonConverters.Serializer.ToString(data, Network);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class InvoiceQuery
|
public class InvoiceQuery
|
||||||
{
|
{
|
||||||
public string StoreId
|
public string StoreId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string UserId
|
public string UserId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string TextSearch
|
public string TextSearch
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public DateTimeOffset? StartDate
|
public DateTimeOffset? StartDate
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTimeOffset? EndDate
|
public DateTimeOffset? EndDate
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int? Skip
|
public int? Skip
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int? Count
|
public int? Count
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string OrderId
|
public string OrderId
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ItemCode
|
public string ItemCode
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Status
|
public string Status
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public string InvoiceId
|
public string InvoiceId
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,342 +16,342 @@ using BTCPayServer.Services.Wallets;
|
|||||||
|
|
||||||
namespace BTCPayServer.Services.Invoices
|
namespace BTCPayServer.Services.Invoices
|
||||||
{
|
{
|
||||||
public class InvoiceWatcher : IHostedService
|
public class InvoiceWatcher : IHostedService
|
||||||
{
|
{
|
||||||
InvoiceRepository _InvoiceRepository;
|
InvoiceRepository _InvoiceRepository;
|
||||||
ExplorerClient _ExplorerClient;
|
ExplorerClient _ExplorerClient;
|
||||||
DerivationStrategyFactory _DerivationFactory;
|
DerivationStrategyFactory _DerivationFactory;
|
||||||
InvoiceNotificationManager _NotificationManager;
|
InvoiceNotificationManager _NotificationManager;
|
||||||
BTCPayWallet _Wallet;
|
BTCPayWallet _Wallet;
|
||||||
|
|
||||||
public InvoiceWatcher(ExplorerClient explorerClient,
|
public InvoiceWatcher(ExplorerClient explorerClient,
|
||||||
InvoiceRepository invoiceRepository,
|
InvoiceRepository invoiceRepository,
|
||||||
BTCPayWallet wallet,
|
BTCPayWallet wallet,
|
||||||
InvoiceNotificationManager notificationManager)
|
InvoiceNotificationManager notificationManager)
|
||||||
{
|
{
|
||||||
LongPollingMode = explorerClient.Network == Network.RegTest;
|
LongPollingMode = explorerClient.Network == Network.RegTest;
|
||||||
PollInterval = explorerClient.Network == Network.RegTest ? TimeSpan.FromSeconds(10.0) : TimeSpan.FromMinutes(1.0);
|
PollInterval = explorerClient.Network == Network.RegTest ? TimeSpan.FromSeconds(10.0) : TimeSpan.FromMinutes(1.0);
|
||||||
_Wallet = wallet ?? throw new ArgumentNullException(nameof(wallet));
|
_Wallet = wallet ?? throw new ArgumentNullException(nameof(wallet));
|
||||||
_ExplorerClient = explorerClient ?? throw new ArgumentNullException(nameof(explorerClient));
|
_ExplorerClient = explorerClient ?? throw new ArgumentNullException(nameof(explorerClient));
|
||||||
_DerivationFactory = new DerivationStrategyFactory(_ExplorerClient.Network);
|
_DerivationFactory = new DerivationStrategyFactory(_ExplorerClient.Network);
|
||||||
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
|
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
|
||||||
_NotificationManager = notificationManager ?? throw new ArgumentNullException(nameof(notificationManager));
|
_NotificationManager = notificationManager ?? throw new ArgumentNullException(nameof(notificationManager));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LongPollingMode
|
public bool LongPollingMode
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task NotifyReceived(Script scriptPubKey)
|
public async Task NotifyReceived(Script scriptPubKey)
|
||||||
{
|
{
|
||||||
var invoice = await _InvoiceRepository.GetInvoiceIdFromScriptPubKey(scriptPubKey);
|
var invoice = await _InvoiceRepository.GetInvoiceIdFromScriptPubKey(scriptPubKey);
|
||||||
if(invoice != null)
|
if (invoice != null)
|
||||||
_WatchRequests.Add(invoice);
|
_WatchRequests.Add(invoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task NotifyBlock()
|
public async Task NotifyBlock()
|
||||||
{
|
{
|
||||||
foreach(var invoice in await _InvoiceRepository.GetPendingInvoices())
|
foreach (var invoice in await _InvoiceRepository.GetPendingInvoices())
|
||||||
{
|
{
|
||||||
_WatchRequests.Add(invoice);
|
_WatchRequests.Add(invoice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateInvoice(string invoiceId)
|
private async Task UpdateInvoice(string invoiceId)
|
||||||
{
|
{
|
||||||
UTXOChanges changes = null;
|
UTXOChanges changes = null;
|
||||||
while(true)
|
while (true)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId).ConfigureAwait(false);
|
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId).ConfigureAwait(false);
|
||||||
if(invoice == null)
|
if (invoice == null)
|
||||||
break;
|
break;
|
||||||
var stateBefore = invoice.Status;
|
var stateBefore = invoice.Status;
|
||||||
var result = await UpdateInvoice(changes, invoice).ConfigureAwait(false);
|
var result = await UpdateInvoice(changes, invoice).ConfigureAwait(false);
|
||||||
changes = result.Changes;
|
changes = result.Changes;
|
||||||
if(result.NeedSave)
|
if (result.NeedSave)
|
||||||
await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.Status, invoice.ExceptionStatus).ConfigureAwait(false);
|
await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.Status, invoice.ExceptionStatus).ConfigureAwait(false);
|
||||||
|
|
||||||
var changed = stateBefore != invoice.Status;
|
var changed = stateBefore != invoice.Status;
|
||||||
if(changed)
|
if (changed)
|
||||||
{
|
{
|
||||||
Logs.PayServer.LogInformation($"Invoice {invoice.Id}: {stateBefore} => {invoice.Status}");
|
Logs.PayServer.LogInformation($"Invoice {invoice.Id}: {stateBefore} => {invoice.Status}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var expirationMonitoring = invoice.MonitoringExpiration.HasValue ? invoice.MonitoringExpiration.Value : invoice.InvoiceTime + TimeSpan.FromMinutes(60);
|
var expirationMonitoring = invoice.MonitoringExpiration.HasValue ? invoice.MonitoringExpiration.Value : invoice.InvoiceTime + TimeSpan.FromMinutes(60);
|
||||||
if(invoice.Status == "complete" ||
|
if (invoice.Status == "complete" ||
|
||||||
((invoice.Status == "invalid" || invoice.Status == "expired") && expirationMonitoring < DateTimeOffset.UtcNow))
|
((invoice.Status == "invalid" || invoice.Status == "expired") && expirationMonitoring < DateTimeOffset.UtcNow))
|
||||||
{
|
{
|
||||||
if(await _InvoiceRepository.RemovePendingInvoice(invoice.Id).ConfigureAwait(false))
|
if (await _InvoiceRepository.RemovePendingInvoice(invoice.Id).ConfigureAwait(false))
|
||||||
Logs.PayServer.LogInformation("Stopped watching invoice " + invoiceId);
|
Logs.PayServer.LogInformation("Stopped watching invoice " + invoiceId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!changed || _Cts.Token.IsCancellationRequested)
|
if (!changed || _Cts.Token.IsCancellationRequested)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch(OperationCanceledException) when(_Cts.Token.IsCancellationRequested)
|
catch (OperationCanceledException) when (_Cts.Token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch(Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logs.PayServer.LogError(ex, "Unhandled error on watching invoice " + invoiceId);
|
Logs.PayServer.LogError(ex, "Unhandled error on watching invoice " + invoiceId);
|
||||||
await Task.Delay(10000, _Cts.Token).ConfigureAwait(false);
|
await Task.Delay(10000, _Cts.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task<(bool NeedSave, UTXOChanges Changes)> UpdateInvoice(UTXOChanges changes, InvoiceEntity invoice)
|
private async Task<(bool NeedSave, UTXOChanges Changes)> UpdateInvoice(UTXOChanges changes, InvoiceEntity invoice)
|
||||||
{
|
{
|
||||||
bool needSave = false;
|
bool needSave = false;
|
||||||
//Fetch unknown payments
|
//Fetch unknown payments
|
||||||
var strategy = _DerivationFactory.Parse(invoice.DerivationStrategy);
|
var strategy = _DerivationFactory.Parse(invoice.DerivationStrategy);
|
||||||
changes = await _ExplorerClient.SyncAsync(strategy, changes, !LongPollingMode, _Cts.Token).ConfigureAwait(false);
|
changes = await _ExplorerClient.SyncAsync(strategy, changes, !LongPollingMode, _Cts.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
var utxos = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).ToArray();
|
var utxos = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).ToArray();
|
||||||
var invoiceIds = utxos.Select(u => _InvoiceRepository.GetInvoiceIdFromScriptPubKey(u.Output.ScriptPubKey)).ToArray();
|
var invoiceIds = utxos.Select(u => _InvoiceRepository.GetInvoiceIdFromScriptPubKey(u.Output.ScriptPubKey)).ToArray();
|
||||||
utxos =
|
utxos =
|
||||||
utxos
|
utxos
|
||||||
.Where((u, i) => invoiceIds[i].GetAwaiter().GetResult() == invoice.Id)
|
.Where((u, i) => invoiceIds[i].GetAwaiter().GetResult() == invoice.Id)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
List<Coin> receivedCoins = new List<Coin>();
|
List<Coin> receivedCoins = new List<Coin>();
|
||||||
foreach(var received in utxos)
|
foreach (var received in utxos)
|
||||||
if(received.Output.ScriptPubKey == invoice.DepositAddress.ScriptPubKey)
|
if (received.Output.ScriptPubKey == invoice.DepositAddress.ScriptPubKey)
|
||||||
receivedCoins.Add(new Coin(received.Outpoint, received.Output));
|
receivedCoins.Add(new Coin(received.Outpoint, received.Output));
|
||||||
|
|
||||||
var alreadyAccounted = new HashSet<OutPoint>(invoice.Payments.Select(p => p.Outpoint));
|
var alreadyAccounted = new HashSet<OutPoint>(invoice.Payments.Select(p => p.Outpoint));
|
||||||
BitcoinAddress generatedAddress = null;
|
BitcoinAddress generatedAddress = null;
|
||||||
bool dirtyAddress = false;
|
bool dirtyAddress = false;
|
||||||
foreach(var coin in receivedCoins.Where(c => !alreadyAccounted.Contains(c.Outpoint)))
|
foreach (var coin in receivedCoins.Where(c => !alreadyAccounted.Contains(c.Outpoint)))
|
||||||
{
|
{
|
||||||
var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin).ConfigureAwait(false);
|
var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin).ConfigureAwait(false);
|
||||||
invoice.Payments.Add(payment);
|
invoice.Payments.Add(payment);
|
||||||
if(coin.ScriptPubKey == invoice.DepositAddress.ScriptPubKey && generatedAddress == null)
|
if (coin.ScriptPubKey == invoice.DepositAddress.ScriptPubKey && generatedAddress == null)
|
||||||
{
|
{
|
||||||
dirtyAddress = true;
|
dirtyAddress = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//////
|
//////
|
||||||
|
|
||||||
if(invoice.Status == "new" && invoice.ExpirationTime < DateTimeOffset.UtcNow)
|
if (invoice.Status == "new" && invoice.ExpirationTime < DateTimeOffset.UtcNow)
|
||||||
{
|
{
|
||||||
needSave = true;
|
needSave = true;
|
||||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||||
invoice.Status = "expired";
|
invoice.Status = "expired";
|
||||||
}
|
}
|
||||||
|
|
||||||
if(invoice.Status == "new" || invoice.Status == "expired")
|
if (invoice.Status == "new" || invoice.Status == "expired")
|
||||||
{
|
{
|
||||||
var totalPaid = invoice.Payments.Select(p => p.Output.Value).Sum();
|
var totalPaid = invoice.Payments.Select(p => p.Output.Value).Sum();
|
||||||
if(totalPaid >= invoice.GetTotalCryptoDue())
|
if (totalPaid >= invoice.GetTotalCryptoDue())
|
||||||
{
|
{
|
||||||
if(invoice.Status == "new")
|
if (invoice.Status == "new")
|
||||||
{
|
{
|
||||||
invoice.Status = "paid";
|
invoice.Status = "paid";
|
||||||
if(invoice.FullNotifications)
|
if (invoice.FullNotifications)
|
||||||
{
|
{
|
||||||
_NotificationManager.Notify(invoice);
|
_NotificationManager.Notify(invoice);
|
||||||
}
|
}
|
||||||
invoice.ExceptionStatus = null;
|
invoice.ExceptionStatus = null;
|
||||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||||
needSave = true;
|
needSave = true;
|
||||||
}
|
}
|
||||||
else if(invoice.Status == "expired")
|
else if (invoice.Status == "expired")
|
||||||
{
|
{
|
||||||
invoice.ExceptionStatus = "paidLate";
|
invoice.ExceptionStatus = "paidLate";
|
||||||
needSave = true;
|
needSave = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(totalPaid > invoice.GetTotalCryptoDue() && invoice.ExceptionStatus != "paidOver")
|
if (totalPaid > invoice.GetTotalCryptoDue() && invoice.ExceptionStatus != "paidOver")
|
||||||
{
|
{
|
||||||
invoice.ExceptionStatus = "paidOver";
|
invoice.ExceptionStatus = "paidOver";
|
||||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||||
needSave = true;
|
needSave = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(totalPaid < invoice.GetTotalCryptoDue() && invoice.Payments.Count != 0 && invoice.ExceptionStatus != "paidPartial")
|
if (totalPaid < invoice.GetTotalCryptoDue() && invoice.Payments.Count != 0 && invoice.ExceptionStatus != "paidPartial")
|
||||||
{
|
{
|
||||||
Logs.PayServer.LogInformation("Paid to " + invoice.DepositAddress);
|
Logs.PayServer.LogInformation("Paid to " + invoice.DepositAddress);
|
||||||
invoice.ExceptionStatus = "paidPartial";
|
invoice.ExceptionStatus = "paidPartial";
|
||||||
needSave = true;
|
needSave = true;
|
||||||
if(dirtyAddress)
|
if (dirtyAddress)
|
||||||
{
|
{
|
||||||
var address = await _Wallet.ReserveAddressAsync(_DerivationFactory.Parse(invoice.DerivationStrategy));
|
var address = await _Wallet.ReserveAddressAsync(_DerivationFactory.Parse(invoice.DerivationStrategy));
|
||||||
Logs.PayServer.LogInformation("Generate new " + address);
|
Logs.PayServer.LogInformation("Generate new " + address);
|
||||||
await _InvoiceRepository.NewAddress(invoice.Id, address);
|
await _InvoiceRepository.NewAddress(invoice.Id, address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(invoice.Status == "paid")
|
if (invoice.Status == "paid")
|
||||||
{
|
{
|
||||||
if(!invoice.MonitoringExpiration.HasValue || invoice.MonitoringExpiration > DateTimeOffset.UtcNow)
|
if (!invoice.MonitoringExpiration.HasValue || invoice.MonitoringExpiration > DateTimeOffset.UtcNow)
|
||||||
{
|
{
|
||||||
var transactions = await GetPaymentsWithTransaction(invoice);
|
var transactions = await GetPaymentsWithTransaction(invoice);
|
||||||
if(invoice.SpeedPolicy == SpeedPolicy.HighSpeed)
|
if (invoice.SpeedPolicy == SpeedPolicy.HighSpeed)
|
||||||
{
|
{
|
||||||
transactions = transactions.Where(t => !t.Transaction.Transaction.RBF);
|
transactions = transactions.Where(t => !t.Transaction.Transaction.RBF);
|
||||||
}
|
}
|
||||||
else if(invoice.SpeedPolicy == SpeedPolicy.MediumSpeed)
|
else if (invoice.SpeedPolicy == SpeedPolicy.MediumSpeed)
|
||||||
{
|
{
|
||||||
transactions = transactions.Where(t => t.Transaction.Confirmations >= 1);
|
transactions = transactions.Where(t => t.Transaction.Confirmations >= 1);
|
||||||
}
|
}
|
||||||
else if(invoice.SpeedPolicy == SpeedPolicy.LowSpeed)
|
else if (invoice.SpeedPolicy == SpeedPolicy.LowSpeed)
|
||||||
{
|
{
|
||||||
transactions = transactions.Where(t => t.Transaction.Confirmations >= 6);
|
transactions = transactions.Where(t => t.Transaction.Confirmations >= 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
var totalConfirmed = transactions.Select(t => t.Payment.Output.Value).Sum();
|
var totalConfirmed = transactions.Select(t => t.Payment.Output.Value).Sum();
|
||||||
if(totalConfirmed >= invoice.GetTotalCryptoDue())
|
if (totalConfirmed >= invoice.GetTotalCryptoDue())
|
||||||
{
|
{
|
||||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||||
invoice.Status = "confirmed";
|
invoice.Status = "confirmed";
|
||||||
_NotificationManager.Notify(invoice);
|
_NotificationManager.Notify(invoice);
|
||||||
needSave = true;
|
needSave = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||||
invoice.Status = "invalid";
|
invoice.Status = "invalid";
|
||||||
needSave = true;
|
needSave = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(invoice.Status == "confirmed")
|
if (invoice.Status == "confirmed")
|
||||||
{
|
{
|
||||||
var transactions = await GetPaymentsWithTransaction(invoice);
|
var transactions = await GetPaymentsWithTransaction(invoice);
|
||||||
transactions = transactions.Where(t => t.Transaction.Confirmations >= 6);
|
transactions = transactions.Where(t => t.Transaction.Confirmations >= 6);
|
||||||
var totalConfirmed = transactions.Select(t => t.Payment.Output.Value).Sum();
|
var totalConfirmed = transactions.Select(t => t.Payment.Output.Value).Sum();
|
||||||
if(totalConfirmed >= invoice.GetTotalCryptoDue())
|
if (totalConfirmed >= invoice.GetTotalCryptoDue())
|
||||||
{
|
{
|
||||||
invoice.Status = "complete";
|
invoice.Status = "complete";
|
||||||
if(invoice.FullNotifications)
|
if (invoice.FullNotifications)
|
||||||
_NotificationManager.Notify(invoice);
|
_NotificationManager.Notify(invoice);
|
||||||
needSave = true;
|
needSave = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (needSave, changes);
|
return (needSave, changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IEnumerable<(PaymentEntity Payment, TransactionResult Transaction)>> GetPaymentsWithTransaction(InvoiceEntity invoice)
|
private async Task<IEnumerable<(PaymentEntity Payment, TransactionResult Transaction)>> GetPaymentsWithTransaction(InvoiceEntity invoice)
|
||||||
{
|
{
|
||||||
var getPayments = invoice.Payments
|
var getPayments = invoice.Payments
|
||||||
.Select(async o => (Payment: o, Transaction: await _ExplorerClient.GetTransactionAsync(o.Outpoint.Hash, _Cts.Token)))
|
.Select(async o => (Payment: o, Transaction: await _ExplorerClient.GetTransactionAsync(o.Outpoint.Hash, _Cts.Token)))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
await Task.WhenAll(getPayments).ConfigureAwait(false);
|
await Task.WhenAll(getPayments).ConfigureAwait(false);
|
||||||
var transactions = getPayments.Select(c => (Payment: c.Result.Payment, Transaction: c.Result.Transaction));
|
var transactions = getPayments.Select(c => (Payment: c.Result.Payment, Transaction: c.Result.Transaction));
|
||||||
return transactions;
|
return transactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeSpan _PollInterval;
|
TimeSpan _PollInterval;
|
||||||
public TimeSpan PollInterval
|
public TimeSpan PollInterval
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _PollInterval;
|
return _PollInterval;
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_PollInterval = value;
|
_PollInterval = value;
|
||||||
if(_UpdatePendingInvoices != null)
|
if (_UpdatePendingInvoices != null)
|
||||||
{
|
{
|
||||||
_UpdatePendingInvoices.Change(0, (int)value.TotalMilliseconds);
|
_UpdatePendingInvoices.Change(0, (int)value.TotalMilliseconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task WatchAsync(string invoiceId, bool singleShot = false)
|
public async Task WatchAsync(string invoiceId, bool singleShot = false)
|
||||||
{
|
{
|
||||||
if(invoiceId == null)
|
if (invoiceId == null)
|
||||||
throw new ArgumentNullException(nameof(invoiceId));
|
throw new ArgumentNullException(nameof(invoiceId));
|
||||||
if(!singleShot)
|
if (!singleShot)
|
||||||
await _InvoiceRepository.AddPendingInvoice(invoiceId).ConfigureAwait(false);
|
await _InvoiceRepository.AddPendingInvoice(invoiceId).ConfigureAwait(false);
|
||||||
_WatchRequests.Add(invoiceId);
|
_WatchRequests.Add(invoiceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockingCollection<string> _WatchRequests = new BlockingCollection<string>(new ConcurrentQueue<string>());
|
BlockingCollection<string> _WatchRequests = new BlockingCollection<string>(new ConcurrentQueue<string>());
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_Cts.Cancel();
|
_Cts.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Thread _Thread;
|
Thread _Thread;
|
||||||
TaskCompletionSource<bool> _RunningTask;
|
TaskCompletionSource<bool> _RunningTask;
|
||||||
CancellationTokenSource _Cts;
|
CancellationTokenSource _Cts;
|
||||||
Timer _UpdatePendingInvoices;
|
Timer _UpdatePendingInvoices;
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_RunningTask = new TaskCompletionSource<bool>();
|
_RunningTask = new TaskCompletionSource<bool>();
|
||||||
_Cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
_Cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
_Thread = new Thread(Run) { Name = "InvoiceWatcher" };
|
_Thread = new Thread(Run) { Name = "InvoiceWatcher" };
|
||||||
_Thread.Start();
|
_Thread.Start();
|
||||||
_UpdatePendingInvoices = new Timer(async s =>
|
_UpdatePendingInvoices = new Timer(async s =>
|
||||||
{
|
{
|
||||||
foreach(var pending in await _InvoiceRepository.GetPendingInvoices())
|
foreach (var pending in await _InvoiceRepository.GetPendingInvoices())
|
||||||
{
|
{
|
||||||
_WatchRequests.Add(pending);
|
_WatchRequests.Add(pending);
|
||||||
}
|
}
|
||||||
}, null, 0, (int)PollInterval.TotalMilliseconds);
|
}, null, 0, (int)PollInterval.TotalMilliseconds);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Run()
|
void Run()
|
||||||
{
|
{
|
||||||
Logs.PayServer.LogInformation("Start watching invoices");
|
Logs.PayServer.LogInformation("Start watching invoices");
|
||||||
ConcurrentDictionary<string, Lazy<Task>> updating = new ConcurrentDictionary<string, Lazy<Task>>();
|
ConcurrentDictionary<string, Lazy<Task>> updating = new ConcurrentDictionary<string, Lazy<Task>>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach(var item in _WatchRequests.GetConsumingEnumerable(_Cts.Token))
|
foreach (var item in _WatchRequests.GetConsumingEnumerable(_Cts.Token))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_Cts.Token.ThrowIfCancellationRequested();
|
_Cts.Token.ThrowIfCancellationRequested();
|
||||||
var localItem = item;
|
var localItem = item;
|
||||||
// If the invoice is already updating, ignore
|
// If the invoice is already updating, ignore
|
||||||
Lazy<Task> updateInvoice = new Lazy<Task>(() => UpdateInvoice(localItem), false);
|
Lazy<Task> updateInvoice = new Lazy<Task>(() => UpdateInvoice(localItem), false);
|
||||||
if(updating.TryAdd(item, updateInvoice))
|
if (updating.TryAdd(item, updateInvoice))
|
||||||
{
|
{
|
||||||
updateInvoice.Value.ContinueWith(i => updating.TryRemove(item, out updateInvoice));
|
updateInvoice.Value.ContinueWith(i => updating.TryRemove(item, out updateInvoice));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(Exception ex) when(!_Cts.Token.IsCancellationRequested)
|
catch (Exception ex) when (!_Cts.Token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
Logs.PayServer.LogCritical(ex, $"Error in the InvoiceWatcher loop (Invoice {item})");
|
Logs.PayServer.LogCritical(ex, $"Error in the InvoiceWatcher loop (Invoice {item})");
|
||||||
_Cts.Token.WaitHandle.WaitOne(2000);
|
_Cts.Token.WaitHandle.WaitOne(2000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Task.WaitAll(updating.Select(c => c.Value.Value).ToArray());
|
Task.WaitAll(updating.Select(c => c.Value.Value).ToArray());
|
||||||
}
|
}
|
||||||
catch(AggregateException) { }
|
catch (AggregateException) { }
|
||||||
_RunningTask.TrySetResult(true);
|
_RunningTask.TrySetResult(true);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Logs.PayServer.LogInformation("Stop watching invoices");
|
Logs.PayServer.LogInformation("Stop watching invoices");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_UpdatePendingInvoices.Dispose();
|
_UpdatePendingInvoices.Dispose();
|
||||||
_Cts.Cancel();
|
_Cts.Cancel();
|
||||||
return Task.WhenAny(_RunningTask.Task, Task.Delay(-1, cancellationToken));
|
return Task.WhenAny(_RunningTask.Task, Task.Delay(-1, cancellationToken));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,34 +7,34 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Services.Mails
|
namespace BTCPayServer.Services.Mails
|
||||||
{
|
{
|
||||||
// This class is used by the application to send email for account confirmation and password reset.
|
// This class is used by the application to send email for account confirmation and password reset.
|
||||||
// For more details see https://go.microsoft.com/fwlink/?LinkID=532713
|
// For more details see https://go.microsoft.com/fwlink/?LinkID=532713
|
||||||
public class EmailSender : IEmailSender
|
public class EmailSender : IEmailSender
|
||||||
{
|
{
|
||||||
IBackgroundJobClient _JobClient;
|
IBackgroundJobClient _JobClient;
|
||||||
SettingsRepository _Repository;
|
SettingsRepository _Repository;
|
||||||
public EmailSender(IBackgroundJobClient jobClient, SettingsRepository repository)
|
public EmailSender(IBackgroundJobClient jobClient, SettingsRepository repository)
|
||||||
{
|
{
|
||||||
if(jobClient == null)
|
if (jobClient == null)
|
||||||
throw new ArgumentNullException(nameof(jobClient));
|
throw new ArgumentNullException(nameof(jobClient));
|
||||||
_JobClient = jobClient;
|
_JobClient = jobClient;
|
||||||
_Repository = repository;
|
_Repository = repository;
|
||||||
}
|
}
|
||||||
public Task SendEmailAsync(string email, string subject, string message)
|
public Task SendEmailAsync(string email, string subject, string message)
|
||||||
{
|
{
|
||||||
_JobClient.Schedule(() => SendMailCore(email, subject, message), TimeSpan.Zero);
|
_JobClient.Schedule(() => SendMailCore(email, subject, message), TimeSpan.Zero);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendMailCore(string email, string subject, string message)
|
public async Task SendMailCore(string email, string subject, string message)
|
||||||
{
|
{
|
||||||
var settings = await _Repository.GetSettingAsync<EmailSettings>();
|
var settings = await _Repository.GetSettingAsync<EmailSettings>();
|
||||||
if(settings == null)
|
if (settings == null)
|
||||||
throw new InvalidOperationException("Email settings not configured");
|
throw new InvalidOperationException("Email settings not configured");
|
||||||
var smtp = settings.CreateSmtpClient();
|
var smtp = settings.CreateSmtpClient();
|
||||||
MailMessage mail = new MailMessage(settings.From, email, subject, message);
|
MailMessage mail = new MailMessage(settings.From, email, subject, message);
|
||||||
mail.IsBodyHtml = true;
|
mail.IsBodyHtml = true;
|
||||||
await smtp.SendMailAsync(mail);
|
await smtp.SendMailAsync(mail);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,50 +10,50 @@ namespace BTCPayServer.Services.Mails
|
|||||||
{
|
{
|
||||||
public class EmailSettings
|
public class EmailSettings
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
public string Server
|
public string Server
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public int? Port
|
public int? Port
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public String Login
|
public String Login
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public String Password
|
public String Password
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[EmailAddress]
|
[EmailAddress]
|
||||||
public string From
|
public string From
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool EnableSSL
|
public bool EnableSSL
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SmtpClient CreateSmtpClient()
|
public SmtpClient CreateSmtpClient()
|
||||||
{
|
{
|
||||||
SmtpClient client = new SmtpClient(Server, Port.Value);
|
SmtpClient client = new SmtpClient(Server, Port.Value);
|
||||||
client.EnableSsl = true;
|
client.EnableSsl = true;
|
||||||
client.UseDefaultCredentials = false;
|
client.UseDefaultCredentials = false;
|
||||||
client.Credentials = new NetworkCredential(Login, Password);
|
client.Credentials = new NetworkCredential(Login, Password);
|
||||||
client.DeliveryMethod = SmtpDeliveryMethod.Network;
|
client.DeliveryMethod = SmtpDeliveryMethod.Network;
|
||||||
client.Timeout = 10000;
|
client.Timeout = 10000;
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ namespace BTCPayServer.Services
|
|||||||
{
|
{
|
||||||
public class PoliciesSettings
|
public class PoliciesSettings
|
||||||
{
|
{
|
||||||
public bool RequiresConfirmedEmail
|
public bool RequiresConfirmedEmail
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,30 +7,30 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Services.Rates
|
namespace BTCPayServer.Services.Rates
|
||||||
{
|
{
|
||||||
public class BitpayRateProvider : IRateProvider
|
public class BitpayRateProvider : IRateProvider
|
||||||
{
|
{
|
||||||
Bitpay _Bitpay;
|
Bitpay _Bitpay;
|
||||||
public BitpayRateProvider(Bitpay bitpay)
|
public BitpayRateProvider(Bitpay bitpay)
|
||||||
{
|
{
|
||||||
if(bitpay == null)
|
if (bitpay == null)
|
||||||
throw new ArgumentNullException(nameof(bitpay));
|
throw new ArgumentNullException(nameof(bitpay));
|
||||||
_Bitpay = bitpay;
|
_Bitpay = bitpay;
|
||||||
}
|
}
|
||||||
public async Task<decimal> GetRateAsync(string currency)
|
public async Task<decimal> GetRateAsync(string currency)
|
||||||
{
|
{
|
||||||
var rates = await _Bitpay.GetRatesAsync().ConfigureAwait(false);
|
var rates = await _Bitpay.GetRatesAsync().ConfigureAwait(false);
|
||||||
var rate = rates.GetRate(currency);
|
var rate = rates.GetRate(currency);
|
||||||
if(rate == 0m)
|
if (rate == 0m)
|
||||||
throw new RateUnavailableException(currency);
|
throw new RateUnavailableException(currency);
|
||||||
return (decimal)rate;
|
return (decimal)rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ICollection<Rate>> GetRatesAsync()
|
public async Task<ICollection<Rate>> GetRatesAsync()
|
||||||
{
|
{
|
||||||
return (await _Bitpay.GetRatesAsync().ConfigureAwait(false))
|
return (await _Bitpay.GetRatesAsync().ConfigureAwait(false))
|
||||||
.AllRates
|
.AllRates
|
||||||
.Select(r => new Rate() { Currency = r.Code, Value = r.Value })
|
.Select(r => new Rate() { Currency = r.Code, Value = r.Value })
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,49 +6,49 @@ using Microsoft.Extensions.Caching.Memory;
|
|||||||
|
|
||||||
namespace BTCPayServer.Services.Rates
|
namespace BTCPayServer.Services.Rates
|
||||||
{
|
{
|
||||||
public class CachedRateProvider : IRateProvider
|
public class CachedRateProvider : IRateProvider
|
||||||
{
|
{
|
||||||
private IRateProvider _Inner;
|
private IRateProvider _Inner;
|
||||||
private IMemoryCache _MemoryCache;
|
private IMemoryCache _MemoryCache;
|
||||||
|
|
||||||
public CachedRateProvider(IRateProvider inner, IMemoryCache memoryCache)
|
public CachedRateProvider(IRateProvider inner, IMemoryCache memoryCache)
|
||||||
{
|
{
|
||||||
if(inner == null)
|
if (inner == null)
|
||||||
throw new ArgumentNullException(nameof(inner));
|
throw new ArgumentNullException(nameof(inner));
|
||||||
if(memoryCache == null)
|
if (memoryCache == null)
|
||||||
throw new ArgumentNullException(nameof(memoryCache));
|
throw new ArgumentNullException(nameof(memoryCache));
|
||||||
this._Inner = inner;
|
this._Inner = inner;
|
||||||
this._MemoryCache = memoryCache;
|
this._MemoryCache = memoryCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeSpan CacheSpan
|
public TimeSpan CacheSpan
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
} = TimeSpan.FromMinutes(1.0);
|
} = TimeSpan.FromMinutes(1.0);
|
||||||
|
|
||||||
public Task<decimal> GetRateAsync(string currency)
|
public Task<decimal> GetRateAsync(string currency)
|
||||||
{
|
{
|
||||||
return _MemoryCache.GetOrCreateAsync("CURR_" + currency, (ICacheEntry entry) =>
|
return _MemoryCache.GetOrCreateAsync("CURR_" + currency, (ICacheEntry entry) =>
|
||||||
{
|
{
|
||||||
entry.AbsoluteExpiration = DateTimeOffset.UtcNow + CacheSpan;
|
entry.AbsoluteExpiration = DateTimeOffset.UtcNow + CacheSpan;
|
||||||
return _Inner.GetRateAsync(currency);
|
return _Inner.GetRateAsync(currency);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryGetFromCache(string key, out object obj)
|
private bool TryGetFromCache(string key, out object obj)
|
||||||
{
|
{
|
||||||
obj = _MemoryCache.Get(key);
|
obj = _MemoryCache.Get(key);
|
||||||
return obj != null;
|
return obj != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ICollection<Rate>> GetRatesAsync()
|
public Task<ICollection<Rate>> GetRatesAsync()
|
||||||
{
|
{
|
||||||
return _MemoryCache.GetOrCreateAsync("GLOBAL_RATES", (ICacheEntry entry) =>
|
return _MemoryCache.GetOrCreateAsync("GLOBAL_RATES", (ICacheEntry entry) =>
|
||||||
{
|
{
|
||||||
entry.AbsoluteExpiration = DateTimeOffset.UtcNow + CacheSpan;
|
entry.AbsoluteExpiration = DateTimeOffset.UtcNow + CacheSpan;
|
||||||
return _Inner.GetRatesAsync();
|
return _Inner.GetRatesAsync();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,109 +8,109 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Services.Rates
|
namespace BTCPayServer.Services.Rates
|
||||||
{
|
{
|
||||||
public class CoinAverageException : Exception
|
public class CoinAverageException : Exception
|
||||||
{
|
{
|
||||||
public CoinAverageException(string message) : base(message)
|
public CoinAverageException(string message) : base(message)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public class CoinAverageRateProvider : IRateProvider
|
public class CoinAverageRateProvider : IRateProvider
|
||||||
{
|
{
|
||||||
public class RatesJson
|
public class RatesJson
|
||||||
{
|
{
|
||||||
public class RateJson
|
public class RateJson
|
||||||
{
|
{
|
||||||
public string Code
|
public string Code
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public decimal Rate
|
public decimal Rate
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty("rates")]
|
[JsonProperty("rates")]
|
||||||
public JObject RatesInternal
|
public JObject RatesInternal
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public List<RateJson> Rates
|
public List<RateJson> Rates
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public Dictionary<string, decimal> RatesByCurrency
|
public Dictionary<string, decimal> RatesByCurrency
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public decimal GetRate(string currency)
|
public decimal GetRate(string currency)
|
||||||
{
|
{
|
||||||
if(!RatesByCurrency.TryGetValue(currency.ToUpperInvariant(), out decimal currUSD))
|
if (!RatesByCurrency.TryGetValue(currency.ToUpperInvariant(), out decimal currUSD))
|
||||||
throw new RateUnavailableException(currency);
|
throw new RateUnavailableException(currency);
|
||||||
|
|
||||||
if(!RatesByCurrency.TryGetValue("BTC", out decimal btcUSD))
|
if (!RatesByCurrency.TryGetValue("BTC", out decimal btcUSD))
|
||||||
throw new RateUnavailableException(currency);
|
throw new RateUnavailableException(currency);
|
||||||
|
|
||||||
return currUSD / btcUSD;
|
return currUSD / btcUSD;
|
||||||
}
|
}
|
||||||
public void CalculateDictionary()
|
public void CalculateDictionary()
|
||||||
{
|
{
|
||||||
RatesByCurrency = new Dictionary<string, decimal>();
|
RatesByCurrency = new Dictionary<string, decimal>();
|
||||||
Rates = new List<RateJson>();
|
Rates = new List<RateJson>();
|
||||||
foreach(var rate in RatesInternal.OfType<JProperty>())
|
foreach (var rate in RatesInternal.OfType<JProperty>())
|
||||||
{
|
{
|
||||||
var rateJson = new RateJson();
|
var rateJson = new RateJson();
|
||||||
rateJson.Code = rate.Name;
|
rateJson.Code = rate.Name;
|
||||||
rateJson.Rate = rate.Value["rate"].Value<decimal>();
|
rateJson.Rate = rate.Value["rate"].Value<decimal>();
|
||||||
RatesByCurrency.Add(rate.Name, rateJson.Rate);
|
RatesByCurrency.Add(rate.Name, rateJson.Rate);
|
||||||
Rates.Add(rateJson);
|
Rates.Add(rateJson);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static HttpClient _Client = new HttpClient();
|
static HttpClient _Client = new HttpClient();
|
||||||
|
|
||||||
public string Market
|
public string Market
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
} = "global";
|
} = "global";
|
||||||
public async Task<decimal> GetRateAsync(string currency)
|
public async Task<decimal> GetRateAsync(string currency)
|
||||||
{
|
{
|
||||||
RatesJson rates = await GetRatesCore();
|
RatesJson rates = await GetRatesCore();
|
||||||
return rates.GetRate(currency);
|
return rates.GetRate(currency);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<RatesJson> GetRatesCore()
|
private async Task<RatesJson> GetRatesCore()
|
||||||
{
|
{
|
||||||
var resp = await _Client.GetAsync("https://apiv2.bitcoinaverage.com/constants/exchangerates/" + Market);
|
var resp = await _Client.GetAsync("https://apiv2.bitcoinaverage.com/constants/exchangerates/" + Market);
|
||||||
using(resp)
|
using (resp)
|
||||||
{
|
{
|
||||||
|
|
||||||
if((int)resp.StatusCode == 401)
|
if ((int)resp.StatusCode == 401)
|
||||||
throw new CoinAverageException("Unauthorized access to the API");
|
throw new CoinAverageException("Unauthorized access to the API");
|
||||||
if((int)resp.StatusCode == 429)
|
if ((int)resp.StatusCode == 429)
|
||||||
throw new CoinAverageException("Exceed API limits");
|
throw new CoinAverageException("Exceed API limits");
|
||||||
if((int)resp.StatusCode == 403)
|
if ((int)resp.StatusCode == 403)
|
||||||
throw new CoinAverageException("Unauthorized access to the API, premium plan needed");
|
throw new CoinAverageException("Unauthorized access to the API, premium plan needed");
|
||||||
resp.EnsureSuccessStatusCode();
|
resp.EnsureSuccessStatusCode();
|
||||||
var rates = JsonConvert.DeserializeObject<RatesJson>(await resp.Content.ReadAsStringAsync());
|
var rates = JsonConvert.DeserializeObject<RatesJson>(await resp.Content.ReadAsStringAsync());
|
||||||
rates.CalculateDictionary();
|
rates.CalculateDictionary();
|
||||||
return rates;
|
return rates;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ICollection<Rate>> GetRatesAsync()
|
public async Task<ICollection<Rate>> GetRatesAsync()
|
||||||
{
|
{
|
||||||
RatesJson rates = await GetRatesCore();
|
RatesJson rates = await GetRatesCore();
|
||||||
return rates.Rates.Select(o => new Rate()
|
return rates.Rates.Select(o => new Rate()
|
||||||
{
|
{
|
||||||
Currency = o.Code,
|
Currency = o.Code,
|
||||||
Value = rates.GetRate(o.Code)
|
Value = rates.GetRate(o.Code)
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,77 +7,77 @@ using System.Text;
|
|||||||
|
|
||||||
namespace BTCPayServer.Services.Rates
|
namespace BTCPayServer.Services.Rates
|
||||||
{
|
{
|
||||||
public class CurrencyData
|
public class CurrencyData
|
||||||
{
|
|
||||||
public string Name
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
internal set;
|
|
||||||
}
|
|
||||||
public string Code
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
internal set;
|
|
||||||
}
|
|
||||||
public int Divisibility
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
internal set;
|
|
||||||
}
|
|
||||||
public string Symbol
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
internal set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public class CurrencyNameTable
|
|
||||||
{
|
{
|
||||||
public CurrencyNameTable()
|
public string Name
|
||||||
{
|
{
|
||||||
_Currencies = LoadCurrency().ToDictionary(k => k.Code);
|
get;
|
||||||
}
|
internal set;
|
||||||
|
}
|
||||||
|
public string Code
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
internal set;
|
||||||
|
}
|
||||||
|
public int Divisibility
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
internal set;
|
||||||
|
}
|
||||||
|
public string Symbol
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
internal set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public class CurrencyNameTable
|
||||||
|
{
|
||||||
|
public CurrencyNameTable()
|
||||||
|
{
|
||||||
|
_Currencies = LoadCurrency().ToDictionary(k => k.Code);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Dictionary<string, CurrencyData> _Currencies;
|
Dictionary<string, CurrencyData> _Currencies;
|
||||||
|
|
||||||
static CurrencyData[] LoadCurrency()
|
static CurrencyData[] LoadCurrency()
|
||||||
{
|
{
|
||||||
var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("BTCPayServer.Currencies.txt");
|
var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("BTCPayServer.Currencies.txt");
|
||||||
string content = null;
|
string content = null;
|
||||||
using(var reader = new StreamReader(stream, Encoding.UTF8))
|
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
||||||
{
|
{
|
||||||
content = reader.ReadToEnd();
|
content = reader.ReadToEnd();
|
||||||
}
|
}
|
||||||
var currencies = content.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
var currencies = content.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
Dictionary<string, CurrencyData> dico = new Dictionary<string, CurrencyData>();
|
Dictionary<string, CurrencyData> dico = new Dictionary<string, CurrencyData>();
|
||||||
foreach(var currency in currencies)
|
foreach (var currency in currencies)
|
||||||
{
|
{
|
||||||
var splitted = currency.Split(new[] { '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
var splitted = currency.Split(new[] { '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if(splitted.Length < 3)
|
if (splitted.Length < 3)
|
||||||
continue;
|
continue;
|
||||||
CurrencyData info = new CurrencyData();
|
CurrencyData info = new CurrencyData();
|
||||||
info.Name = splitted[0];
|
info.Name = splitted[0];
|
||||||
info.Code = splitted[1];
|
info.Code = splitted[1];
|
||||||
int divisibility;
|
int divisibility;
|
||||||
if(!int.TryParse(splitted[2], out divisibility))
|
if (!int.TryParse(splitted[2], out divisibility))
|
||||||
continue;
|
continue;
|
||||||
info.Divisibility = divisibility;
|
info.Divisibility = divisibility;
|
||||||
if(!dico.ContainsKey(info.Code))
|
if (!dico.ContainsKey(info.Code))
|
||||||
dico.Add(info.Code, info);
|
dico.Add(info.Code, info);
|
||||||
if(splitted.Length >= 4)
|
if (splitted.Length >= 4)
|
||||||
{
|
{
|
||||||
info.Symbol = splitted[3];
|
info.Symbol = splitted[3];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dico.Values.ToArray();
|
return dico.Values.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CurrencyData GetCurrencyData(string currency)
|
public CurrencyData GetCurrencyData(string currency)
|
||||||
{
|
{
|
||||||
CurrencyData result;
|
CurrencyData result;
|
||||||
_Currencies.TryGetValue(currency.ToUpperInvariant(), out result);
|
_Currencies.TryGetValue(currency.ToUpperInvariant(), out result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,29 +5,29 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Services.Rates
|
namespace BTCPayServer.Services.Rates
|
||||||
{
|
{
|
||||||
public class Rate
|
public class Rate
|
||||||
{
|
{
|
||||||
public Rate()
|
public Rate()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
public Rate(string currency, decimal value)
|
public Rate(string currency, decimal value)
|
||||||
{
|
{
|
||||||
Value = value;
|
Value = value;
|
||||||
Currency = currency;
|
Currency = currency;
|
||||||
}
|
}
|
||||||
public string Currency
|
public string Currency
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public decimal Value
|
public decimal Value
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public interface IRateProvider
|
public interface IRateProvider
|
||||||
{
|
{
|
||||||
Task<decimal> GetRateAsync(string currency);
|
Task<decimal> GetRateAsync(string currency);
|
||||||
Task<ICollection<Rate>> GetRatesAsync();
|
Task<ICollection<Rate>> GetRatesAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,30 +6,30 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace BTCPayServer.Services.Rates
|
namespace BTCPayServer.Services.Rates
|
||||||
{
|
{
|
||||||
public class MockRateProvider : IRateProvider
|
public class MockRateProvider : IRateProvider
|
||||||
{
|
{
|
||||||
List<Rate> _Rates;
|
List<Rate> _Rates;
|
||||||
|
|
||||||
public MockRateProvider(params Rate[] rates)
|
public MockRateProvider(params Rate[] rates)
|
||||||
{
|
{
|
||||||
_Rates = new List<Rate>(rates);
|
_Rates = new List<Rate>(rates);
|
||||||
}
|
}
|
||||||
public MockRateProvider(List<Rate> rates)
|
public MockRateProvider(List<Rate> rates)
|
||||||
{
|
{
|
||||||
_Rates = rates;
|
_Rates = rates;
|
||||||
}
|
}
|
||||||
public Task<decimal> GetRateAsync(string currency)
|
public Task<decimal> GetRateAsync(string currency)
|
||||||
{
|
{
|
||||||
var rate = _Rates.FirstOrDefault(r => r.Currency.Equals(currency, StringComparison.OrdinalIgnoreCase));
|
var rate = _Rates.FirstOrDefault(r => r.Currency.Equals(currency, StringComparison.OrdinalIgnoreCase));
|
||||||
if(rate == null)
|
if (rate == null)
|
||||||
throw new RateUnavailableException(currency);
|
throw new RateUnavailableException(currency);
|
||||||
return Task.FromResult(rate.Value);
|
return Task.FromResult(rate.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ICollection<Rate>> GetRatesAsync()
|
public Task<ICollection<Rate>> GetRatesAsync()
|
||||||
{
|
{
|
||||||
ICollection<Rate> rates = _Rates;
|
ICollection<Rate> rates = _Rates;
|
||||||
return Task.FromResult(rates);
|
return Task.FromResult(rates);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,16 @@ namespace BTCPayServer.Services.Rates
|
|||||||
{
|
{
|
||||||
public class RateUnavailableException : Exception
|
public class RateUnavailableException : Exception
|
||||||
{
|
{
|
||||||
public RateUnavailableException(string currency) : base("Rate unavailable for currency " + currency)
|
public RateUnavailableException(string currency) : base("Rate unavailable for currency " + currency)
|
||||||
{
|
{
|
||||||
if(currency == null)
|
if (currency == null)
|
||||||
throw new ArgumentNullException(nameof(currency));
|
throw new ArgumentNullException(nameof(currency));
|
||||||
Currency = currency;
|
Currency = currency;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Currency
|
public string Currency
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,56 +11,56 @@ using Newtonsoft.Json;
|
|||||||
|
|
||||||
namespace BTCPayServer.Services
|
namespace BTCPayServer.Services
|
||||||
{
|
{
|
||||||
public class SettingsRepository
|
public class SettingsRepository
|
||||||
{
|
{
|
||||||
private ApplicationDbContextFactory _ContextFactory;
|
private ApplicationDbContextFactory _ContextFactory;
|
||||||
public SettingsRepository(ApplicationDbContextFactory contextFactory)
|
public SettingsRepository(ApplicationDbContextFactory contextFactory)
|
||||||
{
|
{
|
||||||
_ContextFactory = contextFactory;
|
_ContextFactory = contextFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<T> GetSettingAsync<T>()
|
public async Task<T> GetSettingAsync<T>()
|
||||||
{
|
{
|
||||||
var name = typeof(T).FullName;
|
var name = typeof(T).FullName;
|
||||||
using(var ctx = _ContextFactory.CreateContext())
|
using (var ctx = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var data = await ctx.Settings.Where(s => s.Id == name).FirstOrDefaultAsync();
|
var data = await ctx.Settings.Where(s => s.Id == name).FirstOrDefaultAsync();
|
||||||
if(data == null)
|
if (data == null)
|
||||||
return default(T);
|
return default(T);
|
||||||
return Deserialize<T>(data.Value);
|
return Deserialize<T>(data.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateSetting<T>(T obj)
|
public async Task UpdateSetting<T>(T obj)
|
||||||
{
|
{
|
||||||
var name = obj.GetType().FullName;
|
var name = obj.GetType().FullName;
|
||||||
using(var ctx = _ContextFactory.CreateContext())
|
using (var ctx = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var settings = new SettingData();
|
var settings = new SettingData();
|
||||||
settings.Id = name;
|
settings.Id = name;
|
||||||
settings.Value = Serialize(obj);
|
settings.Value = Serialize(obj);
|
||||||
ctx.Attach(settings);
|
ctx.Attach(settings);
|
||||||
ctx.Entry(settings).State = EntityState.Modified;
|
ctx.Entry(settings).State = EntityState.Modified;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
catch(DbUpdateException)
|
catch (DbUpdateException)
|
||||||
{
|
{
|
||||||
ctx.Entry(settings).State = EntityState.Added;
|
ctx.Entry(settings).State = EntityState.Added;
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private T Deserialize<T>(string value)
|
private T Deserialize<T>(string value)
|
||||||
{
|
{
|
||||||
return JsonConvert.DeserializeObject<T>(value);
|
return JsonConvert.DeserializeObject<T>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string Serialize<T>(T obj)
|
private string Serialize<T>(T obj)
|
||||||
{
|
{
|
||||||
return JsonConvert.SerializeObject(obj);
|
return JsonConvert.SerializeObject(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user