Compare commits
184 Commits
v1.0.3.135
...
v1.0.3.138
Author | SHA1 | Date | |
---|---|---|---|
4c349803b6 | |||
35a4278ae8 | |||
06365b7b73 | |||
f31e013986 | |||
fe1df743ce | |||
22ffd48cd4 | |||
c83bcd259d | |||
8ebac28ccf | |||
4e5001d7ef | |||
84fe14a1ed | |||
6828730313 | |||
e540079220 | |||
e99f806b66 | |||
b452f1d125 | |||
f0a9d209a5 | |||
984aa2912a | |||
c8937f6d46 | |||
cb849ab23e | |||
3bca3427eb | |||
a127b13db1 | |||
a2f608266b | |||
05f8b8d8af | |||
f1cef81d76 | |||
e1bf376f10 | |||
9298eab30d | |||
584528f903 | |||
3501920956 | |||
65a867e82d | |||
2bfea50014 | |||
89539fca01 | |||
240f6fa45a | |||
c004065eea | |||
1cfba82d77 | |||
3bdcbd8907 | |||
fd53811fdd | |||
5eeda40098 | |||
7bce316e78 | |||
68f172b9ef | |||
9b3e7338ba | |||
c6ef6fc17d | |||
e4c797eaa7 | |||
bdca51a178 | |||
e147ce8606 | |||
830286ecc1 | |||
bc56cab488 | |||
dd683db6d5 | |||
5436999b13 | |||
29946763ba | |||
7160f7dc22 | |||
e44db30ddb | |||
458b2fd72e | |||
66ffa5f987 | |||
dd8caa46c4 | |||
55de85d829 | |||
10f782aa11 | |||
5bb6918465 | |||
205fd3c9ce | |||
50bf55cbdf | |||
3a9ecd8b33 | |||
5942a840b2 | |||
6dd3c79c23 | |||
a25e0ef592 | |||
29465cbadc | |||
fdc1aa25e4 | |||
04fbe42bda | |||
57a6949565 | |||
bcb85e2084 | |||
6900964c03 | |||
3d2b8d2969 | |||
c7da4e3eff | |||
9e31154808 | |||
89353b8e7c | |||
b8eea53e01 | |||
9bafce8069 | |||
a9de1c2c64 | |||
e6505040e0 | |||
92bdb20e1c | |||
c6bba9188b | |||
c3a84f6577 | |||
ba54b5aacf | |||
e92a15e072 | |||
ad853c6985 | |||
e7ff5c0a4b | |||
fef1bde9b6 | |||
eb87d7cadc | |||
5394e0a4a0 | |||
0f0b846e04 | |||
ae869489b5 | |||
cdc973cbea | |||
bae9ca2a7a | |||
0df7506ad5 | |||
974101624b | |||
621a7d998d | |||
539c7d6e17 | |||
2743641122 | |||
1d08a811e7 | |||
c5ca02e48e | |||
bce201038f | |||
c24bb443d3 | |||
9c165d84ef | |||
58b834fe9d | |||
2b1aac9aa9 | |||
dfdb99165b | |||
b75eaee6dd | |||
888b06376d | |||
7f6b1e7e6e | |||
8dd1efc126 | |||
9cd64bf81a | |||
2c14cece6b | |||
11c269693e | |||
ae9b93ca8f | |||
563e95df7c | |||
5b37a9df0b | |||
28396206de | |||
9d3026f676 | |||
0f7458254e | |||
2305a0a5c8 | |||
7ce2ddb400 | |||
13c8372329 | |||
8fd7ee0763 | |||
aab7c08500 | |||
ff8de47e69 | |||
3ff9cc85ef | |||
0a2440e14c | |||
d37abb53f0 | |||
656a3956b1 | |||
c31e7593b6 | |||
7be9083220 | |||
b85667a191 | |||
892d906ac7 | |||
0277708b2a | |||
d757aa2978 | |||
cf7bba9600 | |||
a0d498812d | |||
29e8783beb | |||
57b87a55bc | |||
9ff9377bc7 | |||
163ef031b9 | |||
1cfdd1489b | |||
0a9290a980 | |||
f0c2fb62df | |||
24d6532dac | |||
fee1fe22a7 | |||
d8103f7293 | |||
3d3446e46a | |||
22af4d2836 | |||
06b2a3383d | |||
06c3f01e41 | |||
d392880102 | |||
55dd8da284 | |||
e76f5b19b1 | |||
5650b8560d | |||
2d80dbfa8f | |||
c62aeb670a | |||
1a407a2da3 | |||
72d7e2dc5b | |||
a126ec1718 | |||
bf59114a00 | |||
6b587bfc31 | |||
28a2017b65 | |||
c3a6e9a799 | |||
45c66a167b | |||
380629a961 | |||
74fdd1358c | |||
e9d63650b9 | |||
9ecb27a9bd | |||
b0ae878ef6 | |||
28cc4facd4 | |||
749146ad3c | |||
a4ba93d249 | |||
7eae1f3b3c | |||
d66e8f2d13 | |||
058b768b0a | |||
3425f84507 | |||
aad586232c | |||
b514533814 | |||
3b1f851f8a | |||
99095f25d9 | |||
d79a28ac6f | |||
120fe1ba85 | |||
ad8d8c31ab | |||
3e5ab70583 | |||
cd5b334a00 | |||
9a99b3fdc2 |
@ -31,7 +31,11 @@ jobs:
|
||||
- checkout
|
||||
- run:
|
||||
command: |
|
||||
cd .circleci && ./run-tests.sh "ExternalIntegration=ExternalIntegration"
|
||||
if [ "$CIRCLE_PROJECT_USERNAME" == "btcpayserver" ] && [ "$CIRCLE_PROJECT_REPONAME" == "btcpayserver" ]; then
|
||||
cd .circleci && ./run-tests.sh "ExternalIntegration=ExternalIntegration"
|
||||
else
|
||||
echo "Skipping running ExternalIntegration tests outside of context of main user and repository that have access to secrets"
|
||||
fi
|
||||
|
||||
|
||||
# publish jobs require $DOCKERHUB_REPO, $DOCKERHUB_USER, $DOCKERHUB_PASS defined
|
||||
@ -100,15 +104,16 @@ workflows:
|
||||
# ignore any commit on any branch by default
|
||||
branches:
|
||||
ignore: /.*/
|
||||
# only act on version tags
|
||||
# only act on version tags v1.0.0.88
|
||||
# OR feature tags like vlndseedbackup
|
||||
tags:
|
||||
only: /v[1-9]+(\.[0-9]+)*/
|
||||
only: /(v[1-9]+(\.[0-9]+)*)|(v[a-z]+)/
|
||||
- arm32v7:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /v[1-9]+(\.[0-9]+)*/
|
||||
only: /(v[1-9]+(\.[0-9]+)*)|(v[a-z]+)/
|
||||
- multiarch:
|
||||
requires:
|
||||
- amd64
|
||||
@ -117,4 +122,4 @@ workflows:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /v[1-9]+(\.[0-9]+)*/
|
||||
only: /(v[1-9]+(\.[0-9]+)*)|(v[a-z]+)/
|
||||
|
@ -4,9 +4,9 @@ namespace BTCPayServer.Services.Altcoins.Monero.Utils
|
||||
{
|
||||
public class MoneroMoney
|
||||
{
|
||||
public static decimal Convert(long atoms)
|
||||
public static decimal Convert(long piconero)
|
||||
{
|
||||
var amt = atoms.ToString(CultureInfo.InvariantCulture).PadLeft(12, '0');
|
||||
var amt = piconero.ToString(CultureInfo.InvariantCulture).PadLeft(12, '0');
|
||||
amt = amt.Length == 12 ? $"0.{amt}" : amt.Insert(amt.Length - 12, ".");
|
||||
|
||||
return decimal.Parse(amt, CultureInfo.InvariantCulture);
|
||||
@ -14,7 +14,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.Utils
|
||||
|
||||
public static long Convert(decimal monero)
|
||||
{
|
||||
return System.Convert.ToInt64(monero);
|
||||
return System.Convert.ToInt64(monero * 1000000000000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.9" Condition="'$(TargetFramework)' == 'netcoreapp2.1'" />
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="2.0.0.21" />
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="2.0.0.26" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -8,11 +8,11 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.9" Condition="'$(TargetFramework)' == 'netcoreapp2.1'" />
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.3.1" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.5.3" />
|
||||
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.6.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -36,27 +36,27 @@ namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
await new SynchronizationContextRemover();
|
||||
var rates = await _ExchangeAPI.GetTickersAsync();
|
||||
lock (notFoundSymbols)
|
||||
{
|
||||
var exchangeRates =
|
||||
rates
|
||||
|
||||
var exchangeRateTasks = rates
|
||||
.Where(t => t.Value.Ask != 0m && t.Value.Bid != 0m)
|
||||
.Select(t => CreateExchangeRate(t))
|
||||
.Where(t => t != null)
|
||||
.ToArray();
|
||||
return new ExchangeRates(exchangeRates);
|
||||
}
|
||||
.Select(t => CreateExchangeRate(t));
|
||||
|
||||
var exchangeRates = await Task.WhenAll(exchangeRateTasks);
|
||||
|
||||
return new ExchangeRates(exchangeRates
|
||||
.Where(t => t != null)
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
// ExchangeSymbolToGlobalSymbol throws exception which would kill perf
|
||||
ConcurrentDictionary<string, string> notFoundSymbols = new ConcurrentDictionary<string, string>();
|
||||
private ExchangeRate CreateExchangeRate(KeyValuePair<string, ExchangeTicker> ticker)
|
||||
private async Task<ExchangeRate> CreateExchangeRate(KeyValuePair<string, ExchangeTicker> ticker)
|
||||
{
|
||||
if (notFoundSymbols.ContainsKey(ticker.Key))
|
||||
if (notFoundSymbols.TryGetValue(ticker.Key, out _))
|
||||
return null;
|
||||
try
|
||||
{
|
||||
var tickerName = _ExchangeAPI.ExchangeSymbolToGlobalSymbol(ticker.Key);
|
||||
var tickerName = await _ExchangeAPI.ExchangeMarketSymbolToGlobalMarketSymbolAsync(ticker.Key);
|
||||
if (!CurrencyPair.TryParse(tickerName, out var pair))
|
||||
{
|
||||
notFoundSymbols.TryAdd(ticker.Key, ticker.Key);
|
||||
|
@ -91,7 +91,7 @@ namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
var result = new ExchangeRates();
|
||||
var symbols = await GetSymbolsAsync(cancellationToken);
|
||||
var normalizedPairsList = symbols.Where(s => !notFoundSymbols.ContainsKey(s)).Select(s => _Helper.NormalizeSymbol(s)).ToList();
|
||||
var normalizedPairsList = symbols.Where(s => !notFoundSymbols.ContainsKey(s)).Select(s => _Helper.NormalizeMarketSymbol(s)).ToList();
|
||||
var csvPairsList = string.Join(",", normalizedPairsList);
|
||||
JToken apiTickers = await MakeJsonRequestAsync<JToken>("/0/public/Ticker", null, new Dictionary<string, object> { { "pair", csvPairsList } }, cancellationToken: cancellationToken);
|
||||
var tickers = new List<KeyValuePair<string, ExchangeTicker>>();
|
||||
@ -114,7 +114,7 @@ namespace BTCPayServer.Services.Rates
|
||||
}
|
||||
else
|
||||
{
|
||||
global = _Helper.ExchangeSymbolToGlobalSymbol(symbol);
|
||||
global = await _Helper.ExchangeMarketSymbolToGlobalMarketSymbolAsync(symbol);
|
||||
}
|
||||
if (CurrencyPair.TryParse(global, out var pair))
|
||||
result.Add(new ExchangeRate("kraken", pair.Inverse(), new BidAsk(ticker.Bid, ticker.Ask)));
|
||||
@ -142,10 +142,10 @@ namespace BTCPayServer.Services.Rates
|
||||
Last = last,
|
||||
Volume = new ExchangeVolume
|
||||
{
|
||||
BaseVolume = ticker["v"][1].ConvertInvariant<decimal>(),
|
||||
BaseSymbol = symbol,
|
||||
ConvertedVolume = ticker["v"][1].ConvertInvariant<decimal>() * last,
|
||||
ConvertedSymbol = symbol,
|
||||
BaseCurrencyVolume = ticker["v"][1].ConvertInvariant<decimal>(),
|
||||
BaseCurrency = symbol,
|
||||
QuoteCurrencyVolume = ticker["v"][1].ConvertInvariant<decimal>() * last,
|
||||
QuoteCurrency = symbol,
|
||||
Timestamp = DateTime.UtcNow
|
||||
}
|
||||
};
|
||||
|
@ -1,36 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Rating;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
public class NdaxRateProvider : IRateProvider, IHasExchangeName
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public NdaxRateProvider(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
}
|
||||
|
||||
public string ExchangeName => "ndax";
|
||||
|
||||
public async Task<ExchangeRates> GetRatesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _httpClient.GetAsync("https://ndax.io/api/returnTicker", cancellationToken);
|
||||
var jobj = await response.Content.ReadAsAsync<Dictionary<string, JObject>>(cancellationToken);
|
||||
return new ExchangeRates(jobj.Select(pair => new ExchangeRate(ExchangeName, CurrencyPair.Parse(pair.Key),
|
||||
new BidAsk(GetValue(pair.Value["highestBid"]), GetValue(pair.Value["lowestAsk"])))));
|
||||
}
|
||||
|
||||
private static decimal GetValue(JToken jobj)
|
||||
{
|
||||
return string.IsNullOrEmpty(jobj.ToString()) ? 0 : jobj.Value<decimal>();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ using ExchangeSharp;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MemoryCache = Microsoft.Extensions.Caching.Memory.MemoryCache;
|
||||
|
||||
namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
@ -103,7 +104,8 @@ namespace BTCPayServer.Services.Rates
|
||||
Providers.Add("binance", new ExchangeSharpRateProvider("binance", new ExchangeBinanceAPI(), true));
|
||||
Providers.Add("bittrex", new ExchangeSharpRateProvider("bittrex", new ExchangeBittrexAPI(), true));
|
||||
Providers.Add("poloniex", new ExchangeSharpRateProvider("poloniex", new ExchangePoloniexAPI(), true));
|
||||
Providers.Add("hitbtc", new ExchangeSharpRateProvider("hitbtc", new ExchangeHitbtcAPI(), false));
|
||||
Providers.Add("hitbtc", new ExchangeSharpRateProvider("hitbtc", new ExchangeHitBTCAPI(), true));
|
||||
Providers.Add("ndax", new ExchangeSharpRateProvider("ndax", new ExchangeNDAXAPI(), true));
|
||||
|
||||
// Cryptopia is often not available
|
||||
// Disabled because of https://twitter.com/Cryptopia_NZ/status/1085084168852291586
|
||||
@ -115,7 +117,6 @@ namespace BTCPayServer.Services.Rates
|
||||
Providers.Add("bylls", new ByllsRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BYLLS")));
|
||||
Providers.Add("bitbank", new BitbankRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITBANK")));
|
||||
Providers.Add("bitpay", new BitpayRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITPAY")));
|
||||
Providers.Add("ndax", new NdaxRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_NDAX")));
|
||||
|
||||
// Those exchanges make multiple requests when calling GetTickers so we remove them
|
||||
//DirectProviders.Add("gemini", new ExchangeSharpRateProvider("gemini", new ExchangeGeminiAPI()));
|
||||
@ -172,7 +173,7 @@ namespace BTCPayServer.Services.Rates
|
||||
// Add other exchanges supported here
|
||||
exchanges.Add(new CoinAverageExchange(CoinAverageRateProvider.CoinAverageName, "Coin Average", $"https://apiv2.bitcoinaverage.com/indices/global/ticker/short"));
|
||||
exchanges.Add(new CoinAverageExchange("bylls", "Bylls", "https://bylls.com/api/price?from_currency=BTC&to_currency=CAD"));
|
||||
//exchanges.Add(new CoinAverageExchange("ndax", "NDAX", "https://ndax.io/api/returnTicker")); Buggy
|
||||
exchanges.Add(new CoinAverageExchange("ndax", "NDAX", "https://ndax.io/api/returnTicker"));
|
||||
exchanges.Add(new CoinAverageExchange("bitbank", "Bitbank", "https://public.bitbank.cc/prices"));
|
||||
|
||||
return exchanges;
|
||||
|
@ -12,11 +12,15 @@
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
|
||||
<DefineConstants>$(DefineConstants);NETCOREAPP21</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(CI_TESTS)' == 'true'">
|
||||
<DefineConstants>$(DefineConstants);SHORT_TIMEOUT</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="76.0.3809.6801" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="78.0.3904.7000" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
@ -102,7 +102,6 @@ namespace BTCPayServer.Tests
|
||||
if (!Directory.Exists(chainDirectory))
|
||||
Directory.CreateDirectory(chainDirectory);
|
||||
|
||||
|
||||
StringBuilder config = new StringBuilder();
|
||||
config.AppendLine($"{chain.ToLowerInvariant()}=1");
|
||||
if (InContainer)
|
||||
@ -118,6 +117,12 @@ namespace BTCPayServer.Tests
|
||||
config.AppendLine($"ltc.explorer.url={LTCNBXplorerUri.AbsoluteUri}");
|
||||
config.AppendLine($"ltc.explorer.cookiefile=0");
|
||||
config.AppendLine($"btc.lightning={IntegratedLightning.AbsoluteUri}");
|
||||
config.AppendLine($"torrcfile={TestUtils.GetTestDataFullPath("Tor/torrc")}");
|
||||
config.AppendLine($"debuglog=debug.log");
|
||||
|
||||
var localLndBackupFile = Path.Combine(_Directory, "walletunlock.json");
|
||||
File.Copy(TestUtils.GetTestDataFullPath("LndSeedBackup/walletunlock.json"), localLndBackupFile, true);
|
||||
config.AppendLine($"btc.external.lndseedbackup={localLndBackupFile}");
|
||||
if (!string.IsNullOrEmpty(SSHPassword) && string.IsNullOrEmpty(SSHKeyFile))
|
||||
config.AppendLine($"sshpassword={SSHPassword}");
|
||||
if (!string.IsNullOrEmpty(SSHKeyFile))
|
||||
@ -228,9 +233,10 @@ namespace BTCPayServer.Tests
|
||||
rateProvider.Providers.Add("bittrex", bittrex);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Logs.Tester.LogInformation("Waiting site is operational...");
|
||||
await WaitSiteIsOperational();
|
||||
Logs.Tester.LogInformation("Site is now operational");
|
||||
}
|
||||
|
||||
private async Task WaitSiteIsOperational()
|
||||
|
@ -18,7 +18,7 @@ namespace BTCPayServer.Tests
|
||||
[Trait("Selenium", "Selenium")]
|
||||
public class CheckoutUITests
|
||||
{
|
||||
public const int TestTimeout = 60_000;
|
||||
public const int TestTimeout = TestUtils.TestTimeout;
|
||||
public CheckoutUITests(ITestOutputHelper helper)
|
||||
{
|
||||
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
|
||||
@ -32,6 +32,7 @@ namespace BTCPayServer.Tests
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser();
|
||||
var store = s.CreateNewStore();
|
||||
s.AddDerivationScheme("BTC");
|
||||
@ -80,6 +81,7 @@ namespace BTCPayServer.Tests
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser();
|
||||
var store = s.CreateNewStore();
|
||||
s.AddDerivationScheme("BTC");
|
||||
@ -109,6 +111,7 @@ namespace BTCPayServer.Tests
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser();
|
||||
var store = s.CreateNewStore();
|
||||
s.AddDerivationScheme("BTC");
|
||||
@ -152,6 +155,7 @@ namespace BTCPayServer.Tests
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser();
|
||||
var store = s.CreateNewStore();
|
||||
s.AddInternalLightningNode("BTC");
|
||||
|
@ -35,7 +35,7 @@ namespace BTCPayServer.Tests
|
||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanCreateAndDeleteCrowdfundApp()
|
||||
{
|
||||
@ -75,7 +75,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
|
||||
|
||||
[Fact]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanContributeOnlyWhenAllowed()
|
||||
{
|
||||
@ -100,7 +100,7 @@ namespace BTCPayServer.Tests
|
||||
crowdfundViewModel.Enabled = false;
|
||||
crowdfundViewModel.EndDate = null;
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
|
||||
|
||||
var anonAppPubsController = tester.PayTester.GetController<AppsPublicController>();
|
||||
var publicApps = user.GetController<AppsPublicController>();
|
||||
@ -126,7 +126,7 @@ namespace BTCPayServer.Tests
|
||||
crowdfundViewModel.StartDate= DateTime.Today.AddDays(2);
|
||||
crowdfundViewModel.Enabled = true;
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund()
|
||||
{
|
||||
Amount = new decimal(0.01)
|
||||
@ -138,7 +138,7 @@ namespace BTCPayServer.Tests
|
||||
crowdfundViewModel.EndDate= DateTime.Today.AddDays(-1);
|
||||
crowdfundViewModel.Enabled = true;
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund()
|
||||
{
|
||||
Amount = new decimal(0.01)
|
||||
@ -152,7 +152,7 @@ namespace BTCPayServer.Tests
|
||||
crowdfundViewModel.TargetAmount = 1;
|
||||
crowdfundViewModel.TargetCurrency = "BTC";
|
||||
crowdfundViewModel.EnforceTargetAmount = true;
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund()
|
||||
{
|
||||
Amount = new decimal(1.01)
|
||||
@ -167,7 +167,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanComputeCrowdfundModel()
|
||||
{
|
||||
@ -195,7 +195,7 @@ namespace BTCPayServer.Tests
|
||||
crowdfundViewModel.TargetCurrency = "BTC";
|
||||
crowdfundViewModel.UseAllStoreInvoices = true;
|
||||
crowdfundViewModel.EnforceTargetAmount = true;
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
|
||||
|
||||
var anonAppPubsController = tester.PayTester.GetController<AppsPublicController>();
|
||||
var publicApps = user.GetController<AppsPublicController>();
|
||||
@ -253,7 +253,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains(AppService.GetAppInternalTag(appId), invoiceEntity.InternalTags);
|
||||
|
||||
crowdfundViewModel.UseAllStoreInvoices = false;
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
|
||||
|
||||
Logs.Tester.LogInformation("Because UseAllStoreInvoices is false, let's make sure the invoice is not tagged");
|
||||
invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
@ -272,7 +272,7 @@ namespace BTCPayServer.Tests
|
||||
Logs.Tester.LogInformation("After turning setting a softcap, let's check that only actual payments are counted");
|
||||
crowdfundViewModel.EnforceTargetAmount = false;
|
||||
crowdfundViewModel.UseAllStoreInvoices = true;
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
|
||||
invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Buyer = new Buyer() { email = "test@fwf.com" },
|
||||
|
@ -25,6 +25,6 @@ ENV SCREEN_HEIGHT 600 \
|
||||
SCREEN_WIDTH 1200
|
||||
|
||||
COPY . .
|
||||
RUN cd BTCPayServer.Tests && dotnet build
|
||||
RUN cd BTCPayServer.Tests && dotnet build /p:CI_TESTS=true
|
||||
WORKDIR /source/BTCPayServer.Tests
|
||||
ENTRYPOINT ["./docker-entrypoint.sh"]
|
||||
|
@ -28,6 +28,11 @@ namespace BTCPayServer.Tests
|
||||
try
|
||||
{
|
||||
Assert.NotEmpty(driver.FindElements(By.ClassName("navbar-brand")));
|
||||
if (driver.PageSource.Contains("alert-danger"))
|
||||
{
|
||||
foreach (var dangerAlert in driver.FindElements(By.ClassName("alert-danger")))
|
||||
Assert.False(dangerAlert.Displayed, "No alert should be displayed");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@ -11,6 +11,7 @@ using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using BTCPayServer.Models;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -68,10 +69,9 @@ namespace BTCPayServer.Tests
|
||||
var vmLedger = await walletController.WalletSend(walletId, sendModel, command: "ledger").AssertViewModelAsync<WalletSendLedgerModel>();
|
||||
PSBT.Parse(vmLedger.PSBT, user.SupportedNetwork.NBitcoinNetwork);
|
||||
BitcoinAddress.Create(vmLedger.HintChange, user.SupportedNetwork.NBitcoinNetwork);
|
||||
Assert.NotNull(vmLedger.SuccessPath);
|
||||
Assert.NotNull(vmLedger.WebsocketPath);
|
||||
|
||||
var redirectedPSBT = (string)Assert.IsType<RedirectToActionResult>(await walletController.WalletSend(walletId, sendModel, command: "analyze-psbt")).RouteValues["psbt"];
|
||||
string redirectedPSBT = AssertRedirectedPSBT(await walletController.WalletSend(walletId, sendModel, command: "analyze-psbt"));
|
||||
var vmPSBT = await walletController.WalletPSBT(walletId, new WalletPSBTViewModel() { PSBT = redirectedPSBT }).AssertViewModelAsync<WalletPSBTViewModel>();
|
||||
var unsignedPSBT = PSBT.Parse(vmPSBT.PSBT, user.SupportedNetwork.NBitcoinNetwork);
|
||||
Assert.NotNull(vmPSBT.Decoded);
|
||||
@ -98,9 +98,9 @@ namespace BTCPayServer.Tests
|
||||
var combineVM = await walletController.WalletPSBT(walletId, vmPSBT, "combine").AssertViewModelAsync<WalletPSBTCombineViewModel>();
|
||||
Assert.Equal(vmPSBT.PSBT, combineVM.OtherPSBT);
|
||||
combineVM.PSBT = signedPSBT.ToBase64();
|
||||
vmPSBT = await walletController.WalletPSBTCombine(walletId, combineVM).AssertViewModelAsync<WalletPSBTViewModel>();
|
||||
var psbt = AssertRedirectedPSBT(await walletController.WalletPSBTCombine(walletId, combineVM));
|
||||
|
||||
var signedPSBT2 = PSBT.Parse(vmPSBT.PSBT, user.SupportedNetwork.NBitcoinNetwork);
|
||||
var signedPSBT2 = PSBT.Parse(psbt, user.SupportedNetwork.NBitcoinNetwork);
|
||||
Assert.True(signedPSBT.TryFinalize(out _));
|
||||
Assert.True(signedPSBT2.TryFinalize(out _));
|
||||
Assert.Equal(signedPSBT, signedPSBT2);
|
||||
@ -108,19 +108,27 @@ namespace BTCPayServer.Tests
|
||||
// Can use uploaded file?
|
||||
combineVM.PSBT = null;
|
||||
combineVM.UploadedPSBTFile = TestUtils.GetFormFile("signedPSBT", signedPSBT.ToBytes());
|
||||
vmPSBT = await walletController.WalletPSBTCombine(walletId, combineVM).AssertViewModelAsync<WalletPSBTViewModel>();
|
||||
signedPSBT2 = PSBT.Parse(vmPSBT.PSBT, user.SupportedNetwork.NBitcoinNetwork);
|
||||
psbt = AssertRedirectedPSBT(await walletController.WalletPSBTCombine(walletId, combineVM));
|
||||
signedPSBT2 = PSBT.Parse(psbt, user.SupportedNetwork.NBitcoinNetwork);
|
||||
Assert.True(signedPSBT.TryFinalize(out _));
|
||||
Assert.True(signedPSBT2.TryFinalize(out _));
|
||||
Assert.Equal(signedPSBT, signedPSBT2);
|
||||
|
||||
var ready = (await walletController.WalletPSBTReady(walletId, signedPSBT.ToBase64())).AssertViewModel<WalletPSBTReadyViewModel>();
|
||||
Assert.Equal(signedPSBT.ToBase64(), ready.PSBT);
|
||||
redirect = Assert.IsType<RedirectToActionResult>(await walletController.WalletPSBTReady(walletId, ready, command: "analyze-psbt"));
|
||||
Assert.Equal(signedPSBT.ToBase64(), (string)redirect.RouteValues["psbt"]);
|
||||
psbt = AssertRedirectedPSBT(await walletController.WalletPSBTReady(walletId, ready, command: "analyze-psbt"));
|
||||
Assert.Equal(signedPSBT.ToBase64(), psbt);
|
||||
redirect = Assert.IsType<RedirectToActionResult>(await walletController.WalletPSBTReady(walletId, ready, command: "broadcast"));
|
||||
Assert.Equal(nameof(walletController.WalletTransactions), redirect.ActionName);
|
||||
}
|
||||
}
|
||||
|
||||
private static string AssertRedirectedPSBT(IActionResult view)
|
||||
{
|
||||
var postRedirectView = Assert.IsType<ViewResult>(view);
|
||||
var postRedirectViewModel = Assert.IsType<PostRedirectViewModel>(postRedirectView.Model);
|
||||
var redirectedPSBT = postRedirectViewModel.Parameters.Single(p => p.Key == "psbt").Value;
|
||||
return redirectedPSBT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,20 +66,28 @@ namespace BTCPayServer.Tests
|
||||
Logs.Tester.LogInformation("Selenium: Browsing to " + Server.PayTester.ServerUri);
|
||||
Logs.Tester.LogInformation($"Selenium: Resolution {Driver.Manage().Window.Size}");
|
||||
Driver.Manage().Timeouts().ImplicitWait = ImplicitWait;
|
||||
Driver.Navigate().GoToUrl(Server.PayTester.ServerUri);
|
||||
GoToRegister();
|
||||
Driver.AssertNoError();
|
||||
}
|
||||
|
||||
internal void AssertHappyMessage()
|
||||
{
|
||||
Assert.Single(Driver.FindElements(By.ClassName("alert-success")).Where(el => el.Displayed));
|
||||
}
|
||||
|
||||
public static readonly TimeSpan ImplicitWait = TimeSpan.FromSeconds(10);
|
||||
public string Link(string relativeLink)
|
||||
{
|
||||
return Server.PayTester.ServerUri.AbsoluteUri.WithoutEndingSlash() + relativeLink.WithStartingSlash();
|
||||
}
|
||||
|
||||
public void GoToRegister()
|
||||
{
|
||||
Driver.Navigate().GoToUrl(this.Link("/Account/Register"));
|
||||
}
|
||||
public string RegisterNewUser(bool isAdmin = false)
|
||||
{
|
||||
var usr = RandomUtils.GetUInt256().ToString().Substring(64 - 20) + "@a.com";
|
||||
Driver.FindElement(By.Id("Register")).Click();
|
||||
Driver.FindElement(By.Id("Email")).SendKeys(usr);
|
||||
Driver.FindElement(By.Id("Password")).SendKeys("123456");
|
||||
Driver.FindElement(By.Id("ConfirmPassword")).SendKeys("123456");
|
||||
@ -107,8 +115,7 @@ namespace BTCPayServer.Tests
|
||||
Driver.FindElement(By.ClassName("store-derivation-scheme")).SendKeys(derivationScheme);
|
||||
Driver.FindElement(By.Id("Continue")).ForceClick();
|
||||
Driver.FindElement(By.Id("Confirm")).ForceClick();
|
||||
Driver.FindElement(By.Id("Save")).ForceClick();
|
||||
return;
|
||||
AssertHappyMessage();
|
||||
}
|
||||
|
||||
public void AddLightningNode(string cryptoCode, LightningConnectionType connectionType)
|
||||
@ -142,6 +149,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.NotEmpty(links);
|
||||
foreach (var l in links)
|
||||
{
|
||||
Logs.Tester.LogInformation($"Checking no error on {l}");
|
||||
Driver.Navigate().GoToUrl(l);
|
||||
Driver.AssertNoError();
|
||||
}
|
||||
|
@ -32,6 +32,28 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("ServerSettings")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
s.ClickOnAllSideMenus();
|
||||
s.Driver.FindElement(By.LinkText("Services")).Click();
|
||||
|
||||
Logs.Tester.LogInformation("Let's if we can access LND's seed");
|
||||
Assert.Contains("server/services/lndseedbackup/BTC", s.Driver.PageSource);
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/lndseedbackup/BTC"));
|
||||
s.Driver.FindElement(By.Id("details")).Click();
|
||||
var seedEl = s.Driver.FindElement(By.Id("SeedTextArea"));
|
||||
Assert.True(seedEl.Displayed);
|
||||
Assert.Contains("about over million", seedEl.Text, StringComparison.OrdinalIgnoreCase);
|
||||
var passEl = s.Driver.FindElement(By.Id("PasswordInput"));
|
||||
Assert.True(passEl.Displayed);
|
||||
Assert.Contains(passEl.Text, "hellorockstar", StringComparison.OrdinalIgnoreCase);
|
||||
s.Driver.FindElement(By.Id("delete")).Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.AssertHappyMessage();
|
||||
seedEl = s.Driver.FindElement(By.Id("SeedTextArea"));
|
||||
Assert.Contains("Seed removed", seedEl.Text, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
Logs.Tester.LogInformation("Let's check if we can access the logs");
|
||||
s.Driver.FindElement(By.LinkText("Logs")).Click();
|
||||
s.Driver.FindElement(By.PartialLinkText(".log")).Click();
|
||||
Assert.Contains("Starting listening NBXplorer", s.Driver.PageSource);
|
||||
s.Driver.Quit();
|
||||
}
|
||||
}
|
||||
@ -46,8 +68,9 @@ namespace BTCPayServer.Tests
|
||||
var email = s.RegisterNewUser();
|
||||
s.Driver.FindElement(By.Id("Logout")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
s.Driver.FindElement(By.Id("Login")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
Assert.Contains("Account/Login", s.Driver.Url);
|
||||
// Should show the Tor address
|
||||
Assert.Contains("wsaxew3qa5ljfuenfebmaf3m5ykgatct3p6zjrqwoouj3foererde3id.onion", s.Driver.PageSource);
|
||||
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/invoices"));
|
||||
Assert.Contains("ReturnUrl=%2Finvoices", s.Driver.Url);
|
||||
@ -76,7 +99,6 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.AssertNoError();
|
||||
|
||||
//Log In With New Password
|
||||
s.Driver.FindElement(By.Id("Login")).Click();
|
||||
s.Driver.FindElement(By.Id("Email")).SendKeys(email);
|
||||
s.Driver.FindElement(By.Id("Password")).SendKeys("abc???");
|
||||
s.Driver.FindElement(By.Id("LoginButton")).Click();
|
||||
@ -91,7 +113,6 @@ namespace BTCPayServer.Tests
|
||||
|
||||
static void LogIn(SeleniumTester s, string email)
|
||||
{
|
||||
s.Driver.FindElement(By.Id("Login")).Click();
|
||||
s.Driver.FindElement(By.Id("Email")).SendKeys(email);
|
||||
s.Driver.FindElement(By.Id("Password")).SendKeys("123456");
|
||||
s.Driver.FindElement(By.Id("LoginButton")).Click();
|
||||
@ -199,6 +220,7 @@ namespace BTCPayServer.Tests
|
||||
s.ClickOnAllSideMenus();
|
||||
s.GoToInvoices();
|
||||
s.CreateInvoice(store);
|
||||
s.AssertHappyMessage();
|
||||
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
|
||||
var invoiceUrl = s.Driver.Url;
|
||||
|
||||
@ -208,7 +230,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("ReturnUrl", s.Driver.Url);
|
||||
s.Driver.Navigate().GoToUrl(invoiceUrl);
|
||||
Assert.Contains("ReturnUrl", s.Driver.Url);
|
||||
|
||||
s.GoToRegister();
|
||||
// When logged we should not be able to access store and invoice details
|
||||
var bob = s.RegisterNewUser();
|
||||
s.Driver.Navigate().GoToUrl(storeUrl);
|
||||
@ -231,6 +253,16 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("ReturnUrl", s.Driver.Url);
|
||||
s.Driver.Navigate().GoToUrl(invoiceUrl);
|
||||
s.Driver.AssertNoError();
|
||||
|
||||
// Alice should be able to delete the store
|
||||
s.Logout();
|
||||
LogIn(s, alice);
|
||||
s.Driver.FindElement(By.Id("Stores")).Click();
|
||||
s.Driver.FindElement(By.LinkText("Remove")).Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.Driver.FindElement(By.Id("Stores")).Click();
|
||||
s.Driver.Navigate().GoToUrl(storeUrl);
|
||||
Assert.Contains("ReturnUrl", s.Driver.Url);
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,7 +274,7 @@ namespace BTCPayServer.Tests
|
||||
await s.StartAsync();
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/api-access-request"));
|
||||
Assert.Contains("ReturnUrl", s.Driver.Url);
|
||||
|
||||
s.GoToRegister();
|
||||
var alice = s.RegisterNewUser();
|
||||
var store = s.CreateNewStore().storeName;
|
||||
s.AddDerivationScheme();
|
||||
@ -250,12 +282,10 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Tokens")).Click();
|
||||
s.Driver.FindElement(By.Id("CreateNewToken")).Click();
|
||||
s.Driver.FindElement(By.Id("RequestPairing")).Click();
|
||||
|
||||
var regex = Regex.Match(new Uri(s.Driver.Url, UriKind.Absolute).Query, "pairingCode=([^&]*)");
|
||||
Assert.True(regex.Success, $"{s.Driver.Url} does not match expected regex");
|
||||
var pairingCode = regex.Groups[1].Value;
|
||||
string pairingCode = AssertUrlHasPairingCode(s);
|
||||
|
||||
s.Driver.FindElement(By.Id("ApprovePairing")).Click();
|
||||
s.AssertHappyMessage();
|
||||
Assert.Contains(pairingCode, s.Driver.PageSource);
|
||||
|
||||
var client = new NBitpayClient.Bitpay(new Key(), s.Server.PayTester.ServerUri);
|
||||
@ -279,9 +309,21 @@ namespace BTCPayServer.Tests
|
||||
Currency = "USD",
|
||||
FullNotifications = true
|
||||
}, NBitpayClient.Facade.Merchant);
|
||||
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/api-tokens"));
|
||||
s.Driver.FindElement(By.Id("RequestPairing")).Click();
|
||||
s.Driver.FindElement(By.Id("ApprovePairing")).Click();
|
||||
AssertUrlHasPairingCode(s);
|
||||
}
|
||||
}
|
||||
|
||||
private static string AssertUrlHasPairingCode(SeleniumTester s)
|
||||
{
|
||||
var regex = Regex.Match(new Uri(s.Driver.Url, UriKind.Absolute).Query, "pairingCode=([^&]*)");
|
||||
Assert.True(regex.Success, $"{s.Driver.Url} does not match expected regex");
|
||||
var pairingCode = regex.Groups[1].Value;
|
||||
return pairingCode;
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
|
@ -28,7 +28,7 @@ namespace BTCPayServer.Tests
|
||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Timeout = TestUtils.TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanConfigureStorage()
|
||||
{
|
||||
@ -139,7 +139,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Timeout = TestUtils.TestTimeout)]
|
||||
[Trait("ExternalIntegration", "ExternalIntegration")]
|
||||
public async Task CanUseAzureBlobStorage()
|
||||
{
|
||||
@ -210,8 +210,8 @@ namespace BTCPayServer.Tests
|
||||
TimeAmount = 1,
|
||||
TimeType = ServerController.CreateTemporaryFileUrlViewModel.TmpFileTimeType.Minutes
|
||||
}));
|
||||
Assert.True(tmpLinkGenerate.RouteValues.ContainsKey("StatusMessage"));
|
||||
var statusMessageModel = new StatusMessageModel(tmpLinkGenerate.RouteValues["StatusMessage"].ToString());
|
||||
var statusMessageModel = controller.TempData.GetStatusMessageModel();
|
||||
Assert.NotNull(statusMessageModel);
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Success, statusMessageModel.Severity);
|
||||
var index = statusMessageModel.Html.IndexOf("target='_blank'>");
|
||||
var url = statusMessageModel.Html.Substring(index).ReplaceMultiple(new Dictionary<string, string>()
|
||||
@ -221,12 +221,14 @@ namespace BTCPayServer.Tests
|
||||
//verify tmpfile is available and the same
|
||||
data = await net.DownloadStringTaskAsync(new Uri(url));
|
||||
Assert.Equal(fileContent, data);
|
||||
|
||||
|
||||
|
||||
|
||||
//delete file
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Success, new StatusMessageModel(Assert
|
||||
.IsType<RedirectToActionResult>(await controller.DeleteFile(fileId))
|
||||
.RouteValues["statusMessage"].ToString()).Severity);
|
||||
Assert.IsType<RedirectToActionResult>(await controller.DeleteFile(fileId));
|
||||
controller.TempData.GetStatusMessageModel();
|
||||
Assert.NotNull(statusMessageModel);
|
||||
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Success, statusMessageModel.Severity);
|
||||
|
||||
//attempt to fetch deleted file
|
||||
viewFilesViewModel =
|
||||
|
@ -0,0 +1 @@
|
||||
{"wallet_password":"hellorockstar", "cipher_seed_mnemonic":["about","over","million","coast","nature","gold","fabric","aware","adult","salute","sock","envelope","oblige","shed","basic","need","analyst","spot","worth","garbage","planet","genre","leave","cereal"]}
|
@ -0,0 +1 @@
|
||||
rhrk7ncuqfcbkt7deaysn7st32hrf2tzztoumkj2vp3cqzy2d2arvdad.onion
|
@ -0,0 +1 @@
|
||||
r32tqfpchpiptrfltyd4ku7q3ir4yidyfe4em63iz2g5wjcauyvyj4id.onion
|
@ -0,0 +1 @@
|
||||
wsaxew3qa5ljfuenfebmaf3m5ykgatct3p6zjrqwoouj3foererde3id.onion
|
@ -0,0 +1 @@
|
||||
wzaq2ek6a5g6jyhr7giqsz2gbr6qdtcuwywcyvfplvrk7ugup3virvad.onion
|
@ -0,0 +1 @@
|
||||
jzebv74oaugje6aeykzcypzxs6pvt27wdmrcpe4pkjfprxgem2vyi7qd.onion
|
@ -0,0 +1 @@
|
||||
aeyn6mou5himgmog4tlnbjlgx5bwtwsqttd7sn7hay6lksvlfwmtkqyd.onion
|
@ -0,0 +1 @@
|
||||
uewlcjmuuwcgs43lpcc7d64k2pf4xs7yyer7ztsq6cblyjxue3bc35qd.onion
|
@ -0,0 +1 @@
|
||||
4iz3q7dp5xn5isqxsslrvbjrltqoqqocq25zmjgzvvxm5yp57jj4buid.onion
|
@ -0,0 +1 @@
|
||||
ii3kqeayarsxr2jqkmr2l5np2f4vn6lznk55upd3udfbfskbjdzoq4yd.onion
|
24
BTCPayServer.Tests/TestData/Tor/torrc
Normal file
24
BTCPayServer.Tests/TestData/Tor/torrc
Normal file
@ -0,0 +1,24 @@
|
||||
# For the hidden service BTC-P2P
|
||||
HiddenServiceDir hidden_services/BTC-P2P
|
||||
# Redirecting to btcpayserver_bitcoind
|
||||
HiddenServicePort 8333 172.19.0.2:39388
|
||||
# For the hidden service BTC-RPC
|
||||
HiddenServiceDir hidden_services/BTC-RPC
|
||||
# Redirecting to btcpayserver_bitcoind
|
||||
HiddenServicePort 8332 172.19.0.2:43782
|
||||
# For the hidden service BTCPayServer
|
||||
HiddenServiceDir hidden_services/BTCPayServer
|
||||
# Redirecting to nginx
|
||||
HiddenServicePort 80 172.19.0.13:80
|
||||
# For the hidden service BTCTransmuter
|
||||
HiddenServiceDir hidden_services/BTCTransmuter
|
||||
# Redirecting to nginx
|
||||
HiddenServicePort 80 172.19.0.13:80
|
||||
# For the hidden service WooCommerce
|
||||
HiddenServiceDir hidden_services/WooCommerce
|
||||
# Redirecting to generated_woocommerce_1
|
||||
HiddenServicePort 80 172.19.0.15:80
|
||||
# For the hidden service c-lightning
|
||||
HiddenServiceDir hidden_services/c-lightning
|
||||
# Redirecting to btcpayserver_clightning_bitcoin
|
||||
HiddenServicePort 9735 172.19.0.10:9735
|
@ -15,7 +15,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
public static class TestUtils
|
||||
{
|
||||
#if DEBUG
|
||||
#if DEBUG && !SHORT_TIMEOUT
|
||||
public const int TestTimeout = 600_000;
|
||||
#else
|
||||
public const int TestTimeout = 60_000;
|
||||
@ -30,6 +30,18 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
return directory;
|
||||
}
|
||||
|
||||
|
||||
public static string GetTestDataFullPath(string relativeFilePath)
|
||||
{
|
||||
var directory = new DirectoryInfo(Directory.GetCurrentDirectory());
|
||||
while (directory != null && !directory.GetFiles("*.csproj").Any())
|
||||
{
|
||||
directory = directory.Parent;
|
||||
}
|
||||
return Path.Combine(directory.FullName, "TestData", relativeFilePath);
|
||||
}
|
||||
|
||||
public static FormFile GetFormFile(string filename, string content)
|
||||
{
|
||||
File.WriteAllText(filename, content);
|
||||
|
@ -60,6 +60,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using BTCPayServer.U2F.Models;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
using MemoryCache = Microsoft.Extensions.Caching.Memory.MemoryCache;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -532,6 +533,22 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Fast", "Fast")]
|
||||
public async Task CanEnumerateTorServices()
|
||||
{
|
||||
var tor = new TorServices(new BTCPayNetworkProvider(NetworkType.Regtest), new BTCPayServerOptions()
|
||||
{
|
||||
TorrcFile = TestUtils.GetTestDataFullPath("Tor/torrc")
|
||||
});
|
||||
await tor.Refresh();
|
||||
|
||||
Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.BTCPayServer));
|
||||
Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.P2P));
|
||||
Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.RPC));
|
||||
Assert.True(tor.Services.Where(t => t.ServiceType == TorServiceType.Other).Count() > 1);
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanSetLightningServer()
|
||||
@ -551,7 +568,8 @@ namespace BTCPayServer.Tests
|
||||
ConnectionString = "type=charge;server=" + tester.MerchantCharge.Client.Uri.AbsoluteUri,
|
||||
SkipPortTest = true // We can't test this as the IP can't be resolved by the test host :(
|
||||
}, "test", "BTC").GetAwaiter().GetResult();
|
||||
Assert.DoesNotContain("Error", ((LightningNodeViewModel)Assert.IsType<ViewResult>(testResult).Model).StatusMessage, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.False(storeController.TempData.ContainsKey(WellKnownTempData.ErrorMessage));
|
||||
storeController.TempData.Clear();
|
||||
Assert.True(storeController.ModelState.IsValid);
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(storeController.AddLightningNode(user.StoreId, new LightningNodeViewModel()
|
||||
@ -647,11 +665,12 @@ namespace BTCPayServer.Tests
|
||||
acc.CreateStore();
|
||||
|
||||
var controller = acc.GetController<StoresController>();
|
||||
var token = (RedirectToActionResult)controller.CreateToken(new Models.StoreViewModels.CreateTokenViewModel()
|
||||
var token = (RedirectToActionResult)await controller.CreateToken2(new Models.StoreViewModels.CreateTokenViewModel()
|
||||
{
|
||||
Label = "bla",
|
||||
PublicKey = null
|
||||
}).GetAwaiter().GetResult();
|
||||
PublicKey = null,
|
||||
StoreId = acc.StoreId
|
||||
});
|
||||
|
||||
var pairingCode = (string)token.RouteValues["pairingCode"];
|
||||
|
||||
@ -684,7 +703,7 @@ namespace BTCPayServer.Tests
|
||||
FullNotifications = true,
|
||||
ExtendedNotifications = true
|
||||
});
|
||||
BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21);
|
||||
BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21, tester.NetworkProvider.BTC.NBitcoinNetwork);
|
||||
bool receivedPayment = false;
|
||||
bool paid = false;
|
||||
bool confirmed = false;
|
||||
@ -746,7 +765,7 @@ namespace BTCPayServer.Tests
|
||||
acc.CreateStore();
|
||||
var store2 = acc.GetController<StoresController>();
|
||||
await store2.Pair(pairingCode.ToString(), store2.CurrentStore.Id);
|
||||
Assert.Contains(nameof(PairingResult.ReusedKey), store2.StatusMessage, StringComparison.CurrentCultureIgnoreCase);
|
||||
Assert.Contains(nameof(PairingResult.ReusedKey), (string)store2.TempData[WellKnownTempData.ErrorMessage], StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1140,7 +1159,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// Test request pairing code client side
|
||||
var storeController = user.GetController<StoresController>();
|
||||
storeController.CreateToken(new CreateTokenViewModel()
|
||||
storeController.CreateToken(user.StoreId, new CreateTokenViewModel()
|
||||
{
|
||||
Label = "test2",
|
||||
StoreId = user.StoreId
|
||||
@ -2002,7 +2021,7 @@ noninventoryitem:
|
||||
//verify invoices where created
|
||||
invoices = user.BitPay.GetInvoices();
|
||||
Assert.Equal(2, invoices.Count(invoice => invoice.ItemCode.Equals("noninventoryitem")));
|
||||
var inventoryItemInvoice = invoices.SingleOrDefault(invoice => invoice.ItemCode.Equals("inventoryitem"));
|
||||
var inventoryItemInvoice = Assert.Single(invoices.Where(invoice => invoice.ItemCode.Equals("inventoryitem")));
|
||||
Assert.NotNull(inventoryItemInvoice);
|
||||
|
||||
//let's mark the inventoryitem invoice as invalid, thsi should return the item to back in stock
|
||||
@ -2880,42 +2899,6 @@ noninventoryitem:
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromColdcard("{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"vpub5YjYxTemJ39tFRnuAhwduyxG2tKGjoEpmvqVQRPqdYrqa6YGoeSzBtHXaJUYB19zDbXs3JjbEcVWERjQBPf9bEfUUMZNMv1QnMyHV8JPqyf\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/84'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}", testnet, out settings));
|
||||
Assert.True(settings.AccountDerivation is DirectDerivationStrategy s3 && s3.Segwit);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void CheckParseStatusMessageModel()
|
||||
{
|
||||
var legacyStatus = "Error: some bad shit happened";
|
||||
var parsed = new StatusMessageModel(legacyStatus);
|
||||
Assert.Equal(legacyStatus, parsed.Message);
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Error, parsed.Severity);
|
||||
|
||||
var legacyStatus2 = "Some normal shit happened";
|
||||
parsed = new StatusMessageModel(legacyStatus2);
|
||||
Assert.Equal(legacyStatus2, parsed.Message);
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Success, parsed.Severity);
|
||||
|
||||
var newStatus = new StatusMessageModel()
|
||||
{
|
||||
Html = "<a href='xxx'>something new</a>",
|
||||
Severity = StatusMessageModel.StatusSeverity.Info
|
||||
};
|
||||
parsed = new StatusMessageModel(newStatus.ToString());
|
||||
Assert.Null(parsed.Message);
|
||||
Assert.Equal(newStatus.Html, parsed.Html);
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Info, parsed.Severity);
|
||||
|
||||
var newStatus2 = new StatusMessageModel()
|
||||
{
|
||||
Message = "something new",
|
||||
Severity = StatusMessageModel.StatusSeverity.Success
|
||||
};
|
||||
parsed = new StatusMessageModel(newStatus2.ToString());
|
||||
Assert.Null(parsed.Html);
|
||||
Assert.Equal(newStatus2.Message, parsed.Message);
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Success, parsed.Severity);
|
||||
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
@ -2978,13 +2961,11 @@ noninventoryitem:
|
||||
var addRequest = Assert.IsType<AddU2FDeviceViewModel>(Assert.IsType<ViewResult>(manageController.AddU2FDevice("label")).Model);
|
||||
//name should match the one provided in beginning
|
||||
Assert.Equal("label",addRequest.Name);
|
||||
|
||||
//sending an invalid response model back to server, should error out
|
||||
var statusMessage = Assert
|
||||
.IsType<RedirectToActionResult>(await manageController.AddU2FDevice(addRequest))
|
||||
.RouteValues["StatusMessage"].ToString();
|
||||
Assert.NotNull(statusMessage);
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Error, new StatusMessageModel(statusMessage).Severity);
|
||||
|
||||
//sending an invalid response model back to server, should error out
|
||||
Assert.IsType<RedirectToActionResult>(await manageController.AddU2FDevice(addRequest));
|
||||
var statusModel = manageController.TempData.GetStatusMessageModel();
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Error, statusModel.Severity);
|
||||
|
||||
var contextFactory = tester.PayTester.GetService<ApplicationDbContextFactory>();
|
||||
|
||||
|
@ -76,7 +76,7 @@ services:
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.0.0.62
|
||||
image: nicolasdorier/nbxplorer:2.0.0.66
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
@ -127,7 +127,7 @@ services:
|
||||
- "bitcoin_datadir:/data"
|
||||
|
||||
customer_lightningd:
|
||||
image: btcpayserver/lightning:v0.7.2-1-dev
|
||||
image: btcpayserver/lightning:v0.7.3-dev
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
@ -140,7 +140,7 @@ services:
|
||||
announce-addr=customer_lightningd
|
||||
log-level=debug
|
||||
funding-confirms=1
|
||||
dev-broadcast-interval=1000
|
||||
dev-fast-gossip
|
||||
dev-bitcoind-poll=1
|
||||
ports:
|
||||
- "30992:9835" # api port
|
||||
@ -154,7 +154,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
lightning-charged:
|
||||
image: shesek/lightning-charge:0.4.6-standalone
|
||||
image: shesek/lightning-charge:0.4.11-standalone
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NETWORK: regtest
|
||||
@ -174,7 +174,7 @@ services:
|
||||
- merchant_lightningd
|
||||
|
||||
merchant_lightningd:
|
||||
image: btcpayserver/lightning:v0.7.2-1-dev
|
||||
image: btcpayserver/lightning:v0.7.3-dev
|
||||
stop_signal: SIGKILL
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
@ -186,7 +186,8 @@ services:
|
||||
announce-addr=merchant_lightningd
|
||||
funding-confirms=1
|
||||
log-level=debug
|
||||
dev-broadcast-interval=1000
|
||||
dev-fast-gossip
|
||||
dev-bitcoind-poll=1
|
||||
ports:
|
||||
- "30993:9835" # api port
|
||||
expose:
|
||||
@ -223,7 +224,7 @@ services:
|
||||
- "5432"
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.7.0-beta
|
||||
image: btcpayserver/lnd:v0.7.1-beta-withseed
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -241,7 +242,6 @@ services:
|
||||
bitcoin.defaultchanconfs=1
|
||||
no-macaroons=1
|
||||
debuglevel=debug
|
||||
noseedbackup=1
|
||||
trickledelay=1000
|
||||
ports:
|
||||
- "53280:8080"
|
||||
@ -254,7 +254,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.7.0-beta
|
||||
image: btcpayserver/lnd:v0.7.1-beta-withseed
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -272,7 +272,6 @@ services:
|
||||
bitcoin.defaultchanconfs=1
|
||||
no-macaroons=1
|
||||
debuglevel=debug
|
||||
noseedbackup=1
|
||||
trickledelay=1000
|
||||
ports:
|
||||
- "53281:8080"
|
||||
|
@ -8,19 +8,15 @@
|
||||
<Compile Remove="Build\**" />
|
||||
<Compile Remove="Storage\Services\Providers\GoogleCloudStorage\**" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
|
||||
<Compile Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<Compile Remove="wwwroot\css\**" />
|
||||
<Compile Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
<Content Remove="Build\**" />
|
||||
<Content Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<Content Remove="wwwroot\css\**" />
|
||||
<Content Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
<EmbeddedResource Remove="Build\**" />
|
||||
<EmbeddedResource Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<EmbeddedResource Remove="wwwroot\css\**" />
|
||||
<EmbeddedResource Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
<None Remove="Build\**" />
|
||||
<None Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<None Remove="wwwroot\css\**" />
|
||||
<None Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@ -31,7 +27,7 @@
|
||||
<EmbeddedResource Include="Currencies.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.1" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.5" />
|
||||
<PackageReference Include="BuildBundlerMinifier" Version="3.1.430" />
|
||||
<PackageReference Include="BundlerMinifier.Core" Version="3.1.430" />
|
||||
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.1.430" />
|
||||
@ -80,11 +76,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="wwwroot\vendor\bootstrap4-creativestart\creative.js" />
|
||||
<None Include="wwwroot\main\bootstrap4-creativestart\creative.js" />
|
||||
<None Include="wwwroot\vendor\font-awesome\fonts\fontawesome-webfont.svg" />
|
||||
<None Include="wwwroot\vendor\font-awesome\fonts\fontawesome-webfont.woff2" />
|
||||
<None Include="wwwroot\vendor\font-awesome\less\animated.less" />
|
||||
@ -158,6 +150,12 @@
|
||||
<Content Update="Views\Server\LightningWalletServices.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\LndSeedBackup.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\RPCService.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\P2PService.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
@ -221,4 +219,4 @@
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
@ -58,7 +58,16 @@ namespace BTCPayServer.Configuration
|
||||
|
||||
public static string GetDebugLog(IConfiguration configuration)
|
||||
{
|
||||
return configuration.GetValue<string>("debuglog", null);
|
||||
var logfile = configuration.GetValue<string>("debuglog", null);
|
||||
if (!string.IsNullOrEmpty(logfile))
|
||||
{
|
||||
if (!Path.IsPathRooted(logfile))
|
||||
{
|
||||
var networkType = DefaultConfiguration.GetNetworkType(configuration);
|
||||
logfile = Path.Combine(configuration.GetDataDir(networkType), logfile);
|
||||
}
|
||||
}
|
||||
return logfile;
|
||||
}
|
||||
public static LogEventLevel GetDebugLogLevel(IConfiguration configuration)
|
||||
{
|
||||
|
@ -142,6 +142,12 @@ namespace BTCPayServer.Configuration
|
||||
Macaroons = Macaroons?.Clone()
|
||||
};
|
||||
}
|
||||
public bool? IsOnion()
|
||||
{
|
||||
if (this.Server == null || !this.Server.IsAbsoluteUri)
|
||||
return null;
|
||||
return this.Server.DnsSafeHost.EndsWith(".onion", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
public static bool TryParse(string str, out ExternalConnectionString result, out string error)
|
||||
{
|
||||
if (str == null)
|
||||
|
@ -24,6 +24,10 @@ namespace BTCPayServer.Configuration
|
||||
"lnd server: 'server=https://lnd.example.com;macaroondirectorypath=/root/.lnd;certthumbprint=2abdf302...'" + Environment.NewLine +
|
||||
"Error: {1}",
|
||||
"LND (REST server)");
|
||||
Load(configuration, cryptoCode, "lndseedbackup", ExternalServiceTypes.LNDSeedBackup, "Invalid setting {0}, " + Environment.NewLine +
|
||||
"lnd seed backup: /etc/merchant_lnd/data/chain/bitcoin/regtest/walletunlock.json'" + Environment.NewLine +
|
||||
"Error: {1}",
|
||||
"LND Seed Backup");
|
||||
Load(configuration, cryptoCode, "spark", ExternalServiceTypes.Spark, "Invalid setting {0}, " + Environment.NewLine +
|
||||
$"Valid example: 'server=https://btcpay.example.com/spark/btc/;cookiefile=/etc/clightning_bitcoin_spark/.cookie'" + Environment.NewLine +
|
||||
"Error: {1}",
|
||||
@ -45,11 +49,17 @@ namespace BTCPayServer.Configuration
|
||||
var connStr = configuration.GetOrDefault<string>(setting, string.Empty);
|
||||
if (connStr.Length != 0)
|
||||
{
|
||||
if (!ExternalConnectionString.TryParse(connStr, out var connectionString, out var error))
|
||||
ExternalConnectionString serviceConnection;
|
||||
if (type == ExternalServiceTypes.LNDSeedBackup)
|
||||
{
|
||||
// just using CookieFilePath to hold variable instead of refactoring whole holder class to better conform
|
||||
serviceConnection = new ExternalConnectionString { CookieFilePath = connStr };
|
||||
}
|
||||
else if (!ExternalConnectionString.TryParse(connStr, out serviceConnection, out var error))
|
||||
{
|
||||
throw new ConfigException(string.Format(CultureInfo.InvariantCulture, errorMessage, setting, error));
|
||||
}
|
||||
this.Add(new ExternalService() { Type = type, ConnectionString = connectionString, CryptoCode = cryptoCode, DisplayName = displayName, ServiceName = serviceName });
|
||||
this.Add(new ExternalService() { Type = type, ConnectionString = serviceConnection, CryptoCode = cryptoCode, DisplayName = displayName, ServiceName = serviceName });
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,9 +83,11 @@ namespace BTCPayServer.Configuration
|
||||
{
|
||||
LNDRest,
|
||||
LNDGRPC,
|
||||
LNDSeedBackup,
|
||||
Spark,
|
||||
RTL,
|
||||
Charge,
|
||||
P2P
|
||||
P2P,
|
||||
RPC
|
||||
}
|
||||
}
|
||||
|
@ -74,21 +74,32 @@ namespace BTCPayServer.Controllers
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Login(string returnUrl = null)
|
||||
{
|
||||
|
||||
if (User.Identity.IsAuthenticated && string.IsNullOrEmpty(returnUrl))
|
||||
return RedirectToLocal();
|
||||
// Clear the existing external cookie to ensure a clean login process
|
||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
||||
|
||||
if (!CanLoginOrRegister())
|
||||
{
|
||||
SetInsecureFlags();
|
||||
}
|
||||
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
return View();
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
[RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)]
|
||||
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
|
||||
{
|
||||
if (!CanLoginOrRegister())
|
||||
{
|
||||
return RedirectToAction("Login");
|
||||
}
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
@ -199,6 +210,11 @@ namespace BTCPayServer.Controllers
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> LoginWithU2F(LoginWithU2FViewModel viewModel, string returnUrl = null)
|
||||
{
|
||||
if (!CanLoginOrRegister())
|
||||
{
|
||||
return RedirectToAction("Login");
|
||||
}
|
||||
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
var user = await _userManager.FindByIdAsync(viewModel.UserId);
|
||||
|
||||
@ -242,6 +258,11 @@ namespace BTCPayServer.Controllers
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> LoginWith2fa(bool rememberMe, string returnUrl = null)
|
||||
{
|
||||
if (!CanLoginOrRegister())
|
||||
{
|
||||
return RedirectToAction("Login");
|
||||
}
|
||||
|
||||
// Ensure the user has gone through the username & password screen first
|
||||
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
||||
|
||||
@ -264,6 +285,11 @@ namespace BTCPayServer.Controllers
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> LoginWith2fa(LoginWith2faViewModel model, bool rememberMe, string returnUrl = null)
|
||||
{
|
||||
if (!CanLoginOrRegister())
|
||||
{
|
||||
return RedirectToAction("Login");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
@ -305,6 +331,11 @@ namespace BTCPayServer.Controllers
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> LoginWithRecoveryCode(string returnUrl = null)
|
||||
{
|
||||
if (!CanLoginOrRegister())
|
||||
{
|
||||
return RedirectToAction("Login");
|
||||
}
|
||||
|
||||
// Ensure the user has gone through the username & password screen first
|
||||
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
||||
if (user == null)
|
||||
@ -322,6 +353,11 @@ namespace BTCPayServer.Controllers
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> LoginWithRecoveryCode(LoginWithRecoveryCodeViewModel model, string returnUrl = null)
|
||||
{
|
||||
if (!CanLoginOrRegister())
|
||||
{
|
||||
return RedirectToAction("Login");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
@ -364,14 +400,19 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Register(string returnUrl = null, bool logon = true)
|
||||
public async Task<IActionResult> Register(string returnUrl = null, bool logon = true, bool useBasicLayout = false)
|
||||
{
|
||||
if (!CanLoginOrRegister())
|
||||
{
|
||||
SetInsecureFlags();
|
||||
}
|
||||
var policies = await _SettingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
|
||||
if (policies.LockSubscription && !User.IsInRole(Roles.ServerAdmin))
|
||||
return RedirectToAction(nameof(HomeController.Index), "Home");
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
ViewData["Logon"] = logon.ToString(CultureInfo.InvariantCulture).ToLowerInvariant();
|
||||
ViewData["AllowIsAdmin"] = _Options.AllowAdminRegistration;
|
||||
ViewData["UseBasicLayout"] = useBasicLayout;
|
||||
return View();
|
||||
}
|
||||
|
||||
@ -380,6 +421,11 @@ namespace BTCPayServer.Controllers
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null, bool logon = true)
|
||||
{
|
||||
if (!CanLoginOrRegister())
|
||||
{
|
||||
return RedirectToAction("Register");
|
||||
}
|
||||
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
ViewData["Logon"] = logon.ToString(CultureInfo.InvariantCulture).ToLowerInvariant();
|
||||
ViewData["AllowIsAdmin"] = _Options.AllowAdminRegistration;
|
||||
@ -398,7 +444,9 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
await _RoleManager.CreateAsync(new IdentityRole(Roles.ServerAdmin));
|
||||
await _userManager.AddToRoleAsync(user, Roles.ServerAdmin);
|
||||
|
||||
var settings = await _SettingsRepository.GetSettingAsync<ThemeSettings>();
|
||||
settings.FirstRun = false;
|
||||
await _SettingsRepository.UpdateSetting<ThemeSettings>(settings);
|
||||
if(_Options.DisableRegistration)
|
||||
{
|
||||
// Once the admin user has been created lock subsequent user registrations (needs to be disabled for unit tests that require multiple users).
|
||||
@ -420,7 +468,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
TempData["StatusMessage"] = "Account created, please confirm your email";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Account created, please confirm your email";
|
||||
return View();
|
||||
}
|
||||
}
|
||||
@ -447,88 +495,6 @@ namespace BTCPayServer.Controllers
|
||||
return RedirectToAction(nameof(HomeController.Index), "Home");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public IActionResult ExternalLogin(string provider, string returnUrl = null)
|
||||
{
|
||||
// Request a redirect to the external login provider.
|
||||
var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Account", new
|
||||
{
|
||||
returnUrl
|
||||
});
|
||||
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
|
||||
return Challenge(properties, provider);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
|
||||
{
|
||||
if (remoteError != null)
|
||||
{
|
||||
ErrorMessage = $"Error from external provider: {remoteError}";
|
||||
return RedirectToAction(nameof(Login));
|
||||
}
|
||||
var info = await _signInManager.GetExternalLoginInfoAsync();
|
||||
if (info == null)
|
||||
{
|
||||
return RedirectToAction(nameof(Login));
|
||||
}
|
||||
|
||||
// Sign in the user with this external login provider if the user already has a login.
|
||||
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
_logger.LogInformation("User logged in with {Name} provider.", info.LoginProvider);
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
if (result.IsLockedOut)
|
||||
{
|
||||
return RedirectToAction(nameof(Lockout));
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the user does not have an account, then ask the user to create an account.
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
ViewData["LoginProvider"] = info.LoginProvider;
|
||||
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
|
||||
return View("ExternalLogin", new ExternalLoginViewModel { Email = email });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> ExternalLoginConfirmation(ExternalLoginViewModel model, string returnUrl = null)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
// Get the information about the user from the external login provider
|
||||
var info = await _signInManager.GetExternalLoginInfoAsync();
|
||||
if (info == null)
|
||||
{
|
||||
throw new ApplicationException("Error loading external login information during confirmation.");
|
||||
}
|
||||
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
|
||||
var result = await _userManager.CreateAsync(user);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
result = await _userManager.AddLoginAsync(user, info);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
}
|
||||
AddErrors(result);
|
||||
}
|
||||
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
return View(nameof(ExternalLogin), model);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> ConfirmEmail(string userId, string code)
|
||||
@ -659,6 +625,23 @@ namespace BTCPayServer.Controllers
|
||||
return RedirectToAction(nameof(HomeController.Index), "Home");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool CanLoginOrRegister()
|
||||
{
|
||||
return _btcPayServerEnvironment.IsDevelopping || _btcPayServerEnvironment.IsSecure;
|
||||
}
|
||||
|
||||
private void SetInsecureFlags()
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = "You cannot login over an insecure connection. Please use HTTPS or Tor."
|
||||
});
|
||||
|
||||
ViewData["disabled"] = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("{appId}/settings/crowdfund")]
|
||||
public async Task<IActionResult> UpdateCrowdfund(string appId, UpdateCrowdfundViewModel vm)
|
||||
public async Task<IActionResult> UpdateCrowdfund(string appId, UpdateCrowdfundViewModel vm, string command)
|
||||
{
|
||||
if (!string.IsNullOrEmpty( vm.TargetCurrency) && _currencies.GetCurrencyData(vm.TargetCurrency, false) == null)
|
||||
ModelState.AddModelError(nameof(vm.TargetCurrency), "Invalid currency");
|
||||
@ -156,16 +156,25 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
app.TagAllInvoices = vm.UseAllStoreInvoices;
|
||||
app.SetSettings(newSettings);
|
||||
await _AppService.UpdateOrCreateApp(app);
|
||||
|
||||
_EventAggregator.Publish(new AppUpdated()
|
||||
if (command == "save")
|
||||
{
|
||||
AppId = appId,
|
||||
StoreId = app.StoreDataId,
|
||||
Settings = newSettings
|
||||
});
|
||||
StatusMessage = "App updated";
|
||||
return RedirectToAction(nameof(UpdateCrowdfund), new {appId});
|
||||
await _AppService.UpdateOrCreateApp(app);
|
||||
|
||||
_EventAggregator.Publish(new AppUpdated()
|
||||
{
|
||||
AppId = appId,
|
||||
StoreId = app.StoreDataId,
|
||||
Settings = newSettings
|
||||
});
|
||||
TempData[WellKnownTempData.SuccessMessage] = "App updated";
|
||||
return RedirectToAction(nameof(UpdateCrowdfund), new { appId });
|
||||
}
|
||||
else if (command == "viewapp")
|
||||
{
|
||||
return RedirectToAction(nameof(AppsPublicController.ViewCrowdfund), "AppsPublic", new { appId });
|
||||
}
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -201,7 +201,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
});
|
||||
await _AppService.UpdateOrCreateApp(app);
|
||||
StatusMessage = "App updated";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "App updated";
|
||||
return RedirectToAction(nameof(UpdatePointOfSale), new { appId });
|
||||
}
|
||||
|
||||
|
@ -50,8 +50,6 @@ namespace BTCPayServer.Controllers
|
||||
private readonly EmailSenderFactory _emailSenderFactory;
|
||||
private AppService _AppService;
|
||||
|
||||
[TempData]
|
||||
public string StatusMessage { get; set; }
|
||||
public string CreatedAppId { get; set; }
|
||||
|
||||
public async Task<IActionResult> ListApps()
|
||||
@ -71,7 +69,7 @@ namespace BTCPayServer.Controllers
|
||||
if (appData == null)
|
||||
return NotFound();
|
||||
if (await _AppService.DeleteApp(appData))
|
||||
StatusMessage = "App removed successfully";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "App removed successfully";
|
||||
return RedirectToAction(nameof(ListApps));
|
||||
}
|
||||
|
||||
@ -82,12 +80,12 @@ namespace BTCPayServer.Controllers
|
||||
var stores = await _AppService.GetOwnedStores(GetUserId());
|
||||
if (stores.Length == 0)
|
||||
{
|
||||
StatusMessage = new StatusMessageModel()
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Html =
|
||||
$"Error: You need to create at least one store. <a href='{(Url.Action("CreateStore", "UserStores"))}'>Create store</a>",
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
}.ToString();
|
||||
});
|
||||
return RedirectToAction(nameof(ListApps));
|
||||
}
|
||||
var vm = new CreateAppViewModel();
|
||||
@ -102,12 +100,12 @@ namespace BTCPayServer.Controllers
|
||||
var stores = await _AppService.GetOwnedStores(GetUserId());
|
||||
if (stores.Length == 0)
|
||||
{
|
||||
StatusMessage = new StatusMessageModel()
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Html =
|
||||
$"Error: You need to create at least one store. <a href='{(Url.Action("CreateStore", "UserStores"))}'>Create store</a>",
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
}.ToString();
|
||||
});
|
||||
return RedirectToAction(nameof(ListApps));
|
||||
}
|
||||
var selectedStore = vm.SelectedStore;
|
||||
@ -124,7 +122,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
if (!stores.Any(s => s.Id == selectedStore))
|
||||
{
|
||||
StatusMessage = "Error: You are not owner of this store";
|
||||
TempData[WellKnownTempData.ErrorMessage] = "You are not owner of this store";
|
||||
return RedirectToAction(nameof(ListApps));
|
||||
}
|
||||
var appData = new AppData
|
||||
@ -134,7 +132,7 @@ namespace BTCPayServer.Controllers
|
||||
AppType = appType.ToString()
|
||||
};
|
||||
await _AppService.UpdateOrCreateApp(appData);
|
||||
StatusMessage = "App successfully created";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "App successfully created";
|
||||
CreatedAppId = appData.Id;
|
||||
|
||||
switch (appType)
|
||||
|
@ -165,26 +165,39 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
}
|
||||
var store = await _AppService.GetStore(app);
|
||||
var invoice = await _InvoiceController.CreateInvoiceCore(new CreateInvoiceRequest()
|
||||
try
|
||||
{
|
||||
ItemCode = choice?.Id,
|
||||
ItemDesc = title,
|
||||
Currency = settings.Currency,
|
||||
Price = price,
|
||||
BuyerEmail = email,
|
||||
OrderId = orderId,
|
||||
NotificationURL =
|
||||
string.IsNullOrEmpty(notificationUrl) ? settings.NotificationUrl : notificationUrl,
|
||||
NotificationEmail = settings.NotificationEmail,
|
||||
RedirectURL = redirectUrl ?? Request.GetDisplayUrl(),
|
||||
FullNotifications = true,
|
||||
ExtendedNotifications = true,
|
||||
PosData = string.IsNullOrEmpty(posData) ? null : posData,
|
||||
RedirectAutomatically = settings.RedirectAutomatically,
|
||||
}, store, HttpContext.Request.GetAbsoluteRoot(),
|
||||
new List<string>() { AppService.GetAppInternalTag(appId) },
|
||||
cancellationToken);
|
||||
return RedirectToAction(nameof(InvoiceController.Checkout), "Invoice", new { invoiceId = invoice.Data.Id });
|
||||
var invoice = await _InvoiceController.CreateInvoiceCore(new CreateInvoiceRequest()
|
||||
{
|
||||
ItemCode = choice?.Id,
|
||||
ItemDesc = title,
|
||||
Currency = settings.Currency,
|
||||
Price = price,
|
||||
BuyerEmail = email,
|
||||
OrderId = orderId,
|
||||
NotificationURL =
|
||||
string.IsNullOrEmpty(notificationUrl) ? settings.NotificationUrl : notificationUrl,
|
||||
NotificationEmail = settings.NotificationEmail,
|
||||
RedirectURL = redirectUrl ?? Request.GetDisplayUrl(),
|
||||
FullNotifications = true,
|
||||
ExtendedNotifications = true,
|
||||
PosData = string.IsNullOrEmpty(posData) ? null : posData,
|
||||
RedirectAutomatically = settings.RedirectAutomatically,
|
||||
}, store, HttpContext.Request.GetAbsoluteRoot(),
|
||||
new List<string>() { AppService.GetAppInternalTag(appId) },
|
||||
cancellationToken);
|
||||
return RedirectToAction(nameof(InvoiceController.Checkout), "Invoice", new { invoiceId = invoice.Data.Id });
|
||||
}
|
||||
catch (BitpayHttpException e)
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Html = e.Message.Replace("\n", "<br />", StringComparison.OrdinalIgnoreCase),
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
AllowDismiss = true
|
||||
});
|
||||
return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
|
@ -12,6 +12,8 @@ using Newtonsoft.Json;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using BTCPayServer.Data;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -20,11 +22,15 @@ namespace BTCPayServer.Controllers
|
||||
private readonly CssThemeManager _cachedServerSettings;
|
||||
|
||||
public IHttpClientFactory HttpClientFactory { get; }
|
||||
SignInManager<ApplicationUser> SignInManager { get; }
|
||||
|
||||
public HomeController(IHttpClientFactory httpClientFactory, CssThemeManager cachedServerSettings)
|
||||
public HomeController(IHttpClientFactory httpClientFactory,
|
||||
CssThemeManager cachedServerSettings,
|
||||
SignInManager<ApplicationUser> signInManager)
|
||||
{
|
||||
HttpClientFactory = httpClientFactory;
|
||||
_cachedServerSettings = cachedServerSettings;
|
||||
SignInManager = signInManager;
|
||||
}
|
||||
|
||||
private async Task<ViewResult> GoToApp(string appId, AppType? appType)
|
||||
@ -71,14 +77,26 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
if (_cachedServerSettings.FirstRun)
|
||||
{
|
||||
return RedirectToAction(nameof(AccountController.Register), "Account");
|
||||
}
|
||||
var matchedDomainMapping = _cachedServerSettings.DomainToAppMapping.FirstOrDefault(item =>
|
||||
item.Domain.Equals(Request.Host.Host, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (matchedDomainMapping != null)
|
||||
{
|
||||
return await GoToApp(matchedDomainMapping.AppId, matchedDomainMapping.AppType) ?? View("Home");
|
||||
return await GoToApp(matchedDomainMapping.AppId, matchedDomainMapping.AppType) ?? GoToHome();
|
||||
}
|
||||
|
||||
return await GoToApp(_cachedServerSettings.RootAppId, _cachedServerSettings.RootAppType) ?? View("Home");
|
||||
return await GoToApp(_cachedServerSettings.RootAppId, _cachedServerSettings.RootAppType) ?? GoToHome();
|
||||
}
|
||||
|
||||
private IActionResult GoToHome()
|
||||
{
|
||||
if (SignInManager.IsSignedIn(User))
|
||||
return View("Home");
|
||||
else
|
||||
return RedirectToAction(nameof(AccountController.Login), "Account");
|
||||
}
|
||||
|
||||
[Route("translate")]
|
||||
@ -103,7 +121,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
BitcoinUrlBuilder urlBuilder = new BitcoinUrlBuilder(vm.BitpayLink);
|
||||
BitcoinUrlBuilder urlBuilder = new BitcoinUrlBuilder(vm.BitpayLink, Network.Main);
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (!urlBuilder.PaymentRequestUrl.DnsSafeHost.EndsWith("bitpay.com", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
@ -71,9 +71,7 @@ namespace BTCPayServer.Controllers
|
||||
ProductInformation = invoice.ProductInformation,
|
||||
StatusException = invoice.ExceptionStatus,
|
||||
Events = invoice.Events,
|
||||
PosData = PosDataParser.ParsePosData(invoice.PosData),
|
||||
StatusMessage = StatusMessage,
|
||||
|
||||
PosData = PosDataParser.ParsePosData(invoice.PosData)
|
||||
};
|
||||
|
||||
model.Addresses = invoice.HistoricalAddresses.Select(h =>
|
||||
@ -234,8 +232,8 @@ namespace BTCPayServer.Controllers
|
||||
InvoiceId = invoice.Id,
|
||||
DefaultLang = storeBlob.DefaultLang ?? "en",
|
||||
HtmlTitle = storeBlob.HtmlTitle ?? "BTCPay Invoice",
|
||||
CustomCSSLink = storeBlob.CustomCSS?.AbsoluteUri,
|
||||
CustomLogoLink = storeBlob.CustomLogo?.AbsoluteUri,
|
||||
CustomCSSLink = storeBlob.CustomCSS,
|
||||
CustomLogoLink = storeBlob.CustomLogo,
|
||||
CryptoImage = Request.GetRelativePathOrAbsolute(paymentMethodHandler.GetCryptoImage(paymentMethodId)),
|
||||
BtcAddress = paymentMethodDetails.GetPaymentDestination(),
|
||||
BtcDue = accounting.Due.ToString(),
|
||||
@ -400,12 +398,15 @@ namespace BTCPayServer.Controllers
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> ListInvoices(string searchTerm = null, int skip = 0, int count = 50, int timezoneOffset = 0)
|
||||
{
|
||||
var fs = new SearchString(searchTerm);
|
||||
var storeIds = fs.GetFilterArray("storeid") != null ? fs.GetFilterArray("storeid") : new List<string>().ToArray();
|
||||
|
||||
var model = new InvoicesModel
|
||||
{
|
||||
SearchTerm = searchTerm,
|
||||
Skip = skip,
|
||||
Count = count,
|
||||
StatusMessage = StatusMessage,
|
||||
StoreIds = storeIds,
|
||||
TimezoneOffset = timezoneOffset
|
||||
};
|
||||
InvoiceQuery invoiceQuery = GetInvoiceQuery(searchTerm, timezoneOffset);
|
||||
@ -497,7 +498,7 @@ namespace BTCPayServer.Controllers
|
||||
var stores = new SelectList(await _StoreRepository.GetStoresByUserId(GetUserId()), nameof(StoreData.Id), nameof(StoreData.StoreName), null);
|
||||
if (stores.Count() == 0)
|
||||
{
|
||||
StatusMessage = "Error: You need to create at least one store before creating a transaction";
|
||||
TempData[WellKnownTempData.ErrorMessage] = "You need to create at least one store before creating a transaction";
|
||||
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
|
||||
}
|
||||
|
||||
@ -518,22 +519,13 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
StatusMessage = null;
|
||||
|
||||
if (store.GetSupportedPaymentMethods(_NetworkProvider).Count() == 0)
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.StoreId), "You need to configure the derivation scheme in order to create an invoice");
|
||||
return View(model);
|
||||
}
|
||||
|
||||
|
||||
if (StatusMessage != null)
|
||||
{
|
||||
return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new
|
||||
{
|
||||
storeId = store.Id
|
||||
});
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await CreateInvoiceCore(new CreateInvoiceRequest()
|
||||
@ -554,7 +546,7 @@ namespace BTCPayServer.Controllers
|
||||
})
|
||||
}, store, HttpContext.Request.GetAbsoluteRoot(), cancellationToken: cancellationToken);
|
||||
|
||||
StatusMessage = $"Invoice {result.Data.Id} just created!";
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Invoice {result.Data.Id} just created!";
|
||||
return RedirectToAction(nameof(ListInvoices));
|
||||
}
|
||||
catch (BitpayHttpException ex)
|
||||
@ -603,13 +595,6 @@ namespace BTCPayServer.Controllers
|
||||
public string StatusString { get; set; }
|
||||
}
|
||||
|
||||
[TempData]
|
||||
public string StatusMessage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private string GetUserId()
|
||||
{
|
||||
return _UserManager.GetUserId(User);
|
||||
|
@ -9,11 +9,10 @@ namespace BTCPayServer.Controllers
|
||||
public partial class ManageController
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> U2FAuthentication(string statusMessage = null)
|
||||
public async Task<IActionResult> U2FAuthentication()
|
||||
{
|
||||
return View(new U2FAuthenticationViewModel()
|
||||
{
|
||||
StatusMessage = statusMessage,
|
||||
Devices = await _u2FService.GetDevices(_userManager.GetUserId(User))
|
||||
});
|
||||
}
|
||||
@ -33,14 +32,12 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (!_btcPayServerEnvironment.IsSecure)
|
||||
{
|
||||
return RedirectToAction("U2FAuthentication", new
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
StatusMessage = new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = "Cannot register U2F device while not on https or tor"
|
||||
}
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = "Cannot register U2F device while not on https or tor"
|
||||
});
|
||||
return RedirectToAction("U2FAuthentication");
|
||||
}
|
||||
|
||||
var serverRegisterResponse = _u2FService.StartDeviceRegistration(_userManager.GetUserId(User),
|
||||
@ -64,11 +61,8 @@ namespace BTCPayServer.Controllers
|
||||
if (await _u2FService.CompleteRegistration(_userManager.GetUserId(User), viewModel.DeviceResponse,
|
||||
string.IsNullOrEmpty(viewModel.Name) ? "Unlabelled U2F Device" : viewModel.Name))
|
||||
{
|
||||
return RedirectToAction("U2FAuthentication", new
|
||||
|
||||
{
|
||||
StatusMessage = "Device added!"
|
||||
});
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Device added!";
|
||||
return RedirectToAction("U2FAuthentication");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@ -76,14 +70,12 @@ namespace BTCPayServer.Controllers
|
||||
errorMessage = e.Message;
|
||||
}
|
||||
|
||||
return RedirectToAction("U2FAuthentication", new
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
StatusMessage = new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = string.IsNullOrEmpty(errorMessage) ? "Could not add device." : errorMessage
|
||||
}
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = string.IsNullOrEmpty(errorMessage) ? "Could not add device." : errorMessage
|
||||
});
|
||||
return RedirectToAction("U2FAuthentication");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,12 +64,6 @@ namespace BTCPayServer.Controllers
|
||||
_StoreRepository = storeRepository;
|
||||
}
|
||||
|
||||
[TempData]
|
||||
public string StatusMessage
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
@ -84,8 +78,7 @@ namespace BTCPayServer.Controllers
|
||||
Username = user.UserName,
|
||||
Email = user.Email,
|
||||
PhoneNumber = user.PhoneNumber,
|
||||
IsEmailConfirmed = user.EmailConfirmed,
|
||||
StatusMessage = StatusMessage
|
||||
IsEmailConfirmed = user.EmailConfirmed
|
||||
};
|
||||
return View(model);
|
||||
}
|
||||
@ -137,7 +130,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
StatusMessage = "Your profile has been updated";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Your profile has been updated";
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
@ -160,7 +153,7 @@ namespace BTCPayServer.Controllers
|
||||
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
|
||||
var email = user.Email;
|
||||
_EmailSenderFactory.GetEmailSender().SendEmailConfirmation(email, callbackUrl);
|
||||
StatusMessage = "Verification email sent. Please check your email.";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Verification email sent. Please check your email.";
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
@ -179,7 +172,7 @@ namespace BTCPayServer.Controllers
|
||||
return RedirectToAction(nameof(SetPassword));
|
||||
}
|
||||
|
||||
var model = new ChangePasswordViewModel { StatusMessage = StatusMessage };
|
||||
var model = new ChangePasswordViewModel();
|
||||
return View(model);
|
||||
}
|
||||
|
||||
@ -207,7 +200,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
_logger.LogInformation("User changed their password successfully.");
|
||||
StatusMessage = "Your password has been changed.";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Your password has been changed.";
|
||||
|
||||
return RedirectToAction(nameof(ChangePassword));
|
||||
}
|
||||
@ -228,7 +221,7 @@ namespace BTCPayServer.Controllers
|
||||
return RedirectToAction(nameof(ChangePassword));
|
||||
}
|
||||
|
||||
var model = new SetPasswordViewModel { StatusMessage = StatusMessage };
|
||||
var model = new SetPasswordViewModel();
|
||||
return View(model);
|
||||
}
|
||||
|
||||
@ -255,92 +248,11 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
StatusMessage = "Your password has been set.";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Your password has been set.";
|
||||
|
||||
return RedirectToAction(nameof(SetPassword));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> ExternalLogins()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var model = new ExternalLoginsViewModel { CurrentLogins = await _userManager.GetLoginsAsync(user) };
|
||||
model.OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
|
||||
.Where(auth => model.CurrentLogins.All(ul => auth.Name != ul.LoginProvider))
|
||||
.ToList();
|
||||
model.ShowRemoveButton = await _userManager.HasPasswordAsync(user) || model.CurrentLogins.Count > 1;
|
||||
model.StatusMessage = StatusMessage;
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> LinkLogin(string provider)
|
||||
{
|
||||
// Clear the existing external cookie to ensure a clean login process
|
||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
||||
|
||||
// Request a redirect to the external login provider to link a login for the current user
|
||||
var redirectUrl = Url.Action(nameof(LinkLoginCallback));
|
||||
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
|
||||
return new ChallengeResult(provider, properties);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> LinkLoginCallback()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var info = await _signInManager.GetExternalLoginInfoAsync(user.Id);
|
||||
if (info == null)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occurred loading external login info for user with ID '{user.Id}'.");
|
||||
}
|
||||
|
||||
var result = await _userManager.AddLoginAsync(user, info);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occurred adding external login for user with ID '{user.Id}'.");
|
||||
}
|
||||
|
||||
// Clear the existing external cookie to ensure a clean login process
|
||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
||||
|
||||
StatusMessage = "The external login was added.";
|
||||
return RedirectToAction(nameof(ExternalLogins));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> RemoveLogin(RemoveLoginViewModel model)
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var result = await _userManager.RemoveLoginAsync(user, model.LoginProvider, model.ProviderKey);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occurred removing external login for user with ID '{user.Id}'.");
|
||||
}
|
||||
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
StatusMessage = "The external login was removed.";
|
||||
return RedirectToAction(nameof(ExternalLogins));
|
||||
}
|
||||
|
||||
|
||||
#region Helpers
|
||||
|
||||
|
@ -66,7 +66,7 @@ namespace BTCPayServer.Controllers
|
||||
[HttpGet]
|
||||
[Route("")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> GetPaymentRequests(int skip = 0, int count = 50, string statusMessage = null)
|
||||
public async Task<IActionResult> GetPaymentRequests(int skip = 0, int count = 50)
|
||||
{
|
||||
var result = await _PaymentRequestRepository.FindPaymentRequests(new PaymentRequestQuery()
|
||||
{
|
||||
@ -75,7 +75,6 @@ namespace BTCPayServer.Controllers
|
||||
return View(new ListPaymentRequestsViewModel()
|
||||
{
|
||||
Skip = skip,
|
||||
StatusMessage = statusMessage,
|
||||
Count = count,
|
||||
Total = result.Total,
|
||||
Items = result.Items.Select(data => new ViewPaymentRequestViewModel(data)).ToList()
|
||||
@ -84,7 +83,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpGet]
|
||||
[Route("edit/{id?}")]
|
||||
public async Task<IActionResult> EditPaymentRequest(string id, string statusMessage = null)
|
||||
public async Task<IActionResult> EditPaymentRequest(string id)
|
||||
{
|
||||
SelectList stores = null;
|
||||
var data = await _PaymentRequestRepository.FindPaymentRequest(id, GetUserId());
|
||||
@ -97,22 +96,17 @@ namespace BTCPayServer.Controllers
|
||||
nameof(StoreData.StoreName), data?.StoreDataId);
|
||||
if (!stores.Any())
|
||||
{
|
||||
return RedirectToAction("GetPaymentRequests",
|
||||
new
|
||||
{
|
||||
StatusMessage = new StatusMessageModel()
|
||||
{
|
||||
Html =
|
||||
$"Error: You need to create at least one store. <a href='{Url.Action("CreateStore", "UserStores")}'>Create store</a>",
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
}
|
||||
});
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Html = $"Error: You need to create at least one store. <a href='{Url.Action("CreateStore", "UserStores")}'>Create store</a>",
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
});
|
||||
return RedirectToAction("GetPaymentRequests");
|
||||
}
|
||||
|
||||
return View(new UpdatePaymentRequestViewModel(data)
|
||||
{
|
||||
Stores = stores,
|
||||
StatusMessage = statusMessage
|
||||
Stores = stores
|
||||
});
|
||||
}
|
||||
|
||||
@ -169,7 +163,8 @@ namespace BTCPayServer.Controllers
|
||||
PaymentRequestId = data.Id,
|
||||
});
|
||||
|
||||
return RedirectToAction("EditPaymentRequest", new {id = data.Id, StatusMessage = "Saved"});
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Saved";
|
||||
return RedirectToAction("EditPaymentRequest", new {id = data.Id});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@ -200,24 +195,20 @@ namespace BTCPayServer.Controllers
|
||||
var result = await _PaymentRequestRepository.RemovePaymentRequest(id, GetUserId());
|
||||
if (result)
|
||||
{
|
||||
return RedirectToAction("GetPaymentRequests",
|
||||
new {StatusMessage = "Payment request successfully removed"});
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Payment request successfully removed";
|
||||
return RedirectToAction("GetPaymentRequests");
|
||||
}
|
||||
else
|
||||
{
|
||||
return RedirectToAction("GetPaymentRequests",
|
||||
new
|
||||
{
|
||||
StatusMessage =
|
||||
"Error: Payment request could not be removed. Any request that has generated invoices cannot be removed."
|
||||
});
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Payment request could not be removed. Any request that has generated invoices cannot be removed.";
|
||||
return RedirectToAction("GetPaymentRequests");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{id}")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> ViewPaymentRequest(string id, string statusMessage = null)
|
||||
public async Task<IActionResult> ViewPaymentRequest(string id)
|
||||
{
|
||||
var result = await _PaymentRequestService.GetPaymentRequest(id, GetUserId());
|
||||
if (result == null)
|
||||
@ -225,8 +216,6 @@ namespace BTCPayServer.Controllers
|
||||
return NotFound();
|
||||
}
|
||||
result.HubPath = PaymentRequestHub.GetHubPath(this.Request);
|
||||
result.StatusMessage = statusMessage;
|
||||
|
||||
return View(result);
|
||||
}
|
||||
|
||||
@ -342,10 +331,10 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
if (redirect)
|
||||
{
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Payment cancelled";
|
||||
return RedirectToAction(nameof(ViewPaymentRequest), new
|
||||
{
|
||||
Id = id,
|
||||
StatusMessage = "Payment cancelled"
|
||||
Id = id
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -85,9 +85,9 @@ namespace BTCPayServer.Controllers
|
||||
[Route("rates")]
|
||||
[HttpGet]
|
||||
[BitpayAPIConstraint]
|
||||
public async Task<IActionResult> GetRates(string currencyPairs, CancellationToken cancellationToken)
|
||||
public async Task<IActionResult> GetRates(string currencyPairs, string storeId = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = await GetRates2(currencyPairs, null, cancellationToken);
|
||||
var result = await GetRates2(currencyPairs, storeId, cancellationToken);
|
||||
var rates = (result as JsonResult)?.Value as Rate[];
|
||||
if (rates == null)
|
||||
return result;
|
||||
|
@ -26,9 +26,8 @@ namespace BTCPayServer.Controllers
|
||||
public partial class ServerController
|
||||
{
|
||||
[HttpGet("server/files/{fileId?}")]
|
||||
public async Task<IActionResult> Files(string fileId = null, string statusMessage = null)
|
||||
public async Task<IActionResult> Files(string fileId = null)
|
||||
{
|
||||
TempData["StatusMessage"] = statusMessage;
|
||||
var fileUrl = string.IsNullOrEmpty(fileId) ? null : await _FileService.GetFileUrl(Request.GetAbsoluteRootUri(), fileId);
|
||||
|
||||
return View(new ViewFilesViewModel()
|
||||
@ -54,14 +53,12 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return RedirectToAction(nameof(Files), new
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
statusMessage = new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = e.Message
|
||||
}
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = e.Message
|
||||
});
|
||||
return RedirectToAction(nameof(Files));
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,16 +116,14 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
var url = await _FileService.GetTemporaryFileUrl(Request.GetAbsoluteRootUri(), fileId, expiry, viewModel.IsDownload);
|
||||
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
Html = $"Generated Temporary Url for file {file.FileName} which expires at {expiry.ToBrowserDate()}. <a href='{url}' target='_blank'>{url}</a>"
|
||||
});
|
||||
return RedirectToAction(nameof(Files), new
|
||||
{
|
||||
StatusMessage = new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
Html =
|
||||
$"Generated Temporary Url for file {file.FileName} which expires at {expiry.ToBrowserDate()}. <a href='{url}' target='_blank'>{url}</a>"
|
||||
}.ToString(),
|
||||
fileId,
|
||||
fileId
|
||||
});
|
||||
|
||||
}
|
||||
@ -165,9 +160,8 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[HttpGet("server/storage")]
|
||||
public async Task<IActionResult> Storage(bool forceChoice = false, string statusMessage = null)
|
||||
public async Task<IActionResult> Storage(bool forceChoice = false)
|
||||
{
|
||||
TempData["StatusMessage"] = statusMessage;
|
||||
var savedSettings = await _SettingsRepository.GetSettingAsync<StorageSettings>();
|
||||
if (forceChoice || savedSettings == null)
|
||||
{
|
||||
@ -198,14 +192,12 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (!Enum.TryParse(typeof(StorageProvider), provider, out var storageProvider))
|
||||
{
|
||||
return RedirectToAction(nameof(Storage), new
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
StatusMessage = new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = $"{provider} provider is not supported"
|
||||
}.ToString()
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = $"{provider} provider is not supported"
|
||||
});
|
||||
return RedirectToAction(nameof(Storage));
|
||||
}
|
||||
|
||||
var data = (await _SettingsRepository.GetSettingAsync<StorageSettings>()) ?? new StorageSettings();
|
||||
@ -216,14 +208,12 @@ namespace BTCPayServer.Controllers
|
||||
switch (storageProviderService)
|
||||
{
|
||||
case null:
|
||||
return RedirectToAction(nameof(Storage), new
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
StatusMessage = new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = $"{storageProvider} is not supported"
|
||||
}.ToString()
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = $"{storageProvider} is not supported"
|
||||
});
|
||||
return RedirectToAction(nameof(Storage));
|
||||
case AzureBlobStorageFileProviderService fileProviderService:
|
||||
return View(nameof(EditAzureBlobStorageStorageProvider),
|
||||
fileProviderService.GetProviderConfiguration(data));
|
||||
@ -288,11 +278,11 @@ namespace BTCPayServer.Controllers
|
||||
data.Provider = storageProvider;
|
||||
data.Configuration = JObject.FromObject(viewModel);
|
||||
await _SettingsRepository.UpdateSetting(data);
|
||||
TempData["StatusMessage"] = new StatusMessageModel()
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
Message = "Storage settings updated successfully"
|
||||
}.ToString();
|
||||
});
|
||||
return View(viewModel);
|
||||
}
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ namespace BTCPayServer.Controllers
|
||||
return View(vm);
|
||||
}
|
||||
await _SettingsRepository.UpdateSetting(rates);
|
||||
StatusMessage = "Rate settings successfully updated";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Rate settings successfully updated";
|
||||
return RedirectToAction(nameof(Rates));
|
||||
}
|
||||
|
||||
@ -158,7 +158,6 @@ namespace BTCPayServer.Controllers
|
||||
public IActionResult ListUsers(int skip = 0, int count = 50)
|
||||
{
|
||||
var users = new UsersViewModel();
|
||||
users.StatusMessage = StatusMessage;
|
||||
users.Users = _UserManager.Users.Skip(skip).Take(count)
|
||||
.Select(u => new UsersViewModel.UserViewModel
|
||||
{
|
||||
@ -191,6 +190,8 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
MaintenanceViewModel vm = new MaintenanceViewModel();
|
||||
vm.CanUseSSH = _sshState.CanUseSSH;
|
||||
if (!vm.CanUseSSH)
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Maintenance feature requires access to SSH properly configured in BTCPayServer configuration";
|
||||
vm.DNSDomain = this.Request.Host.Host;
|
||||
if (IPAddress.TryParse(vm.DNSDomain, out var unused))
|
||||
vm.DNSDomain = null;
|
||||
@ -263,21 +264,21 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
builder.Path = null;
|
||||
builder.Query = null;
|
||||
StatusMessage = $"Domain name changing... the server will restart, please use \"{builder.Uri.AbsoluteUri}\"";
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Domain name changing... the server will restart, please use \"{builder.Uri.AbsoluteUri}\" (this page won't reload automatically)";
|
||||
}
|
||||
else if (command == "update")
|
||||
{
|
||||
var error = await RunSSH(vm, $"btcpay-update.sh");
|
||||
if (error != null)
|
||||
return error;
|
||||
StatusMessage = $"The server might restart soon if an update is available...";
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"The server might restart soon if an update is available... (this page won't reload automatically)";
|
||||
}
|
||||
else if (command == "clean")
|
||||
{
|
||||
var error = await RunSSH(vm, $"btcpay-clean.sh");
|
||||
if (error != null)
|
||||
return error;
|
||||
StatusMessage = $"The old docker images will be cleaned soon...";
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"The old docker images will be cleaned soon...";
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -357,12 +358,10 @@ namespace BTCPayServer.Controllers
|
||||
if (user == null)
|
||||
return NotFound();
|
||||
|
||||
viewModel.StatusMessage = "";
|
||||
|
||||
var admins = await _UserManager.GetUsersInRoleAsync(Roles.ServerAdmin);
|
||||
if (!viewModel.IsAdmin && admins.Count == 1)
|
||||
{
|
||||
viewModel.StatusMessage = "This is the only Admin, so their role can't be removed until another Admin is added.";
|
||||
TempData[WellKnownTempData.ErrorMessage] = "This is the only Admin, so their role can't be removed until another Admin is added.";
|
||||
return View(viewModel); // return
|
||||
}
|
||||
|
||||
@ -374,10 +373,10 @@ namespace BTCPayServer.Controllers
|
||||
else
|
||||
await _UserManager.RemoveFromRoleAsync(user, Roles.ServerAdmin);
|
||||
|
||||
viewModel.StatusMessage = "User successfully updated";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "User successfully updated";
|
||||
}
|
||||
|
||||
return View(viewModel);
|
||||
return RedirectToAction(nameof(User), new { userId = userId });
|
||||
}
|
||||
|
||||
|
||||
@ -420,15 +419,9 @@ namespace BTCPayServer.Controllers
|
||||
return NotFound();
|
||||
await _UserManager.DeleteAsync(user);
|
||||
await _StoreRepository.CleanUnreachableStores();
|
||||
StatusMessage = "User deleted";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "User deleted";
|
||||
return RedirectToAction(nameof(ListUsers));
|
||||
}
|
||||
|
||||
[TempData]
|
||||
public string StatusMessage
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public IHttpClientFactory HttpClientFactory { get; }
|
||||
|
||||
[Route("server/policies")]
|
||||
@ -489,7 +482,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
await _SettingsRepository.UpdateSetting(settings);
|
||||
TempData["StatusMessage"] = "Policies updated successfully";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Policies updated successfully";
|
||||
return RedirectToAction(nameof(Policies));
|
||||
}
|
||||
|
||||
@ -498,6 +491,8 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var result = new ServicesViewModel();
|
||||
result.ExternalServices = _Options.ExternalServices.ToList();
|
||||
|
||||
// other services
|
||||
foreach (var externalService in _Options.OtherExternalServices)
|
||||
{
|
||||
result.OtherExternalServices.Add(new ServicesViewModel.OtherExternalService()
|
||||
@ -543,6 +538,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
// external storage services
|
||||
var storageSettings = await _SettingsRepository.GetSettingAsync<StorageSettings>();
|
||||
result.ExternalStorageServices.Add(new ServicesViewModel.OtherExternalService()
|
||||
{
|
||||
@ -574,6 +570,17 @@ namespace BTCPayServer.Controllers
|
||||
ServiceName = torService.Name,
|
||||
};
|
||||
}
|
||||
if (torService.ServiceType == TorServiceType.RPC)
|
||||
{
|
||||
externalService = new ExternalService()
|
||||
{
|
||||
CryptoCode = torService.Network.CryptoCode,
|
||||
DisplayName = "Full node RPC",
|
||||
Type = ExternalServiceTypes.RPC,
|
||||
ConnectionString = new ExternalConnectionString(new Uri($"btcrpc://btcrpc:btcpayserver4ever@{torService.OnionHost}:{torService.VirtualPort}?label=BTCPayNode", UriKind.Absolute)),
|
||||
ServiceName = torService.Name
|
||||
};
|
||||
}
|
||||
return externalService != null;
|
||||
}
|
||||
|
||||
@ -582,16 +589,21 @@ namespace BTCPayServer.Controllers
|
||||
var result = _Options.ExternalServices.GetService(serviceName, cryptoCode);
|
||||
if (result != null)
|
||||
return result;
|
||||
_torServices.Services.FirstOrDefault(s => TryParseAsExternalService(s, out result));
|
||||
return result;
|
||||
foreach (var torService in _torServices.Services)
|
||||
{
|
||||
if (TryParseAsExternalService(torService, out var torExternalService) &&
|
||||
torExternalService.ServiceName == serviceName)
|
||||
return torExternalService;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
[Route("server/services/{serviceName}/{cryptoCode}")]
|
||||
public async Task<IActionResult> Service(string serviceName, string cryptoCode, bool showQR = false, uint? nonce = null)
|
||||
{
|
||||
if (!_dashBoard.IsFullySynched(cryptoCode, out var unusud))
|
||||
if (!_dashBoard.IsFullySynched(cryptoCode, out _))
|
||||
{
|
||||
StatusMessage = $"Error: {cryptoCode} is not fully synched";
|
||||
TempData[WellKnownTempData.ErrorMessage] = $"{cryptoCode} is not fully synched";
|
||||
return RedirectToAction(nameof(Services));
|
||||
}
|
||||
var service = GetService(serviceName, cryptoCode);
|
||||
@ -609,6 +621,30 @@ namespace BTCPayServer.Controllers
|
||||
ServiceLink = service.ConnectionString.Server.AbsoluteUri.WithoutEndingSlash()
|
||||
});
|
||||
}
|
||||
if (service.Type == ExternalServiceTypes.LNDSeedBackup)
|
||||
{
|
||||
var model = LndSeedBackupViewModel.Parse(service.ConnectionString.CookieFilePath);
|
||||
if (!model.IsWalletUnlockPresent)
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Warning,
|
||||
Html = "Your LND does not seem to allow seed backup.<br />" +
|
||||
"It's recommended, but not required, that you migrate as instructed by <a href=\"https://blog.btcpayserver.org/btcpay-lnd-migration\">our migration blog post</a>.<br />" +
|
||||
"You will need to close all of your channels, and migrate your funds as <a href=\"https://blog.btcpayserver.org/btcpay-lnd-migration\">we documented</a>."
|
||||
});
|
||||
}
|
||||
return View("LndSeedBackup", model);
|
||||
}
|
||||
if (service.Type == ExternalServiceTypes.RPC)
|
||||
{
|
||||
return View("RPCService", new LightningWalletServices()
|
||||
{
|
||||
ShowQR = showQR,
|
||||
WalletName = service.ServiceName,
|
||||
ServiceLink = service.ConnectionString.Server.AbsoluteUri.WithoutEndingSlash()
|
||||
});
|
||||
}
|
||||
var connectionString = await service.ConnectionString.Expand(this.Request.GetAbsoluteUriNoPathBase(), service.Type, _Options.NetworkType);
|
||||
switch (service.Type)
|
||||
{
|
||||
@ -618,7 +654,7 @@ namespace BTCPayServer.Controllers
|
||||
case ExternalServiceTypes.Spark:
|
||||
if (connectionString.AccessKey == null)
|
||||
{
|
||||
StatusMessage = $"Error: The access key of the service is not set";
|
||||
TempData[WellKnownTempData.ErrorMessage] = $"The access key of the service is not set";
|
||||
return RedirectToAction(nameof(Services));
|
||||
}
|
||||
LightningWalletServices vm = new LightningWalletServices();
|
||||
@ -635,7 +671,52 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = $"Error: {ex.Message}";
|
||||
TempData[WellKnownTempData.ErrorMessage] = ex.Message;
|
||||
return RedirectToAction(nameof(Services));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("server/services/{serviceName}/{cryptoCode}/removelndseed")]
|
||||
public IActionResult RemoveLndSeed(string serviceName, string cryptoCode)
|
||||
{
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Title = "Delete LND Seed",
|
||||
Description = "Please make sure you made a backup of the seed and password before deleting the LND backup seed from the server, are you sure to continue?",
|
||||
Action = "Delete"
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("server/services/{serviceName}/{cryptoCode}/removelndseed")]
|
||||
public async Task<IActionResult> RemoveLndSeedPost(string serviceName, string cryptoCode)
|
||||
{
|
||||
var service = GetService(serviceName, cryptoCode);
|
||||
if (service == null)
|
||||
return NotFound();
|
||||
|
||||
var model = LndSeedBackupViewModel.Parse(service.ConnectionString.CookieFilePath);
|
||||
if (!model.IsWalletUnlockPresent)
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = $"File with wallet password and seed info not present";
|
||||
return RedirectToAction(nameof(Services));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(model.Seed))
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = $"Seed information was already removed";
|
||||
return RedirectToAction(nameof(Services));
|
||||
}
|
||||
|
||||
if (await model.RemoveSeedAndWrite(service.ConnectionString.CookieFilePath))
|
||||
{
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Seed successfully removed";
|
||||
return RedirectToAction(nameof(Service), new { serviceName, cryptoCode });
|
||||
}
|
||||
else
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = $"Seed removal failed";
|
||||
return RedirectToAction(nameof(Services));
|
||||
}
|
||||
}
|
||||
@ -654,7 +735,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
private IActionResult LndServices(ExternalService service, ExternalConnectionString connectionString, uint? nonce)
|
||||
{
|
||||
var model = new LndGrpcServicesViewModel();
|
||||
var model = new LndServicesViewModel();
|
||||
if (service.Type == ExternalServiceTypes.LNDGRPC)
|
||||
{
|
||||
model.Host = $"{connectionString.Server.DnsSafeHost}:{connectionString.Server.Port}";
|
||||
@ -715,7 +796,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (!_dashBoard.IsFullySynched(cryptoCode, out var unusud))
|
||||
{
|
||||
StatusMessage = $"Error: {cryptoCode} is not fully synched";
|
||||
TempData[WellKnownTempData.ErrorMessage] = $"{cryptoCode} is not fully synched";
|
||||
return RedirectToAction(nameof(Services));
|
||||
}
|
||||
var service = GetService(serviceName, cryptoCode);
|
||||
@ -729,7 +810,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = $"Error: {ex.Message}";
|
||||
TempData[WellKnownTempData.ErrorMessage] = ex.Message;
|
||||
return RedirectToAction(nameof(Services));
|
||||
}
|
||||
|
||||
@ -811,7 +892,7 @@ namespace BTCPayServer.Controllers
|
||||
string errorMessage = await viewModel.Settings.SendUpdateRequest(HttpClientFactory.CreateClient());
|
||||
if (errorMessage == null)
|
||||
{
|
||||
StatusMessage = $"The Dynamic DNS has been successfully queried, your configuration is saved";
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"The Dynamic DNS has been successfully queried, your configuration is saved";
|
||||
viewModel.Settings.LastUpdated = DateTimeOffset.UtcNow;
|
||||
settings.Services.Add(viewModel.Settings);
|
||||
await _SettingsRepository.UpdateSetting(settings);
|
||||
@ -847,7 +928,7 @@ namespace BTCPayServer.Controllers
|
||||
viewModel.Settings.Hostname = viewModel.Settings.Hostname.Trim().ToLowerInvariant();
|
||||
if (!viewModel.Settings.Enabled)
|
||||
{
|
||||
StatusMessage = $"The Dynamic DNS service has been disabled";
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"The Dynamic DNS service has been disabled";
|
||||
viewModel.Settings.LastUpdated = null;
|
||||
}
|
||||
else
|
||||
@ -855,7 +936,7 @@ namespace BTCPayServer.Controllers
|
||||
string errorMessage = await viewModel.Settings.SendUpdateRequest(HttpClientFactory.CreateClient());
|
||||
if (errorMessage == null)
|
||||
{
|
||||
StatusMessage = $"The Dynamic DNS has been successfully queried, your configuration is saved";
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"The Dynamic DNS has been successfully queried, your configuration is saved";
|
||||
viewModel.Settings.LastUpdated = DateTimeOffset.UtcNow;
|
||||
}
|
||||
else
|
||||
@ -894,7 +975,7 @@ namespace BTCPayServer.Controllers
|
||||
return NotFound();
|
||||
settings.Services.RemoveAt(i);
|
||||
await _SettingsRepository.UpdateSetting(settings);
|
||||
StatusMessage = "Dynamic DNS service successfully removed";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Dynamic DNS service successfully removed";
|
||||
this.RouteData.Values.Remove(nameof(hostname));
|
||||
return RedirectToAction(nameof(DynamicDnsServices));
|
||||
}
|
||||
@ -965,7 +1046,7 @@ namespace BTCPayServer.Controllers
|
||||
try
|
||||
{
|
||||
await System.IO.File.WriteAllTextAsync(_Options.SSHSettings.AuthorizedKeysFile, newContent);
|
||||
StatusMessage = "authorized_keys has been updated";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "authorized_keys has been updated";
|
||||
updated = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -994,11 +1075,11 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
if (exception is null)
|
||||
{
|
||||
StatusMessage = "authorized_keys has been updated";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "authorized_keys has been updated";
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusMessage = $"Error: {exception.Message}";
|
||||
TempData[WellKnownTempData.ErrorMessage] = exception.Message;
|
||||
}
|
||||
return RedirectToAction(nameof(SSHService));
|
||||
}
|
||||
@ -1014,7 +1095,7 @@ namespace BTCPayServer.Controllers
|
||||
public async Task<IActionResult> Theme(ThemeSettings settings)
|
||||
{
|
||||
await _SettingsRepository.UpdateSetting(settings);
|
||||
TempData["StatusMessage"] = "Theme settings updated successfully";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Theme settings updated successfully";
|
||||
return View(settings);
|
||||
}
|
||||
|
||||
@ -1032,7 +1113,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (!model.Settings.IsComplete())
|
||||
{
|
||||
model.StatusMessage = "Error: Required fields missing";
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Required fields missing";
|
||||
return View(model);
|
||||
}
|
||||
|
||||
@ -1043,18 +1124,18 @@ namespace BTCPayServer.Controllers
|
||||
var client = model.Settings.CreateSmtpClient();
|
||||
var message = model.Settings.CreateMailMessage(new MailAddress(model.TestEmail), "BTCPay test", "BTCPay test");
|
||||
await client.SendMailAsync(message);
|
||||
model.StatusMessage = "Email sent to " + model.TestEmail + ", please, verify you received it";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Email sent to " + model.TestEmail + ", please, verify you received it";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
model.StatusMessage = "Error: " + ex.Message;
|
||||
TempData[WellKnownTempData.ErrorMessage] = ex.Message;
|
||||
}
|
||||
return View(model);
|
||||
}
|
||||
else // if(command == "Save")
|
||||
{
|
||||
await _SettingsRepository.UpdateSetting(model.Settings);
|
||||
model.StatusMessage = "Email settings saved";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Email settings saved";
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
@ -1071,7 +1152,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
if (string.IsNullOrEmpty(_Options.LogFile))
|
||||
{
|
||||
vm.StatusMessage = "Error: File Logging Option not specified. " +
|
||||
TempData[WellKnownTempData.ErrorMessage] = "File Logging Option not specified. " +
|
||||
"You need to set debuglog and optionally " +
|
||||
"debugloglevel in the configuration or through runtime arguments";
|
||||
}
|
||||
@ -1080,7 +1161,7 @@ namespace BTCPayServer.Controllers
|
||||
var di = Directory.GetParent(_Options.LogFile);
|
||||
if (di == null)
|
||||
{
|
||||
vm.StatusMessage = "Error: Could not load log files";
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Could not load log files";
|
||||
}
|
||||
|
||||
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(_Options.LogFile);
|
||||
|
@ -88,8 +88,7 @@ namespace BTCPayServer.Controllers
|
||||
var segwit = network.NBitcoinNetwork.Consensus.SupportSegwit;
|
||||
var derivation = new DerivationStrategyFactory(network.NBitcoinNetwork).CreateDirectDerivationStrategy(getxpubResult.ExtPubKey, new DerivationStrategyOptions()
|
||||
{
|
||||
P2SH = segwit,
|
||||
Legacy = !segwit
|
||||
ScriptPubKeyType = segwit ? ScriptPubKeyType.SegwitP2SH : ScriptPubKeyType.Legacy
|
||||
});
|
||||
getxpubResult.DerivationScheme = derivation;
|
||||
getxpubResult.RootFingerprint = (await hw.GetExtPubKey(network, new KeyPath(), normalOperationTimeout.Token)).ExtPubKey.PubKey.GetHDFingerPrint();
|
||||
@ -174,11 +173,11 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (!DerivationSchemeSettings.TryParseFromJson(vm.Config, network, out strategy))
|
||||
{
|
||||
vm.StatusMessage = new StatusMessageModel()
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = "Config file was not in the correct format"
|
||||
}.ToString();
|
||||
});
|
||||
vm.Confirmation = false;
|
||||
return View(vm);
|
||||
}
|
||||
@ -188,11 +187,11 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (!DerivationSchemeSettings.TryParseFromColdcard(await ReadAllText(vm.ColdcardPublicFile), network, out strategy))
|
||||
{
|
||||
vm.StatusMessage = new StatusMessageModel()
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = "Coldcard public file was not in the correct format"
|
||||
}.ToString();
|
||||
});
|
||||
vm.Confirmation = false;
|
||||
return View(vm);
|
||||
}
|
||||
@ -275,11 +274,11 @@ namespace BTCPayServer.Controllers
|
||||
if (willBeExcluded != wasExcluded)
|
||||
{
|
||||
var label = willBeExcluded ? "disabled" : "enabled";
|
||||
StatusMessage = $"On-Chain payments for {network.CryptoCode} has been {label}.";
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"On-Chain payments for {network.CryptoCode} has been {label}.";
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusMessage = $"Derivation settings for {network.CryptoCode} has been modified.";
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Derivation settings for {network.CryptoCode} has been modified.";
|
||||
}
|
||||
return RedirectToAction(nameof(UpdateStore), new {storeId = storeId});
|
||||
}
|
||||
@ -312,7 +311,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
vm.HintAddress = "";
|
||||
vm.StatusMessage =
|
||||
TempData[WellKnownTempData.SuccessMessage] =
|
||||
"Address successfully found, please verify that the rest is correct and click on \"Confirm\"";
|
||||
ModelState.Remove(nameof(vm.HintAddress));
|
||||
ModelState.Remove(nameof(vm.DerivationScheme));
|
||||
|
@ -71,7 +71,7 @@ namespace BTCPayServer.Controllers
|
||||
storeBlob.ChangellySettings = changellySettings;
|
||||
store.SetStoreBlob(storeBlob);
|
||||
await _Repo.UpdateStore(store);
|
||||
StatusMessage = "Changelly settings modified";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Changelly settings modified";
|
||||
_changellyClientProvider.InvalidateClient(storeId);
|
||||
return RedirectToAction(nameof(UpdateStore), new {
|
||||
storeId});
|
||||
@ -81,12 +81,12 @@ namespace BTCPayServer.Controllers
|
||||
var client = new Changelly(_httpClientFactory.CreateClient(), changellySettings.ApiKey, changellySettings.ApiSecret,
|
||||
changellySettings.ApiUrl);
|
||||
var result = await client.GetCurrenciesFull();
|
||||
vm.StatusMessage = "Test Successful";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Test Successful";
|
||||
return View(vm);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
vm.StatusMessage = $"Error: {ex.Message}";
|
||||
TempData[WellKnownTempData.ErrorMessage] = ex.Message;
|
||||
return View(vm);
|
||||
}
|
||||
default:
|
||||
|
@ -62,7 +62,7 @@ namespace BTCPayServer.Controllers
|
||||
storeBlob.CoinSwitchSettings = coinSwitchSettings;
|
||||
store.SetStoreBlob(storeBlob);
|
||||
await _Repo.UpdateStore(store);
|
||||
StatusMessage = "CoinSwitch settings modified";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "CoinSwitch settings modified";
|
||||
return RedirectToAction(nameof(UpdateStore), new {
|
||||
storeId});
|
||||
|
||||
|
@ -36,17 +36,17 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (!model.Settings.IsComplete())
|
||||
{
|
||||
model.StatusMessage = "Error: Required fields missing";
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Required fields missing";
|
||||
return View(model);
|
||||
}
|
||||
var client = model.Settings.CreateSmtpClient();
|
||||
var message = model.Settings.CreateMailMessage(new MailAddress(model.TestEmail), "BTCPay test", "BTCPay test");
|
||||
await client.SendMailAsync(message);
|
||||
model.StatusMessage = "Email sent to " + model.TestEmail + ", please, verify you received it";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Email sent to " + model.TestEmail + ", please, verify you received it";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
model.StatusMessage = "Error: " + ex.Message;
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Error: " + ex.Message;
|
||||
}
|
||||
return View(model);
|
||||
}
|
||||
@ -57,7 +57,7 @@ namespace BTCPayServer.Controllers
|
||||
storeBlob.EmailSettings = model.Settings;
|
||||
store.SetStoreBlob(storeBlob);
|
||||
await _Repo.UpdateStore(store);
|
||||
StatusMessage = "Email settings modified";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Email settings modified";
|
||||
return RedirectToAction(nameof(UpdateStore), new {
|
||||
storeId});
|
||||
|
||||
|
@ -146,7 +146,7 @@ namespace BTCPayServer.Controllers
|
||||
store.SetStoreBlob(storeBlob);
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);
|
||||
await _Repo.UpdateStore(store);
|
||||
StatusMessage = $"Lightning node modified ({network.CryptoCode})";
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Lightning node modified ({network.CryptoCode})";
|
||||
return RedirectToAction(nameof(UpdateStore), new { storeId = storeId });
|
||||
case "test" when paymentMethod == null:
|
||||
ModelState.AddModelError(nameof(vm.ConnectionString), "Missing url parameter");
|
||||
@ -163,11 +163,11 @@ namespace BTCPayServer.Controllers
|
||||
await handler.TestConnection(info, cts.Token);
|
||||
}
|
||||
}
|
||||
vm.StatusMessage = $"Connection to the lightning node succeeded ({info})";
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Connection to the lightning node succeeded ({info})";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
vm.StatusMessage = $"Error: {ex.Message}";
|
||||
TempData[WellKnownTempData.ErrorMessage] = ex.Message;
|
||||
return View(vm);
|
||||
}
|
||||
return View(vm);
|
||||
|
@ -102,11 +102,6 @@ namespace BTCPayServer.Controllers
|
||||
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
|
||||
private readonly CssThemeManager _CssThemeManager;
|
||||
|
||||
[TempData]
|
||||
public string StatusMessage
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
[TempData]
|
||||
public bool StoreNotConfigured
|
||||
{
|
||||
@ -168,7 +163,7 @@ namespace BTCPayServer.Controllers
|
||||
ModelState.AddModelError(nameof(vm.Email), "The user already has access to this store");
|
||||
return View(vm);
|
||||
}
|
||||
StatusMessage = "User added successfully";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "User added successfully";
|
||||
return RedirectToAction(nameof(StoreUsers));
|
||||
}
|
||||
|
||||
@ -193,7 +188,7 @@ namespace BTCPayServer.Controllers
|
||||
public async Task<IActionResult> DeleteStoreUserPost(string storeId, string userId)
|
||||
{
|
||||
await _Repo.RemoveStoreUser(storeId, userId);
|
||||
StatusMessage = "User removed successfully";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "User removed successfully";
|
||||
return RedirectToAction(nameof(StoreUsers), new { storeId = storeId, userId = userId });
|
||||
}
|
||||
|
||||
@ -314,7 +309,7 @@ namespace BTCPayServer.Controllers
|
||||
if (CurrentStore.SetStoreBlob(blob))
|
||||
{
|
||||
await _Repo.UpdateStore(CurrentStore);
|
||||
StatusMessage = "Rate settings updated";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Rate settings updated";
|
||||
}
|
||||
return RedirectToAction(nameof(Rates), new
|
||||
{
|
||||
@ -347,7 +342,7 @@ namespace BTCPayServer.Controllers
|
||||
blob.RateScript = blob.GetDefaultRateRules(_NetworkProvider).ToString();
|
||||
CurrentStore.SetStoreBlob(blob);
|
||||
await _Repo.UpdateStore(CurrentStore);
|
||||
StatusMessage = "Rate rules scripting activated";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Rate rules scripting activated";
|
||||
return RedirectToAction(nameof(Rates), new { storeId = CurrentStore.Id });
|
||||
}
|
||||
|
||||
@ -358,12 +353,13 @@ namespace BTCPayServer.Controllers
|
||||
var storeBlob = CurrentStore.GetStoreBlob();
|
||||
var vm = new CheckoutExperienceViewModel();
|
||||
SetCryptoCurrencies(vm, CurrentStore);
|
||||
vm.CustomCSS = storeBlob.CustomCSS?.AbsoluteUri;
|
||||
vm.CustomLogo = storeBlob.CustomLogo?.AbsoluteUri;
|
||||
vm.CustomCSS = storeBlob.CustomCSS;
|
||||
vm.CustomLogo = storeBlob.CustomLogo;
|
||||
vm.HtmlTitle = storeBlob.HtmlTitle;
|
||||
vm.SetLanguages(_LangService, storeBlob.DefaultLang);
|
||||
vm.RequiresRefundEmail = storeBlob.RequiresRefundEmail;
|
||||
vm.ShowRecommendedFee = storeBlob.ShowRecommendedFee;
|
||||
vm.RecommendedFeeBlockTarget = storeBlob.RecommendedFeeBlockTarget;
|
||||
vm.OnChainMinValue = storeBlob.OnChainMinValue?.ToString() ?? "";
|
||||
vm.LightningMaxValue = storeBlob.LightningMaxValue?.ToString() ?? "";
|
||||
vm.LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi;
|
||||
@ -417,12 +413,13 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
blob.CustomLogo = string.IsNullOrWhiteSpace(model.CustomLogo) ? null : new Uri(model.CustomLogo, UriKind.Absolute);
|
||||
blob.CustomCSS = string.IsNullOrWhiteSpace(model.CustomCSS) ? null : new Uri(model.CustomCSS, UriKind.Absolute);
|
||||
blob.CustomLogo = model.CustomLogo;
|
||||
blob.CustomCSS = model.CustomCSS;
|
||||
blob.HtmlTitle = string.IsNullOrWhiteSpace(model.HtmlTitle) ? null : model.HtmlTitle;
|
||||
blob.DefaultLang = model.DefaultLang;
|
||||
blob.RequiresRefundEmail = model.RequiresRefundEmail;
|
||||
blob.ShowRecommendedFee = model.ShowRecommendedFee;
|
||||
blob.RecommendedFeeBlockTarget = model.RecommendedFeeBlockTarget;
|
||||
blob.OnChainMinValue = onchainMinValue;
|
||||
blob.LightningMaxValue = lightningMaxValue;
|
||||
blob.LightningAmountInSatoshi = model.LightningAmountInSatoshi;
|
||||
@ -434,7 +431,7 @@ namespace BTCPayServer.Controllers
|
||||
if (needUpdate)
|
||||
{
|
||||
await _Repo.UpdateStore(CurrentStore);
|
||||
StatusMessage = "Store successfully updated";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Store successfully updated";
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(CheckoutExperience), new
|
||||
@ -563,7 +560,7 @@ namespace BTCPayServer.Controllers
|
||||
if (needUpdate)
|
||||
{
|
||||
await _Repo.UpdateStore(CurrentStore);
|
||||
StatusMessage = "Store successfully updated";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Store successfully updated";
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(UpdateStore), new
|
||||
@ -579,7 +576,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Action = "Delete this store",
|
||||
Action = "Delete",
|
||||
Title = "Delete this store",
|
||||
Description = "This action is irreversible and will remove all information related to this store. (Invoices, Apps etc...)",
|
||||
ButtonClass = "btn-danger"
|
||||
@ -591,7 +588,7 @@ namespace BTCPayServer.Controllers
|
||||
public async Task<IActionResult> DeleteStorePost(string storeId)
|
||||
{
|
||||
await _Repo.DeleteStore(CurrentStore.Id);
|
||||
StatusMessage = "Success: Store successfully deleted";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Store successfully deleted";
|
||||
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
|
||||
}
|
||||
|
||||
@ -617,7 +614,6 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var model = new TokensViewModel();
|
||||
var tokens = await _TokenRepository.GetTokensByStoreIdAsync(CurrentStore.Id);
|
||||
model.StatusMessage = StatusMessage;
|
||||
model.StoreNotConfigured = StoreNotConfigured;
|
||||
model.Tokens = tokens.Select(t => new TokenViewModel()
|
||||
{
|
||||
@ -643,7 +639,7 @@ namespace BTCPayServer.Controllers
|
||||
return NotFound();
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Action = "Revoke the token",
|
||||
Action = "Revoke",
|
||||
Title = "Revoke the token",
|
||||
Description = $"The access token with the label \"{token.Label}\" will be revoked, do you wish to continue?",
|
||||
ButtonClass = "btn-danger"
|
||||
@ -657,9 +653,9 @@ namespace BTCPayServer.Controllers
|
||||
if (token == null ||
|
||||
token.StoreId != CurrentStore.Id ||
|
||||
!await _TokenRepository.DeleteToken(tokenId))
|
||||
StatusMessage = "Failure to revoke this token";
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Failure to revoke this token";
|
||||
else
|
||||
StatusMessage = "Token revoked";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Token revoked";
|
||||
return RedirectToAction(nameof(ListTokens));
|
||||
}
|
||||
|
||||
@ -674,28 +670,21 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("/api-tokens")]
|
||||
[Route("{storeId}/Tokens/Create")]
|
||||
public async Task<IActionResult> CreateToken(CreateTokenViewModel model)
|
||||
public async Task<IActionResult> CreateToken(string storeId, CreateTokenViewModel model)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
return View(nameof(CreateToken), model);
|
||||
}
|
||||
model.Label = model.Label ?? String.Empty;
|
||||
var userId = GetUserId();
|
||||
if (userId == null)
|
||||
return Challenge(AuthenticationSchemes.Cookie);
|
||||
|
||||
var store = CurrentStore;
|
||||
var storeId = CurrentStore?.Id;
|
||||
if (storeId == null)
|
||||
{
|
||||
storeId = model.StoreId;
|
||||
store = await _Repo.FindStore(storeId, userId);
|
||||
if (store == null)
|
||||
return Challenge(AuthenticationSchemes.Cookie);
|
||||
}
|
||||
storeId = model.StoreId;
|
||||
var store = CurrentStore ?? await _Repo.FindStore(storeId, userId);
|
||||
if (store == null)
|
||||
return Challenge(AuthenticationSchemes.Cookie);
|
||||
var tokenRequest = new TokenRequest()
|
||||
{
|
||||
Label = model.Label,
|
||||
@ -730,8 +719,20 @@ namespace BTCPayServer.Controllers
|
||||
public string GeneratedPairingCode { get; set; }
|
||||
|
||||
[HttpGet]
|
||||
[Route("/api-tokens")]
|
||||
[Route("{storeId}/Tokens/Create")]
|
||||
public IActionResult CreateToken(string storeId)
|
||||
{
|
||||
var model = new CreateTokenViewModel();
|
||||
ViewBag.HidePublicKey = storeId == null;
|
||||
ViewBag.ShowStores = storeId == null;
|
||||
ViewBag.ShowMenu = storeId != null;
|
||||
model.StoreId = storeId;
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("/api-tokens")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> CreateToken()
|
||||
{
|
||||
var userId = GetUserId();
|
||||
@ -739,23 +740,27 @@ namespace BTCPayServer.Controllers
|
||||
return Challenge(AuthenticationSchemes.Cookie);
|
||||
var storeId = CurrentStore?.Id;
|
||||
var model = new CreateTokenViewModel();
|
||||
ViewBag.HidePublicKey = storeId == null;
|
||||
ViewBag.ShowStores = storeId == null;
|
||||
ViewBag.ShowMenu = storeId != null;
|
||||
model.StoreId = storeId;
|
||||
if (storeId == null)
|
||||
ViewBag.HidePublicKey = true;
|
||||
ViewBag.ShowStores = true;
|
||||
ViewBag.ShowMenu = false;
|
||||
var stores = await _Repo.GetStoresByUserId(userId);
|
||||
model.Stores = new SelectList(stores.Where(s => s.Role == StoreRoles.Owner), nameof(CurrentStore.Id), nameof(CurrentStore.StoreName));
|
||||
if (model.Stores.Count() == 0)
|
||||
{
|
||||
var stores = await _Repo.GetStoresByUserId(userId);
|
||||
model.Stores = new SelectList(stores.Where(s => s.Role == StoreRoles.Owner), nameof(CurrentStore.Id), nameof(CurrentStore.StoreName), storeId);
|
||||
if (model.Stores.Count() == 0)
|
||||
{
|
||||
StatusMessage = "Error: You need to be owner of at least one store before pairing";
|
||||
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
|
||||
}
|
||||
TempData[WellKnownTempData.ErrorMessage] = "You need to be owner of at least one store before pairing";
|
||||
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
|
||||
}
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("/api-tokens")]
|
||||
[AllowAnonymous]
|
||||
public Task<IActionResult> CreateToken2(CreateTokenViewModel model)
|
||||
{
|
||||
return CreateToken(model.StoreId, model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/tokens/apikey")]
|
||||
public async Task<IActionResult> GenerateAPIKey()
|
||||
@ -764,7 +769,7 @@ namespace BTCPayServer.Controllers
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
await _TokenRepository.GenerateLegacyAPIKey(CurrentStore.Id);
|
||||
StatusMessage = "API Key re-generated";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "API Key re-generated";
|
||||
return RedirectToAction(nameof(ListTokens));
|
||||
}
|
||||
|
||||
@ -781,7 +786,7 @@ namespace BTCPayServer.Controllers
|
||||
var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
|
||||
if (pairing == null)
|
||||
{
|
||||
StatusMessage = "Unknown pairing code";
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Unknown pairing code";
|
||||
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
|
||||
}
|
||||
else
|
||||
@ -820,9 +825,9 @@ namespace BTCPayServer.Controllers
|
||||
StoreNotConfigured = store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.Where(p => !excludeFilter.Match(p.PaymentId))
|
||||
.Count() == 0;
|
||||
StatusMessage = "Pairing is successful";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Pairing is successful";
|
||||
if (pairingResult == PairingResult.Partial)
|
||||
StatusMessage = "Server initiated pairing code: " + pairingCode;
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Server initiated pairing code: " + pairingCode;
|
||||
return RedirectToAction(nameof(ListTokens), new
|
||||
{
|
||||
storeId = store.Id,
|
||||
@ -831,7 +836,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusMessage = $"Pairing failed ({pairingResult})";
|
||||
TempData[WellKnownTempData.ErrorMessage] = $"Pairing failed ({pairingResult})";
|
||||
return RedirectToAction(nameof(ListTokens), new
|
||||
{
|
||||
storeId = store.Id
|
||||
@ -889,7 +894,7 @@ namespace BTCPayServer.Controllers
|
||||
if (CurrentStore.SetStoreBlob(blob))
|
||||
{
|
||||
await _Repo.UpdateStore(CurrentStore);
|
||||
StatusMessage = "Store successfully updated";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Store successfully updated";
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(PayButton), new
|
||||
|
@ -71,13 +71,10 @@ namespace BTCPayServer.Controllers
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
await _Repo.RemoveStore(storeId, userId);
|
||||
StatusMessage = "Store removed successfully";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Store removed successfully";
|
||||
return RedirectToAction(nameof(ListStores));
|
||||
}
|
||||
|
||||
[TempData]
|
||||
public string StatusMessage { get; set; }
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> ListStores()
|
||||
{
|
||||
@ -107,7 +104,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
var store = await _Repo.CreateStore(GetUserId(), vm.Name);
|
||||
CreatedStoreId = store.Id;
|
||||
StatusMessage = "Store successfully created";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Store successfully created";
|
||||
return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new
|
||||
{
|
||||
storeId = store.Id
|
||||
|
@ -69,6 +69,8 @@ namespace BTCPayServer.Controllers
|
||||
WalletId walletId,
|
||||
WalletPSBTViewModel vm, string command = null)
|
||||
{
|
||||
if (command == null)
|
||||
return await WalletPSBT(walletId, vm);
|
||||
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
|
||||
var psbt = await vm.GetPSBT(network.NBitcoinNetwork);
|
||||
if (psbt == null)
|
||||
@ -78,7 +80,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
switch (command)
|
||||
{
|
||||
case null:
|
||||
case "decode":
|
||||
vm.Decoded = psbt.ToString();
|
||||
ModelState.Remove(nameof(vm.PSBT));
|
||||
ModelState.Remove(nameof(vm.FileName));
|
||||
@ -96,8 +98,8 @@ namespace BTCPayServer.Controllers
|
||||
ModelState.AddModelError(nameof(vm.PSBT), "You need to update your version of NBXplorer");
|
||||
return View(vm);
|
||||
}
|
||||
StatusMessage = "PSBT updated!";
|
||||
return RedirectToAction(nameof(WalletPSBT), new { walletId = walletId, psbt = psbt.ToBase64(), FileName = vm.FileName });
|
||||
TempData[WellKnownTempData.SuccessMessage] = "PSBT updated!";
|
||||
return RedirectToWalletPSBT(walletId, psbt, vm.FileName);
|
||||
case "seed":
|
||||
return SignWithSeed(walletId, psbt.ToBase64());
|
||||
case "broadcast":
|
||||
@ -258,6 +260,8 @@ namespace BTCPayServer.Controllers
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, WalletPSBTReadyViewModel vm, string command = null)
|
||||
{
|
||||
if (command == null)
|
||||
return await WalletPSBTReady(walletId, vm.PSBT, vm.SigningKey, vm.SigningKeyPath);
|
||||
PSBT psbt = null;
|
||||
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
|
||||
try
|
||||
@ -299,7 +303,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
else if (command == "analyze-psbt")
|
||||
{
|
||||
return RedirectToAction(nameof(WalletPSBT), new { walletId = walletId, psbt = psbt.ToBase64() });
|
||||
return RedirectToWalletPSBT(walletId, psbt);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -308,27 +312,6 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
private IActionResult ViewPSBT<T>(PSBT psbt, IEnumerable<T> errors = null)
|
||||
{
|
||||
return ViewPSBT(psbt, null, errors?.Select(e => e.ToString()).ToList());
|
||||
}
|
||||
private IActionResult ViewPSBT(PSBT psbt, IEnumerable<string> errors = null)
|
||||
{
|
||||
return ViewPSBT(psbt, null, errors);
|
||||
}
|
||||
private IActionResult ViewPSBT(PSBT psbt, string fileName, IEnumerable<string> errors = null)
|
||||
{
|
||||
ModelState.Remove(nameof(WalletPSBTViewModel.PSBT));
|
||||
ModelState.Remove(nameof(WalletPSBTViewModel.FileName));
|
||||
return View(nameof(WalletPSBT), new WalletPSBTViewModel()
|
||||
{
|
||||
Decoded = psbt.ToString(),
|
||||
FileName = fileName ?? string.Empty,
|
||||
PSBT = psbt.ToBase64(),
|
||||
Errors = errors?.ToList()
|
||||
});
|
||||
}
|
||||
|
||||
private IActionResult FilePSBT(PSBT psbt, string fileName)
|
||||
{
|
||||
return File(psbt.ToBytes(), "application/octet-stream", fileName);
|
||||
@ -353,8 +336,8 @@ namespace BTCPayServer.Controllers
|
||||
return View(vm);
|
||||
}
|
||||
sourcePSBT = sourcePSBT.Combine(psbt);
|
||||
StatusMessage = "PSBT Successfully combined!";
|
||||
return ViewPSBT(sourcePSBT);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "PSBT Successfully combined!";
|
||||
return RedirectToWalletPSBT(walletId, sourcePSBT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,8 +50,6 @@ namespace BTCPayServer.Controllers
|
||||
private readonly IFeeProviderFactory _feeRateProvider;
|
||||
private readonly BTCPayWalletProvider _walletProvider;
|
||||
public RateFetcher RateFetcher { get; }
|
||||
[TempData]
|
||||
public string StatusMessage { get; set; }
|
||||
|
||||
CurrencyNameTable _currencyTable;
|
||||
public WalletsController(StoreRepository repo,
|
||||
@ -458,21 +456,59 @@ namespace BTCPayServer.Controllers
|
||||
case "analyze-psbt":
|
||||
var name =
|
||||
$"Send-{string.Join('_', vm.Outputs.Select(output => $"{output.Amount}->{output.DestinationAddress}{(output.SubtractFeesFromOutput ? "-Fees" : string.Empty)}"))}.psbt";
|
||||
return RedirectToAction(nameof(WalletPSBT), new { walletId = walletId, psbt = psbt.PSBT.ToBase64(), FileName = name });
|
||||
return RedirectToWalletPSBT(walletId, psbt.PSBT, name);
|
||||
default:
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private IActionResult RedirectToWalletPSBT(WalletId walletId, PSBT psbt, string fileName = null)
|
||||
{
|
||||
var vm = new PostRedirectViewModel()
|
||||
{
|
||||
AspController = "Wallets",
|
||||
AspAction = nameof(WalletPSBT),
|
||||
Parameters =
|
||||
{
|
||||
new KeyValuePair<string, string>("psbt", psbt.ToBase64())
|
||||
}
|
||||
};
|
||||
if (!string.IsNullOrEmpty(fileName))
|
||||
vm.Parameters.Add(new KeyValuePair<string, string>("fileName", fileName));
|
||||
return View("PostRedirect", vm);
|
||||
}
|
||||
|
||||
void SetAmbientPSBT(PSBT psbt)
|
||||
{
|
||||
if (psbt != null)
|
||||
TempData["AmbientPSBT"] = psbt.ToBase64();
|
||||
else
|
||||
TempData.Remove("AmbientPSBT");
|
||||
}
|
||||
PSBT GetAmbientPSBT(Network network, bool peek)
|
||||
{
|
||||
if (network == null)
|
||||
throw new ArgumentNullException(nameof(network));
|
||||
if ((peek ? TempData.Peek("AmbientPSBT") : TempData["AmbientPSBT"]) is string str)
|
||||
{
|
||||
try
|
||||
{
|
||||
return PSBT.Parse(str, network);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ViewResult ViewWalletSendLedger(PSBT psbt, BitcoinAddress hintChange = null)
|
||||
{
|
||||
SetAmbientPSBT(psbt);
|
||||
return View("WalletSendLedger", new WalletSendLedgerModel()
|
||||
{
|
||||
PSBT = psbt.ToBase64(),
|
||||
HintChange = hintChange?.ToString(),
|
||||
WebsocketPath = this.Url.Action(nameof(LedgerConnection)),
|
||||
SuccessPath = this.Url.Action(nameof(WalletPSBTReady))
|
||||
WebsocketPath = this.Url.Action(nameof(LedgerConnection))
|
||||
});
|
||||
}
|
||||
|
||||
@ -573,7 +609,7 @@ namespace BTCPayServer.Controllers
|
||||
var wallet = _walletProvider.GetWallet(network);
|
||||
var derivationSettings = GetDerivationSchemeSettings(walletId);
|
||||
wallet.InvalidateCache(derivationSettings.AccountDerivation);
|
||||
StatusMessage = $"Transaction broadcasted successfully ({transaction.GetHash().ToString()})";
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Transaction broadcasted successfully ({transaction.GetHash().ToString()})";
|
||||
}
|
||||
return RedirectToAction(nameof(WalletTransactions), new { walletId = walletId.ToString() });
|
||||
}
|
||||
@ -704,7 +740,6 @@ namespace BTCPayServer.Controllers
|
||||
// getxpub
|
||||
int account = 0,
|
||||
// sendtoaddress
|
||||
string psbt = null,
|
||||
string hintChange = null
|
||||
)
|
||||
{
|
||||
@ -714,6 +749,7 @@ namespace BTCPayServer.Controllers
|
||||
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
|
||||
if (network == null)
|
||||
throw new FormatException("Invalid value for crypto code");
|
||||
PSBT psbt = GetAmbientPSBT(network.NBitcoinNetwork, true);
|
||||
var derivationSettings = GetDerivationSchemeSettings(walletId);
|
||||
|
||||
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
||||
@ -723,7 +759,6 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30));
|
||||
var hw = new LedgerHardwareWalletService(webSocket);
|
||||
var model = new WalletSendLedgerModel();
|
||||
object result = null;
|
||||
try
|
||||
{
|
||||
@ -777,18 +812,12 @@ namespace BTCPayServer.Controllers
|
||||
await Repository.UpdateStore(storeData);
|
||||
}
|
||||
|
||||
var psbtResponse = new CreatePSBTResponse()
|
||||
{
|
||||
PSBT = PSBT.Parse(psbt, network.NBitcoinNetwork),
|
||||
ChangeAddress = string.IsNullOrEmpty(hintChange) ? null : BitcoinAddress.Create(hintChange, network.NBitcoinNetwork)
|
||||
};
|
||||
|
||||
|
||||
derivationSettings.RebaseKeyPaths(psbtResponse.PSBT);
|
||||
|
||||
derivationSettings.RebaseKeyPaths(psbt);
|
||||
var changeAddress = string.IsNullOrEmpty(hintChange) ? null : BitcoinAddress.Create(hintChange, network.NBitcoinNetwork);
|
||||
signTimeout.CancelAfter(TimeSpan.FromMinutes(5));
|
||||
psbtResponse.PSBT = await hw.SignTransactionAsync(psbtResponse.PSBT, accountKey.GetRootedKeyPath(), accountKey.AccountKey, psbtResponse.ChangeAddress?.ScriptPubKey, signTimeout.Token);
|
||||
result = new SendToAddressResult() { PSBT = psbtResponse.PSBT.ToBase64() };
|
||||
psbt = await hw.SignTransactionAsync(psbt, accountKey.GetRootedKeyPath(), accountKey.AccountKey, changeAddress?.ScriptPubKey, signTimeout.Token);
|
||||
SetAmbientPSBT(null);
|
||||
result = new SendToAddressResult() { PSBT = psbt.ToBase64() };
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@ -863,7 +892,7 @@ namespace BTCPayServer.Controllers
|
||||
var store = (await Repository.FindStore(walletId.StoreId, GetUserId()));
|
||||
store.SetSupportedPaymentMethod(derivationScheme);
|
||||
await Repository.UpdateStore(store);
|
||||
StatusMessage = "Wallet settings updated";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Wallet settings updated";
|
||||
return RedirectToAction(nameof(WalletSettings));
|
||||
}
|
||||
}
|
||||
|
@ -21,8 +21,8 @@ namespace BTCPayServer.Data
|
||||
|
||||
public static bool SetBlob(this PaymentRequestData paymentRequestData, PaymentRequestBlob blob)
|
||||
{
|
||||
var original = new Serializer(Network.Main).ToString(paymentRequestData.GetBlob());
|
||||
var newBlob = new Serializer(Network.Main).ToString(blob);
|
||||
var original = new Serializer(null).ToString(paymentRequestData.GetBlob());
|
||||
var newBlob = new Serializer(null).ToString(blob);
|
||||
if (original == newBlob)
|
||||
return false;
|
||||
paymentRequestData.Blob = ZipUtils.Zip(newBlob);
|
||||
|
@ -21,6 +21,7 @@ namespace BTCPayServer.Data
|
||||
MonitoringExpiration = 1440;
|
||||
PaymentTolerance = 0;
|
||||
ShowRecommendedFee = true;
|
||||
RecommendedFeeBlockTarget = 1;
|
||||
}
|
||||
|
||||
[Obsolete("Use NetworkFeeMode instead")]
|
||||
@ -41,6 +42,8 @@ namespace BTCPayServer.Data
|
||||
|
||||
public bool ShowRecommendedFee { get; set; }
|
||||
|
||||
public int RecommendedFeeBlockTarget { get; set; }
|
||||
|
||||
CurrencyPair[] _DefaultCurrencyPairs;
|
||||
[JsonProperty("defaultCurrencyPairs", ItemConverterType = typeof(CurrencyPairJsonConverter))]
|
||||
public CurrencyPair[] DefaultCurrencyPairs
|
||||
@ -89,10 +92,9 @@ namespace BTCPayServer.Data
|
||||
public CurrencyValue LightningMaxValue { get; set; }
|
||||
public bool LightningAmountInSatoshi { get; set; }
|
||||
|
||||
[JsonConverter(typeof(UriJsonConverter))]
|
||||
public Uri CustomLogo { get; set; }
|
||||
[JsonConverter(typeof(UriJsonConverter))]
|
||||
public Uri CustomCSS { get; set; }
|
||||
public string CustomLogo { get; set; }
|
||||
|
||||
public string CustomCSS { get; set; }
|
||||
public string HtmlTitle { get; set; }
|
||||
|
||||
public bool RateScripting { get; set; }
|
||||
|
@ -45,11 +45,10 @@ namespace BTCPayServer.Data
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
|
||||
static Network Dummy = Network.Main;
|
||||
|
||||
|
||||
public static StoreBlob GetStoreBlob(this StoreData storeData)
|
||||
{
|
||||
var result = storeData.StoreBlob == null ? new StoreBlob() : new Serializer(Dummy).ToObject<StoreBlob>(Encoding.UTF8.GetString(storeData.StoreBlob));
|
||||
var result = storeData.StoreBlob == null ? new StoreBlob() : new Serializer(null).ToObject<StoreBlob>(Encoding.UTF8.GetString(storeData.StoreBlob));
|
||||
if (result.PreferredExchange == null)
|
||||
result.PreferredExchange = CoinAverageRateProvider.CoinAverageName;
|
||||
return result;
|
||||
@ -57,8 +56,8 @@ namespace BTCPayServer.Data
|
||||
|
||||
public static bool SetStoreBlob(this StoreData storeData, StoreBlob storeBlob)
|
||||
{
|
||||
var original = new Serializer(Dummy).ToString(storeData.GetStoreBlob());
|
||||
var newBlob = new Serializer(Dummy).ToString(storeBlob);
|
||||
var original = new Serializer(null).ToString(storeData.GetStoreBlob());
|
||||
var newBlob = new Serializer(null).ToString(storeBlob);
|
||||
if (original == newBlob)
|
||||
return false;
|
||||
storeData.StoreBlob = Encoding.UTF8.GetBytes(newBlob);
|
||||
|
@ -213,13 +213,12 @@ namespace BTCPayServer
|
||||
{
|
||||
foreach (var accountKey in AccountKeySettings)
|
||||
{
|
||||
if (accountKey.AccountKeyPath != null && accountKey.RootFingerprint is HDFingerprint fp)
|
||||
if (accountKey.GetRootedKeyPath() is RootedKeyPath rootedKeyPath)
|
||||
{
|
||||
yield return new NBXplorer.Models.PSBTRebaseKeyRules()
|
||||
{
|
||||
AccountKey = accountKey.AccountKey,
|
||||
AccountKeyPath = accountKey.AccountKeyPath,
|
||||
MasterFingerprint = fp
|
||||
AccountKeyPath = rootedKeyPath
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -250,7 +249,7 @@ namespace BTCPayServer
|
||||
{
|
||||
foreach (var rebase in GetPSBTRebaseKeyRules())
|
||||
{
|
||||
psbt.RebaseKeyPaths(rebase.AccountKey, rebase.GetRootedKeyPath());
|
||||
psbt.RebaseKeyPaths(rebase.AccountKey, rebase.AccountKeyPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
@ -109,6 +111,13 @@ namespace BTCPayServer
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static bool HasStatusMessage(this ITempDataDictionary tempData)
|
||||
{
|
||||
return (tempData.Peek(WellKnownTempData.SuccessMessage) ??
|
||||
tempData.Peek(WellKnownTempData.ErrorMessage) ??
|
||||
tempData.Peek("StatusMessageModel")) != null;
|
||||
}
|
||||
public static PaymentMethodId GetpaymentMethodId(this InvoiceCryptoInfo info)
|
||||
{
|
||||
return new PaymentMethodId(info.CryptoCode, PaymentTypes.Parse(info.PaymentType));
|
||||
@ -206,6 +215,42 @@ namespace BTCPayServer
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void SetStatusMessageModel(this ITempDataDictionary tempData, StatusMessageModel statusMessage)
|
||||
{
|
||||
if (statusMessage == null)
|
||||
{
|
||||
tempData.Remove("StatusMessageModel");
|
||||
return;
|
||||
}
|
||||
tempData["StatusMessageModel"] = JObject.FromObject(statusMessage).ToString(Formatting.None);
|
||||
}
|
||||
|
||||
public static StatusMessageModel GetStatusMessageModel(this ITempDataDictionary tempData)
|
||||
{
|
||||
tempData.TryGetValue(WellKnownTempData.SuccessMessage, out var successMessage);
|
||||
tempData.TryGetValue(WellKnownTempData.ErrorMessage, out var errorMessage);
|
||||
tempData.TryGetValue("StatusMessageModel", out var model);
|
||||
if (successMessage != null || errorMessage != null)
|
||||
{
|
||||
var parsedModel = new StatusMessageModel();
|
||||
parsedModel.Message = (string)successMessage ?? (string)errorMessage;
|
||||
if (successMessage != null)
|
||||
{
|
||||
parsedModel.Severity = StatusMessageModel.StatusSeverity.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
parsedModel.Severity = StatusMessageModel.StatusSeverity.Error;
|
||||
}
|
||||
return parsedModel;
|
||||
}
|
||||
else if (model != null && model is string str)
|
||||
{
|
||||
return JObject.Parse(str).ToObject<StatusMessageModel>();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool IsOnion(this HttpRequest request)
|
||||
{
|
||||
if (request?.Host.Host == null)
|
||||
|
@ -81,7 +81,8 @@ namespace BTCPayServer.HostedServices
|
||||
_cancellationTokenSource.Cancel();
|
||||
try
|
||||
{
|
||||
await _testingConnection;
|
||||
// Renci SSH sometimes is deadlocking, so we just wait at most 5 seconds
|
||||
await Task.WhenAny(_testingConnection, Task.Delay(5000, _cancellationTokenSource.Token));
|
||||
}
|
||||
catch { }
|
||||
Logs.PayServer.LogInformation($"{this.GetType().Name} successfully exited...");
|
||||
|
@ -21,16 +21,27 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
public void Update(ThemeSettings data)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(data.ThemeCssUri))
|
||||
_themeUri = "/main/themes/classic.css";
|
||||
else
|
||||
_themeUri = data.ThemeCssUri;
|
||||
|
||||
if (String.IsNullOrWhiteSpace(data.BootstrapCssUri))
|
||||
_bootstrapUri = "/vendor/bootstrap4/css/bootstrap.css?v=" + DateTime.Now.Ticks;
|
||||
_bootstrapUri = "/main/bootstrap/bootstrap.css";
|
||||
else
|
||||
_bootstrapUri = data.BootstrapCssUri;
|
||||
|
||||
|
||||
if (String.IsNullOrWhiteSpace(data.CreativeStartCssUri))
|
||||
_creativeStartUri = "/vendor/bootstrap4-creativestart/creative.css?v=" + DateTime.Now.Ticks;
|
||||
_creativeStartUri = "/main/bootstrap4-creativestart/creative.css";
|
||||
else
|
||||
_creativeStartUri = data.CreativeStartCssUri;
|
||||
FirstRun = data.FirstRun;
|
||||
}
|
||||
|
||||
private string _themeUri;
|
||||
public string ThemeUri
|
||||
{
|
||||
get { return _themeUri; }
|
||||
}
|
||||
|
||||
private string _bootstrapUri;
|
||||
@ -52,6 +63,8 @@ namespace BTCPayServer.HostedServices
|
||||
public AppType? RootAppType { get; set; }
|
||||
public string RootAppId { get; set; }
|
||||
|
||||
public bool FirstRun { get; set; }
|
||||
|
||||
public List<PoliciesSettings.DomainToAppMappingItem> DomainToAppMapping { get; set; } = new List<PoliciesSettings.DomainToAppMappingItem>();
|
||||
|
||||
internal void Update(PoliciesSettings data)
|
||||
@ -74,7 +87,7 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
public void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
@ -91,6 +104,10 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
policies.Clear();
|
||||
}
|
||||
if (manager.ThemeUri != null && Uri.TryCreate(manager.ThemeUri, UriKind.Absolute, out uri))
|
||||
{
|
||||
policies.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ using Microsoft.Extensions.Hosting;
|
||||
using System.Collections.Concurrent;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace BTCPayServer.HostedServices
|
||||
{
|
||||
@ -179,7 +180,7 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
if (invoiceId == null)
|
||||
throw new ArgumentNullException(nameof(invoiceId));
|
||||
_WatchRequests.Add(invoiceId);
|
||||
_WatchRequests.Writer.TryWrite(invoiceId);
|
||||
}
|
||||
|
||||
private async Task Wait(string invoiceId)
|
||||
@ -205,7 +206,7 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
}
|
||||
|
||||
BlockingCollection<string> _WatchRequests = new BlockingCollection<string>(new ConcurrentQueue<string>());
|
||||
Channel<string> _WatchRequests = Channel.CreateUnbounded<string>();
|
||||
|
||||
Task _Loop;
|
||||
CancellationTokenSource _Cts;
|
||||
@ -245,9 +246,7 @@ namespace BTCPayServer.HostedServices
|
||||
async Task StartLoop(CancellationToken cancellation)
|
||||
{
|
||||
Logs.PayServer.LogInformation("Start watching invoices");
|
||||
await Task.Delay(1).ConfigureAwait(false); // Small hack so that the caller does not block on GetConsumingEnumerable
|
||||
|
||||
foreach (var invoiceId in _WatchRequests.GetConsumingEnumerable(cancellation))
|
||||
while (await _WatchRequests.Reader.WaitToReadAsync(cancellation) && _WatchRequests.Reader.TryRead(out var invoiceId))
|
||||
{
|
||||
int maxLoop = 5;
|
||||
int loopCount = -1;
|
||||
|
@ -21,9 +21,6 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
internal override Task[] InitializeTasks()
|
||||
{
|
||||
// TODO: We should report auto configured services (like bitcoind, lnd or clightning)
|
||||
if (string.IsNullOrEmpty(_options.TorrcFile))
|
||||
return Array.Empty<Task>();
|
||||
return new Task[] { CreateLoopTask(RefreshTorServices) };
|
||||
}
|
||||
|
||||
|
@ -55,6 +55,7 @@ using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
using Serilog;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
{
|
||||
@ -186,8 +187,7 @@ namespace BTCPayServer.Hosting
|
||||
services.TryAddSingleton<CurrencyNameTable>();
|
||||
services.TryAddSingleton<IFeeProviderFactory>(o => new NBXplorerFeeProviderFactory(o.GetRequiredService<ExplorerClientProvider>())
|
||||
{
|
||||
Fallback = new FeeRate(100L, 1),
|
||||
BlockTarget = 20
|
||||
Fallback = new FeeRate(100L, 1)
|
||||
});
|
||||
|
||||
services.AddSingleton<CssThemeManager>();
|
||||
@ -269,9 +269,24 @@ namespace BTCPayServer.Hosting
|
||||
var rateLimits = new RateLimitService();
|
||||
rateLimits.SetZone($"zone={ZoneLimits.Login} rate=5r/min burst=3 nodelay");
|
||||
services.AddSingleton(rateLimits);
|
||||
|
||||
|
||||
services.AddLogging(logBuilder =>
|
||||
{
|
||||
var debugLogFile = BTCPayServerOptions.GetDebugLog(configuration);
|
||||
if (!string.IsNullOrEmpty(debugLogFile))
|
||||
{
|
||||
Serilog.Log.Logger = new LoggerConfiguration()
|
||||
.Enrich.FromLogContext()
|
||||
.MinimumLevel.Is(BTCPayServerOptions.GetDebugLogLevel(configuration))
|
||||
.WriteTo.File(debugLogFile, rollingInterval: RollingInterval.Day, fileSizeLimitBytes: MAX_DEBUG_LOG_FILE_SIZE, rollOnFileSizeLimit: true, retainedFileCountLimit: 1)
|
||||
.CreateLogger();
|
||||
logBuilder.AddSerilog(Serilog.Log.Logger);
|
||||
}
|
||||
});
|
||||
return services;
|
||||
}
|
||||
|
||||
private const long MAX_DEBUG_LOG_FILE_SIZE = 2000000; // If debug log is in use roll it every N MB.
|
||||
private static void AddBtcPayServerAuthenticationSchemes(this IServiceCollection services,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
|
@ -222,6 +222,7 @@ namespace BTCPayServer.Hosting
|
||||
|
||||
private static void ConfigureCore(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider prov, ILoggerFactory loggerFactory, BTCPayServerOptions options)
|
||||
{
|
||||
Logs.Configure(loggerFactory);
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
|
@ -10,6 +10,7 @@ using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Logging;
|
||||
using System.Threading;
|
||||
using Npgsql;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
@ -19,16 +20,19 @@ namespace BTCPayServer
|
||||
private StoreRepository _StoreRepository;
|
||||
private BTCPayNetworkProvider _NetworkProvider;
|
||||
private SettingsRepository _Settings;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
public MigrationStartupTask(
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
StoreRepository storeRepository,
|
||||
ApplicationDbContextFactory dbContextFactory,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
SettingsRepository settingsRepository)
|
||||
{
|
||||
_DBContextFactory = dbContextFactory;
|
||||
_StoreRepository = storeRepository;
|
||||
_NetworkProvider = networkProvider;
|
||||
_Settings = settingsRepository;
|
||||
_userManager = userManager;
|
||||
}
|
||||
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
@ -72,6 +76,15 @@ namespace BTCPayServer
|
||||
settings.ConvertWalletKeyPathRoots = true;
|
||||
await _Settings.UpdateSetting(settings);
|
||||
}
|
||||
if (!settings.CheckedFirstRun)
|
||||
{
|
||||
var themeSettings = await _Settings.GetSettingAsync<ThemeSettings>() ?? new ThemeSettings();
|
||||
var admin = await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin);
|
||||
themeSettings.FirstRun = admin.Count == 0;
|
||||
await _Settings.UpdateSetting(themeSettings);
|
||||
settings.CheckedFirstRun = true;
|
||||
await _Settings.UpdateSetting(settings);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Models.AccountViewModels
|
||||
{
|
||||
public class ExternalLoginViewModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
}
|
||||
}
|
@ -11,7 +11,6 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
public class ViewCrowdfundViewModel
|
||||
{
|
||||
public string HubPath { get; set; }
|
||||
public string StatusMessage{ get; set; }
|
||||
public string StoreId { get; set; }
|
||||
public string AppId { get; set; }
|
||||
public string Title { get; set; }
|
||||
|
@ -49,11 +49,6 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
public string Destination { get; set; }
|
||||
public bool Current { get; set; }
|
||||
}
|
||||
|
||||
public string StatusMessage
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public String Id
|
||||
{
|
||||
get; set;
|
||||
|
@ -13,9 +13,8 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
public int Total { get; set; }
|
||||
public string SearchTerm { get; set; }
|
||||
public int? TimezoneOffset { get; set; }
|
||||
|
||||
public List<InvoiceModel> Invoices { get; set; } = new List<InvoiceModel>();
|
||||
public string StatusMessage { get; set; }
|
||||
public string[] StoreIds { get; set; }
|
||||
}
|
||||
|
||||
public class InvoiceModel
|
||||
@ -34,7 +33,6 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
public bool ShowCheckout { get; set; }
|
||||
public string ExceptionStatus { get; set; }
|
||||
public string AmountCurrency { get; set; }
|
||||
public string StatusMessage { get; set; }
|
||||
|
||||
public InvoiceDetailsModel Details { get; set; }
|
||||
}
|
||||
|
@ -78,5 +78,6 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
public string RootPath { get; set; }
|
||||
public decimal CoinSwitchAmountMarkupPercentage { get; set; }
|
||||
public bool RedirectAutomatically { get; set; }
|
||||
public string RateBaseAmount { get; set; } = "1";
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,5 @@ namespace BTCPayServer.Models.ManageViewModels
|
||||
[Display(Name = "Confirm new password")]
|
||||
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
|
||||
public string StatusMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BTCPayServer.Models.ManageViewModels
|
||||
{
|
||||
public class ExternalLoginsViewModel
|
||||
{
|
||||
public IList<UserLoginInfo> CurrentLogins { get; set; }
|
||||
|
||||
public IList<AuthenticationScheme> OtherLogins { get; set; }
|
||||
|
||||
public bool ShowRemoveButton { get; set; }
|
||||
|
||||
public string StatusMessage { get; set; }
|
||||
}
|
||||
}
|
@ -27,10 +27,5 @@ namespace BTCPayServer.Models.ManageViewModels
|
||||
[MaxLength(50)]
|
||||
public string PhoneNumber { get; set; }
|
||||
|
||||
public string StatusMessage
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,5 @@ namespace BTCPayServer.Models.ManageViewModels
|
||||
[Display(Name = "Confirm new password")]
|
||||
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
|
||||
public string StatusMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||
|
||||
public List<ViewPaymentRequestViewModel> Items { get; set; }
|
||||
|
||||
public string StatusMessage { get; set; }
|
||||
public int Total { get; set; }
|
||||
}
|
||||
|
||||
@ -61,7 +60,6 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||
public DateTime? ExpiryDate { get; set; }
|
||||
[Required] public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string StatusMessage { get; set; }
|
||||
|
||||
public SelectList Stores { get; set; }
|
||||
[EmailAddress]
|
||||
@ -142,7 +140,6 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||
public bool AnyPendingInvoice { get; set; }
|
||||
public bool PendingInvoiceHasPayments { get; set; }
|
||||
public string HubPath { get; set; }
|
||||
public string StatusMessage { get; set; }
|
||||
|
||||
public class PaymentRequestInvoice
|
||||
{
|
||||
|
14
BTCPayServer/Models/PostRedictViewModel.cs
Normal file
14
BTCPayServer/Models/PostRedictViewModel.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Models
|
||||
{
|
||||
public class PostRedirectViewModel
|
||||
{
|
||||
public string AspAction { get; set; }
|
||||
public string AspController { get; set; }
|
||||
public List<KeyValuePair<string,string>> Parameters { get; set; } = new List<KeyValuePair<string, string>>();
|
||||
}
|
||||
}
|
@ -10,10 +10,6 @@ namespace BTCPayServer.Models.ServerViewModels
|
||||
{
|
||||
public class EmailsViewModel
|
||||
{
|
||||
public string StatusMessage
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public EmailSettings Settings
|
||||
{
|
||||
get; set;
|
||||
|
@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Models.ServerViewModels
|
||||
{
|
||||
public class LndRestServicesViewModel
|
||||
{
|
||||
public string BaseApiUrl { get; set; }
|
||||
public string Macaroon { get; set; }
|
||||
public string CertificateThumbprint { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Models.ServerViewModels
|
||||
{
|
||||
public class LndSeedBackupViewModel
|
||||
{
|
||||
public bool IsWalletUnlockPresent { get; set; }
|
||||
|
||||
public string WalletPassword { get; set; }
|
||||
|
||||
public string Seed { get; set; }
|
||||
|
||||
public bool Removed { get; set; }
|
||||
public async Task<bool> RemoveSeedAndWrite(string lndSeedFilePath)
|
||||
{
|
||||
var removedDate = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ssZ", CultureInfo.InvariantCulture);
|
||||
var seedFile = new LndSeedFile
|
||||
{
|
||||
wallet_password = "",
|
||||
cipher_seed_mnemonic = new List<string> { $"Seed removed on {removedDate}" }
|
||||
};
|
||||
var json = JsonConvert.SerializeObject(seedFile);
|
||||
try
|
||||
{
|
||||
await File.WriteAllTextAsync(lndSeedFilePath, json);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// file access exception and such
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static LndSeedBackupViewModel Parse(string lndSeedFilePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!String.IsNullOrEmpty(lndSeedFilePath) && File.Exists(lndSeedFilePath))
|
||||
{
|
||||
var unlockFileContents = File.ReadAllText(lndSeedFilePath);
|
||||
var unlockFile = JsonConvert.DeserializeObject<LndSeedFile>(unlockFileContents);
|
||||
|
||||
if (unlockFile.wallet_password != null)
|
||||
{
|
||||
return new LndSeedBackupViewModel
|
||||
{
|
||||
WalletPassword = unlockFile.wallet_password,
|
||||
Seed = string.Join(' ', unlockFile.cipher_seed_mnemonic),
|
||||
IsWalletUnlockPresent = true,
|
||||
Removed = unlockFile.cipher_seed_mnemonic.Count == 1
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return new LndSeedBackupViewModel();
|
||||
}
|
||||
|
||||
private class LndSeedFile
|
||||
{
|
||||
public string wallet_password { get; set; }
|
||||
public List<string> cipher_seed_mnemonic { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BTCPayServer.Models.ServerViewModels
|
||||
{
|
||||
public class LndGrpcServicesViewModel
|
||||
public class LndServicesViewModel
|
||||
{
|
||||
public string Host { get; set; }
|
||||
public bool SSL { get; set; }
|
@ -5,12 +5,6 @@ namespace BTCPayServer.Models.ServerViewModels
|
||||
{
|
||||
public class LogsViewModel
|
||||
{
|
||||
|
||||
public string StatusMessage
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public List<FileInfo> LogFiles { get; set; } = new List<FileInfo>();
|
||||
public string Log { get; set; }
|
||||
public int LogFileCount { get; set; }
|
||||
|
@ -12,6 +12,5 @@ namespace BTCPayServer.Models.ServerViewModels
|
||||
public string Email { get; set; }
|
||||
[Display(Name = "Is admin")]
|
||||
public bool IsAdmin { get; set; }
|
||||
public string StatusMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ namespace BTCPayServer.Models.ServerViewModels
|
||||
public int Skip { get; set; }
|
||||
public int Count { get; set; }
|
||||
public int Total { get; set; }
|
||||
public string StatusMessage { get; set; }
|
||||
|
||||
public List<UserViewModel> Users { get; set; } = new List<UserViewModel>();
|
||||
}
|
||||
|
@ -10,32 +10,6 @@ namespace BTCPayServer.Models
|
||||
{
|
||||
}
|
||||
|
||||
public StatusMessageModel(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s))
|
||||
return;
|
||||
try
|
||||
{
|
||||
if (s.StartsWith("{", StringComparison.InvariantCultureIgnoreCase) &&
|
||||
s.EndsWith("}", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var model = JObject.Parse(s).ToObject<StatusMessageModel>();
|
||||
Html = model.Html;
|
||||
Message = model.Message;
|
||||
Severity = model.Severity;
|
||||
AllowDismiss = model.AllowDismiss;
|
||||
}
|
||||
else
|
||||
{
|
||||
ParseNonJsonStatus(s);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ParseNonJsonStatus(s);
|
||||
}
|
||||
}
|
||||
|
||||
public string Message { get; set; }
|
||||
public string Html { get; set; }
|
||||
public StatusSeverity Severity { get; set; }
|
||||
@ -60,11 +34,6 @@ namespace BTCPayServer.Models
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return JObject.FromObject(this).ToString(Formatting.None);
|
||||
}
|
||||
|
||||
private void ParseNonJsonStatus(string s)
|
||||
{
|
||||
|
@ -27,10 +27,8 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
public string DefaultLang { get; set; }
|
||||
|
||||
[Display(Name = "Link to a custom CSS stylesheet")]
|
||||
[Uri]
|
||||
public string CustomCSS { get; set; }
|
||||
[Display(Name = "Link to a custom logo")]
|
||||
[Uri]
|
||||
public string CustomLogo { get; set; }
|
||||
|
||||
[Display(Name = "Custom HTML title to display on Checkout page")]
|
||||
@ -42,6 +40,10 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
[Display(Name = "Show recommended fee")]
|
||||
public bool ShowRecommendedFee { get; set; }
|
||||
|
||||
[Display(Name = "Recommended fee confirmation target blocks")]
|
||||
[Range(1, double.PositiveInfinity)]
|
||||
public int RecommendedFeeBlockTarget { get; set; }
|
||||
|
||||
[Display(Name = "Do not propose on chain payment if the value of the invoice is below...")]
|
||||
[MaxLength(20)]
|
||||
public string OnChainMinValue { get; set; }
|
||||
|
@ -32,7 +32,6 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
public bool Confirmation { get; set; }
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
public string StatusMessage { get; internal set; }
|
||||
public KeyPath RootKeyPath { get; set; }
|
||||
|
||||
[Display(Name = "Coldcard Wallet File")]
|
||||
|
@ -21,7 +21,6 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string StatusMessage { get; set; }
|
||||
public string InternalLightningNode { get; internal set; }
|
||||
public bool SkipPortTest { get; set; }
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
@ -53,11 +53,6 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string StatusMessage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Display(Name = "API Key")]
|
||||
public string ApiKey { get; set; }
|
||||
|
@ -27,7 +27,5 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
public decimal AmountMarkupPercentage { get; set; } = new decimal(2);
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public string StatusMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,5 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
new SelectListItem { Value = "popup", Text = "Open in a popup" },
|
||||
new SelectListItem { Value = "inline", Text = "Embed inside Checkout UI " },
|
||||
};
|
||||
|
||||
public string StatusMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,5 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||
public string WebsocketPath { get; set; }
|
||||
public string PSBT { get; set; }
|
||||
public string HintChange { get; set; }
|
||||
public string SuccessPath { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using NBitcoin;
|
||||
using NBXplorer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Payments.Bitcoin
|
||||
@ -22,7 +23,8 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
return NextNetworkFee.ToDecimal(MoneyUnit.BTC);
|
||||
}
|
||||
|
||||
public decimal GetFeeRate() {
|
||||
public decimal GetFeeRate()
|
||||
{
|
||||
return FeeRate.SatoshiPerByte;
|
||||
}
|
||||
|
||||
@ -32,6 +34,21 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
}
|
||||
public Data.NetworkFeeMode NetworkFeeMode { get; set; }
|
||||
|
||||
FeeRate _NetworkFeeRate;
|
||||
[JsonConverter(typeof(FeeRateJsonConverter))]
|
||||
public FeeRate NetworkFeeRate
|
||||
{
|
||||
get
|
||||
{
|
||||
// Some old invoices don't have this field set, so we fallback on FeeRate
|
||||
return _NetworkFeeRate ?? FeeRate;
|
||||
}
|
||||
set
|
||||
{
|
||||
_NetworkFeeRate = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Those properties are JsonIgnore because their data is inside CryptoData class for legacy reason
|
||||
[JsonIgnore]
|
||||
public FeeRate FeeRate { get; set; }
|
||||
|
@ -34,6 +34,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
class Prepare
|
||||
{
|
||||
public Task<FeeRate> GetFeeRate;
|
||||
public Task<FeeRate> GetNetworkFeeRate;
|
||||
public Task<BitcoinAddress> ReserveAddress;
|
||||
}
|
||||
|
||||
@ -101,9 +102,12 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
public override object PreparePayment(DerivationSchemeSettings supportedPaymentMethod, StoreData store,
|
||||
BTCPayNetworkBase network)
|
||||
{
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
return new Prepare()
|
||||
{
|
||||
GetFeeRate = _FeeRateProviderFactory.CreateFeeProvider(network).GetFeeRateAsync(),
|
||||
GetFeeRate = _FeeRateProviderFactory.CreateFeeProvider(network).GetFeeRateAsync(storeBlob.RecommendedFeeBlockTarget),
|
||||
GetNetworkFeeRate = storeBlob.NetworkFeeMode == NetworkFeeMode.Never ? null
|
||||
: _FeeRateProviderFactory.CreateFeeProvider(network).GetFeeRateAsync(),
|
||||
ReserveAddress = _WalletProvider.GetWallet(network)
|
||||
.ReserveAddressAsync(supportedPaymentMethod.AccountDerivation)
|
||||
};
|
||||
@ -125,12 +129,17 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
switch (onchainMethod.NetworkFeeMode)
|
||||
{
|
||||
case NetworkFeeMode.Always:
|
||||
onchainMethod.NextNetworkFee = onchainMethod.FeeRate.GetFee(100); // assume price for 100 bytes
|
||||
onchainMethod.NetworkFeeRate = (await prepare.GetNetworkFeeRate);
|
||||
onchainMethod.NextNetworkFee = onchainMethod.NetworkFeeRate.GetFee(100); // assume price for 100 bytes
|
||||
break;
|
||||
case NetworkFeeMode.Never:
|
||||
case NetworkFeeMode.MultiplePaymentsOnly:
|
||||
onchainMethod.NetworkFeeRate = FeeRate.Zero;
|
||||
onchainMethod.NextNetworkFee = Money.Zero;
|
||||
break;
|
||||
case NetworkFeeMode.MultiplePaymentsOnly:
|
||||
onchainMethod.NetworkFeeRate = (await prepare.GetNetworkFeeRate);
|
||||
onchainMethod.NextNetworkFee = Money.Zero;
|
||||
break;
|
||||
}
|
||||
onchainMethod.DepositAddress = (await prepare.ReserveAddress).ToString();
|
||||
return onchainMethod;
|
||||
|
@ -185,6 +185,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
model.BtcPaid = Money.Parse(model.BtcPaid).ToUnit(MoneyUnit.Satoshi).ToString(CultureInfo.InvariantCulture);
|
||||
model.NetworkFee = new Money(model.NetworkFee, MoneyUnit.BTC).ToUnit(MoneyUnit.Satoshi);
|
||||
model.OrderAmount = Money.Parse(model.OrderAmount).ToUnit(MoneyUnit.Satoshi).ToString(CultureInfo.InvariantCulture);
|
||||
model.RateBaseAmount = Money.FromUnit(1, MoneyUnit.BTC).Satoshi.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
public override string GetCryptoImage(PaymentMethodId paymentMethodId)
|
||||
|
@ -54,6 +54,7 @@ namespace BTCPayServer.Payments
|
||||
throw new ArgumentNullException(nameof(txId));
|
||||
if (network?.BlockExplorerLink == null)
|
||||
return null;
|
||||
txId = txId.Split('-').First();
|
||||
return string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, txId);
|
||||
}
|
||||
public override string InvoiceViewPaymentPartialName { get; } = "ViewBitcoinLikePaymentData";
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user