Compare commits

..

69 Commits

Author SHA1 Message Date
e7c06880a8 Use API keys of bitcoinaverage for getting the exchange list 2018-04-23 15:48:18 +09:00
39463a3202 Merge pull request #132 from lepipele/master
Removing empty folder, fixing build warnings
2018-04-23 12:39:10 +09:00
36136f0f0f Removing empty folder, fixing build warnings 2018-04-22 22:30:37 -05:00
22e5b2869a bump 2018-04-20 12:28:58 +09:00
fc3f32a4e0 Merge branch 'dev-bootstrap' of https://github.com/lepipele/btcpayserver into lepipele-dev-bootstrap 2018-04-20 12:17:53 +09:00
3b0914e89e Migrating ManageNavPages to new navigation enum 2018-04-19 15:57:23 -05:00
76cd9a7b25 Abstracting navigation so it can use any enums 2018-04-19 15:42:12 -05:00
0934bebf7b Merge remote-tracking branch 'source/master' into dev-bootstrap 2018-04-19 11:45:30 -05:00
cd1a4c4749 Fixing modify user page and it's title 2018-04-19 11:44:24 -05:00
8075273ec8 Refactoring pills navigation 2018-04-19 11:40:12 -05:00
97b59be9bf Adding page for Theme settings 2018-04-19 11:39:51 -05:00
b87ec4f3d9 Primitive versioning of css files to ensure update on change 2018-04-19 11:15:45 -05:00
3822358096 Show more info about bitcoin average quota 2018-04-20 01:01:39 +09:00
ba7e8cfe78 Removing Merriweather as default body font, back to Arial
Ref: https://forkbitpay.slack.com/archives/C6PSCRFAM/p1524130675000104
2018-04-19 10:04:59 -05:00
41978f1c59 Remove useless line in invoice.cshtml 2018-04-19 18:39:39 +09:00
e75e691404 Merge branch 'dev-bootstrap' of https://github.com/lepipele/btcpayserver into lepipele-dev-bootstrap 2018-04-19 18:03:04 +09:00
a22216fd04 fix layout 2018-04-19 17:06:08 +09:00
6900e16aa4 bump 2018-04-19 16:54:47 +09:00
10c981b2a0 Update NBXplorer 2018-04-19 16:54:25 +09:00
5f940df1b4 Migrating Invoice styling 2018-04-18 23:44:01 -05:00
3f85918a0c Merge remote-tracking branch 'source/master' into dev-bootstrap
# Conflicts:
#	BTCPayServer/Controllers/ServerController.cs
#	BTCPayServer/Views/Invoice/Invoice.cshtml
2018-04-18 23:38:10 -05:00
e4299c09ea bump 2018-04-18 22:28:31 +09:00
e864cf35f7 bump NBitcoin 2018-04-18 22:28:04 +09:00
3652866660 View offchain payments in Invoice screen 2018-04-18 22:27:01 +09:00
0421004616 fix point of sale view on mobile 2018-04-18 21:52:13 +09:00
6936b034cb Add Bitcoin average quota 2018-04-18 18:23:39 +09:00
73ed4003a3 Use a drop down for preferred exchange list 2018-04-18 16:38:56 +09:00
5cb8cdd511 Refactoring: Do not query database when asking for Coinaverage rates, periodically get exchange list 2018-04-18 16:38:56 +09:00
195b5fdd1a Adding overriding of CreativeStartUri, refactoring PoliciesSettings 2018-04-17 17:24:00 -05:00
d19b78b6cc Moving Creative Start files to dedicated folder 2018-04-17 17:23:33 -05:00
9bbc05c3a7 Cleaning Invoice table, removing style attrs
Ref: https://github.com/btcpayserver/btcpayserver/issues/82

Co-authored-by: Esky33 <support@btcpayjungle.com>
2018-04-17 16:48:50 -05:00
7df3c86649 Tweaking primary color now that creative.css no longer overrides 2018-04-17 16:29:05 -05:00
c6e0a923bb Unifying bg-dark style, cleaning up references to extra colors 2018-04-17 16:22:20 -05:00
637fe1727b Adding missing font styles back in
These are referenced by Creative - Start Bootstrap theme
2018-04-17 16:12:17 -05:00
fd087bbeb8 Streamlining style for footer 2018-04-17 15:33:29 -05:00
2f515e1cc0 Removing unused classes 2018-04-17 15:20:27 -05:00
84cd9e570f Merge pull request #128 from pajasevi/cs-trancaction-count
Transaction count CS translation
2018-04-17 11:31:29 +09:00
ead97a24bd Update NBitcoin 2018-04-16 19:15:44 +09:00
415cde1629 Transaction count CS translation 2018-04-16 09:11:46 +02:00
b438312fde fix js 2018-04-16 11:38:10 +09:00
79ff2cb271 Merge pull request #127 from bitmario/master
Add Portuguese (Portugal) translation
2018-04-16 11:28:54 +09:00
ed1464c405 Merge pull request #125 from LinoxBE/dutch-translation
Dutch update txCount
2018-04-16 11:28:10 +09:00
f85631429b Merge pull request #126 from mutedstorm/patch-2
fix german translation
2018-04-16 11:27:41 +09:00
5ed56d1137 Update JA translations 2018-04-16 11:26:29 +09:00
d7719d25b4 Add Portuguese (Portugal) translation 2018-04-16 01:29:42 +01:00
6267cccc3f fix german translation
minor changes, thanks to (@raindogdance)
2018-04-15 22:47:08 +02:00
fd5c4021f7 Dutch update txCount 2018-04-15 20:00:11 +02:00
b8bf4d99ac Bump 2018-04-15 21:29:44 +09:00
0723eec508 Fix rate handling 2018-04-15 21:21:57 +09:00
7f01a12245 Merge pull request #124 from mutedstorm/patch-1
fix german translation
2018-04-15 21:20:06 +09:00
e1e3e5d953 fix german translation
fixed small errors and changed "Geldbörse / Brieftasche" back to Wallet because its never translated on German sites so its unnecessary.
2018-04-15 12:32:24 +02:00
18986faca8 Merge remote-tracking branch 'source/master' into dev-bootstrap
# Conflicts:
#	BTCPayServer/Controllers/ServerController.cs
2018-04-14 11:11:38 -05:00
2a68f8a90f Merge pull request #121 from lepipele/master
Adding German translations
2018-04-14 10:54:04 -05:00
659936577b Adding German translations
Again using Google Translate, we need native speaker to review them
2018-04-14 10:53:02 -05:00
85efc3b00c fix tests 2018-04-14 23:32:39 +09:00
5efac45d46 bump 2018-04-14 22:55:35 +09:00
c7dce280d7 fix js 2018-04-14 22:53:31 +09:00
04c6107196 Can configure rate caching and bitcoinaverage API keys 2018-04-14 22:52:57 +09:00
54ce9b5dab Merge pull request #120 from rsandrade/patch-2
Update pt_BR.js
2018-04-14 21:40:38 +09:00
cee955fb9d Update pt_BR.js 2018-04-14 07:48:31 -03:00
2e4b0daa48 add french translation, bump NBitcoin 2018-04-14 13:18:56 +09:00
e85ccfb47e Merge pull request #117 from lepipele/master
Improvements to i18n, invoice expiry bugfix
2018-04-14 13:06:45 +09:00
b099f93c78 Adjusting Policies form to look better on different screen sizes 2018-04-13 16:15:21 -05:00
81afe397be CssThemeManager that injects Bootstrap css uri from settings 2018-04-13 16:15:03 -05:00
f869c06aee Adding Bootstrap theme uri field to settings 2018-04-13 15:42:34 -05:00
75099b99d4 TxCount strings in Spanish 2018-04-13 14:44:42 -05:00
7b1b2a0f68 Bugfixing redirect link when invoice expires
Refactoring logic so that it's same for paid and expired
2018-04-13 14:39:45 -05:00
203c28df3d Extracting transaction string and supporting plural form 2018-04-13 14:10:06 -05:00
2e2c3cdec4 bump 2018-04-14 00:06:00 +09:00
89 changed files with 2036 additions and 724 deletions

View File

@ -47,7 +47,7 @@ namespace BTCPayServer.Tests
}
public Uri LTCNBXplorerUri { get; set; }
public Uri ServerUri
{
get;
@ -65,11 +65,14 @@ namespace BTCPayServer.Tests
get; set;
}
public bool MockRates { get; set; } = true;
public void Start()
{
if (!Directory.Exists(_Directory))
Directory.CreateDirectory(_Directory);
string chain = ChainType.Regtest.ToNetwork().Name;
string chain = NBXplorerDefaultSettings.GetFolderName(NetworkType.Regtest);
string chainDirectory = Path.Combine(_Directory, chain);
if (!Directory.Exists(chainDirectory))
Directory.CreateDirectory(chainDirectory);
@ -101,12 +104,15 @@ namespace BTCPayServer.Tests
.UseConfiguration(conf)
.ConfigureServices(s =>
{
var mockRates = new MockRateProviderFactory();
var btc = new MockRateProvider("BTC", new Rate("USD", 5000m), new Rate("CAD", 4500m));
var ltc = new MockRateProvider("LTC", new Rate("USD", 500m));
mockRates.AddMock(btc);
mockRates.AddMock(ltc);
s.AddSingleton<IRateProviderFactory>(mockRates);
if (MockRates)
{
var mockRates = new MockRateProviderFactory();
var btc = new MockRateProvider("BTC", new Rate("USD", 5000m), new Rate("CAD", 4500m));
var ltc = new MockRateProvider("LTC", new Rate("USD", 500m));
mockRates.AddMock(btc);
mockRates.AddMock(ltc);
s.AddSingleton<IRateProviderFactory>(mockRates);
}
s.AddLogging(l =>
{
l.SetMinimumLevel(LogLevel.Information)
@ -121,7 +127,7 @@ namespace BTCPayServer.Tests
_Host.Start();
InvoiceRepository = (InvoiceRepository)_Host.Services.GetService(typeof(InvoiceRepository));
}
public string HostName
{
get;

View File

@ -34,22 +34,12 @@ namespace BTCPayServer.Tests
public ServerTester(string scope)
{
_Directory = scope;
}
public bool Dockerized
{
get; set;
}
public void Start()
{
if (Directory.Exists(_Directory))
Utils.DeleteDirectory(_Directory);
if (!Directory.Exists(_Directory))
Directory.CreateDirectory(_Directory);
NetworkProvider = new BTCPayNetworkProvider(ChainType.Regtest);
NetworkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_BTCRPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork("BTC").NBitcoinNetwork);
LTCExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_LTCRPCCONNECTION", "server=http://127.0.0.1:43783;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork("LTC").NBitcoinNetwork);
@ -72,6 +62,15 @@ namespace BTCPayServer.Tests
PayTester.Port = int.Parse(GetEnvironment("TESTS_PORT", Utils.FreeTcpPort().ToString(CultureInfo.InvariantCulture)), CultureInfo.InvariantCulture);
PayTester.HostName = GetEnvironment("TESTS_HOSTNAME", "127.0.0.1");
PayTester.InContainer = bool.Parse(GetEnvironment("TESTS_INCONTAINER", "false"));
}
public bool Dockerized
{
get; set;
}
public void Start()
{
PayTester.Start();
}

View File

@ -616,12 +616,54 @@ namespace BTCPayServer.Tests
}
}
[Fact]
public void CanUseExchangeSpecificRate()
{
using (var tester = ServerTester.Create())
{
tester.PayTester.MockRates = false;
tester.Start();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
List<decimal> rates = new List<decimal>();
rates.Add(CreateInvoice(tester, user, "coinaverage"));
var bitflyer = CreateInvoice(tester, user, "bitflyer");
var bitflyer2 = CreateInvoice(tester, user, "bitflyer");
Assert.Equal(bitflyer, bitflyer2); // Should be equal because cache
rates.Add(bitflyer);
foreach(var rate in rates)
{
Assert.Single(rates.Where(r => r == rate));
}
}
}
private static decimal CreateInvoice(ServerTester tester, TestAccount user, string exchange)
{
var storeController = tester.PayTester.GetController<StoresController>(user.UserId);
var vm = (StoreViewModel)((ViewResult)storeController.UpdateStore(user.StoreId).Result).Model;
vm.PreferredExchange = exchange;
storeController.UpdateStore(user.StoreId, vm).Wait();
var invoice2 = user.BitPay.CreateInvoice(new Invoice()
{
Price = 5000.0,
Currency = "USD",
PosData = "posData",
OrderId = "orderId",
ItemDesc = "Some description",
FullNotifications = true
}, Facade.Merchant);
return invoice2.CryptoInfo[0].Rate;
}
[Fact]
public void CanTweakRate()
{
using (var tester = ServerTester.Create())
{
tester.PayTester.MockRates = false;
tester.Start();
var user = tester.NewAccount();
user.GrantAccess();
@ -831,7 +873,7 @@ namespace BTCPayServer.Tests
[Fact]
public void CanParseDerivationScheme()
{
var parser = new DerivationSchemeParser(Network.TestNet, NBXplorer.ChainType.Test);
var parser = new DerivationSchemeParser(Network.TestNet);
NBXplorer.DerivationStrategy.DerivationStrategyBase result;
// Passing electrum stuff
// Native

View File

@ -46,7 +46,7 @@ services:
- lightning-charged
nbxplorer:
image: nicolasdorier/nbxplorer:1.0.1.32
image: nicolasdorier/nbxplorer:1.0.2.0
ports:
- "32838:32838"
expose:

View File

@ -14,34 +14,28 @@ namespace BTCPayServer
{
static BTCPayDefaultSettings()
{
_Settings = new Dictionary<ChainType, BTCPayDefaultSettings>();
foreach (var chainType in new[] { ChainType.Main, ChainType.Test, ChainType.Regtest })
_Settings = new Dictionary<NetworkType, BTCPayDefaultSettings>();
foreach (var chainType in new[] { NetworkType.Mainnet, NetworkType.Testnet, NetworkType.Regtest })
{
var btcNetwork = (chainType == ChainType.Main ? Network.Main :
chainType == ChainType.Regtest ? Network.RegTest :
chainType == ChainType.Test ? Network.TestNet : throw new NotSupportedException(chainType.ToString()));
var settings = new BTCPayDefaultSettings();
_Settings.Add(chainType, settings);
settings.ChainType = chainType;
settings.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", btcNetwork.Name);
settings.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", NBXplorerDefaultSettings.GetFolderName(chainType));
settings.DefaultConfigurationFile = Path.Combine(settings.DefaultDataDirectory, "settings.config");
settings.DefaultPort = (chainType == ChainType.Main ? 23000 :
chainType == ChainType.Regtest ? 23002 :
chainType == ChainType.Test ? 23001 : throw new NotSupportedException(chainType.ToString()));
settings.DefaultPort = (chainType == NetworkType.Mainnet ? 23000 :
chainType == NetworkType.Regtest ? 23002 :
chainType == NetworkType.Testnet ? 23001 : throw new NotSupportedException(chainType.ToString()));
}
}
static Dictionary<ChainType, BTCPayDefaultSettings> _Settings;
static Dictionary<NetworkType, BTCPayDefaultSettings> _Settings;
public static BTCPayDefaultSettings GetDefaultSettings(ChainType chainType)
public static BTCPayDefaultSettings GetDefaultSettings(NetworkType chainType)
{
return _Settings[chainType];
}
public string DefaultDataDirectory { get; set; }
public string DefaultConfigurationFile { get; set; }
public ChainType ChainType { get; internal set; }
public int DefaultPort { get; set; }
}
public class BTCPayNetwork
@ -50,7 +44,7 @@ namespace BTCPayServer
public string CryptoCode { get; internal set; }
public string BlockExplorerLink { get; internal set; }
public string UriScheme { get; internal set; }
public IRateProvider DefaultRateProvider { get; set; }
public RateProviderDescription DefaultRateProvider { get; set; }
[Obsolete("Should not be needed")]
public bool IsBTC

View File

@ -14,21 +14,21 @@ namespace BTCPayServer
public void InitBitcoin()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("BTC");
var coinaverage = new CoinAverageRateProvider("BTC");
var bitpay = new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/")));
var btcRate = new FallbackRateProvider(new IRateProvider[] { coinaverage, bitpay });
var coinaverage = new CoinAverageRateProviderDescription("BTC");
var bitpay = new BitpayRateProviderDescription();
var btcRate = new FallbackRateProviderDescription(new RateProviderDescription[] { coinaverage, bitpay });
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
BlockExplorerLink = NBXplorerNetworkProvider.ChainType == ChainType.Main ? "https://www.smartbit.com.au/tx/{0}" : "https://testnet.smartbit.com.au/tx/{0}",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://www.smartbit.com.au/tx/{0}" : "https://testnet.smartbit.com.au/tx/{0}",
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "bitcoin",
DefaultRateProvider = btcRate,
CryptoImagePath = "imlegacy/bitcoin-symbol.svg",
LightningImagePath = "imlegacy/btc-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NBXplorerNetworkProvider.ChainType),
CoinType = NBXplorerNetworkProvider.ChainType == ChainType.Main ? new KeyPath("0'") : new KeyPath("1'")
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("0'") : new KeyPath("1'")
});
}
}

View File

@ -12,21 +12,18 @@ namespace BTCPayServer
{
public void InitDogecoin()
{
NBitcoin.Altcoins.Dogecoin.EnsureRegistered();
var ltcRate = new CoinAverageRateProvider("DOGE");
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("DOGE");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
BlockExplorerLink = NBXplorerNetworkProvider.ChainType == ChainType.Main ? "https://dogechain.info/tx/{0}" : "https://dogechain.info/tx/{0}",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://dogechain.info/tx/{0}" : "https://dogechain.info/tx/{0}",
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "dogecoin",
DefaultRateProvider = ltcRate,
DefaultRateProvider = new CoinAverageRateProviderDescription("DOGE"),
CryptoImagePath = "imlegacy/dogecoin.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NBXplorerNetworkProvider.ChainType),
CoinType = NBXplorerNetworkProvider.ChainType == ChainType.Main ? new KeyPath("3'") : new KeyPath("1'")
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("3'") : new KeyPath("1'")
});
}
}

View File

@ -12,22 +12,19 @@ namespace BTCPayServer
{
public void InitLitecoin()
{
NBitcoin.Altcoins.Litecoin.EnsureRegistered();
var ltcRate = new CoinAverageRateProvider("LTC");
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("LTC");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
BlockExplorerLink = NBXplorerNetworkProvider.ChainType == ChainType.Main ? "https://live.blockcypher.com/ltc/tx/{0}/" : "http://explorer.litecointools.com/tx/{0}",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://live.blockcypher.com/ltc/tx/{0}/" : "http://explorer.litecointools.com/tx/{0}",
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "litecoin",
DefaultRateProvider = ltcRate,
DefaultRateProvider = new CoinAverageRateProviderDescription("LTC"),
CryptoImagePath = "imlegacy/litecoin-symbol.svg",
LightningImagePath = "imlegacy/ltc-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NBXplorerNetworkProvider.ChainType),
CoinType = NBXplorerNetworkProvider.ChainType == ChainType.Main ? new KeyPath("2'") : new KeyPath("1'")
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("2'") : new KeyPath("1'")
});
}
}

View File

@ -27,8 +27,8 @@ namespace BTCPayServer
BTCPayNetworkProvider(BTCPayNetworkProvider filtered, string[] cryptoCodes)
{
ChainType = filtered.ChainType;
_NBXplorerNetworkProvider = new NBXplorerNetworkProvider(filtered.ChainType);
NetworkType = filtered.NetworkType;
_NBXplorerNetworkProvider = new NBXplorerNetworkProvider(filtered.NetworkType);
_Networks = new Dictionary<string, BTCPayNetwork>();
cryptoCodes = cryptoCodes.Select(c => c.ToUpperInvariant()).ToArray();
foreach (var network in filtered._Networks)
@ -40,11 +40,11 @@ namespace BTCPayServer
}
}
public ChainType ChainType { get; set; }
public BTCPayNetworkProvider(ChainType chainType)
public NetworkType NetworkType { get; private set; }
public BTCPayNetworkProvider(NetworkType networkType)
{
_NBXplorerNetworkProvider = new NBXplorerNetworkProvider(chainType);
ChainType = chainType;
_NBXplorerNetworkProvider = new NBXplorerNetworkProvider(networkType);
NetworkType = networkType;
InitBitcoin();
InitLitecoin();
InitDogecoin();

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<Version>1.0.1.80</Version>
<Version>1.0.1.89</Version>
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
</PropertyGroup>
<ItemGroup>
@ -34,15 +34,15 @@
<PackageReference Include="Hangfire" Version="1.6.19" />
<PackageReference Include="Hangfire.MemoryStorage" Version="1.5.2" />
<PackageReference Include="Hangfire.PostgreSql" Version="1.4.8.1" />
<PackageReference Include="LedgerWallet" Version="1.0.1.32" />
<PackageReference Include="LedgerWallet" Version="1.0.1.35" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="2.6.1" />
<PackageReference Include="Meziantou.AspNetCore.BundleTagHelpers" Version="1.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.6.0" />
<PackageReference Include="NBitcoin" Version="4.1.0.9" />
<PackageReference Include="NBitcoin" Version="4.1.1" />
<PackageReference Include="NBitpayClient" Version="1.0.0.18" />
<PackageReference Include="DBreeze" Version="1.87.0" />
<PackageReference Include="NBXplorer.Client" Version="1.0.1.22" />
<PackageReference Include="NBXplorer.Client" Version="1.0.2" />
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.1" />
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.2" />
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.13" />
@ -66,7 +66,7 @@
<ItemGroup>
<None Include="wwwroot\checkout\js\core.js" />
<None Include="wwwroot\main\js\creative.js" />
<None Include="wwwroot\vendor\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" />

View File

@ -23,7 +23,7 @@ namespace BTCPayServer.Configuration
public class BTCPayServerOptions
{
public ChainType ChainType
public NetworkType NetworkType
{
get; set;
}
@ -51,15 +51,15 @@ namespace BTCPayServer.Configuration
public void LoadArgs(IConfiguration conf)
{
ChainType = DefaultConfiguration.GetChainType(conf);
var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(ChainType);
NetworkType = DefaultConfiguration.GetNetworkType(conf);
var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType);
DataDir = conf.GetOrDefault<string>("datadir", defaultSettings.DefaultDataDirectory);
Logs.Configuration.LogInformation("Network: " + ChainType.ToString());
Logs.Configuration.LogInformation("Network: " + NetworkType.ToString());
var supportedChains = conf.GetOrDefault<string>("chains", "btc")
.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(t => t.ToUpperInvariant());
NetworkProvider = new BTCPayNetworkProvider(ChainType).Filter(supportedChains.ToArray());
NetworkProvider = new BTCPayNetworkProvider(NetworkType).Filter(supportedChains.ToArray());
foreach (var chain in supportedChains)
{
if (NetworkProvider.GetNetwork(chain) == null)

View File

@ -18,7 +18,7 @@ namespace BTCPayServer.Configuration
{
protected override CommandLineApplication CreateCommandLineApplicationCore()
{
var provider = new BTCPayNetworkProvider(ChainType.Main);
var provider = new BTCPayNetworkProvider(NetworkType.Mainnet);
var chains = string.Join(",", provider.GetAll().Select(n => n.CryptoCode.ToLowerInvariant()).ToArray());
CommandLineApplication app = new CommandLineApplication(true)
{
@ -48,12 +48,12 @@ namespace BTCPayServer.Configuration
protected override string GetDefaultDataDir(IConfiguration conf)
{
return BTCPayDefaultSettings.GetDefaultSettings(GetChainType(conf)).DefaultDataDirectory;
return BTCPayDefaultSettings.GetDefaultSettings(GetNetworkType(conf)).DefaultDataDirectory;
}
protected override string GetDefaultConfigurationFile(IConfiguration conf)
{
var network = BTCPayDefaultSettings.GetDefaultSettings(GetChainType(conf));
var network = BTCPayDefaultSettings.GetDefaultSettings(GetNetworkType(conf));
var dataDir = conf["datadir"];
if (dataDir == null)
return network.DefaultConfigurationFile;
@ -69,7 +69,7 @@ namespace BTCPayServer.Configuration
return Path.Combine(chainDir, fileName);
}
public static ChainType GetChainType(IConfiguration conf)
public static NetworkType GetNetworkType(IConfiguration conf)
{
var network = conf.GetOrDefault<string>("network", null);
if (network != null)
@ -79,17 +79,18 @@ namespace BTCPayServer.Configuration
{
throw new ConfigException($"Invalid network parameter '{network}'");
}
return n.ToChainType();
return n.NetworkType;
}
var net = conf.GetOrDefault<bool>("regtest", false) ? ChainType.Regtest :
conf.GetOrDefault<bool>("testnet", false) ? ChainType.Test : ChainType.Main;
var net = conf.GetOrDefault<bool>("regtest", false) ? NetworkType.Regtest :
conf.GetOrDefault<bool>("testnet", false) ? NetworkType.Testnet : NetworkType.Mainnet;
return net;
}
protected override string GetDefaultConfigurationFileTemplate(IConfiguration conf)
{
var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(GetChainType(conf));
var networkType = GetNetworkType(conf);
var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(networkType);
StringBuilder builder = new StringBuilder();
builder.AppendLine("### Global settings ###");
builder.AppendLine("#network=mainnet");
@ -102,7 +103,7 @@ namespace BTCPayServer.Configuration
builder.AppendLine("#postgres=User ID=root;Password=myPassword;Host=localhost;Port=5432;Database=myDataBase;");
builder.AppendLine();
builder.AppendLine("### NBXplorer settings ###");
foreach (var n in new BTCPayNetworkProvider(defaultSettings.ChainType).GetAll())
foreach (var n in new BTCPayNetworkProvider(networkType).GetAll())
{
builder.AppendLine($"#{n.CryptoCode}.explorer.url={n.NBXplorerNetwork.DefaultSettings.DefaultUrl}");
builder.AppendLine($"#{n.CryptoCode}.explorer.cookiefile={ n.NBXplorerNetwork.DefaultSettings.DefaultCookieFile}");
@ -116,7 +117,7 @@ namespace BTCPayServer.Configuration
protected override IPEndPoint GetDefaultEndpoint(IConfiguration conf)
{
return new IPEndPoint(IPAddress.Parse("127.0.0.1"), BTCPayDefaultSettings.GetDefaultSettings(GetChainType(conf)).DefaultPort);
return new IPEndPoint(IPAddress.Parse("127.0.0.1"), BTCPayDefaultSettings.GetDefaultSettings(GetNetworkType(conf)).DefaultPort);
}
}
}

View File

@ -85,54 +85,66 @@ namespace BTCPayServer.Controllers
model.CryptoPayments.Add(cryptoPayment);
}
var payments = invoice
var onChainPayments = invoice
.GetPayments()
.Where(p => p.GetPaymentMethodId().PaymentType == PaymentTypes.BTCLike)
.Select(async payment =>
.Select<PaymentEntity, Task<object>>(async payment =>
{
var paymentData = (Payments.Bitcoin.BitcoinLikePaymentData)payment.GetCryptoPaymentData();
var m = new InvoiceDetailsModel.Payment();
var paymentNetwork = _NetworkProvider.GetNetwork(payment.GetCryptoCode());
m.PaymentMethod = ToString(payment.GetPaymentMethodId());
m.DepositAddress = paymentData.Output.ScriptPubKey.GetDestinationAddress(paymentNetwork.NBitcoinNetwork);
int confirmationCount = 0;
if ( (paymentData.ConfirmationCount < paymentNetwork.MaxTrackedConfirmation && payment.Accounted)
&& (paymentData.Legacy || invoice.MonitoringExpiration < DateTimeOffset.UtcNow)) // The confirmation count in the paymentData is not up to date
var paymentData = payment.GetCryptoPaymentData();
if (paymentData is Payments.Bitcoin.BitcoinLikePaymentData onChainPaymentData)
{
confirmationCount = (await ((ExplorerClientProvider)_ServiceProvider.GetService(typeof(ExplorerClientProvider))).GetExplorerClient(payment.GetCryptoCode())?.GetTransactionAsync(paymentData.Outpoint.Hash))?.Confirmations ?? 0;
paymentData.ConfirmationCount = confirmationCount;
payment.SetCryptoPaymentData(paymentData);
await _InvoiceRepository.UpdatePayments(new List<PaymentEntity> { payment });
var m = new InvoiceDetailsModel.Payment();
m.Crypto = payment.GetPaymentMethodId().CryptoCode;
m.DepositAddress = onChainPaymentData.Output.ScriptPubKey.GetDestinationAddress(paymentNetwork.NBitcoinNetwork);
int confirmationCount = 0;
if ((onChainPaymentData.ConfirmationCount < paymentNetwork.MaxTrackedConfirmation && payment.Accounted)
&& (onChainPaymentData.Legacy || invoice.MonitoringExpiration < DateTimeOffset.UtcNow)) // The confirmation count in the paymentData is not up to date
{
confirmationCount = (await ((ExplorerClientProvider)_ServiceProvider.GetService(typeof(ExplorerClientProvider))).GetExplorerClient(payment.GetCryptoCode())?.GetTransactionAsync(onChainPaymentData.Outpoint.Hash))?.Confirmations ?? 0;
onChainPaymentData.ConfirmationCount = confirmationCount;
payment.SetCryptoPaymentData(onChainPaymentData);
await _InvoiceRepository.UpdatePayments(new List<PaymentEntity> { payment });
}
else
{
confirmationCount = onChainPaymentData.ConfirmationCount;
}
if (confirmationCount >= paymentNetwork.MaxTrackedConfirmation)
{
m.Confirmations = "At least " + (paymentNetwork.MaxTrackedConfirmation);
}
else
{
m.Confirmations = confirmationCount.ToString(CultureInfo.InvariantCulture);
}
m.TransactionId = onChainPaymentData.Outpoint.Hash.ToString();
m.ReceivedTime = payment.ReceivedTime;
m.TransactionLink = string.Format(CultureInfo.InvariantCulture, paymentNetwork.BlockExplorerLink, m.TransactionId);
m.Replaced = !payment.Accounted;
return m;
}
else
{
confirmationCount = paymentData.ConfirmationCount;
var lightningPaymentData = (Payments.Lightning.LightningLikePaymentData)paymentData;
return new InvoiceDetailsModel.OffChainPayment()
{
Crypto = paymentNetwork.CryptoCode,
BOLT11 = lightningPaymentData.BOLT11
};
}
if (confirmationCount >= paymentNetwork.MaxTrackedConfirmation)
{
m.Confirmations = "At least " + (paymentNetwork.MaxTrackedConfirmation);
}
else
{
m.Confirmations = confirmationCount.ToString(CultureInfo.InvariantCulture);
}
m.TransactionId = paymentData.Outpoint.Hash.ToString();
m.ReceivedTime = payment.ReceivedTime;
m.TransactionLink = string.Format(CultureInfo.InvariantCulture, paymentNetwork.BlockExplorerLink, m.TransactionId);
m.Replaced = !payment.Accounted;
return m;
})
.ToArray();
await Task.WhenAll(payments);
await Task.WhenAll(onChainPayments);
model.Addresses = invoice.HistoricalAddresses.Select(h => new InvoiceDetailsModel.AddressModel
{
Destination = h.GetAddress(),
PaymentMethod = ToString(h.GetPaymentMethodId()),
Current = !h.UnAssigned.HasValue
}).ToArray();
model.Payments = payments.Select(p => p.GetAwaiter().GetResult()).ToList();
model.OnChainPayments = onChainPayments.Select(p => p.GetAwaiter().GetResult()).OfType<InvoiceDetailsModel.Payment>().ToList();
model.OffChainPayments = onChainPayments.Select(p => p.GetAwaiter().GetResult()).OfType<InvoiceDetailsModel.OffChainPayment>().ToList();
model.StatusMessage = StatusMessage;
return View(model);
}
@ -238,7 +250,8 @@ namespace BTCPayServer.Controllers
BtcPaid = accounting.Paid.ToString(),
Status = invoice.Status,
CryptoImage = "/" + GetImage(paymentMethodId, network),
NetworkFeeDescription = $"{accounting.TxRequired} transaction{(accounting.TxRequired > 1 ? "s" : "")} x {paymentMethodDetails.GetTxFee()} {network.CryptoCode}",
NetworkFee = paymentMethodDetails.GetTxFee(),
IsMultiCurrency = invoice.GetPayments().Select(p => p.GetPaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1,
AllowCoinConversion = storeBlob.AllowCoinConversion,
AvailableCryptos = invoice.GetPaymentMethods(_NetworkProvider)
.Where(i => i.Network != null)
@ -251,12 +264,8 @@ namespace BTCPayServer.Controllers
.ToList()
};
var isMultiCurrency = invoice.GetPayments().Select(p => p.GetPaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1;
if (isMultiCurrency)
model.NetworkFeeDescription = $"{accounting.NetworkFee} {network.CryptoCode}";
var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds);
model.TimeLeft = PrettyPrint(expiration);
model.TimeLeft = expiration.PrettyPrint();
return model;
}
@ -275,17 +284,6 @@ namespace BTCPayServer.Controllers
return price.ToString("C", _CurrencyNameTable.GetCurrencyProvider(currency)) + $" ({currency})";
}
private string PrettyPrint(TimeSpan expiration)
{
StringBuilder builder = new StringBuilder();
if (expiration.Days >= 1)
builder.Append(expiration.Days.ToString(CultureInfo.InvariantCulture));
if (expiration.Hours >= 1)
builder.Append(expiration.Hours.ToString("00", CultureInfo.InvariantCulture));
builder.Append($"{expiration.Minutes.ToString("00", CultureInfo.InvariantCulture)}:{expiration.Seconds.ToString("00", CultureInfo.InvariantCulture)}");
return builder.ToString();
}
[HttpGet]
[Route("i/{invoiceId}/status")]
[Route("i/{invoiceId}/{paymentMethodId}/status")]
@ -376,7 +374,7 @@ namespace BTCPayServer.Controllers
model.Invoices.Add(new InvoiceModel()
{
Status = invoice.Status,
Date = Prettify(invoice.InvoiceTime),
Date = (DateTimeOffset.UtcNow - invoice.InvoiceTime).Prettify() + " ago",
InvoiceId = invoice.Id,
OrderId = invoice.OrderId ?? string.Empty,
RedirectUrl = invoice.RedirectURL ?? string.Empty,
@ -389,30 +387,6 @@ namespace BTCPayServer.Controllers
return View(model);
}
private string Prettify(DateTimeOffset invoiceTime)
{
var ago = DateTime.UtcNow - invoiceTime;
if (ago.TotalMinutes < 1)
{
return $"{(int)ago.TotalSeconds} second{Plural((int)ago.TotalSeconds)} ago";
}
if (ago.TotalHours < 1)
{
return $"{(int)ago.TotalMinutes} minute{Plural((int)ago.TotalMinutes)} ago";
}
if (ago.Days < 1)
{
return $"{(int)ago.TotalHours} hour{Plural((int)ago.TotalHours)} ago";
}
return $"{(int)ago.TotalDays} day{Plural((int)ago.TotalDays)} ago";
}
private string Plural(int totalDays)
{
return totalDays > 1 ? "s" : string.Empty;
}
[HttpGet]
[Route("invoices/create")]
[Authorize(AuthenticationSchemes = "Identity.Application")]
@ -453,7 +427,7 @@ namespace BTCPayServer.Controllers
return View(model);
}
if(StatusMessage != null)
if (StatusMessage != null)
{
return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new
{

View File

@ -165,7 +165,7 @@ namespace BTCPayServer.Controllers
{
var btc = _NetworkProvider.BTC;
var feeProvider = ((IFeeProviderFactory)_ServiceProvider.GetService(typeof(IFeeProviderFactory))).CreateFeeProvider(btc);
var rateProvider = storeBlob.ApplyRateRules(btc, _RateProviders.GetRateProvider(btc, false));
var rateProvider = _RateProviders.GetRateProvider(btc, storeBlob.GetRateRules());
if (feeProvider != null && rateProvider != null)
{
var gettingFee = feeProvider.GetFeeRateAsync();
@ -186,7 +186,7 @@ namespace BTCPayServer.Controllers
private async Task<PaymentMethod> CreatePaymentMethodAsync(IPaymentMethodHandler handler, ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network, InvoiceEntity entity, StoreData store)
{
var storeBlob = store.GetStoreBlob();
var rate = await storeBlob.ApplyRateRules(network, _RateProviders.GetRateProvider(network, false)).GetRateAsync(entity.ProductInformation.Currency);
var rate = await _RateProviders.GetRateProvider(network, storeBlob.GetRateRules()).GetRateAsync(entity.ProductInformation.Currency);
PaymentMethod paymentMethod = new PaymentMethod();
paymentMethod.ParentEntity = entity;
paymentMethod.Network = network;
@ -221,7 +221,7 @@ namespace BTCPayServer.Controllers
if (limitValue.Currency == entity.ProductInformation.Currency)
limitValueRate = paymentMethod.Rate;
else
limitValueRate = await storeBlob.ApplyRateRules(network, _RateProviders.GetRateProvider(network, false)).GetRateAsync(limitValue.Currency);
limitValueRate = await _RateProviders.GetRateProvider(network, storeBlob.GetRateRules()).GetRateAsync(limitValue.Currency);
var limitValueCrypto = Money.Coins(limitValue.Value / limitValueRate);
if (compare(paymentMethod.Calculate().Due, limitValueCrypto))

View File

@ -49,18 +49,20 @@ namespace BTCPayServer.Controllers
var network = _NetworkProvider.GetNetwork(cryptoCode);
if (network == null)
return NotFound();
var rateProvider = _RateProviderFactory.GetRateProvider(network, true);
if (rateProvider == null)
return NotFound();
RateRules rules = null;
if (storeId != null)
{
var store = await _StoreRepo.FindStore(storeId);
if (store == null)
return NotFound();
rateProvider = store.GetStoreBlob().ApplyRateRules(network, rateProvider);
rules = store.GetStoreBlob().GetRateRules();
}
var rateProvider = _RateProviderFactory.GetRateProvider(network, rules);
if (rateProvider == null)
return NotFound();
var allRates = (await rateProvider.GetRatesAsync());
return Json(allRates.Select(r =>
new NBitpayClient.Rate()

View File

@ -1,7 +1,9 @@
using BTCPayServer.Models;
using BTCPayServer.HostedServices;
using BTCPayServer.Models;
using BTCPayServer.Models.ServerViewModels;
using BTCPayServer.Services;
using BTCPayServer.Services.Mails;
using BTCPayServer.Services.Rates;
using BTCPayServer.Validations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
@ -11,6 +13,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Mail;
using System.Threading.Tasks;
@ -21,11 +24,88 @@ namespace BTCPayServer.Controllers
{
private UserManager<ApplicationUser> _UserManager;
SettingsRepository _SettingsRepository;
private IRateProviderFactory _RateProviderFactory;
private CssThemeManager _CssThemeManager;
public ServerController(UserManager<ApplicationUser> userManager, SettingsRepository settingsRepository)
public ServerController(UserManager<ApplicationUser> userManager,
IRateProviderFactory rateProviderFactory,
SettingsRepository settingsRepository,
CssThemeManager cssThemeManager)
{
_UserManager = userManager;
_SettingsRepository = settingsRepository;
_RateProviderFactory = rateProviderFactory;
_CssThemeManager = cssThemeManager;
}
[Route("server/rates")]
public async Task<IActionResult> Rates()
{
var rates = (await _SettingsRepository.GetSettingAsync<RatesSetting>()) ?? new RatesSetting();
var vm = new RatesViewModel()
{
CacheMinutes = rates.CacheInMinutes,
PrivateKey = rates.PrivateKey,
PublicKey = rates.PublicKey
};
await FetchRateLimits(vm);
return View(vm);
}
private static async Task FetchRateLimits(RatesViewModel vm)
{
var coinAverage = GetCoinaverageService(vm, false);
if (coinAverage != null)
{
try
{
vm.RateLimits = await coinAverage.GetRateLimitsAsync();
}
catch { }
}
}
[Route("server/rates")]
[HttpPost]
public async Task<IActionResult> Rates(RatesViewModel vm)
{
var rates = (await _SettingsRepository.GetSettingAsync<RatesSetting>()) ?? new RatesSetting();
rates.PrivateKey = vm.PrivateKey;
rates.PublicKey = vm.PublicKey;
rates.CacheInMinutes = vm.CacheMinutes;
try
{
var service = GetCoinaverageService(vm, true);
if(service != null)
await service.TestAuthAsync();
}
catch
{
ModelState.AddModelError(nameof(vm.PrivateKey), "Invalid API key pair");
}
if (!ModelState.IsValid)
{
await FetchRateLimits(vm);
return View(vm);
}
await _SettingsRepository.UpdateSetting(rates);
StatusMessage = "Rate settings successfully updated";
return RedirectToAction(nameof(Rates));
}
private static CoinAverageRateProvider GetCoinaverageService(RatesViewModel vm, bool withAuth)
{
var settings = new CoinAverageSettings()
{
KeyPair = (vm.PublicKey, vm.PrivateKey)
};
if (!withAuth || settings.GetCoinAverageSignature() != null)
{
return new CoinAverageRateProvider("BTC")
{ Authenticator = settings };
}
return null;
}
[Route("server/users")]
@ -52,6 +132,7 @@ namespace BTCPayServer.Controllers
var roles = await _UserManager.GetRolesAsync(user);
var userVM = new UserViewModel();
userVM.Id = user.Id;
userVM.Email = user.Email;
userVM.IsAdmin = IsAdmin(roles);
return View(userVM);
}
@ -72,7 +153,7 @@ namespace BTCPayServer.Controllers
var isAdmin = IsAdmin(roles);
bool updated = false;
if(isAdmin != viewModel.IsAdmin)
if (isAdmin != viewModel.IsAdmin)
{
if (viewModel.IsAdmin)
await _UserManager.AddToRoleAsync(user, Roles.ServerAdmin);
@ -80,7 +161,7 @@ namespace BTCPayServer.Controllers
await _UserManager.RemoveFromRoleAsync(user, Roles.ServerAdmin);
updated = true;
}
if(updated)
if (updated)
{
viewModel.StatusMessage = "User successfully updated";
}
@ -138,7 +219,25 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> Policies(PoliciesSettings settings)
{
await _SettingsRepository.UpdateSetting(settings);
TempData["StatusMessage"] = "Policies upadated successfully";
TempData["StatusMessage"] = "Policies updated successfully";
return View(settings);
}
[Route("server/theme")]
public async Task<IActionResult> Theme()
{
var data = (await _SettingsRepository.GetSettingAsync<ThemeSettings>()) ?? new ThemeSettings();
return View(data);
}
[Route("server/theme")]
[HttpPost]
public async Task<IActionResult> Theme(ThemeSettings settings)
{
await _SettingsRepository.UpdateSetting(settings);
// TODO: remove controller/class-level property and have only reference to
// CssThemeManager here in this method
_CssThemeManager.Update(settings);
TempData["StatusMessage"] = "Theme settings updated successfully";
return View(settings);
}

View File

@ -5,6 +5,7 @@ using BTCPayServer.HostedServices;
using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Services;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets;
using Microsoft.AspNetCore.Authorization;
@ -47,7 +48,8 @@ namespace BTCPayServer.Controllers
ExplorerClientProvider explorerProvider,
IFeeProviderFactory feeRateProvider,
LanguageService langService,
IHostingEnvironment env)
IHostingEnvironment env,
CoinAverageSettings coinAverage)
{
_Dashboard = dashboard;
_Repo = repo;
@ -64,7 +66,9 @@ namespace BTCPayServer.Controllers
_ServiceProvider = serviceProvider;
_BtcpayServerOptions = btcpayServerOptions;
_BTCPayEnv = btcpayEnv;
_CoinAverage = coinAverage;
}
CoinAverageSettings _CoinAverage;
NBXplorerDashboard _Dashboard;
BTCPayServerOptions _BtcpayServerOptions;
BTCPayServerEnvironment _BTCPayEnv;
@ -237,7 +241,7 @@ namespace BTCPayServer.Controllers
model.SetCryptoCurrencies(_ExplorerProvider, model.DefaultCryptoCurrency);
model.SetLanguages(_LangService, model.DefaultLang);
if(!ModelState.IsValid)
if (!ModelState.IsValid)
{
return View(model);
}
@ -273,6 +277,7 @@ namespace BTCPayServer.Controllers
var storeBlob = store.GetStoreBlob();
var vm = new StoreViewModel();
vm.SetExchangeRates(GetSupportedExchanges(), storeBlob.PreferredExchange.IsCoinAverage() ? "coinaverage" : storeBlob.PreferredExchange);
vm.Id = store.Id;
vm.StoreName = store.StoreName;
vm.StoreWebsite = store.StoreWebsite;
@ -283,7 +288,6 @@ namespace BTCPayServer.Controllers
vm.InvoiceExpiration = storeBlob.InvoiceExpiration;
vm.RateMultiplier = (double)storeBlob.GetRateMultiplier();
vm.LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate;
vm.PreferredExchange = storeBlob.PreferredExchange.IsCoinAverage() ? "coinaverage" : storeBlob.PreferredExchange;
return View(vm);
}
@ -325,6 +329,7 @@ namespace BTCPayServer.Controllers
[Route("{storeId}")]
public async Task<IActionResult> UpdateStore(string storeId, StoreViewModel model)
{
model.SetExchangeRates(GetSupportedExchanges(), model.PreferredExchange);
if (!ModelState.IsValid)
{
return View(model);
@ -371,14 +376,11 @@ namespace BTCPayServer.Controllers
if (!blob.PreferredExchange.IsCoinAverage() && newExchange)
{
using (HttpClient client = new HttpClient())
if (!GetSupportedExchanges().Select(c => c.Name).Contains(blob.PreferredExchange, StringComparer.OrdinalIgnoreCase))
{
var rate = await client.GetAsync(model.RateSource);
if (rate.StatusCode == System.Net.HttpStatusCode.NotFound)
{
ModelState.AddModelError(nameof(model.PreferredExchange), $"Unsupported exchange ({model.RateSource})");
return View(model);
}
ModelState.AddModelError(nameof(model.PreferredExchange), $"Unsupported exchange ({model.RateSource})");
return View(model);
}
}
@ -394,9 +396,14 @@ namespace BTCPayServer.Controllers
});
}
private (String DisplayName, String Name)[] GetSupportedExchanges()
{
return new[] { ("Coin Average", "coinaverage") }.Concat(_CoinAverage.AvailableExchanges).ToArray();
}
private DerivationStrategy ParseDerivationStrategy(string derivationScheme, Script hint, BTCPayNetwork network)
{
var parser = new DerivationSchemeParser(network.NBitcoinNetwork, network.DefaultSettings.ChainType);
var parser = new DerivationSchemeParser(network.NBitcoinNetwork);
parser.HintScriptPubKey = hint;
return new DerivationStrategy(parser.Parse(derivationScheme), network);
}

View File

@ -284,30 +284,12 @@ namespace BTCPayServer.Data
}
}
public IRateProvider ApplyRateRules(BTCPayNetwork network, IRateProvider rateProvider)
public RateRules GetRateRules()
{
if (!PreferredExchange.IsCoinAverage())
return new RateRules(RateRules)
{
// If the original rateProvider is a cache, use the same inner provider as fallback, and same memory cache to wrap it all
if (rateProvider is CachedRateProvider cachedRateProvider)
{
rateProvider = new FallbackRateProvider(new IRateProvider[] {
new CoinAverageRateProvider(network.CryptoCode) { Exchange = PreferredExchange },
cachedRateProvider.Inner
});
rateProvider = new CachedRateProvider(network.CryptoCode, rateProvider, cachedRateProvider.MemoryCache) { AdditionalScope = PreferredExchange };
}
else
{
rateProvider = new FallbackRateProvider(new IRateProvider[] {
new CoinAverageRateProvider(network.CryptoCode) { Exchange = PreferredExchange },
rateProvider
});
}
}
if (RateRules == null || RateRules.Count == 0)
return rateProvider;
return new TweakRateProvider(network, rateProvider, RateRules.ToList());
PreferredExchange = PreferredExchange
};
}
}
}

View File

@ -13,13 +13,11 @@ namespace BTCPayServer
public class DerivationSchemeParser
{
public Network Network { get; set; }
public ChainType ChainType { get; set; }
public Script HintScriptPubKey { get; set; }
public DerivationSchemeParser(Network expectedNetwork, ChainType chainType)
public DerivationSchemeParser(Network expectedNetwork)
{
Network = expectedNetwork;
ChainType = chainType;
}
public DerivationStrategyBase Parse(string str)
@ -78,7 +76,7 @@ namespace BTCPayServer
if (data.Length < 4)
continue;
var prefix = Utils.ToUInt32(data, false);
var standardPrefix = Utils.ToBytes(ChainType == NBXplorer.ChainType.Main ? 0x0488b21eU : 0x043587cf, false);
var standardPrefix = Utils.ToBytes(Network.NetworkType == NetworkType.Mainnet ? 0x0488b21eU : 0x043587cf, false);
for (int ii = 0; ii < 4; ii++)
data[ii] = standardPrefix[ii];

View File

@ -28,11 +28,44 @@ using BTCPayServer.Payments;
using Microsoft.AspNetCore.Identity;
using BTCPayServer.Models;
using System.Security.Claims;
using System.Globalization;
namespace BTCPayServer
{
public static class Extensions
{
public static string Prettify(this TimeSpan timeSpan)
{
if (timeSpan.TotalMinutes < 1)
{
return $"{(int)timeSpan.TotalSeconds} second{Plural((int)timeSpan.TotalSeconds)}";
}
if (timeSpan.TotalHours < 1)
{
return $"{(int)timeSpan.TotalMinutes} minute{Plural((int)timeSpan.TotalMinutes)}";
}
if (timeSpan.Days < 1)
{
return $"{(int)timeSpan.TotalHours} hour{Plural((int)timeSpan.TotalHours)}";
}
return $"{(int)timeSpan.TotalDays} day{Plural((int)timeSpan.TotalDays)}";
}
private static string Plural(int totalDays)
{
return totalDays > 1 ? "s" : string.Empty;
}
public static string PrettyPrint(this TimeSpan expiration)
{
StringBuilder builder = new StringBuilder();
if (expiration.Days >= 1)
builder.Append(expiration.Days.ToString(CultureInfo.InvariantCulture));
if (expiration.Hours >= 1)
builder.Append(expiration.Hours.ToString("00", CultureInfo.InvariantCulture));
builder.Append($"{expiration.Minutes.ToString("00", CultureInfo.InvariantCulture)}:{expiration.Seconds.ToString("00", CultureInfo.InvariantCulture)}");
return builder.ToString();
}
public static decimal RoundUp(decimal value, int precision)
{
for (int i = 0; i < precision; i++)

View File

@ -0,0 +1,62 @@
using System;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Logging;
using Microsoft.Extensions.Hosting;
using NBXplorer;
using NBXplorer.Models;
using System.Collections.Concurrent;
using BTCPayServer.Events;
using BTCPayServer.Services;
namespace BTCPayServer.HostedServices
{
public class CssThemeManager
{
public CssThemeManager(SettingsRepository settingsRepository)
{
Update(settingsRepository);
}
private async void Update(SettingsRepository settingsRepository)
{
var data = (await settingsRepository.GetSettingAsync<ThemeSettings>()) ?? new ThemeSettings();
Update(data);
}
public void Update(ThemeSettings data)
{
UpdateBootstrap(data.BootstrapCssUri);
UpdateCreativeStart(data.CreativeStartCssUri);
}
private string _bootstrapUri = "/vendor/bootstrap4/css/bootstrap.css?v=" + DateTime.Now.Ticks;
public string BootstrapUri
{
get { return _bootstrapUri; }
}
public void UpdateBootstrap(string newUri)
{
if (String.IsNullOrWhiteSpace(newUri))
_bootstrapUri = "/vendor/bootstrap4/css/bootstrap.css?v="+ DateTime.Now.Ticks;
else
_bootstrapUri = newUri;
}
private string _creativeStartUri = "/vendor/bootstrap4-creativestart/creative.css?v=" + DateTime.Now.Ticks;
public string CreativeStartUri
{
get { return _creativeStartUri; }
}
public void UpdateCreativeStart(string newUri)
{
if (String.IsNullOrWhiteSpace(newUri))
_creativeStartUri = "/vendor/bootstrap4-creativestart/creative.css?v=" + DateTime.Now.Ticks;
else
_creativeStartUri = newUri;
}
}
}

View File

@ -184,8 +184,8 @@ namespace BTCPayServer.HostedServices
if(status != null && error == null)
{
if(status.ChainType != _Network.NBXplorerNetwork.DefaultSettings.ChainType)
error = $"{_Network.CryptoCode}: NBXplorer is on a different ChainType (actual: {status.ChainType}, expected: {_Network.NBXplorerNetwork.DefaultSettings.ChainType})";
if(status.NetworkType != _Network.NBitcoinNetwork.NetworkType)
error = $"{_Network.CryptoCode}: NBXplorer is on a different ChainType (actual: {status.NetworkType}, expected: {_Network.NBitcoinNetwork.NetworkType})";
}
if (error != null)

View File

@ -0,0 +1,101 @@
using System;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Services;
using BTCPayServer.Services.Rates;
using Microsoft.Extensions.Hosting;
using BTCPayServer.Logging;
using System.Runtime.CompilerServices;
namespace BTCPayServer.HostedServices
{
public class RatesHostedService : IHostedService
{
private SettingsRepository _SettingsRepository;
private IRateProviderFactory _RateProviderFactory;
private CoinAverageSettings _coinAverageSettings;
public RatesHostedService(SettingsRepository repo,
CoinAverageSettings coinAverageSettings,
IRateProviderFactory rateProviderFactory)
{
this._SettingsRepository = repo;
_RateProviderFactory = rateProviderFactory;
_coinAverageSettings = coinAverageSettings;
}
CancellationTokenSource _Cts = new CancellationTokenSource();
List<Task> _Tasks = new List<Task>();
public Task StartAsync(CancellationToken cancellationToken)
{
_Tasks.Add(RefreshCoinAverageSupportedExchanges(_Cts.Token));
_Tasks.Add(RefreshCoinAverageSettings(_Cts.Token));
return Task.CompletedTask;
}
async Task Timer(Func<Task> act, CancellationToken cancellation, [CallerMemberName]string caller = null)
{
await new SynchronizationContextRemover();
while (!cancellation.IsCancellationRequested)
{
try
{
await act();
}
catch (OperationCanceledException) when (cancellation.IsCancellationRequested)
{
}
catch (Exception ex)
{
Logs.PayServer.LogWarning(ex, caller + " failed");
try
{
await Task.Delay(TimeSpan.FromMinutes(1), cancellation);
}
catch (OperationCanceledException) when (cancellation.IsCancellationRequested) { }
}
}
}
Task RefreshCoinAverageSupportedExchanges(CancellationToken cancellation)
{
return Timer(async () =>
{
await new SynchronizationContextRemover();
var tickers = await new CoinAverageRateProvider("BTC") { Authenticator = _coinAverageSettings }.GetExchangeTickersAsync();
_coinAverageSettings.AvailableExchanges = tickers
.Exchanges
.Select(c => (c.DisplayName, c.Name))
.ToArray();
await Task.Delay(TimeSpan.FromHours(5), cancellation);
}, cancellation);
}
Task RefreshCoinAverageSettings(CancellationToken cancellation)
{
return Timer(async () =>
{
await new SynchronizationContextRemover();
var rates = (await _SettingsRepository.GetSettingAsync<RatesSetting>()) ?? new RatesSetting();
_RateProviderFactory.CacheSpan = TimeSpan.FromMinutes(rates.CacheInMinutes);
if (!string.IsNullOrWhiteSpace(rates.PrivateKey) && !string.IsNullOrWhiteSpace(rates.PublicKey))
{
_coinAverageSettings.KeyPair = (rates.PublicKey, rates.PrivateKey);
}
await _SettingsRepository.WaitSettingsChanged<RatesSetting>(cancellation);
}, cancellation);
}
public Task StopAsync(CancellationToken cancellationToken)
{
_Cts.Cancel();
return Task.WhenAll(_Tasks.ToArray());
}
}
}

View File

@ -109,6 +109,8 @@ namespace BTCPayServer.Hosting
services.AddSingleton<BTCPayServerEnvironment>();
services.TryAddSingleton<TokenRepository>();
services.TryAddSingleton<EventAggregator>();
services.TryAddSingleton<CoinAverageSettings>();
services.TryAddSingleton<ICoinAverageAuthenticator, CoinAverageSettings>();
services.TryAddSingleton<ApplicationDbContextFactory>(o =>
{
var opts = o.GetRequiredService<BTCPayServerOptions>();
@ -136,6 +138,7 @@ namespace BTCPayServer.Hosting
services.TryAddSingleton<LanguageService>();
services.TryAddSingleton<NBXplorerDashboard>();
services.TryAddSingleton<CssThemeManager>();
services.TryAddSingleton<StoreRepository>();
services.TryAddSingleton<BTCPayWalletProvider>();
services.TryAddSingleton<CurrencyNameTable>();
@ -154,16 +157,17 @@ namespace BTCPayServer.Hosting
services.AddSingleton<IHostedService, NBXplorerWaiters>();
services.AddSingleton<IHostedService, InvoiceNotificationManager>();
services.AddSingleton<IHostedService, InvoiceWatcher>();
services.AddSingleton<IHostedService, RatesHostedService>();
services.TryAddSingleton<ExplorerClientProvider>();
services.TryAddSingleton<Bitpay>(o =>
{
if (o.GetRequiredService<BTCPayServerOptions>().ChainType == ChainType.Main)
if (o.GetRequiredService<BTCPayServerOptions>().NetworkType == NetworkType.Mainnet)
return new Bitpay(new Key(), new Uri("https://bitpay.com/"));
else
return new Bitpay(new Key(), new Uri("https://test.bitpay.com/"));
});
services.TryAddSingleton<IRateProviderFactory, CachedDefaultRateProviderFactory>();
services.TryAddSingleton<IRateProviderFactory, BTCPayRateProviderFactory>();
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
services.TryAddSingleton<IAuthorizationHandler, OwnStoreHandler>();

View File

@ -27,7 +27,7 @@ namespace BTCPayServer.Models.InvoicingModels
}
public class Payment
{
public string PaymentMethod { get; set; }
public string Crypto { get; set; }
public string Confirmations
{
get; set;
@ -72,7 +72,13 @@ namespace BTCPayServer.Models.InvoicingModels
get; set;
} = new List<CryptoPayment>();
public List<Payment> Payments { get; set; } = new List<Payment>();
public List<Payment> OnChainPayments { get; set; } = new List<Payment>();
public List<OffChainPayment> OffChainPayments { get; set; } = new List<OffChainPayment>();
public class OffChainPayment
{
public string Crypto { get; set; }
public string BOLT11 { get; set; }
}
public string Status
{

View File

@ -43,7 +43,8 @@ namespace BTCPayServer.Models.InvoicingModels
public string OrderId { get; set; }
public string CryptoImage { get; set; }
public string NetworkFeeDescription { get; internal set; }
public decimal NetworkFee { get; set; }
public bool IsMultiCurrency { get; set; }
public int MaxTimeMinutes { get; internal set; }
public string PaymentType { get; internal set; }
public string PaymentMethodId { get; internal set; }

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Services.Rates;
namespace BTCPayServer.Models.ServerViewModels
{
public class RatesViewModel
{
[Display(Name = "Bitcoin average api keys")]
public string PublicKey { get; set; }
public string PrivateKey { get; set; }
[Display(Name = "Cache the rates for ... minutes")]
[Range(0, 60)]
public int CacheMinutes { get; set; }
public GetRateLimitsResponse RateLimits { get; internal set; }
}
}

View File

@ -12,6 +12,11 @@ namespace BTCPayServer.Models.StoreViewModels
{
public class StoreViewModel
{
class Format
{
public string Name { get; set; }
public string Value { get; set; }
}
public class DerivationScheme
{
public string Crypto { get; set; }
@ -44,6 +49,17 @@ namespace BTCPayServer.Models.StoreViewModels
public List<StoreViewModel.DerivationScheme> DerivationSchemes { get; set; } = new List<StoreViewModel.DerivationScheme>();
public void SetExchangeRates((String DisplayName, String Name)[] supportedList, string preferredExchange)
{
var defaultStore = preferredExchange ?? "coinaverage";
var choices = supportedList.Select(o => new Format() { Name = o.DisplayName, Value = o.Name }).ToArray();
var chosen = choices.FirstOrDefault(f => f.Value == defaultStore) ?? choices.FirstOrDefault();
Exchanges = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen);
PreferredExchange = chosen.Value;
}
public SelectList Exchanges { get; set; }
[Display(Name = "Preferred price source (eg. bitfinex, bitstamp...)")]
public string PreferredExchange { get; set; }

View File

@ -6,6 +6,7 @@ using System.Reflection;
using System.Threading.Tasks;
using System.Text;
using NBXplorer;
using NBitcoin;
namespace BTCPayServer.Services
{
@ -20,13 +21,13 @@ namespace BTCPayServer.Services
Build = "Release";
#endif
Environment = env;
ChainType = provider.NBXplorerNetworkProvider.ChainType;
NetworkType = provider.NetworkType;
}
public IHostingEnvironment Environment
{
get; set;
}
public ChainType ChainType { get; set; }
public NetworkType NetworkType { get; set; }
public string Version
{
get; set;
@ -40,7 +41,7 @@ namespace BTCPayServer.Services
{
get
{
return ChainType == ChainType.Regtest && Environment.IsDevelopment();
return NetworkType == NetworkType.Regtest && Environment.IsDevelopment();
}
}
public override string ToString()

View File

@ -101,7 +101,7 @@ namespace BTCPayServer.Services
var pubKey = await ledger.GetWalletPubKeyAsync(account);
if (pubKey.Address.Network != network.NBitcoinNetwork)
{
if (network.DefaultSettings.ChainType == NBXplorer.ChainType.Main)
if (network.NBitcoinNetwork.NetworkType == NetworkType.Mainnet)
throw new Exception($"The opened ledger app should be for {network.NBitcoinNetwork.Name}, not for {pubKey.Address.Network}");
}
var fingerprint = onlyChaincode ? new byte[4] : (await ledger.GetWalletPubKeyAsync(account.Parent)).UncompressedPublicKey.Compress().Hash.ToBytes().Take(4).ToArray();

View File

@ -26,6 +26,7 @@ namespace BTCPayServer.Services
new Language("ja-JP", "日本語"),
new Language("fr-FR", "Français"),
new Language("es-ES", "Spanish"),
new Language("pt-PT", "Portuguese"),
new Language("pt-BR", "Portuguese (Brazil)"),
new Language("nl-NL", "Dutch"),
new Language("cs-CZ", "Česky"),

View File

@ -0,0 +1,87 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
namespace BTCPayServer.Services.Rates
{
public class BTCPayRateProviderFactory : IRateProviderFactory
{
IMemoryCache _Cache;
private IOptions<MemoryCacheOptions> _CacheOptions;
public IMemoryCache Cache
{
get
{
return _Cache;
}
}
public BTCPayRateProviderFactory(IOptions<MemoryCacheOptions> cacheOptions, IServiceProvider serviceProvider)
{
if (cacheOptions == null)
throw new ArgumentNullException(nameof(cacheOptions));
_Cache = new MemoryCache(cacheOptions);
_CacheOptions = cacheOptions;
// We use 15 min because of limits with free version of bitcoinaverage
CacheSpan = TimeSpan.FromMinutes(15.0);
this.serviceProvider = serviceProvider;
}
IServiceProvider serviceProvider;
TimeSpan _CacheSpan;
public TimeSpan CacheSpan
{
get
{
return _CacheSpan;
}
set
{
_CacheSpan = value;
InvalidateCache();
}
}
public void InvalidateCache()
{
_Cache = new MemoryCache(_CacheOptions);
}
public IRateProvider GetRateProvider(BTCPayNetwork network, RateRules rules)
{
rules = rules ?? new RateRules();
var rateProvider = GetDefaultRateProvider(network);
if (!rules.PreferredExchange.IsCoinAverage())
{
rateProvider = CreateExchangeRateProvider(network, rules.PreferredExchange);
}
rateProvider = CreateCachedRateProvider(network, rateProvider, rules.PreferredExchange);
return new TweakRateProvider(network, rateProvider, rules);
}
private IRateProvider CreateExchangeRateProvider(BTCPayNetwork network, string exchange)
{
var coinAverage = new CoinAverageRateProviderDescription(network.CryptoCode).CreateRateProvider(serviceProvider);
coinAverage.Exchange = exchange;
return coinAverage;
}
private CachedRateProvider CreateCachedRateProvider(BTCPayNetwork network, IRateProvider rateProvider, string additionalScope)
{
return new CachedRateProvider(network.CryptoCode, rateProvider, _Cache) { CacheSpan = CacheSpan, AdditionalScope = additionalScope };
}
private IRateProvider GetDefaultRateProvider(BTCPayNetwork network)
{
if(network.DefaultRateProvider == null)
{
throw new RateUnavailableException(network.CryptoCode);
}
return network.DefaultRateProvider.CreateRateProvider(serviceProvider);
}
}
}

View File

@ -4,9 +4,17 @@ using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using NBitcoin;
namespace BTCPayServer.Services.Rates
{
public class BitpayRateProviderDescription : RateProviderDescription
{
public IRateProvider CreateRateProvider(IServiceProvider serviceProvider)
{
return new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/")));
}
}
public class BitpayRateProvider : IRateProvider
{
Bitpay _Bitpay;

View File

@ -1,43 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
namespace BTCPayServer.Services.Rates
{
public class CachedDefaultRateProviderFactory : IRateProviderFactory
{
IMemoryCache _Cache;
ConcurrentDictionary<string, IRateProvider> _Providers = new ConcurrentDictionary<string, IRateProvider>();
ConcurrentDictionary<string, IRateProvider> _LongCacheProviders = new ConcurrentDictionary<string, IRateProvider>();
public IMemoryCache Cache
{
get
{
return _Cache;
}
}
public CachedDefaultRateProviderFactory(IMemoryCache cache)
{
if (cache == null)
throw new ArgumentNullException(nameof(cache));
_Cache = cache;
// Using same providers because they are both at 15 min actually...
_Providers = _LongCacheProviders;
}
public IRateProvider RateProvider { get; set; }
// We use 15 min because of limits with free version of bitcoinaverage
public TimeSpan CacheSpan { get; set; } = TimeSpan.FromMinutes(15.0);
public TimeSpan LongCacheSpan { get; set; } = TimeSpan.FromMinutes(15.0);
public IRateProvider GetRateProvider(BTCPayNetwork network, bool longCache)
{
return (longCache ? _LongCacheProviders : _Providers).GetOrAdd(network.CryptoCode, new CachedRateProvider(network.CryptoCode, RateProvider ?? network.DefaultRateProvider, _Cache) { CacheSpan = longCache ? LongCacheSpan : CacheSpan, AdditionalScope = longCache ? "LONG" : "SHORT" });
}
}
}

View File

@ -1,11 +1,15 @@
using Newtonsoft.Json;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
namespace BTCPayServer.Services.Rates
{
@ -16,6 +20,57 @@ namespace BTCPayServer.Services.Rates
}
}
public class CoinAverageRateProviderDescription : RateProviderDescription
{
public CoinAverageRateProviderDescription(string crypto)
{
CryptoCode = crypto;
}
public string CryptoCode { get; set; }
public CoinAverageRateProvider CreateRateProvider(IServiceProvider serviceProvider)
{
return new CoinAverageRateProvider(CryptoCode)
{
Authenticator = serviceProvider.GetService<ICoinAverageAuthenticator>()
};
}
IRateProvider RateProviderDescription.CreateRateProvider(IServiceProvider serviceProvider)
{
return CreateRateProvider(serviceProvider);
}
}
public class GetExchangeTickersResponse
{
public class Exchange
{
public string Name { get; set; }
[JsonProperty("display_name")]
public string DisplayName { get; set; }
public string[] Symbols { get; set; }
}
public bool Success { get; set; }
public Exchange[] Exchanges { get; set; }
}
public class RatesSetting
{
public string PublicKey { get; set; }
public string PrivateKey { get; set; }
[DefaultValue(15)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public int CacheInMinutes { get; set; } = 15;
}
public interface ICoinAverageAuthenticator
{
Task AddHeader(HttpRequestMessage message);
}
public class CoinAverageRateProvider : IRateProvider
{
static HttpClient _Client = new HttpClient();
@ -48,17 +103,20 @@ namespace BTCPayServer.Services.Rates
throw new RateUnavailableException(currency);
}
public ICoinAverageAuthenticator Authenticator { get; set; }
private async Task<Dictionary<string, decimal>> GetRatesCore()
{
HttpResponseMessage resp = null;
if (Exchange == null)
string url = Exchange == null ? $"https://apiv2.bitcoinaverage.com/indices/{Market}/ticker/short"
: $"https://apiv2.bitcoinaverage.com/exchanges/{Exchange}";
var request = new HttpRequestMessage(HttpMethod.Get, url);
var auth = Authenticator;
if (auth != null)
{
resp = await _Client.GetAsync($"https://apiv2.bitcoinaverage.com/indices/{Market}/ticker/short");
}
else
{
resp = await _Client.GetAsync($"https://apiv2.bitcoinaverage.com/exchanges/{Exchange}");
await auth.AddHeader(request);
}
var resp = await _Client.SendAsync(request);
using (resp)
{
@ -99,5 +157,77 @@ namespace BTCPayServer.Services.Rates
Value = o.Value
}).ToList();
}
public async Task TestAuthAsync()
{
var request = new HttpRequestMessage(HttpMethod.Get, "https://apiv2.bitcoinaverage.com/blockchain/tx_price/BTCUSD/8a3b4394ba811a9e2b0bbf3cc56888d053ea21909299b2703cdc35e156c860ff");
var auth = Authenticator;
if (auth != null)
{
await auth.AddHeader(request);
}
var resp = await _Client.SendAsync(request);
resp.EnsureSuccessStatusCode();
}
public async Task<GetRateLimitsResponse> GetRateLimitsAsync()
{
var request = new HttpRequestMessage(HttpMethod.Get, "https://apiv2.bitcoinaverage.com/info/ratelimits");
var auth = Authenticator;
if (auth != null)
{
await auth.AddHeader(request);
}
var resp = await _Client.SendAsync(request);
resp.EnsureSuccessStatusCode();
var jobj = JObject.Parse(await resp.Content.ReadAsStringAsync());
var response = new GetRateLimitsResponse();
response.CounterReset = TimeSpan.FromSeconds(jobj["counter_reset"].Value<int>());
var totalPeriod = jobj["total_period"].Value<string>();
if (totalPeriod == "24h")
{
response.TotalPeriod = TimeSpan.FromHours(24);
}
else if (totalPeriod == "30d")
{
response.TotalPeriod = TimeSpan.FromDays(30);
}
else
{
response.TotalPeriod = TimeSpan.FromSeconds(jobj["total_period"].Value<int>());
}
response.RequestsLeft = jobj["requests_left"].Value<int>();
response.RequestsPerPeriod = jobj["requests_per_period"].Value<int>();
return response;
}
public async Task<GetExchangeTickersResponse> GetExchangeTickersAsync()
{
var request = new HttpRequestMessage(HttpMethod.Get, "https://apiv2.bitcoinaverage.com/symbols/exchanges/ticker");
var resp = await _Client.SendAsync(request);
resp.EnsureSuccessStatusCode();
var jobj = JObject.Parse(await resp.Content.ReadAsStringAsync());
var response = new GetExchangeTickersResponse();
response.Success = jobj["success"].Value<bool>();
var exchanges = (JObject)jobj["exchanges"];
response.Exchanges = exchanges
.Properties()
.Select(p =>
{
var exchange = JsonConvert.DeserializeObject<GetExchangeTickersResponse.Exchange>(p.Value.ToString());
exchange.Name = p.Name;
return exchange;
})
.ToArray();
return response;
}
}
public class GetRateLimitsResponse
{
public TimeSpan CounterReset { get; set; }
public int RequestsLeft { get; set; }
public int RequestsPerPeriod { get; set; }
public TimeSpan TotalPeriod { get; set; }
}
}

View File

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace BTCPayServer.Services.Rates
{
public class CoinAverageSettings : ICoinAverageAuthenticator
{
private static readonly DateTime _epochUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public (String PublicKey, String PrivateKey)? KeyPair { get; set; }
public (String DisplayName, String Name)[] AvailableExchanges { get; set; } = Array.Empty<(String DisplayName, String Name)>();
public Task AddHeader(HttpRequestMessage message)
{
var signature = GetCoinAverageSignature();
if (signature != null)
{
message.Headers.Add("X-signature", signature);
}
return Task.CompletedTask;
}
public string GetCoinAverageSignature()
{
var keyPair = KeyPair;
if (!keyPair.HasValue)
return null;
if (string.IsNullOrEmpty(keyPair.Value.PublicKey) || string.IsNullOrEmpty(keyPair.Value.PrivateKey))
return null;
var timestamp = (int)((DateTime.UtcNow - _epochUtc).TotalSeconds);
var payload = timestamp + "." + keyPair.Value.PublicKey;
var digestValueBytes = new HMACSHA256(Encoding.ASCII.GetBytes(keyPair.Value.PrivateKey)).ComputeHash(Encoding.ASCII.GetBytes(payload));
var digestValueHex = NBitcoin.DataEncoders.Encoders.Hex.EncodeData(digestValueBytes);
return payload + "." + digestValueHex;
}
}
}

View File

@ -5,8 +5,24 @@ using System.Threading.Tasks;
namespace BTCPayServer.Services.Rates
{
public class FallbackRateProviderDescription : RateProviderDescription
{
public FallbackRateProviderDescription(RateProviderDescription[] rateProviders)
{
RateProviders = rateProviders;
}
public RateProviderDescription[] RateProviders { get; set; }
public IRateProvider CreateRateProvider(IServiceProvider serviceProvider)
{
return new FallbackRateProvider(RateProviders.Select(r => r.CreateRateProvider(serviceProvider)).ToArray());
}
}
public class FallbackRateProvider : IRateProvider
{
IRateProvider[] _Providers;
public FallbackRateProvider(IRateProvider[] providers)
{

View File

@ -1,12 +1,40 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Data;
namespace BTCPayServer.Services.Rates
{
public class RateRules : IEnumerable<RateRule>
{
private List<RateRule> rateRules;
public RateRules()
{
rateRules = new List<RateRule>();
}
public RateRules(List<RateRule> rateRules)
{
this.rateRules = rateRules?.ToList() ?? new List<RateRule>();
}
public string PreferredExchange { get; set; }
public IEnumerator<RateRule> GetEnumerator()
{
return rateRules.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public interface IRateProviderFactory
{
IRateProvider GetRateProvider(BTCPayNetwork network, bool longCache);
IRateProvider GetRateProvider(BTCPayNetwork network, RateRules rules);
TimeSpan CacheSpan { get; set; }
void InvalidateCache();
}
}

View File

@ -14,14 +14,21 @@ namespace BTCPayServer.Services.Rates
}
public TimeSpan CacheSpan { get; set; }
public void AddMock(MockRateProvider mock)
{
_Mocks.Add(mock);
}
public IRateProvider GetRateProvider(BTCPayNetwork network, bool longCache)
public IRateProvider GetRateProvider(BTCPayNetwork network, RateRules rules)
{
return _Mocks.FirstOrDefault(m => m.CryptoCode == network.CryptoCode);
}
public void InvalidateCache()
{
}
}
public class MockRateProvider : IRateProvider
{

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Services.Rates
{
public interface RateProviderDescription
{
IRateProvider CreateRateProvider(IServiceProvider serviceProvider);
}
}

View File

@ -10,9 +10,9 @@ namespace BTCPayServer.Services.Rates
{
private BTCPayNetwork network;
private IRateProvider rateProvider;
private List<RateRule> rateRules;
private RateRules rateRules;
public TweakRateProvider(BTCPayNetwork network, IRateProvider rateProvider, List<RateRule> rateRules)
public TweakRateProvider(BTCPayNetwork network, IRateProvider rateProvider, RateRules rateRules)
{
if (network == null)
throw new ArgumentNullException(nameof(network));

View File

@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore;
using BTCPayServer.Models;
using Microsoft.EntityFrameworkCore.Infrastructure.Internal;
using Newtonsoft.Json;
using System.Threading;
namespace BTCPayServer.Services
{
@ -51,6 +52,22 @@ namespace BTCPayServer.Services
await ctx.SaveChangesAsync();
}
}
IReadOnlyCollection<TaskCompletionSource<bool>> value;
lock (_Subscriptions)
{
if(_Subscriptions.TryGetValue(typeof(T), out value))
{
_Subscriptions.Remove(typeof(T));
}
}
if(value != null)
{
foreach(var v in value)
{
v.TrySetResult(true);
}
}
}
private T Deserialize<T>(string value)
@ -62,5 +79,35 @@ namespace BTCPayServer.Services
{
return JsonConvert.SerializeObject(obj);
}
MultiValueDictionary<Type, TaskCompletionSource<bool>> _Subscriptions = new MultiValueDictionary<Type, TaskCompletionSource<bool>>();
public async Task WaitSettingsChanged<T>(CancellationToken cancellation)
{
var tcs = new TaskCompletionSource<bool>();
using (cancellation.Register(() =>
{
try
{
tcs.TrySetCanceled();
}
catch { }
}))
{
lock (_Subscriptions)
{
_Subscriptions.Add(typeof(T), tcs);
}
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
tcs.Task.ContinueWith(_ =>
{
lock (_Subscriptions)
{
_Subscriptions.Remove(typeof(T), tcs);
}
}, TaskScheduler.Default);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
await tcs.Task;
}
}
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace BTCPayServer.Services
{
public class ThemeSettings
{
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public string BootstrapCssUri { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public string CreativeStartCssUri { get; set; }
}
}

View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace BTCPayServer
{
public struct SynchronizationContextRemover : INotifyCompletion
{
public bool IsCompleted => SynchronizationContext.Current == null;
public void OnCompleted(Action continuation)
{
var prev = SynchronizationContext.Current;
try
{
SynchronizationContext.SetSynchronizationContext(null);
continuation();
}
finally
{
SynchronizationContext.SetSynchronizationContext(prev);
}
}
public SynchronizationContextRemover GetAwaiter()
{
return this;
}
public void GetResult()
{
}
}
}

View File

@ -15,7 +15,7 @@
</head>
<body class="h-100">
<div class="container d-flex h-100">
<div class="justify-content-center align-self-center text-center mx-auto">
<div class="justify-content-center align-self-center text-center mx-auto" style="margin: auto;">
<h1 class="mb-4">@Model.Title</h1>
<form method="post">
<div class="row">

View File

@ -6,8 +6,9 @@
<header class="masthead">
<div class="header-content">
<div class="header-content-inner">
<h1 id="homeHeading" style="padding-bottom:30px;">Welcome to BTCPay Server</h1>
<div class="header-content-inner text-white">
<h1>Welcome to BTCPay Server</h1>
<hr />
<p>BTCPay Server is a free and open source server for merchants wanting to accept Bitcoin for their business. The API is compatible with Bitpay service to allow seamless migration.</p>
<a style="background-color: #fff;color: #222;display:inline-block;text-align: center;white-space: nowrap;vertical-align: middle;user-select: none;line-height: 1.25;font-size: 1rem;text-decoration:none;font-weight: 700; text-transform: uppercase;border: none;border-radius: 300px;padding: 15px 30px;" href="https://github.com/btcpayserver/btcpayserver-doc">Getting started</a>
</div>
@ -28,21 +29,21 @@
<div class="col-lg-4 col-md-6 text-center">
<div class="service-box">
<img src="~/img/lock-logo.png" />
<h3>Secure</h3>
<h3 class="text-dark">Secure</h3>
<p class="text-muted">The payment server does not need to know your private keys, so your money can't be stolen.</p>
</div>
</div>
<div class="col-lg-4 col-md-6 text-center">
<div class="service-box">
<img src="~/img/qr-logo.png" />
<h3>Easy</h3>
<h3 class="text-dark">Easy</h3>
<p class="text-muted">A user-friendly Bitcoin checkout page for your customers.</p>
</div>
</div>
<div class="col-lg-4 col-md-6 text-center">
<div class="service-box">
<img src="~/img/money-logo.png" />
<h3>Visibility</h3>
<h3 class="text-dark">Visibility</h3>
<p class="text-muted">Manage, generate reports, and search for your invoices easily.</p>
</div>
</div>
@ -52,7 +53,7 @@
<div class="call-to-action bg-dark">
<div class="call-to-action bg-dark text-white">
<div class="container text-center">
<h2>Video tutorials</h2>
<div class="row">
@ -102,12 +103,12 @@
</div>
</section>
<div class="call-to-action bg-dark">
<div class="call-to-action bg-dark text-white">
<div class="container text-center">
<h2>Donate</h2>
<p>Donation to this address will be reinvested into the development of this tool</p>
<img src="~/img/donation.jpg">
<p style="font-size:10px">3BpfdkF93GwFRWdrAN3SNsRAsi6d158YQi</p>
<p><img src="~/img/donation.jpg"></p>
<p>3BpfdkF93GwFRWdrAN3SNsRAsi6d158YQi</p>
</div>
</div>
@ -117,7 +118,10 @@
<div class="col-lg-8 mx-auto text-center">
<h2 class="section-heading">Let's Get In Touch!</h2>
<hr class="primary">
<p>An open source project is nothing without its community, come and get in touch with us!</p>
<p>
An open source project is nothing without its community<br />
Come and get in touch with us!
</p>
</div>
</div>
<div class="row">

View File

@ -87,7 +87,14 @@
<div class="line-items__item__label">
<span>{{$t("Network Cost")}}</span>
</div>
<div class="line-items__item__value" i18n="">{{srvModel.networkFeeDescription }}</div>
<div class="line-items__item__value">
<span v-if="srvModel.IsMultiCurrency">
{{ srvModel.networkFee }} {{ srvModel.cryptoCode }}
</span>
<span v-else>
{{$t("txCount", {count: srvModel.txCount})}} x {{ srvModel.networkFee }} {{ srvModel.cryptoCode }}
</span>
</div>
</div>
<div class="line-items__item">
<div class="line-items__item__label">
@ -249,11 +256,9 @@
</div>
</div>
<div class="success-message">{{$t("This invoice has been paid")}}</div>
<button class="action-button">
<bp-done-text>
<span>{{$t("Return to StoreName", srvModel)}}</span>
</bp-done-text>
</button>
<a class="action-button" :href="srvModel.merchantRefLink" v-show="srvModel.merchantRefLink">
<span>{{$t("Return to StoreName", srvModel)}}</span>
</a>
</div>
</div>
<div class="button-wrapper refund-address-form-container" id="refund-overpayment-button">
@ -295,10 +300,9 @@
{{srvModel.orderId}}
</div>
</div>
<a class="action-button" style="margin-top: 20px;">
<bp-done-text>
<span>{{$t("Return to StoreName", srvModel)}}</span>
</bp-done-text>
<a class="action-button" :href="srvModel.merchantRefLink" v-show="srvModel.merchantRefLink"
style="margin-top: 20px;">
<span>{{$t("Return to StoreName", srvModel)}}</span>
</a>
</div>
</div>

View File

@ -110,6 +110,7 @@
'es-ES': { translation: locales_es },
'ja-JP': { translation: locales_ja },
'fr-FR': { translation: locales_fr },
'pt': { translation: locales_pt },
'pt-BR': { translation: locales_pt_br },
'nl': { translation: locales_nl },
'cs-CZ': { translation: locales_cs },

View File

@ -4,14 +4,8 @@
}
<style type="text/css">
.overflowbox {
max-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.money {
text-align: right;
.linethrough {
text-decoration: line-through;
}
</style>
@ -28,14 +22,13 @@
<div class="col-lg-12 text-center">
<h2 class="section-heading">@ViewData["Title"]</h2>
<hr class="primary">
<p>Invoice details</p>
</div>
</div>
<div class="row">
<div class="col-md-6">
<h3>Information</h3>
<table class="table table-sm">
<table class="table table-sm table-responsive-md">
<tr>
<th>Store</th>
<td><a href="@Model.StoreLink">@Model.StoreName</a></td>
@ -93,10 +86,9 @@
<div class="col-md-6">
<h3>Buyer information</h3>
<table class="table table-sm">
<table class="table table-sm table-responsive-md">
<tr>
<th>Name
<th>
<th>Name</th>
<td>@Model.BuyerInformation.BuyerName</td>
</tr>
<tr>
@ -134,7 +126,7 @@
</table>
<h3>Product information</h3>
<table class="table table-sm">
<table class="table table-sm table-responsive-md">
<tr>
<th>Item code</th>
<td>@Model.ProductInformation.ItemCode</td>
@ -153,77 +145,105 @@
<div class="row">
<div class="col-md-12">
<h3>Paid summary</h3>
<table class="table table-sm">
<table class="table table-sm table-responsive-md">
<thead class="thead-inverse">
<tr>
<th style="white-space:nowrap;">Payment method</th>
<th>Payment method</th>
<th>Address</th>
<th class="money">Rate</th>
<th class="money">Paid</th>
<th class="money">Due</th>
<th class="text-right">Rate</th>
<th class="text-right">Paid</th>
<th class="text-right">Due</th>
</tr>
</thead>
<tbody>
@foreach(var payment in Model.CryptoPayments)
{
<tr>
<td>@payment.PaymentMethod</td>
<td>@payment.Address</td>
<td class="money">@payment.Rate</td>
<td class="money">@payment.Paid</td>
<td class="money">@payment.Due</td>
</tr>
}
@foreach (var payment in Model.CryptoPayments)
{
<tr>
<td>@payment.PaymentMethod</td>
<td>@payment.Address</td>
<td class="text-right">@payment.Rate</td>
<td class="text-right">@payment.Paid</td>
<td class="text-right">@payment.Due</td>
</tr>
}
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col-md-12">
<h3>Payments</h3>
<table class="table table-sm">
<thead class="thead-inverse">
<tr>
<th style="white-space:nowrap;">Payment method</th>
<th>Deposit address</th>
<th>Transaction Id</th>
<th style="text-align:right;">Confirmations</th>
</tr>
</thead>
<tbody>
@foreach(var payment in Model.Payments)
{
var replaced = payment.Replaced ? "text-decoration: line-through;" : "";
<tr>
<td style="@replaced">@payment.PaymentMethod</td>
<td style="@replaced">@payment.DepositAddress</td>
<td style="@replaced"><a href="@payment.TransactionLink" target="_blank">@payment.TransactionId</a></td>
<td style="text-align:right;">@payment.Confirmations</td>
</tr>
}
</tbody>
</table>
@if(Model.OnChainPayments.Count > 0)
{
<div class="row">
<div class="col-md-12">
<h3>On-Chain payments</h3>
<table class="table table-sm table-responsive-md">
<thead class="thead-inverse">
<tr>
<th>Crypto</th>
<th>Deposit address</th>
<th>Transaction Id</th>
<th class="text-right">Confirmations</th>
</tr>
</thead>
<tbody>
@foreach(var payment in Model.OnChainPayments)
{
var replaced = payment.Replaced ? "class='linethrough'" : "";
<tr>
<td @replaced>@payment.Crypto</td>
<td @replaced>@payment.DepositAddress</td>
<td @replaced><a href="@payment.TransactionLink" target="_blank">@payment.TransactionId</a></td>
<td class="text-right">@payment.Confirmations</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
}
@if(Model.OffChainPayments.Count > 0)
{
<div class="row">
<div class="col-md-12">
<h3>Off-Chain payments</h3>
<table class="table table-sm table-responsive-md">
<thead class="thead-inverse">
<tr>
<th>Crypto</th>
<th>BOLT11</th>
</tr>
</thead>
<tbody>
@foreach(var payment in Model.OffChainPayments)
{
<tr>
<td>@payment.Crypto</td>
<td>@payment.BOLT11</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
<div class="row">
<div class="col-md-12">
<h3>Addresses</h3>
<table class="table table-sm">
<table class="table table-sm table-responsive-md">
<thead class="thead-inverse">
<tr>
<th style="white-space:nowrap;">Payment method</th>
<th>Payment method</th>
<th>Address</th>
</tr>
</thead>
<tbody>
@foreach(var address in Model.Addresses)
@foreach (var address in Model.Addresses)
{
var current = address.Current ? "font-weight: bold;" : "";
<tr>
<td style="width:100px;@current">@address.PaymentMethod</td>
<td style="max-width:100px;overflow:hidden;@current">@address.Destination</td>
</tr>
}
var current = address.Current ? "class='font-weight-bold'" : "";
<tr>
<td>@address.PaymentMethod</td>
<td @current>@address.Destination</td>
</tr>
}
</tbody>
</table>
</div>
@ -232,7 +252,7 @@
<div class="row">
<div class="col-md-12">
<h3>Events</h3>
<table class="table table-sm">
<table class="table table-sm table-responsive-md">
<thead class="thead-inverse">
<tr>
<th>Date</th>
@ -240,7 +260,7 @@
</tr>
</thead>
<tbody>
@foreach(var evt in Model.Events)
@foreach (var evt in Model.Events)
{
<tr>
<td>@evt.Timestamp</td>

View File

@ -1,7 +1,6 @@
@model ChangePasswordViewModel
@{
ViewData["Title"] = "Change password";
ViewData.AddActivePage(ManageNavPages.ChangePassword);
ViewData.SetActivePageAndTitle(ManageNavPages.ChangePassword, "Change password");
}
<h4>@ViewData["Title"]</h4>

View File

@ -1,6 +1,5 @@
@{
ViewData["Title"] = "Disable two-factor authentication (2FA)";
ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);
ViewData.SetActivePageAndTitle(ManageNavPages.TwoFactorAuthentication, "Disable two-factor authentication (2FA)");
}
<h2>@ViewData["Title"]</h2>

View File

@ -1,7 +1,6 @@
@model EnableAuthenticatorViewModel
@{
ViewData["Title"] = "Enable authenticator";
ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);
ViewData.SetActivePageAndTitle(ManageNavPages.TwoFactorAuthentication, "Enable authenticator");
}
<h4>@ViewData["Title"]</h4>

View File

@ -1,7 +1,6 @@
@model ExternalLoginsViewModel
@{
ViewData["Title"] = "Manage your external logins";
ViewData.AddActivePage(ManageNavPages.ExternalLogins);
ViewData.SetActivePageAndTitle(ManageNavPages.ExternalLogins, "Manage your external logins");
}
@Html.Partial("_StatusMessage", Model.StatusMessage)

View File

@ -1,7 +1,6 @@
@model GenerateRecoveryCodesViewModel
@{
ViewData["Title"] = "Recovery codes";
ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);
ViewData.SetActivePageAndTitle(ManageNavPages.TwoFactorAuthentication, "Recovery codes");
}
<h4>@ViewData["Title"]</h4>

View File

@ -1,7 +1,6 @@
@model IndexViewModel
@{
ViewData["Title"] = "Profile";
ViewData.AddActivePage(ManageNavPages.Index);
ViewData.SetActivePageAndTitle(ManageNavPages.Index, "Profile");
}
<h4>@ViewData["Title"]</h4>

View File

@ -2,41 +2,11 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace BTCPayServer.Views.Manage
{
public static class ManageNavPages
public enum ManageNavPages
{
public static string ActivePageKey => "ActivePage";
public static string Index => "Index";
public static string ChangePassword => "ChangePassword";
public static string ExternalLogins => "ExternalLogins";
public static string TwoFactorAuthentication => "TwoFactorAuthentication";
public static string Tokens => "Tokens";
public static string TokensNavClass(ViewContext viewContext) => PageNavClass(viewContext, Tokens);
public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);
public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);
public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);
public static string PageNavClass(ViewContext viewContext, string page)
{
var activePage = viewContext.ViewData["ActivePage"] as string;
return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
}
public static void AddActivePage(this ViewDataDictionary viewData, string activePage) => viewData[ActivePageKey] = activePage;
Index, ChangePassword, ExternalLogins, TwoFactorAuthentication, Tokens
}
}

View File

@ -1,6 +1,5 @@
@{
ViewData["Title"] = "Reset authenticator key";
ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);
ViewData.SetActivePageAndTitle(ManageNavPages.TwoFactorAuthentication, "Reset authenticator key");
}
<h4>@ViewData["Title"]</h4>

View File

@ -1,7 +1,6 @@
@model SetPasswordViewModel
@{
ViewData["Title"] = "Set password";
ViewData.AddActivePage(ManageNavPages.ChangePassword);
ViewData.SetActivePageAndTitle(ManageNavPages.ChangePassword, "Set password");
}
<h4>Set your password</h4>

View File

@ -1,7 +1,6 @@
@model TwoFactorAuthenticationViewModel
@{
ViewData["Title"] = "Two-factor authentication";
ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);
ViewData.SetActivePageAndTitle(ManageNavPages.TwoFactorAuthentication, "Two-factor authentication");
}
<h4>@ViewData["Title"]</h4>

View File

@ -1,16 +1,15 @@
@using BTCPayServer.Views.Manage
@inject SignInManager<ApplicationUser> SignInManager
@inject SignInManager<ApplicationUser> SignInManager
@{
var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
}
<div class="nav flex-column nav-pills">
<a class="nav-link @ManageNavPages.IndexNavClass(ViewContext)" asp-action="Index">Profile</a>
<a class="nav-link @ManageNavPages.ChangePasswordNavClass(ViewContext)" asp-action="ChangePassword">Password</a>
@if(hasExternalLogins)
<a class="nav-link @ViewData.IsActivePage(ManageNavPages.Index)" asp-action="Index">Profile</a>
<a class="nav-link @ViewData.IsActivePage(ManageNavPages.ChangePassword)" asp-action="ChangePassword">Password</a>
@if (hasExternalLogins)
{
<a class="nav-link @ManageNavPages.ExternalLoginsNavClass(ViewContext)" asp-action="ExternalLogins">External logins</a>
<a class="nav-link @ViewData.IsActivePage(ManageNavPages.ExternalLogins)" asp-action="ExternalLogins">External logins</a>
}
<a class="nav-link @ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)" asp-action="TwoFactorAuthentication">Two-factor authentication</a>
<a class="nav-link @ViewData.IsActivePage(ManageNavPages.TwoFactorAuthentication)" asp-action="TwoFactorAuthentication">Two-factor authentication</a>
</div>

View File

@ -1,7 +1,6 @@
@model EmailsViewModel
@{
ViewData["Title"] = ServerNavPages.Emails;
ViewData.AddActivePage(ServerNavPages.Emails);
ViewData.SetActivePageAndTitle(ServerNavPages.Emails);
}

View File

@ -1,7 +1,6 @@
@model UsersViewModel
@{
ViewData["Title"] = "Users";
ViewData.AddActivePage(ServerNavPages.Users);
ViewData.SetActivePageAndTitle(ServerNavPages.Users);
}

View File

@ -1,19 +1,18 @@
@model BTCPayServer.Services.PoliciesSettings
@{
ViewData["Title"] = ServerNavPages.Policies;
ViewData.AddActivePage(ServerNavPages.Policies);
ViewData.SetActivePageAndTitle(ServerNavPages.Policies);
}
<h4>@ViewData["Title"]</h4>
@Html.Partial("_StatusMessage", TempData["StatusMessage"])
<div class="row">
<div class="col-md-6">
<div class="col-lg-6">
<div asp-validation-summary="All" class="text-danger"></div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="col-lg-6">
<form method="post">
<div class="form-group">
<label asp-for="RequiresConfirmedEmail"></label>

View File

@ -0,0 +1,62 @@
@model RatesViewModel
@{
ViewData.SetActivePageAndTitle(ServerNavPages.Rates);
}
<h4>@ViewData["Title"]</h4>
@Html.Partial("_StatusMessage", TempData["TempDataProperty-StatusMessage"])
<div class="row">
<div class="col-md-6">
<div asp-validation-summary="All" class="text-danger"></div>
</div>
</div>
<div class="row">
<div class="col-md-8">
<div class="form-group">
<h5>Bitcoin Average</h5>
<p>BTCPay relies on Bitcoin Average for getting crypto-currency to fiat rates</p>
<p>If you want BTCPay rate cache to be smaller than 15min, you should register to BitcoinAverage and get a paid API Key.</p>
<p>If your BTCPay server instance supports lot's of merchant or is used a lot, BitcoinAverage will rate limit your server, and invoice will be created using less accurate rates. (From Bitpay)<br /></p>
</div>
<form method="post">
<label asp-for="PublicKey"></label>
<div class="form-inline">
<input asp-for="PublicKey" style="width:50%;" class="form-control" placeholder="Public key" />
<label class="sr-only" asp-for="PrivateKey"></label>
<input asp-for="PrivateKey" style="width:50%;" class="form-control" placeholder="Private key" />
<p class="form-text text-muted">You can find the information on <a target="_blank" href="https://bitcoinaverage.com/en/apikeys">bitcoinaverage api key page</a></p>
</div>
<div class="form-group">
<label asp-for="CacheMinutes"></label>
<input asp-for="CacheMinutes" class="form-control" />
<span asp-validation-for="CacheMinutes" class="text-danger"></span>
</div>
@if(Model.RateLimits != null)
{
<h5>Current Bitcoin Average Quotas:</h5>
<table class="table table-sm">
<tr>
<th>Quota period</th>
<td>@Model.RateLimits.TotalPeriod.Prettify()</td>
</tr>
<tr>
<th>Requests quota</th>
<td>@Model.RateLimits.RequestsLeft/@Model.RateLimits.RequestsPerPeriod</td>
</tr>
<tr>
<th>Quota reset in</th>
<td>@Model.RateLimits.CounterReset.Prettify()</td>
</tr>
</table>
}
<button type="submit" class="btn btn-primary" name="command" value="Save">Save</button>
</form>
</div>
</div>
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
}

View File

@ -2,35 +2,11 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace BTCPayServer.Views.Server
{
public static class ServerNavPages
public enum ServerNavPages
{
public static string ActivePageKey => "ActivePage";
public static string Index => "Index";
public static string Users => "Users";
public static string Emails => "Email server";
public static string Policies => "Policies";
public static string Hangfire => "Hangfire";
public static string UsersNavClass(ViewContext viewContext) => PageNavClass(viewContext, Users);
public static string EmailsNavClass(ViewContext viewContext) => PageNavClass(viewContext, Emails);
public static string PoliciesNavClass(ViewContext viewContext) => PageNavClass(viewContext, Policies);
public static string HangfireNavClass(ViewContext viewContext) => PageNavClass(viewContext, Hangfire);
public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
public static string PageNavClass(ViewContext viewContext, string page)
{
var activePage = viewContext.ViewData["ActivePage"] as string;
return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
}
public static void AddActivePage(this ViewDataDictionary viewData, string activePage) => viewData[ActivePageKey] = activePage;
Index, Users, Rates, Emails, Policies, Theme, Hangfire
}
}

View File

@ -0,0 +1,43 @@
@model BTCPayServer.Services.ThemeSettings
@{
ViewData.SetActivePageAndTitle(ServerNavPages.Theme);
}
<h4>@ViewData["Title"]</h4>
@Html.Partial("_StatusMessage", TempData["StatusMessage"])
<div class="row">
<div class="col-lg-6">
<div asp-validation-summary="All" class="text-danger"></div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<form method="post">
<div class="form-group">
<label asp-for="BootstrapCssUri"></label>
<input asp-for="BootstrapCssUri" class="form-control" />
<span asp-validation-for="BootstrapCssUri" class="text-danger"></span>
<p class="form-text text-muted">
<a href="https://bootstrap.build/app/v4.0/" target="_blank">Build your own theme</a>
or <a href="https://bootswatch.com/" target="_blank">pick one already made</a>
</p>
</div>
<div class="form-group">
<label asp-for="CreativeStartCssUri"></label>
<input asp-for="CreativeStartCssUri" class="form-control" />
<span asp-validation-for="CreativeStartCssUri" class="text-danger"></span>
<p class="form-text text-muted">
<a href="https://startbootstrap.com/template-overviews/creative/" target="_blank">Creative Start theme</a>
is used on top of Bootstrap
</p>
</div>
<button type="submit" class="btn btn-primary" name="command" value="Save">Save</button>
</form>
</div>
</div>
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
}

View File

@ -1,11 +1,10 @@
@model UserViewModel
@{
ViewData["Title"] = Model.Email;
ViewData.AddActivePage(ServerNavPages.Users);
ViewData.SetActivePageAndTitle(ServerNavPages.Users);
}
<h4>@ViewData["Title"]</h4>
<h4>Modify User - @Model.Email</h4>
@Html.Partial("_StatusMessage", Model.StatusMessage)

View File

@ -1,9 +1,9 @@
@using BTCPayServer.Views.Server
<div class="nav flex-column nav-pills">
<a class="nav-link @ServerNavPages.UsersNavClass(ViewContext)" asp-action="Users">Users</a>
<a class="nav-link @ServerNavPages.EmailsNavClass(ViewContext)" asp-action="Emails">Email server</a>
<a class="nav-link @ServerNavPages.PoliciesNavClass(ViewContext)" asp-action="Policies">Policies</a>
<a class="nav-link @ServerNavPages.HangfireNavClass(ViewContext)" href="~/hangfire" target="_blank">Hangfire</a>
<div class="nav flex-column nav-pills">
<a class="nav-link @ViewData.IsActivePage(ServerNavPages.Users)" asp-action="Users">Users</a>
<a class="nav-link @ViewData.IsActivePage(ServerNavPages.Rates)" asp-action="Rates">Rates</a>
<a class="nav-link @ViewData.IsActivePage(ServerNavPages.Emails)" asp-action="Emails">Email server</a>
<a class="nav-link @ViewData.IsActivePage(ServerNavPages.Policies)" asp-action="Policies">Policies</a>
<a class="nav-link @ViewData.IsActivePage(ServerNavPages.Theme)" asp-action="Theme">Theme</a>
<a class="nav-link @ViewData.IsActivePage(ServerNavPages.Hangfire)" href="~/hangfire" target="_blank">Hangfire</a>
</div>

View File

@ -3,6 +3,7 @@
@inject RoleManager<IdentityRole> RoleManager
@inject BTCPayServer.Services.BTCPayServerEnvironment env
@inject BTCPayServer.HostedServices.NBXplorerDashboard dashboard
@inject BTCPayServer.HostedServices.CssThemeManager themeManager
@addTagHelper *, Meziantou.AspNetCore.BundleTagHelpers
<!DOCTYPE html>
@ -17,6 +18,9 @@
<title>BTCPay Server</title>
@* CSS *@
<link href="@themeManager.BootstrapUri" rel="stylesheet" />
<link href="@themeManager.CreativeStartUri" rel="stylesheet" />
<bundle name="wwwroot/bundles/main-bundle.min.css" />
@* JS *@
@ -38,9 +42,9 @@
<div class="container">
<a class="navbar-brand js-scroll-trigger" href="~/">
<img src="~/img/logo.png" height="45">
@if (env.ChainType != NBXplorer.ChainType.Main)
@if (env.NetworkType != NBitcoin.NetworkType.Mainnet)
{
<span class="badge badge-warning" style="font-size:10px;">@env.ChainType.ToString()</span>
<span class="badge badge-warning" style="font-size:10px;">@env.NetworkType.ToString()</span>
}
</a>
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
@ -48,25 +52,25 @@
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
@if(SignInManager.IsSignedIn(User))
{
@if(User.IsInRole(Roles.ServerAdmin))
{
<li class="nav-item"><a asp-area="" asp-controller="Server" asp-action="ListUsers" class="nav-link js-scroll-trigger">Server settings</a></li>
}
<li class="nav-item"><a asp-area="" asp-controller="UserStores" asp-action="ListStores" class="nav-link js-scroll-trigger">Stores</a></li>
<li class="nav-item"><a asp-area="" asp-controller="Apps" asp-action="ListApps" class="nav-link js-scroll-trigger">Apps</a></li>
<li class="nav-item"><a asp-area="" asp-controller="Invoice" asp-action="ListInvoices" class="nav-link js-scroll-trigger">Invoices</a></li>
<li class="nav-item">
<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage" class="nav-link js-scroll-trigger">My settings</a>
</li>
<li class="nav-item">
<a asp-area="" asp-controller="Account" asp- asp-action="Logout" title="Manage" class="nav-link js-scroll-trigger">Log out</a>
</li>}
else
{
<li class="nav-item"><a asp-area="" asp-controller="Account" asp-action="Register" class="nav-link js-scroll-trigger">Register</a></li>
<li class="nav-item"><a asp-area="" asp-controller="Account" asp-action="Login" class="nav-link js-scroll-trigger">Log in</a></li>}
@if (SignInManager.IsSignedIn(User))
{
@if (User.IsInRole(Roles.ServerAdmin))
{
<li class="nav-item"><a asp-area="" asp-controller="Server" asp-action="ListUsers" class="nav-link js-scroll-trigger">Server settings</a></li>
}
<li class="nav-item"><a asp-area="" asp-controller="UserStores" asp-action="ListStores" class="nav-link js-scroll-trigger">Stores</a></li>
<li class="nav-item"><a asp-area="" asp-controller="Apps" asp-action="ListApps" class="nav-link js-scroll-trigger">Apps</a></li>
<li class="nav-item"><a asp-area="" asp-controller="Invoice" asp-action="ListInvoices" class="nav-link js-scroll-trigger">Invoices</a></li>
<li class="nav-item">
<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage" class="nav-link js-scroll-trigger">My settings</a>
</li>
<li class="nav-item">
<a asp-area="" asp-controller="Account" asp- asp-action="Logout" title="Manage" class="nav-link js-scroll-trigger">Log out</a>
</li>}
else
{
<li class="nav-item"><a asp-area="" asp-controller="Account" asp-action="Register" class="nav-link js-scroll-trigger">Register</a></li>
<li class="nav-item"><a asp-area="" asp-controller="Account" asp-action="Login" class="nav-link js-scroll-trigger">Log in</a></li>}
</ul>
</div>
@ -75,8 +79,8 @@ else
@RenderBody()
<footer class="bg-dark">
<div class="container text-right"><span style="font-size:8px;">@env.ToString()</span></div>
<footer class="siteFooter bg-dark text-white">
<div class="container text-right">@env.ToString()</div>
</footer>
@if (!dashboard.IsFullySynched())

View File

@ -36,10 +36,10 @@
</div>
<div class="form-group">
<label asp-for="PreferredExchange"></label>
<input asp-for="PreferredExchange" class="form-control" />
<select asp-for="PreferredExchange" asp-items="Model.Exchanges" class="form-control"></select>
<span asp-validation-for="PreferredExchange" class="text-danger"></span>
<p id="PreferredExchangeHelpBlock" class="form-text text-muted">
Current price source is <a href="@Model.RateSource" target="_blank">@Model.PreferredExchange</a>.<small> (using 1 minute cache)</small>
Current price source is <a href="@Model.RateSource" target="_blank">@Model.PreferredExchange</a>.
</p>
</div>
<div class="form-group">
@ -81,13 +81,13 @@
</tr>
</thead>
<tbody>
@foreach (var scheme in Model.DerivationSchemes)
@foreach(var scheme in Model.DerivationSchemes)
{
<tr>
<td>@scheme.Crypto</td>
<td style="max-width:300px;overflow:hidden;">@scheme.Value</td>
<td style="text-align:right">
@if (!string.IsNullOrWhiteSpace(scheme.Value))
@if(!string.IsNullOrWhiteSpace(scheme.Value))
{
<a asp-action="Wallet" asp-route-cryptoCode="@scheme.Crypto">Wallet</a><span> - </span>
}
@ -117,7 +117,7 @@
</tr>
</thead>
<tbody>
@foreach (var scheme in Model.LightningNodes)
@foreach(var scheme in Model.LightningNodes)
{
<tr>
<td>@scheme.CryptoCode</td>

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace BTCPayServer.Views
{
public static class ViewsRazor
{
public const string ACTIVE_PAGE_KEY = "ActivePage";
public static void SetActivePageAndTitle<T>(this ViewDataDictionary viewData, T activePage, string title = null)
where T : IConvertible
{
viewData["Title"] = title ?? activePage.ToString();
viewData[ACTIVE_PAGE_KEY] = activePage;
}
public static string IsActivePage<T>(this ViewDataDictionary viewData, T page)
where T : IConvertible
{
var activePage = (T)viewData[ACTIVE_PAGE_KEY];
return page.Equals(activePage) ? "active" : null;
}
}
}

View File

@ -1,5 +1,6 @@
@using Microsoft.AspNetCore.Identity
@using BTCPayServer
@using BTCPayServer.Views
@using BTCPayServer.Models
@using BTCPayServer.Models.AccountViewModels
@using BTCPayServer.Models.InvoicingModels

View File

@ -2,7 +2,7 @@
{
"outputFileName": "wwwroot/bundles/main-bundle.min.css",
"inputFiles": [
"wwwroot/vendor/bootstrap4/css/bootstrap.css",
"wwwroot/vendor/bootstrap4-creativestart/Open-Sans.css",
"wwwroot/vendor/magnific-popup/magnific-popup.css",
"wwwroot/vendor/font-awesome/css/font-awesome.css",
"wwwroot/main/**/*.css",
@ -18,7 +18,7 @@
"wwwroot/vendor/jquery-easing/jquery.easing.js",
"wwwroot/vendor/scrollreveal/scrollreveal.min.js",
"wwwroot/vendor/magnific-popup/jquery.magnific-popup.js",
"wwwroot/main/**/js/*.js"
"wwwroot/vendor/bootstrap4-creativestart/*.js"
]
},
{

View File

@ -32,15 +32,6 @@ function onDataCallback(jsonData) {
$(".modal-dialog").removeClass("expired");
}
if (srvModel.merchantRefLink !== "") {
$(".action-button").click(function () {
window.location.href = srvModel.merchantRefLink;
});
}
else {
$(".action-button").hide();
}
$(".modal-dialog").addClass("paid");
resetTabsSlider();

View File

@ -47,5 +47,8 @@ Můžete se vrátit do {{storeName}}, pokud chcete svojí objednávku založit z
"Archived_Body": "Prosíme kontaktujte prodejce pro informace o objednávce a případnou pomoc",
// Lightning
"BOLT 11 Invoice": "BOLT 11 Faktura",
"Node Info": "Info o uzlu"
"Node Info": "Info o uzlu",
//
"txCount": "{{count}} transakce",
"txCount_plural": "{{count}} transakcí"
};

View File

@ -5,8 +5,8 @@ const locales_de = {
"Awaiting Payment...": "Warten auf Zahlung...",
"Pay with": "Bezahlen mit",
"Contact and Refund Email": "Kontakt und Rückerstattungs Email",
"Contact_Body": "Bitte geben Sie unten eine E-Mail-Adresse an. Wir werden Sie unter dieser Adresse kontaktieren, wenn ein Problem mit Ihrer Zahlung vorliegt.",
"Your email": "Deine Email",
"Contact_Body": "Bitte geben Sie unten eine E-Mail-Adresse an. Wir werden Sie unter dieser Adresse kontaktieren, falls ein Problem mit Ihrer Zahlung vorliegt.",
"Your email": "Ihre Email-Adresse",
"Continue": "Fortsetzen",
"Please enter a valid email address": "Bitte geben Sie eine gültige E-Mail-Adresse ein",
"Order Amount": "Bestellbetrag",
@ -16,19 +16,19 @@ const locales_de = {
// Tabs
"Scan": "Scan",
"Copy": "Kopieren",
"Conversion": "Umwandlung",
"Conversion": "Umrechnung",
// Scan tab
"Open in wallet": "In der Brieftasche öffnen",
"Open in wallet": "In der Wallet öffnen",
// Copy tab
"CompletePay_Body": "Um Ihre Zahlung abzuschließen, senden Sie bitte {{btcDue}} {{cryptoCode}} an die unten angegebene Adresse.",
"Amount": "Menge",
"Address": "Adresse",
"Copied": "Kopiert",
// Conversion tab
"ConversionTab_BodyTop": "Sie können {{btcDue}} {{cryptoCode}} mit altcoins bezahlen, die nicht direkt vom Händler unterstützt werden.",
"ConversionTab_BodyDesc": "Dieser Service wird von Drittanbietern bereitgestellt. Bitte beachten Sie, dass wir keine Kontrolle darüber haben, wie die Anbieter Ihre Gelder weiterleiten. Die Rechnung wird erst bezahlt, wenn das Geld in {{cryptoCode}} Blockchain eingegangen ist.",
"ConversionTab_BodyTop": "Sie können {{btcDue}} {{cryptoCode}} mit Altcoins bezahlen, die nicht direkt vom Händler unterstützt werden.",
"ConversionTab_BodyDesc": "Dieser Service wird von Drittanbietern bereitgestellt. Bitte beachten Sie, dass wir keine Kontrolle darüber haben, wie die Anbieter Ihre Gelder weiterleiten. Die Rechnung wird erst als bezahlt markiert, wenn das Geld in {{cryptoCode}} Blockchain eingegangen ist.",
"Shapeshift_Button_Text": "Bezahlen mit Altcoins",
"ConversionTab_Lightning": "Für Lightning Network-Zahlungen sind keine Conversion-Anbieter verfügbar.",
"ConversionTab_Lightning": "Für Lightning Network-Zahlungen sind keine Umrechnungsanbieter verfügbar.",
// Invoice expired
"Invoice expiring soon...": "Die Rechnung läuft bald ab...",
"Invoice expired": "Die Rechnung ist abgelaufen",
@ -36,7 +36,7 @@ const locales_de = {
"InvoiceExpired_Body_1": "Diese Rechnung ist abgelaufen. Eine Rechnung ist nur für {{maxTimeMinutes}} Minuten gültig. \
Sie können zu {{storeName}} zurückkehren, wenn Sie Ihre Zahlung erneut senden möchten.",
"InvoiceExpired_Body_2": "Wenn Sie versucht haben, eine Zahlung zu senden, wurde sie vom Bitcoin-Netzwerk noch nicht akzeptiert. Wir haben Ihre Gelder noch nicht erhalten.",
"InvoiceExpired_Body_3": "Wenn die Transaktion vom Bitcoin-Netzwerk nicht akzeptiert wird, ist das Geld wieder in Ihrer Brieftasche verfügbar. Abhängig von Ihrem Geldbeutel, kann dies 48-72 Stunden dauern.",
"InvoiceExpired_Body_3": "Wenn die Transaktion vom Bitcoin-Netzwerk nicht akzeptiert wird, ist das Geld wieder in Ihrer Wallet verfügbar. Abhängig von Ihrer Wallet, kann dies 48-72 Stunden dauern.",
"Invoice ID": "Rechnungs ID",
"Order ID": "Auftrag ID",
"Return to StoreName": "Zurück zu {{storeName}}",
@ -44,5 +44,11 @@ Sie können zu {{storeName}} zurückkehren, wenn Sie Ihre Zahlung erneut senden
"This invoice has been paid": "Diese Rechnung wurde bezahlt",
// Invoice archived
"This invoice has been archived": "Diese Rechnung wurde archiviert",
"Archived_Body": "Bitte kontaktieren Sie den Shop für Bestellinformationen oder Hilfe"
"Archived_Body": "Bitte kontaktieren Sie den Shop für Bestellinformationen oder Hilfe",
// Lightning
"BOLT 11 Invoice": "BOLT 11 Rechnung",
"Node Info": "Netzwerkknoten Info",
//
"txCount": "{{count}} transaktion",
"txCount_plural": "{{count}} transaktionen"
};

View File

@ -47,5 +47,8 @@ You can return to {{storeName}} if you would like to submit your payment again."
"Archived_Body": "Please contact the store for order information or assistance",
// Lightning
"BOLT 11 Invoice": "BOLT 11 Invoice",
"Node Info": "Node Info"
"Node Info": "Node Info",
//
"txCount": "{{count}} transaction",
"txCount_plural": "{{count}} transactions"
};

View File

@ -44,7 +44,10 @@ const locales_es = {
// Invoice archived
"This invoice has been archived": "Esta factura ha sido archivada",
"Archived_Body": "Por favor, comuníquese con la tienda para obtener información de su pedido o asistencia",
// Lightning
// Lightning
"BOLT 11 Invoice": "Factura BOLT 11",
"Node Info": "Información del nodo"
};
"Node Info": "Información del nodo",
//
"txCount": "{{count}} transacción",
"txCount_plural": "{{count}} transacciones"
};

View File

@ -47,5 +47,8 @@ Vous pouvez revenir sur {{storeName}} si vous voulez resoumettre votre paiement.
"Archived_Body": "Merci de contacter le marchand pour plus d'assistance ou d'information sur cette commande.",
// Lightning
"BOLT 11 Invoice": "Facture BOLT 11",
"Node Info": "Information du noeud"
"Node Info": "Information du noeud",
//
"txCount": "{{count}} transaction",
"txCount_plural": "{{count}} transactions"
};

View File

@ -47,5 +47,8 @@
"Archived_Body": "ご注文に関わる詳細などでお困りの場合はお店の担当窓口へお問い合わせください。",
// Lightning
"BOLT 11 Invoice": "お支払いコード",
"Node Info": "接続情報"
"Node Info": "接続情報",
//
"txCount": "取引 {{count}} 個",
"txCount_plural": "取引 {{count}} 個"
};

View File

@ -47,5 +47,8 @@ Je kan terug komen naar {{storeName}} indien je nog eens je betaling wilt prober
"Archived_Body": "Bedankt om de winkel te contacteren voor bijstand met of informatie over deze bestelling",
// Lightning
"BOLT 11 Invoice": "BOLT 11 Factuur",
"Node Info": "Node Info"
"Node Info": "Node Info",
//
"txCount": "{{count}} transactie",
"txCount_plural": "{{count}} transacties"
};

View File

@ -0,0 +1,54 @@
const locales_pt = {
nested: {
lang: 'Idioma'
},
"Awaiting Payment...": "A Aguardar Pagamento...",
"Pay with": "Pague com",
"Contact and Refund Email": "E-mail de Contacto e Reembolso",
"Contact_Body": "Por favor indique um e-mail abaixo. Entraremos em contacto para este endereço se ocorrer algum problema com o seu pagamento.",
"Your email": "O seu e-mail",
"Continue": "Continuar",
"Please enter a valid email address": "Por favor introduza um e-mail válido",
"Order Amount": "Valor da Encomenda",
"Network Cost": "Custo da Rede",
"Already Paid": "Já Pago",
"Due": "Devido",
// Tabs
"Scan": "Digitalizar",
"Copy": "Copiar",
"Conversion": "Conversão",
// Scan tab
"Open in wallet": "Abrir na carteira",
// Copy tab
"CompletePay_Body": "Para completar o seu pagamento, por favor envie {{btcDue}} {{cryptoCode}} para o endereço abaixo.",
"Amount": "Quantia",
"Address": "Endereço",
"Copied": "Copiado",
// Conversion tab
"ConversionTab_BodyTop": "Pode pagar {{btcDue}} {{cryptoCode}} utilizando outras altcoins além das que a loja aceita diretamente.",
"ConversionTab_BodyDesc": "Este serviço é oferecido por terceiros. Por favor tenha em mente que não temos qualquer controlo sobre como os seus fundos serão utilizados. A fatura será marcada como paga apenas quando os fundos forem recebidos na Blockchain {{cryptoCode}}.",
"Shapeshift_Button_Text": "Pagar com Altcoins",
"ConversionTab_Lightning": "Não há fornecedores de conversão disponíveis para pagamentos via Lightning Network.",
// Invoice expired
"Invoice expiring soon...": "A fatura está a expirar...",
"Invoice expired": "Fatura expirada",
"What happened?": "O que aconteceu?",
"InvoiceExpired_Body_1": "Esta fatura expirou. Uma fatura é válida durante {{maxTimeMinutes}} minutos. \
Pode voltar para {{storeName}} se quiser enviar o seu pagamento novamente.",
"InvoiceExpired_Body_2": "Se tentou enviar um pagamento, ele ainda não foi aceite pela rede Bitcoin. Nós ainda não recebemos o valor enviado.",
"InvoiceExpired_Body_3": "Se a transação não for aceite pela rede Bitcoin, o valor voltará para sua carteira. Dependendo da sua carteira, isto pode demorar entre 48 e 72 horas.",
"Invoice ID": "Nº da Fatura",
"Order ID": "Nº da Encomenda",
"Return to StoreName": "Voltar para {{storeName}}",
// Invoice paid
"This invoice has been paid": "Esta fatura foi paga",
// Invoice archived
"This invoice has been archived": "Esta fatura foi arquivada",
"Archived_Body": "Por favor, entre em contacto com o vendedor para informações e suporte",
// Lightning
"BOLT 11 Invoice": "Fatura BOLT 11",
"Node Info": "Informação do Nó",
//
"txCount": "{{count}} transação",
"txCount_plural": "{{count}} transações"
};

View File

@ -44,5 +44,11 @@ Você pode voltar para {{storeName}} se quiser enviar o seu pagamento novamente.
"This invoice has been paid": "Essa fatura foi paga",
// Invoice archived
"This invoice has been archived": "Essa fatura foi arquivada",
"Archived_Body": "Por favor, entre em contato com o estabelecimento para informações e suporte"
"Archived_Body": "Por favor, entre em contato com o estabelecimento para informações e suporte",
// Lightning
"BOLT 11 Invoice": "Fatura BOLT 11",
"Node Info": "Informação de nó",
//
"txCount": "{{count}} transação",
"txCount_plural": "{{count}} transações"
};

View File

@ -1,44 +1,16 @@
/* Wrapping element */
/* Set some basic padding to keep content from hitting the edges */
.body-content {
padding-left: 15px;
padding-right: 15px;
}
/* Carousel */
.carousel-caption p {
font-size: 20px;
line-height: 1.4;
}
/* Make .svg files in the carousel display properly in older browsers */
.carousel-inner .item img[src$=".svg"] {
width: 100%;
}
/* Hide/rearrange for smaller screens */
@media screen and (max-width: 767px) {
/* Hide captions */
.carousel-caption {
display: none;
}
}
html {
html {
position: relative;
min-height: 100%;
}
body {
margin: 0 0 18px !important;
}
footer {
.siteFooter {
position: absolute;
left: 0;
bottom: 0;
height: 18px;
font-size: 14px;
line-height: 18px;
vertical-align: middle;
font-size: 8px;
width: 100%;
overflow: hidden;
}

View File

@ -0,0 +1,560 @@
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
src: local('Open Sans Light Italic'), local('OpenSans-LightItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKWyV9hmIqOjjg.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
src: local('Open Sans Light Italic'), local('OpenSans-LightItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKWyV9hvIqOjjg.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
src: local('Open Sans Light Italic'), local('OpenSans-LightItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKWyV9hnIqOjjg.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
src: local('Open Sans Light Italic'), local('OpenSans-LightItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKWyV9hoIqOjjg.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
src: local('Open Sans Light Italic'), local('OpenSans-LightItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKWyV9hkIqOjjg.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
src: local('Open Sans Light Italic'), local('OpenSans-LightItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKWyV9hlIqOjjg.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
src: local('Open Sans Light Italic'), local('OpenSans-LightItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKWyV9hrIqM.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
src: local('Open Sans Italic'), local('OpenSans-Italic'), url(https://fonts.gstatic.com/s/opensans/v15/mem6YaGs126MiZpBA-UFUK0Udc1UAw.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
src: local('Open Sans Italic'), local('OpenSans-Italic'), url(https://fonts.gstatic.com/s/opensans/v15/mem6YaGs126MiZpBA-UFUK0ddc1UAw.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
src: local('Open Sans Italic'), local('OpenSans-Italic'), url(https://fonts.gstatic.com/s/opensans/v15/mem6YaGs126MiZpBA-UFUK0Vdc1UAw.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
src: local('Open Sans Italic'), local('OpenSans-Italic'), url(https://fonts.gstatic.com/s/opensans/v15/mem6YaGs126MiZpBA-UFUK0adc1UAw.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
src: local('Open Sans Italic'), local('OpenSans-Italic'), url(https://fonts.gstatic.com/s/opensans/v15/mem6YaGs126MiZpBA-UFUK0Wdc1UAw.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
src: local('Open Sans Italic'), local('OpenSans-Italic'), url(https://fonts.gstatic.com/s/opensans/v15/mem6YaGs126MiZpBA-UFUK0Xdc1UAw.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
src: local('Open Sans Italic'), local('OpenSans-Italic'), url(https://fonts.gstatic.com/s/opensans/v15/mem6YaGs126MiZpBA-UFUK0Zdc0.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
src: local('Open Sans SemiBold Italic'), local('OpenSans-SemiBoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKXGUdhmIqOjjg.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
src: local('Open Sans SemiBold Italic'), local('OpenSans-SemiBoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKXGUdhvIqOjjg.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
src: local('Open Sans SemiBold Italic'), local('OpenSans-SemiBoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKXGUdhnIqOjjg.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
src: local('Open Sans SemiBold Italic'), local('OpenSans-SemiBoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKXGUdhoIqOjjg.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
src: local('Open Sans SemiBold Italic'), local('OpenSans-SemiBoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKXGUdhkIqOjjg.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
src: local('Open Sans SemiBold Italic'), local('OpenSans-SemiBoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKXGUdhlIqOjjg.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
src: local('Open Sans SemiBold Italic'), local('OpenSans-SemiBoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKXGUdhrIqM.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKWiUNhmIqOjjg.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKWiUNhvIqOjjg.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKWiUNhnIqOjjg.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKWiUNhoIqOjjg.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKWiUNhkIqOjjg.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKWiUNhlIqOjjg.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKWiUNhrIqM.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 800;
src: local('Open Sans ExtraBold Italic'), local('OpenSans-ExtraBoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKW-U9hmIqOjjg.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 800;
src: local('Open Sans ExtraBold Italic'), local('OpenSans-ExtraBoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKW-U9hvIqOjjg.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 800;
src: local('Open Sans ExtraBold Italic'), local('OpenSans-ExtraBoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKW-U9hnIqOjjg.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 800;
src: local('Open Sans ExtraBold Italic'), local('OpenSans-ExtraBoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKW-U9hoIqOjjg.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 800;
src: local('Open Sans ExtraBold Italic'), local('OpenSans-ExtraBoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKW-U9hkIqOjjg.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 800;
src: local('Open Sans ExtraBold Italic'), local('OpenSans-ExtraBoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKW-U9hlIqOjjg.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 800;
src: local('Open Sans ExtraBold Italic'), local('OpenSans-ExtraBoldItalic'), url(https://fonts.gstatic.com/s/opensans/v15/memnYaGs126MiZpBA-UFUKW-U9hrIqM.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
src: local('Open Sans Light'), local('OpenSans-Light'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN_r8OX-hpOqc.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
src: local('Open Sans Light'), local('OpenSans-Light'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN_r8OVuhpOqc.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
src: local('Open Sans Light'), local('OpenSans-Light'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN_r8OXuhpOqc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
src: local('Open Sans Light'), local('OpenSans-Light'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN_r8OUehpOqc.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
src: local('Open Sans Light'), local('OpenSans-Light'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN_r8OXehpOqc.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
src: local('Open Sans Light'), local('OpenSans-Light'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN_r8OXOhpOqc.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
src: local('Open Sans Light'), local('OpenSans-Light'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN_r8OUuhp.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/mem8YaGs126MiZpBA-UFWJ0bbck.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/mem8YaGs126MiZpBA-UFUZ0bbck.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/mem8YaGs126MiZpBA-UFWZ0bbck.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/mem8YaGs126MiZpBA-UFVp0bbck.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/mem8YaGs126MiZpBA-UFWp0bbck.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/mem8YaGs126MiZpBA-UFW50bbck.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/mem8YaGs126MiZpBA-UFVZ0b.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UNirkOX-hpOqc.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UNirkOVuhpOqc.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UNirkOXuhpOqc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UNirkOUehpOqc.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UNirkOXehpOqc.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UNirkOXOhpOqc.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UNirkOUuhp.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
src: local('Open Sans Bold'), local('OpenSans-Bold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN7rgOX-hpOqc.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
src: local('Open Sans Bold'), local('OpenSans-Bold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN7rgOVuhpOqc.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
src: local('Open Sans Bold'), local('OpenSans-Bold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN7rgOXuhpOqc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
src: local('Open Sans Bold'), local('OpenSans-Bold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN7rgOUehpOqc.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
src: local('Open Sans Bold'), local('OpenSans-Bold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN7rgOXehpOqc.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
src: local('Open Sans Bold'), local('OpenSans-Bold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN7rgOXOhpOqc.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
src: local('Open Sans Bold'), local('OpenSans-Bold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN7rgOUuhp.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
src: local('Open Sans ExtraBold'), local('OpenSans-ExtraBold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN8rsOX-hpOqc.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
src: local('Open Sans ExtraBold'), local('OpenSans-ExtraBold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN8rsOVuhpOqc.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
src: local('Open Sans ExtraBold'), local('OpenSans-ExtraBold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN8rsOXuhpOqc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
src: local('Open Sans ExtraBold'), local('OpenSans-ExtraBold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN8rsOUehpOqc.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
src: local('Open Sans ExtraBold'), local('OpenSans-ExtraBold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN8rsOXehpOqc.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
src: local('Open Sans ExtraBold'), local('OpenSans-ExtraBold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN8rsOXOhpOqc.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
src: local('Open Sans ExtraBold'), local('OpenSans-ExtraBold'), url(https://fonts.gstatic.com/s/opensans/v15/mem5YaGs126MiZpBA-UN8rsOUuhp.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

View File

@ -1,5 +1,5 @@
/*!
* Start Bootstrap - Createive v4.0.0-beta (http://startbootstrap.com/template-overviews/creative)
* Start Bootstrap - Creative v4.0.0-beta (http://startbootstrap.com/template-overviews/creative)
* Copyright 2013-2017 Start Bootstrap
* Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap-creative/blob/master/LICENSE)
*/
@ -9,7 +9,7 @@ html {
}
body {
font-family: 'Merriweather', 'Helvetica Neue', Arial, sans-serif;
font-family: 'Helvetica Neue', Arial, sans-serif;
}
hr {
@ -22,17 +22,6 @@ hr {
border-color: white;
}
a {
color: #329F80;
-webkit-transition: all 0.2s;
-moz-transition: all 0.2s;
transition: all 0.2s;
}
a:focus, a:hover {
color: #0F3723;
}
h1,
h2,
h3,
@ -48,19 +37,6 @@ p {
margin-bottom: 20px;
}
.bg-primary {
background-color: #0F3B21 !important;
}
.bg-dark {
color: white;
background-color: #0F3723 !important;
}
.text-faded {
color: rgba(255, 255, 255, 0.7);
}
section {
padding: 100px 0;
}
@ -195,7 +171,6 @@ header.masthead {
width: 100%;
min-height: auto;
text-align: center;
color: white;
background-image: url("../../img/bg.png");
background-position: center;
-webkit-background-size: cover;
@ -227,7 +202,6 @@ header.masthead {
font-size: 16px;
font-weight: 300;
margin-bottom: 50px;
color: rgba(255, 255, 255, 0.7);
}
@media (min-width: 768px) {
@ -268,10 +242,6 @@ header.masthead {
margin: 50px auto 0;
}
.service-box h3 {
color: #0F3B21;
}
@media (min-width: 992px) {
.service-box {
margin: 20px auto 0;
@ -297,7 +267,6 @@ header.masthead {
height: 100%;
text-align: center;
opacity: 0;
color: white;
background: rgba(240, 95, 64, 0.9);
-webkit-transition: all 0.2s;
-moz-transition: all 0.2s;
@ -354,100 +323,7 @@ header.masthead {
margin: 0 auto 20px;
}
.text-primary {
color: #0F3B21 !important;
}
.no-gutter > [class*='col-'] {
padding-right: 0;
padding-left: 0;
}
.nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus {
color: #fff;
background-color: #0F3723;
}
/*.btn-default {
color: #222222;
border-color: white;
background-color: white; }
.btn-default.focus, .btn-default:focus {
color: #222222;
border-color: #bfbfbf;
background-color: #e6e6e6; }
.btn-default:hover {
color: #222222;
border-color: #e0e0e0;
background-color: #e6e6e6; }
.btn-default.active, .btn-default:active,
.open > .btn-default.dropdown-toggle {
color: #222222;
border-color: #e0e0e0;
background-color: #e6e6e6; }
.btn-default.active.focus, .btn-default.active:focus, .btn-default.active:hover, .btn-default:active.focus, .btn-default:active:focus, .btn-default:active:hover,
.open > .btn-default.dropdown-toggle.focus,
.open > .btn-default.dropdown-toggle:focus,
.open > .btn-default.dropdown-toggle:hover {
color: #222222;
border-color: #bfbfbf;
background-color: #d4d4d4; }
.btn-default.active, .btn-default:active,
.open > .btn-default.dropdown-toggle {
background-image: none; }
.btn-default.disabled.focus, .btn-default.disabled:focus, .btn-default.disabled:hover, .btn-default[disabled].focus, .btn-default[disabled]:focus, .btn-default[disabled]:hover,
fieldset[disabled] .btn-default.focus,
fieldset[disabled] .btn-default:focus,
fieldset[disabled] .btn-default:hover {
border-color: white;
background-color: white; }
.btn-default .badge {
color: white;
background-color: #222222; }
.btn-primary {
color: white;
border-color: #0F3B21;
background-color: #0F3B21; }
.btn-primary.focus, .btn-primary:focus {
color: white;
border-color: #a4270d;
background-color: #eb3812; }
.btn-primary:hover {
color: white;
border-color: #e13612;
background-color: #eb3812; }
.btn-primary.active, .btn-primary:active,
.open > .btn-primary.dropdown-toggle {
color: white;
border-color: #e13612;
background-color: #eb3812; }
.btn-primary.active.focus, .btn-primary.active:focus, .btn-primary.active:hover, .btn-primary:active.focus, .btn-primary:active:focus, .btn-primary:active:hover,
.open > .btn-primary.dropdown-toggle.focus,
.open > .btn-primary.dropdown-toggle:focus,
.open > .btn-primary.dropdown-toggle:hover {
color: white;
border-color: #a4270d;
background-color: #c93110; }
.btn-primary.active, .btn-primary:active,
.open > .btn-primary.dropdown-toggle {
background-image: none; }
.btn-primary.disabled.focus, .btn-primary.disabled:focus, .btn-primary.disabled:hover, .btn-primary[disabled].focus, .btn-primary[disabled]:focus, .btn-primary[disabled]:hover,
fieldset[disabled] .btn-primary.focus,
fieldset[disabled] .btn-primary:focus,
fieldset[disabled] .btn-primary:hover {
border-color: #0F3B21;
background-color: #0F3B21; }
.btn-primary .badge {
color: #0F3B21;
background-color: white; }
.btn {
font-weight: 700;
text-transform: uppercase;
border: none;
border-radius: 300px;
font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif; }
.btn-xl {
padding: 15px 30px; }*/

View File

@ -1,3 +1,4 @@
$primary: #23735c;
$primary: #329f80;
$secondary: #2284A6;
$dark: #0f3b21;
$font-size-base: 0.9rem;

View File

@ -17,15 +17,15 @@
--cyan: #17a2b8;
--white: #fff;
--gray: #6c757d;
--gray-dark: #343a40;
--primary: #23735c;
--gray-dark: #0f3b21;
--primary: #329f80;
--secondary: #2284A6;
--success: #28a745;
--info: #17a2b8;
--warning: #ffc107;
--danger: #dc3545;
--light: #f8f9fa;
--dark: #343a40;
--dark: #0f3b21;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
@ -137,7 +137,7 @@ sup {
top: -.5em; }
a {
color: #23735c;
color: #329f80;
text-decoration: none;
background-color: transparent;
-webkit-text-decoration-skip: objects; }
@ -1635,8 +1635,8 @@ fieldset:disabled a.btn {
.btn-primary {
color: #fff;
background-color: #23735c;
border-color: #23735c; }
background-color: #329f80;
border-color: #329f80; }
.btn-primary:hover {
color: #fff;
background-color: #1a5645;
@ -1645,8 +1645,8 @@ fieldset:disabled a.btn {
box-shadow: 0 0 0 0.2rem rgba(35, 115, 92, 0.5); }
.btn-primary.disabled, .btn-primary:disabled {
color: #fff;
background-color: #23735c;
border-color: #23735c; }
background-color: #329f80;
border-color: #329f80; }
.btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active,
.show > .btn-primary.dropdown-toggle {
color: #fff;
@ -1796,8 +1796,8 @@ fieldset:disabled a.btn {
.btn-dark {
color: #fff;
background-color: #343a40;
border-color: #343a40; }
background-color: #0f3b21;
border-color: #0f3b21; }
.btn-dark:hover {
color: #fff;
background-color: #23272b;
@ -1806,8 +1806,8 @@ fieldset:disabled a.btn {
box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); }
.btn-dark.disabled, .btn-dark:disabled {
color: #fff;
background-color: #343a40;
border-color: #343a40; }
background-color: #0f3b21;
border-color: #0f3b21; }
.btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active,
.show > .btn-dark.dropdown-toggle {
color: #fff;
@ -1818,24 +1818,24 @@ fieldset:disabled a.btn {
box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); }
.btn-outline-primary {
color: #23735c;
color: #329f80;
background-color: transparent;
background-image: none;
border-color: #23735c; }
border-color: #329f80; }
.btn-outline-primary:hover {
color: #fff;
background-color: #23735c;
border-color: #23735c; }
background-color: #329f80;
border-color: #329f80; }
.btn-outline-primary:focus, .btn-outline-primary.focus {
box-shadow: 0 0 0 0.2rem rgba(35, 115, 92, 0.5); }
.btn-outline-primary.disabled, .btn-outline-primary:disabled {
color: #23735c;
color: #329f80;
background-color: transparent; }
.btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active,
.show > .btn-outline-primary.dropdown-toggle {
color: #fff;
background-color: #23735c;
border-color: #23735c; }
background-color: #329f80;
border-color: #329f80; }
.btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus,
.show > .btn-outline-primary.dropdown-toggle:focus {
box-shadow: 0 0 0 0.2rem rgba(35, 115, 92, 0.5); }
@ -1979,31 +1979,31 @@ fieldset:disabled a.btn {
box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); }
.btn-outline-dark {
color: #343a40;
color: #0f3b21;
background-color: transparent;
background-image: none;
border-color: #343a40; }
border-color: #0f3b21; }
.btn-outline-dark:hover {
color: #fff;
background-color: #343a40;
border-color: #343a40; }
background-color: #0f3b21;
border-color: #0f3b21; }
.btn-outline-dark:focus, .btn-outline-dark.focus {
box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); }
.btn-outline-dark.disabled, .btn-outline-dark:disabled {
color: #343a40;
color: #0f3b21;
background-color: transparent; }
.btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active,
.show > .btn-outline-dark.dropdown-toggle {
color: #fff;
background-color: #343a40;
border-color: #343a40; }
background-color: #0f3b21;
border-color: #0f3b21; }
.btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus,
.show > .btn-outline-dark.dropdown-toggle:focus {
box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); }
.btn-link {
font-weight: 400;
color: #23735c;
color: #329f80;
background-color: transparent; }
.btn-link:hover {
color: #11382d;
@ -2197,7 +2197,7 @@ tbody.collapse.show {
.dropdown-item.active, .dropdown-item:active {
color: #fff;
text-decoration: none;
background-color: #23735c; }
background-color: #329f80; }
.dropdown-item.disabled, .dropdown-item:disabled {
color: #6c757d;
background-color: transparent; }
@ -2429,7 +2429,7 @@ tbody.collapse.show {
opacity: 0; }
.custom-control-input:checked ~ .custom-control-label::before {
color: #fff;
background-color: #23735c; }
background-color: #329f80; }
.custom-control-input:focus ~ .custom-control-label::before {
box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(35, 115, 92, 0.25); }
.custom-control-input:active ~ .custom-control-label::before {
@ -2469,13 +2469,13 @@ tbody.collapse.show {
border-radius: 0.25rem; }
.custom-checkbox .custom-control-input:checked ~ .custom-control-label::before {
background-color: #23735c; }
background-color: #329f80; }
.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E"); }
.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before {
background-color: #23735c; }
background-color: #329f80; }
.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E"); }
@ -2490,7 +2490,7 @@ tbody.collapse.show {
border-radius: 50%; }
.custom-radio .custom-control-input:checked ~ .custom-control-label::before {
background-color: #23735c; }
background-color: #329f80; }
.custom-radio .custom-control-input:checked ~ .custom-control-label::after {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E"); }
@ -2636,7 +2636,7 @@ tbody.collapse.show {
.nav-pills .nav-link.active,
.nav-pills .show > .nav-link {
color: #fff;
background-color: #23735c; }
background-color: #329f80; }
.nav-fill .nav-item {
flex: 1 1 auto;
@ -3148,7 +3148,7 @@ tbody.collapse.show {
padding: 0.5rem 0.75rem;
margin-left: -1px;
line-height: 1.25;
color: #23735c;
color: #329f80;
background-color: #fff;
border: 1px solid #dee2e6; }
.page-link:hover {
@ -3175,8 +3175,8 @@ tbody.collapse.show {
.page-item.active .page-link {
z-index: 1;
color: #fff;
background-color: #23735c;
border-color: #23735c; }
background-color: #329f80;
border-color: #329f80; }
.page-item.disabled .page-link {
color: #6c757d;
@ -3235,7 +3235,7 @@ tbody.collapse.show {
.badge-primary {
color: #fff;
background-color: #23735c; }
background-color: #329f80; }
.badge-primary[href]:hover, .badge-primary[href]:focus {
color: #fff;
text-decoration: none;
@ -3291,7 +3291,7 @@ tbody.collapse.show {
.badge-dark {
color: #fff;
background-color: #343a40; }
background-color: #0f3b21; }
.badge-dark[href]:hover, .badge-dark[href]:focus {
color: #fff;
text-decoration: none;
@ -3425,7 +3425,7 @@ tbody.collapse.show {
justify-content: center;
color: #fff;
text-align: center;
background-color: #23735c;
background-color: #329f80;
transition: width 0.6s ease; }
.progress-bar-striped {
@ -3483,8 +3483,8 @@ tbody.collapse.show {
.list-group-item.active {
z-index: 2;
color: #fff;
background-color: #23735c;
border-color: #23735c; }
background-color: #329f80;
border-color: #329f80; }
.list-group-flush .list-group-item {
border-right: 0;
@ -4099,7 +4099,7 @@ button.close {
vertical-align: text-top !important; }
.bg-primary {
background-color: #23735c !important; }
background-color: #329f80 !important; }
a.bg-primary:hover, a.bg-primary:focus,
button.bg-primary:hover,
@ -4155,7 +4155,7 @@ button.bg-light:focus {
background-color: #dae0e5 !important; }
.bg-dark {
background-color: #343a40 !important; }
background-color: #0f3b21 !important; }
a.bg-dark:hover, a.bg-dark:focus,
button.bg-dark:hover,
@ -4199,7 +4199,7 @@ button.bg-dark:focus {
border-left: 0 !important; }
.border-primary {
border-color: #23735c !important; }
border-color: #329f80 !important; }
.border-secondary {
border-color: #2284A6 !important; }
@ -4220,7 +4220,7 @@ button.bg-dark:focus {
border-color: #f8f9fa !important; }
.border-dark {
border-color: #343a40 !important; }
border-color: #0f3b21 !important; }
.border-white {
border-color: #fff !important; }
@ -5929,7 +5929,7 @@ button.bg-dark:focus {
color: #fff !important; }
.text-primary {
color: #23735c !important; }
color: #329f80 !important; }
a.text-primary:hover, a.text-primary:focus {
color: #174c3d !important; }
@ -5971,7 +5971,7 @@ a.text-light:hover, a.text-light:focus {
color: #dae0e5 !important; }
.text-dark {
color: #343a40 !important; }
color: #0f3b21 !important; }
a.text-dark:hover, a.text-dark:focus {
color: #1d2124 !important; }