Compare commits
68 Commits
Author | SHA1 | Date | |
---|---|---|---|
ead97a24bd | |||
b438312fde | |||
79ff2cb271 | |||
ed1464c405 | |||
f85631429b | |||
5ed56d1137 | |||
d7719d25b4 | |||
6267cccc3f | |||
fd5c4021f7 | |||
b8bf4d99ac | |||
0723eec508 | |||
7f01a12245 | |||
e1e3e5d953 | |||
2a68f8a90f | |||
659936577b | |||
85efc3b00c | |||
5efac45d46 | |||
c7dce280d7 | |||
04c6107196 | |||
54ce9b5dab | |||
cee955fb9d | |||
2e4b0daa48 | |||
e85ccfb47e | |||
75099b99d4 | |||
7b1b2a0f68 | |||
203c28df3d | |||
2e2c3cdec4 | |||
6f827c86a4 | |||
5aced90a3f | |||
4646f88e3a | |||
2b11cc1077 | |||
77b42eb085 | |||
7de067cd7a | |||
9da6df50b7 | |||
66b1623109 | |||
2432834f3d | |||
01fa483f95 | |||
1ddf47256f | |||
25fe32c3f8 | |||
ac9b8d03d7 | |||
8fdfb2c4f6 | |||
b1da136f77 | |||
9a6f85fa21 | |||
7308453a74 | |||
b798a17ef8 | |||
4b8899860e | |||
f46c8a0a0f | |||
48832f9ac3 | |||
9c798fc2e2 | |||
4704587f0a | |||
58e6b63fd7 | |||
3c76dfb584 | |||
10055d987d | |||
be49c60e83 | |||
14016e2f84 | |||
0f63162254 | |||
49200a4a9c | |||
7d71757de3 | |||
0fb492a70f | |||
7ccc1abb95 | |||
d61858e260 | |||
0ecd40f299 | |||
d9d4e74126 | |||
42d04bff61 | |||
f9cc29f014 | |||
40092b60fa | |||
5356b74490 | |||
e832ce5b4a |
@ -47,7 +47,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
public Uri LTCNBXplorerUri { get; set; }
|
||||
|
||||
|
||||
public Uri ServerUri
|
||||
{
|
||||
get;
|
||||
@ -65,6 +65,9 @@ namespace BTCPayServer.Tests
|
||||
get; set;
|
||||
}
|
||||
|
||||
|
||||
public bool MockRates { get; set; } = true;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (!Directory.Exists(_Directory))
|
||||
@ -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)
|
||||
@ -120,9 +126,8 @@ namespace BTCPayServer.Tests
|
||||
.Build();
|
||||
_Host.Start();
|
||||
InvoiceRepository = (InvoiceRepository)_Host.Services.GetService(typeof(InvoiceRepository));
|
||||
((LightningLikePaymentHandler)_Host.Services.GetService(typeof(IPaymentMethodHandler<LightningSupportedPaymentMethod>))).SkipP2PTest = !InContainer;
|
||||
}
|
||||
|
||||
|
||||
public string HostName
|
||||
{
|
||||
get;
|
||||
|
@ -34,21 +34,11 @@ 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);
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -132,7 +132,8 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Url = connectionType == LightningConnectionType.Charge ? parent.MerchantCharge.Client.Uri.AbsoluteUri :
|
||||
connectionType == LightningConnectionType.CLightning ? parent.MerchantLightningD.Address.AbsoluteUri
|
||||
: throw new NotSupportedException(connectionType.ToString())
|
||||
: throw new NotSupportedException(connectionType.ToString()),
|
||||
SkipPortTest = true
|
||||
}, "save", "BTC");
|
||||
if (storeController.ModelState.ErrorCount != 0)
|
||||
Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
|
||||
|
@ -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();
|
||||
@ -1164,7 +1206,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx)
|
||||
{
|
||||
var h = BitcoinAddress.Create(invoice.BitcoinAddress).ScriptPubKey.Hash.ToString();
|
||||
var h = BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest).ScriptPubKey.Hash.ToString();
|
||||
return ctx.AddressInvoices.FirstOrDefault(i => i.InvoiceDataId == invoice.Id && i.GetAddress() == h) != null;
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ services:
|
||||
- lightning-charged
|
||||
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:1.0.1.25
|
||||
image: nicolasdorier/nbxplorer:1.0.1.34
|
||||
ports:
|
||||
- "32838:32838"
|
||||
expose:
|
||||
@ -89,7 +89,7 @@ services:
|
||||
- "bitcoin_datadir:/data"
|
||||
|
||||
customer_lightningd:
|
||||
image: nicolasdorier/clightning:0.0.0.2
|
||||
image: nicolasdorier/clightning:0.0.0.3
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
LIGHTNINGD_OPT: |
|
||||
@ -110,7 +110,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
lightning-charged:
|
||||
image: shesek/lightning-charge:0.3.7
|
||||
image: shesek/lightning-charge:0.3.9
|
||||
environment:
|
||||
NETWORK: regtest
|
||||
API_TOKEN: foiewnccewuify
|
||||
@ -129,7 +129,7 @@ services:
|
||||
- merchant_lightningd
|
||||
|
||||
merchant_lightningd:
|
||||
image: nicolasdorier/clightning:0.0.0.2
|
||||
image: nicolasdorier/clightning:0.0.0.5-dev
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
LIGHTNINGD_OPT: |
|
||||
@ -138,6 +138,7 @@ services:
|
||||
ipaddr=merchant_lightningd
|
||||
network=regtest
|
||||
log-level=debug
|
||||
dev-broadcast-interval=1000
|
||||
ports:
|
||||
- "30993:9835" # api port
|
||||
expose:
|
||||
|
@ -50,7 +50,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
|
||||
@ -72,6 +72,12 @@ namespace BTCPayServer
|
||||
public override string ToString()
|
||||
{
|
||||
return CryptoCode;
|
||||
}
|
||||
}
|
||||
|
||||
internal KeyPath GetRootKeyPath()
|
||||
{
|
||||
return new KeyPath(NBitcoinNetwork.Consensus.SupportSegwit ? "49'" : "44'")
|
||||
.Derive(CoinType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,9 +14,9 @@ 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,
|
||||
|
32
BTCPayServer/BTCPayNetworkProvider.Dogecoin.cs
Normal file
32
BTCPayServer/BTCPayNetworkProvider.Dogecoin.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public partial class BTCPayNetworkProvider
|
||||
{
|
||||
public void InitDogecoin()
|
||||
{
|
||||
NBitcoin.Altcoins.Dogecoin.EnsureRegistered();
|
||||
|
||||
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}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "dogecoin",
|
||||
DefaultRateProvider = new CoinAverageRateProviderDescription("DOGE"),
|
||||
CryptoImagePath = "imlegacy/dogecoin.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NBXplorerNetworkProvider.ChainType),
|
||||
CoinType = NBXplorerNetworkProvider.ChainType == ChainType.Main ? new KeyPath("3'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,6 @@ namespace BTCPayServer
|
||||
public void InitLitecoin()
|
||||
{
|
||||
NBitcoin.Altcoins.Litecoin.EnsureRegistered();
|
||||
var ltcRate = new CoinAverageRateProvider("LTC");
|
||||
|
||||
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("LTC");
|
||||
Add(new BTCPayNetwork()
|
||||
@ -23,11 +22,11 @@ namespace BTCPayServer
|
||||
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("3'")
|
||||
CoinType = NBXplorerNetworkProvider.ChainType == ChainType.Main ? new KeyPath("2'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ namespace BTCPayServer
|
||||
ChainType = chainType;
|
||||
InitBitcoin();
|
||||
InitLitecoin();
|
||||
InitDogecoin();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -2,21 +2,25 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<Version>1.0.1.77</Version>
|
||||
<Version>1.0.1.85</Version>
|
||||
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Build\dockerfiles\**" />
|
||||
<Compile Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<Compile Remove="wwwroot\css\**" />
|
||||
<Compile Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
<Content Remove="Build\dockerfiles\**" />
|
||||
<Content Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<Content Remove="wwwroot\css\**" />
|
||||
<Content Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
<EmbeddedResource Remove="Build\dockerfiles\**" />
|
||||
<EmbeddedResource Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<EmbeddedResource Remove="wwwroot\css\**" />
|
||||
<EmbeddedResource Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
<None Remove="Build\dockerfiles\**" />
|
||||
<None Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<None Remove="wwwroot\css\**" />
|
||||
<None Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@ -26,19 +30,19 @@
|
||||
<EmbeddedResource Include="Currencies.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BuildBundlerMinifier" Version="2.6.362" />
|
||||
<PackageReference Include="Hangfire" Version="1.6.17" />
|
||||
<PackageReference Include="BuildBundlerMinifier" Version="2.6.375" />
|
||||
<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.3" />
|
||||
<PackageReference Include="NBitcoin" Version="4.1.0.11" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.18" />
|
||||
<PackageReference Include="DBreeze" Version="1.87.0" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="1.0.1.17" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="1.0.1.24" />
|
||||
<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" />
|
||||
@ -61,13 +65,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="wwwroot\js\checkout\core.js" />
|
||||
<None Include="wwwroot\js\creative.js" />
|
||||
<None Include="wwwroot\js\creative.min.js" />
|
||||
<None Include="wwwroot\js\site.js" />
|
||||
<None Include="wwwroot\js\site.min.js" />
|
||||
<None Include="wwwroot\vendor\bootstrap\js\bootstrap.js" />
|
||||
<None Include="wwwroot\vendor\bootstrap\js\bootstrap.min.js" />
|
||||
<None Include="wwwroot\checkout\js\core.js" />
|
||||
<None Include="wwwroot\main\js\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" />
|
||||
|
@ -94,7 +94,8 @@ namespace BTCPayServer.Configuration
|
||||
ExternalUrl = conf.GetOrDefault<Uri>("externalurl", null);
|
||||
|
||||
RootPath = conf.GetOrDefault<string>("rootpath", "/");
|
||||
|
||||
if (!RootPath.StartsWith("/", StringComparison.InvariantCultureIgnoreCase))
|
||||
RootPath = "/" + RootPath;
|
||||
var old = conf.GetOrDefault<Uri>("internallightningnode", null);
|
||||
if(old != null)
|
||||
throw new ConfigException($"internallightningnode should not be used anymore, use btclightning instead");
|
||||
@ -118,5 +119,14 @@ namespace BTCPayServer.Configuration
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
internal string GetRootUri()
|
||||
{
|
||||
if (ExternalUrl == null)
|
||||
return null;
|
||||
UriBuilder builder = new UriBuilder(ExternalUrl);
|
||||
builder.Path = RootPath;
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -238,7 +238,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,10 +252,6 @@ 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);
|
||||
return model;
|
||||
@ -358,7 +355,7 @@ namespace BTCPayServer.Controllers
|
||||
[Route("invoices")]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> ListInvoices(string searchTerm = null, int skip = 0, int count = 20)
|
||||
public async Task<IActionResult> ListInvoices(string searchTerm = null, int skip = 0, int count = 50)
|
||||
{
|
||||
var model = new InvoicesModel();
|
||||
var filterString = new SearchString(searchTerm);
|
||||
|
@ -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))
|
||||
@ -278,11 +278,6 @@ namespace BTCPayServer.Controllers
|
||||
buyerInformation.BuyerZip = buyerInformation.BuyerZip ?? buyer.zip;
|
||||
}
|
||||
|
||||
private DerivationStrategyBase ParseDerivationStrategy(string derivationStrategy, BTCPayNetwork network)
|
||||
{
|
||||
return new DerivationStrategyFactory(network.NBitcoinNetwork).Parse(derivationStrategy);
|
||||
}
|
||||
|
||||
private TDest Map<TFrom, TDest>(TFrom data)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<TDest>(JsonConvert.SerializeObject(data));
|
||||
|
@ -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()
|
||||
|
@ -2,6 +2,7 @@
|
||||
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 +12,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 +23,72 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
private UserManager<ApplicationUser> _UserManager;
|
||||
SettingsRepository _SettingsRepository;
|
||||
private IRateProviderFactory _RateProviderFactory;
|
||||
|
||||
public ServerController(UserManager<ApplicationUser> userManager, SettingsRepository settingsRepository)
|
||||
public ServerController(UserManager<ApplicationUser> userManager,
|
||||
IRateProviderFactory rateProviderFactory,
|
||||
SettingsRepository settingsRepository)
|
||||
{
|
||||
_UserManager = userManager;
|
||||
_SettingsRepository = settingsRepository;
|
||||
_RateProviderFactory = rateProviderFactory;
|
||||
}
|
||||
|
||||
[Route("server/rates")]
|
||||
public async Task<IActionResult> Rates()
|
||||
{
|
||||
var rates = (await _SettingsRepository.GetSettingAsync<RatesSetting>()) ?? new RatesSetting();
|
||||
return View(new RatesViewModel()
|
||||
{
|
||||
CacheMinutes = rates.CacheInMinutes,
|
||||
PrivateKey = rates.PrivateKey,
|
||||
PublicKey = rates.PublicKey
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
class TestCoinAverageAuthenticator : ICoinAverageAuthenticator
|
||||
{
|
||||
private RatesSetting settings;
|
||||
|
||||
public TestCoinAverageAuthenticator(RatesSetting settings)
|
||||
{
|
||||
this.settings = settings;
|
||||
}
|
||||
public Task AddHeader(HttpRequestMessage message)
|
||||
{
|
||||
var sig = settings.GetCoinAverageSignature();
|
||||
if (sig != null)
|
||||
message.Headers.Add("X-signature", settings.GetCoinAverageSignature());
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
[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
|
||||
{
|
||||
if (rates.GetCoinAverageSignature() != null)
|
||||
{
|
||||
await new CoinAverageRateProvider("BTC")
|
||||
{ Authenticator = new TestCoinAverageAuthenticator(rates) }.TestAuthAsync();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.PrivateKey), "Invalid API key pair");
|
||||
}
|
||||
if (!ModelState.IsValid)
|
||||
return View(vm);
|
||||
await _SettingsRepository.UpdateSetting(rates);
|
||||
((BTCPayRateProviderFactory)_RateProviderFactory).CacheSpan = TimeSpan.FromMinutes(vm.CacheMinutes);
|
||||
StatusMessage = "Rate settings successfully updated";
|
||||
return RedirectToAction(nameof(Rates));
|
||||
}
|
||||
|
||||
[Route("server/users")]
|
||||
@ -72,7 +135,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 +143,7 @@ namespace BTCPayServer.Controllers
|
||||
await _UserManager.RemoveFromRoleAsync(user, Roles.ServerAdmin);
|
||||
updated = true;
|
||||
}
|
||||
if(updated)
|
||||
if (updated)
|
||||
{
|
||||
viewModel.StatusMessage = "User successfully updated";
|
||||
}
|
||||
|
@ -26,10 +26,16 @@ namespace BTCPayServer.Controllers
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
var network = cryptoCode == null ? null : _ExplorerProvider.GetNetwork(cryptoCode);
|
||||
if (network == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
DerivationSchemeViewModel vm = new DerivationSchemeViewModel();
|
||||
vm.ServerUrl = GetStoreUrl(storeId);
|
||||
vm.CryptoCode = cryptoCode;
|
||||
vm.RootKeyPath = network.GetRootKeyPath();
|
||||
SetExistingValues(store, vm);
|
||||
return View(vm);
|
||||
}
|
||||
@ -63,6 +69,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
vm.RootKeyPath = network.GetRootKeyPath();
|
||||
var wallet = _WalletProvider.GetWallet(network);
|
||||
if (wallet == null)
|
||||
{
|
||||
@ -204,7 +211,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
try
|
||||
{
|
||||
destinationAddress = BitcoinAddress.Create(destination);
|
||||
destinationAddress = BitcoinAddress.Create(destination, network.NBitcoinNetwork);
|
||||
}
|
||||
catch { }
|
||||
if (destinationAddress == null)
|
||||
@ -251,8 +258,6 @@ namespace BTCPayServer.Controllers
|
||||
if (command == "getxpub")
|
||||
{
|
||||
var getxpubResult = await hw.GetExtPubKey(network, account);
|
||||
;
|
||||
getxpubResult.CoinType = (int)(getxpubResult.KeyPath.Indexes[1] - 0x80000000);
|
||||
result = getxpubResult;
|
||||
}
|
||||
if (command == "getinfo")
|
||||
|
@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using System.Net;
|
||||
using BTCPayServer.Data;
|
||||
using System.Threading;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -127,13 +128,20 @@ namespace BTCPayServer.Controllers
|
||||
try
|
||||
{
|
||||
var info = await handler.Test(paymentMethod, network);
|
||||
if (!vm.SkipPortTest)
|
||||
{
|
||||
using (CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(20)))
|
||||
{
|
||||
await handler.TestConnection(info, cts.Token);
|
||||
}
|
||||
}
|
||||
vm.StatusMessage = $"Connection to the lightning node succeed ({info})";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
vm.StatusMessage = $"Error: {ex.Message}";
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,9 @@ namespace BTCPayServer
|
||||
}
|
||||
}
|
||||
|
||||
if(!Network.Consensus.SupportSegwit)
|
||||
hintedLabels.Add("legacy");
|
||||
|
||||
try
|
||||
{
|
||||
var result = new DerivationStrategyFactory(Network).Parse(str);
|
||||
|
55
BTCPayServer/HostedServices/RatesHostedService.cs
Normal file
55
BTCPayServer/HostedServices/RatesHostedService.cs
Normal file
@ -0,0 +1,55 @@
|
||||
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;
|
||||
|
||||
namespace BTCPayServer.HostedServices
|
||||
{
|
||||
public class RatesHostedService : IHostedService
|
||||
{
|
||||
private SettingsRepository _SettingsRepository;
|
||||
private IRateProviderFactory _RateProviderFactory;
|
||||
public RatesHostedService(SettingsRepository repo,
|
||||
IRateProviderFactory rateProviderFactory)
|
||||
{
|
||||
this._SettingsRepository = repo;
|
||||
_RateProviderFactory = rateProviderFactory;
|
||||
}
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Init();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
async void Init()
|
||||
{
|
||||
var rates = (await _SettingsRepository.GetSettingAsync<RatesSetting>()) ?? new RatesSetting();
|
||||
_RateProviderFactory.CacheSpan = TimeSpan.FromMinutes(rates.CacheInMinutes);
|
||||
|
||||
//string[] availableExchanges = null;
|
||||
//// So we don't run this in testing
|
||||
//if(_RateProviderFactory is BTCPayRateProviderFactory)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// await new CoinAverageRateProvider("BTC").GetExchangeTickersAsync();
|
||||
// }
|
||||
// catch(Exception ex)
|
||||
// {
|
||||
// Logs.PayServer.LogWarning(ex, "Failed to get exchange tickers");
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -109,6 +109,7 @@ namespace BTCPayServer.Hosting
|
||||
services.AddSingleton<BTCPayServerEnvironment>();
|
||||
services.TryAddSingleton<TokenRepository>();
|
||||
services.TryAddSingleton<EventAggregator>();
|
||||
services.TryAddSingleton<ICoinAverageAuthenticator, BTCPayCoinAverageAuthenticator>();
|
||||
services.TryAddSingleton<ApplicationDbContextFactory>(o =>
|
||||
{
|
||||
var opts = o.GetRequiredService<BTCPayServerOptions>();
|
||||
@ -154,6 +155,7 @@ 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 =>
|
||||
@ -163,7 +165,7 @@ namespace BTCPayServer.Hosting
|
||||
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>();
|
||||
|
@ -76,8 +76,6 @@ namespace BTCPayServer.Hosting
|
||||
.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
// Big hack, tests fails because Hangfire fail at initializing at the second test run
|
||||
AddHangfireFix(services);
|
||||
services.AddBTCPayServer();
|
||||
services.AddMvc(o =>
|
||||
{
|
||||
@ -93,6 +91,24 @@ namespace BTCPayServer.Hosting
|
||||
options.Password.RequireUppercase = false;
|
||||
});
|
||||
|
||||
services.AddHangfire((o) =>
|
||||
{
|
||||
var scope = AspNetCoreJobActivator.Current.BeginScope(null);
|
||||
var options = (ApplicationDbContextFactory)scope.Resolve(typeof(ApplicationDbContextFactory));
|
||||
options.ConfigureHangfireBuilder(o);
|
||||
});
|
||||
services.AddCors(o =>
|
||||
{
|
||||
o.AddPolicy("BitpayAPI", b =>
|
||||
{
|
||||
b.AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin();
|
||||
});
|
||||
});
|
||||
services.Configure<IOptions<ApplicationInsightsServiceOptions>>(o =>
|
||||
{
|
||||
o.Value.DeveloperMode = _Env.IsDevelopment();
|
||||
});
|
||||
|
||||
// Needed to debug U2F for ledger support
|
||||
//services.Configure<KestrelServerOptions>(kestrel =>
|
||||
//{
|
||||
@ -103,46 +119,6 @@ namespace BTCPayServer.Hosting
|
||||
//});
|
||||
}
|
||||
|
||||
// Big hack, tests fails if only call AddHangfire because Hangfire fail at initializing at the second test run
|
||||
private void AddHangfireFix(IServiceCollection services)
|
||||
{
|
||||
Action<IGlobalConfiguration> configuration = o =>
|
||||
{
|
||||
var scope = AspNetCoreJobActivator.Current.BeginScope(null);
|
||||
var options = (ApplicationDbContextFactory)scope.Resolve(typeof(ApplicationDbContextFactory));
|
||||
options.ConfigureHangfireBuilder(o);
|
||||
};
|
||||
|
||||
ServiceCollectionDescriptorExtensions.TryAddSingleton<Action<IGlobalConfiguration>>(services, (IServiceProvider serviceProvider) => new Action<IGlobalConfiguration>((config) =>
|
||||
{
|
||||
ILoggerFactory service = ServiceProviderServiceExtensions.GetService<ILoggerFactory>(serviceProvider);
|
||||
if (service != null)
|
||||
{
|
||||
Hangfire.GlobalConfigurationExtensions.UseLogProvider<AspNetCoreLogProvider>(config, new AspNetCoreLogProvider(service));
|
||||
}
|
||||
IServiceScopeFactory service2 = ServiceProviderServiceExtensions.GetService<IServiceScopeFactory>(serviceProvider);
|
||||
if (service2 != null)
|
||||
{
|
||||
Hangfire.GlobalConfigurationExtensions.UseActivator<AspNetCoreJobActivator>(config, new AspNetCoreJobActivator(service2));
|
||||
}
|
||||
configuration(config);
|
||||
}));
|
||||
|
||||
services.AddHangfire(configuration);
|
||||
services.AddCors(o =>
|
||||
{
|
||||
o.AddPolicy("BitpayAPI", b =>
|
||||
{
|
||||
b.AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin();
|
||||
});
|
||||
});
|
||||
|
||||
services.Configure<IOptions<ApplicationInsightsServiceOptions>>(o =>
|
||||
{
|
||||
o.Value.DeveloperMode = _Env.IsDevelopment();
|
||||
});
|
||||
}
|
||||
|
||||
public void Configure(
|
||||
IApplicationBuilder app,
|
||||
IHostingEnvironment env,
|
||||
@ -154,18 +130,18 @@ namespace BTCPayServer.Hosting
|
||||
Logs.Configuration.LogInformation($"Root Path: {options.RootPath}");
|
||||
if (options.RootPath.Equals("/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ConfigureCore(app, env, prov, loggerFactory);
|
||||
ConfigureCore(app, env, prov, loggerFactory, options);
|
||||
}
|
||||
else
|
||||
{
|
||||
app.Map(options.RootPath, appChild =>
|
||||
{
|
||||
ConfigureCore(appChild, env, prov, loggerFactory);
|
||||
ConfigureCore(appChild, env, prov, loggerFactory, options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void ConfigureCore(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider prov, ILoggerFactory loggerFactory)
|
||||
private static void ConfigureCore(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider prov, ILoggerFactory loggerFactory, BTCPayServerOptions options)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
@ -180,7 +156,11 @@ namespace BTCPayServer.Hosting
|
||||
app.UseStaticFiles();
|
||||
app.UseAuthentication();
|
||||
app.UseHangfireServer();
|
||||
app.UseHangfireDashboard("/hangfire", new DashboardOptions() { Authorization = new[] { new NeedRole(Roles.ServerAdmin) } });
|
||||
app.UseHangfireDashboard("/hangfire", new DashboardOptions()
|
||||
{
|
||||
AppPath = options.GetRootUri(),
|
||||
Authorization = new[] { new NeedRole(Roles.ServerAdmin) }
|
||||
});
|
||||
app.UseWebSockets();
|
||||
app.UseMvc(routes =>
|
||||
{
|
||||
|
@ -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; }
|
||||
|
18
BTCPayServer/Models/ServerViewModels/RatesViewModel.cs
Normal file
18
BTCPayServer/Models/ServerViewModels/RatesViewModel.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
@ -29,5 +30,6 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
|
||||
public string ServerUrl { get; set; }
|
||||
public string StatusMessage { get; internal set; }
|
||||
public KeyPath RootKeyPath { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -23,5 +23,6 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
}
|
||||
public string StatusMessage { get; set; }
|
||||
public string InternalLightningNode { get; internal set; }
|
||||
public bool SkipPortTest { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -177,7 +177,7 @@ namespace BTCPayServer.Payments.Lightning.CLightning
|
||||
async Task<LightningInvoice> ILightningInvoiceClient.CreateInvoice(LightMoney amount, string description, TimeSpan expiry, CancellationToken cancellation)
|
||||
{
|
||||
var id = InvoiceIdEncoder.EncodeData(RandomUtils.GetBytes(20));
|
||||
var invoice = await SendCommandAsync<CLightningInvoice>("invoice", new object[] { amount.MilliSatoshi, id, description ?? "" }, cancellation: cancellation);
|
||||
var invoice = await SendCommandAsync<CLightningInvoice>("invoice", new object[] { amount.MilliSatoshi, id, description ?? "", Math.Max(0, (int)expiry.TotalSeconds) }, cancellation: cancellation);
|
||||
invoice.Label = id;
|
||||
invoice.MilliSatoshi = amount;
|
||||
invoice.Status = "unpaid";
|
||||
|
@ -44,7 +44,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
.Replace("{OrderId}", invoice.OrderId ?? "", StringComparison.OrdinalIgnoreCase);
|
||||
lightningInvoice = await client.CreateInvoice(new LightMoney(due, LightMoneyUnit.BTC), description, expiry);
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException($"Impossible to create lightning invoice ({ex.Message})", ex);
|
||||
}
|
||||
@ -57,11 +57,6 @@ namespace BTCPayServer.Payments.Lightning
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for testing
|
||||
/// </summary>
|
||||
public bool SkipP2PTest { get; set; }
|
||||
|
||||
public async Task<NodeInfo> Test(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network)
|
||||
{
|
||||
if (!_Dashboard.IsFullySynched(network.CryptoCode, out var summary))
|
||||
@ -94,38 +89,34 @@ namespace BTCPayServer.Payments.Lightning
|
||||
throw new PaymentMethodUnavailableException($"The lightning is not synched ({blocksGap} blocks)");
|
||||
}
|
||||
|
||||
return new NodeInfo(info.NodeId, info.Address, info.P2PPort);
|
||||
}
|
||||
|
||||
public async Task TestConnection(NodeInfo nodeInfo, CancellationToken cancellation)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!SkipP2PTest)
|
||||
IPAddress address = null;
|
||||
try
|
||||
{
|
||||
await TestConnection(info.Address, info.P2PPort, cts.Token);
|
||||
address = IPAddress.Parse(nodeInfo.Host);
|
||||
}
|
||||
catch
|
||||
{
|
||||
address = (await Dns.GetHostAddressesAsync(nodeInfo.Host)).FirstOrDefault();
|
||||
}
|
||||
|
||||
if (address == null)
|
||||
throw new PaymentMethodUnavailableException($"DNS did not resolved {nodeInfo.Host}");
|
||||
|
||||
using (var tcp = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
|
||||
{
|
||||
await WithTimeout(tcp.ConnectAsync(new IPEndPoint(address, nodeInfo.Port)), cancellation);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException($"Error while connecting to the lightning node via {info.Address}:{info.P2PPort} ({ex.Message})");
|
||||
}
|
||||
return new NodeInfo(info.NodeId, info.Address, info.P2PPort);
|
||||
}
|
||||
|
||||
private async Task TestConnection(string addressStr, int port, CancellationToken cancellation)
|
||||
{
|
||||
IPAddress address = null;
|
||||
try
|
||||
{
|
||||
address = IPAddress.Parse(addressStr);
|
||||
}
|
||||
catch
|
||||
{
|
||||
address = (await Dns.GetHostAddressesAsync(addressStr)).FirstOrDefault();
|
||||
}
|
||||
|
||||
if (address == null)
|
||||
throw new PaymentMethodUnavailableException($"DNS did not resolved {addressStr}");
|
||||
|
||||
using (var tcp = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
|
||||
{
|
||||
await WithTimeout(tcp.ConnectAsync(new IPEndPoint(address, port)), cancellation);
|
||||
throw new PaymentMethodUnavailableException($"Error while connecting to the lightning node via {nodeInfo.Host}:{nodeInfo.Port} ({ex.Message})");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,9 +52,20 @@ namespace BTCPayServer.Payments.Lightning
|
||||
|
||||
_ListenPoller = new Timer(async s =>
|
||||
{
|
||||
await Task.WhenAll((await _InvoiceRepository.GetPendingInvoices())
|
||||
.Select(async invoiceId => await EnsureListening(invoiceId, true))
|
||||
.ToArray());
|
||||
try
|
||||
{
|
||||
await Task.WhenAll((await _InvoiceRepository.GetPendingInvoices())
|
||||
.Select(async invoiceId => await EnsureListening(invoiceId, true))
|
||||
.ToArray());
|
||||
}
|
||||
catch (AggregateException ex)
|
||||
{
|
||||
Logs.PayServer.LogError(ex.InnerException ?? ex.InnerExceptions.FirstOrDefault(), $"Lightning: Uncaught error");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logs.PayServer.LogError(ex, $"Lightning: Uncaught error");
|
||||
}
|
||||
}, null, 0, (int)PollInterval.TotalMilliseconds);
|
||||
leases.Add(_ListenPoller);
|
||||
return Task.CompletedTask;
|
||||
@ -90,7 +101,16 @@ namespace BTCPayServer.Payments.Lightning
|
||||
if (poll)
|
||||
{
|
||||
var charge = _LightningClientFactory.CreateClient(lightningSupportedMethod, network);
|
||||
var chargeInvoice = await charge.GetInvoice(lightningMethod.InvoiceId);
|
||||
LightningInvoice chargeInvoice = null;
|
||||
try
|
||||
{
|
||||
chargeInvoice = await charge.GetInvoice(lightningMethod.InvoiceId);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Logs.PayServer.LogError(ex, $"{lightningSupportedMethod.CryptoCode} (Lightning): Can't connect to the lightning server");
|
||||
continue;
|
||||
}
|
||||
if (chargeInvoice == null)
|
||||
continue;
|
||||
if (chargeInvoice.Status == "paid")
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
@ -82,12 +83,13 @@ namespace BTCPayServer.Services
|
||||
if (network == null)
|
||||
throw new ArgumentNullException(nameof(network));
|
||||
|
||||
var path = new KeyPath("49'").Derive(network.CoinType).Derive(account, true);
|
||||
var segwit = network.NBitcoinNetwork.Consensus.SupportSegwit;
|
||||
var path = network.GetRootKeyPath().Derive(account, true);
|
||||
var pubkey = await GetExtPubKey(_Ledger, network, path, false);
|
||||
var derivation = new DerivationStrategyFactory(network.NBitcoinNetwork).CreateDirectDerivationStrategy(pubkey, new DerivationStrategyOptions()
|
||||
{
|
||||
P2SH = true,
|
||||
Legacy = false
|
||||
P2SH = segwit,
|
||||
Legacy = !segwit
|
||||
});
|
||||
return new GetXPubResult() { ExtPubKey = derivation.ToString(), KeyPath = path };
|
||||
}
|
||||
@ -125,9 +127,13 @@ namespace BTCPayServer.Services
|
||||
|
||||
private static async Task<KeyPath> GetKeyPath(LedgerClient ledger, BTCPayNetwork network, DirectDerivationStrategy directStrategy)
|
||||
{
|
||||
List<KeyPath> derivations = new List<KeyPath>();
|
||||
if(network.NBitcoinNetwork.Consensus.SupportSegwit)
|
||||
derivations.Add(new KeyPath("49'"));
|
||||
derivations.Add(new KeyPath("44'"));
|
||||
KeyPath foundKeyPath = null;
|
||||
foreach (var account in
|
||||
new[] { new KeyPath("49'"), new KeyPath("44'") }
|
||||
derivations
|
||||
.Select(purpose => purpose.Derive(network.CoinType))
|
||||
.SelectMany(coinType => Enumerable.Range(0, 5).Select(i => coinType.Derive(i, true))))
|
||||
{
|
||||
@ -235,6 +241,5 @@ namespace BTCPayServer.Services
|
||||
public string ExtPubKey { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.KeyPathJsonConverter))]
|
||||
public KeyPath KeyPath { get; set; }
|
||||
public int CoinType { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
@ -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"),
|
||||
|
87
BTCPayServer/Services/Rates/BTCPayRateProviderFactory.cs
Normal file
87
BTCPayServer/Services/Rates/BTCPayRateProviderFactory.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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" });
|
||||
}
|
||||
}
|
||||
}
|
@ -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,87 @@ 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
|
||||
{
|
||||
private static readonly DateTime _epochUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
public string PublicKey { get; set; }
|
||||
public string PrivateKey { get; set; }
|
||||
[DefaultValue(15)]
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
public int CacheInMinutes { get; set; } = 15;
|
||||
|
||||
public string GetCoinAverageSignature()
|
||||
{
|
||||
if (string.IsNullOrEmpty(PublicKey) || string.IsNullOrEmpty(PrivateKey))
|
||||
return null;
|
||||
var timestamp = (int)((DateTime.UtcNow - _epochUtc).TotalSeconds);
|
||||
var payload = timestamp + "." + PublicKey;
|
||||
var digestValueBytes = new HMACSHA256(Encoding.ASCII.GetBytes(PrivateKey)).ComputeHash(Encoding.ASCII.GetBytes(payload));
|
||||
var digestValueHex = NBitcoin.DataEncoders.Encoders.Hex.EncodeData(digestValueBytes);
|
||||
return payload + "." + digestValueHex;
|
||||
}
|
||||
}
|
||||
public class BTCPayCoinAverageAuthenticator : ICoinAverageAuthenticator
|
||||
{
|
||||
private SettingsRepository settingsRepo;
|
||||
|
||||
public BTCPayCoinAverageAuthenticator(SettingsRepository settingsRepo)
|
||||
{
|
||||
this.settingsRepo = settingsRepo;
|
||||
}
|
||||
public async Task AddHeader(HttpRequestMessage message)
|
||||
{
|
||||
var settings = (await settingsRepo.GetSettingAsync<RatesSetting>()) ?? new RatesSetting();
|
||||
var signature = settings.GetCoinAverageSignature();
|
||||
if (signature != null)
|
||||
{
|
||||
message.Headers.Add("X-signature", signature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface ICoinAverageAuthenticator
|
||||
{
|
||||
Task AddHeader(HttpRequestMessage message);
|
||||
}
|
||||
|
||||
public class CoinAverageRateProvider : IRateProvider
|
||||
{
|
||||
static HttpClient _Client = new HttpClient();
|
||||
@ -48,17 +133,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 +187,38 @@ 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<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
12
BTCPayServer/Services/Rates/RateProviderDescription.cs
Normal file
12
BTCPayServer/Services/Rates/RateProviderDescription.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
|
@ -22,7 +22,7 @@
|
||||
<input asp-for="Email" class="form-control" />
|
||||
<span asp-validation-for="Email" class="text-danger"></span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default">Register</button>
|
||||
<button type="submit" class="btn btn-primary">Register</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,23 +3,37 @@
|
||||
ViewData["Title"] = "Forgot your password?";
|
||||
}
|
||||
|
||||
<h2>@ViewData["Title"]</h2>
|
||||
<h4>Enter your email.</h4>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<form asp-action="ForgotPassword" method="post">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Email"></label>
|
||||
<input asp-for="Email" class="form-control" />
|
||||
<span asp-validation-for="Email" class="text-danger"></span>
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
@Html.Partial("_StatusMessage", TempData["StatusMessage"])
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<h2 class="section-heading">@ViewData["Title"]</h2>
|
||||
<hr class="primary">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<form asp-action="ForgotPassword" method="post">
|
||||
<h4>Start password reset</h4>
|
||||
<hr />
|
||||
<p>
|
||||
We all forget passwords every now and then. Just provide email address tied to your account and we'll start the process of helping you recover your account.
|
||||
</p>
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Email"></label>
|
||||
<input asp-for="Email" class="form-control" />
|
||||
<span asp-validation-for="Email" class="text-danger"></span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
||||
|
@ -1,8 +1,22 @@
|
||||
@{
|
||||
ViewData["Title"] = "Forgot password confirmation";
|
||||
ViewData["Title"] = "Email sent!";
|
||||
}
|
||||
|
||||
<h2>@ViewData["Title"]</h2>
|
||||
<h2></h2>
|
||||
<p>
|
||||
Please check your email to reset your password.
|
||||
</p>
|
||||
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<hr />
|
||||
<p>
|
||||
Please check your email to reset your password.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -41,7 +41,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-default">Log in</button>
|
||||
<button type="submit" class="btn btn-primary">Log in</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<p>
|
||||
@ -76,7 +76,7 @@
|
||||
<p>
|
||||
@foreach(var provider in loginProviders)
|
||||
{
|
||||
<button type="submit" class="btn btn-default" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.Name</button>
|
||||
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.Name</button>
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -31,7 +31,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-default">Log in</button>
|
||||
<button type="submit" class="btn btn-primary">Log in</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -18,7 +18,7 @@
|
||||
<input asp-for="RecoveryCode" class="form-control" autocomplete="off" />
|
||||
<span asp-validation-for="RecoveryCode" class="text-danger"></span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default">Log in</button>
|
||||
<button type="submit" class="btn btn-primary">Log in</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -35,7 +35,7 @@
|
||||
<input asp-for="ConfirmPassword" class="form-control" />
|
||||
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default">Register</button>
|
||||
<button type="submit" class="btn btn-primary">Register</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -26,7 +26,7 @@
|
||||
<input asp-for="ConfirmPassword" class="form-control" />
|
||||
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default">Reset</button>
|
||||
<button type="submit" class="btn btn-primary">Reset</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -28,7 +28,7 @@
|
||||
<select asp-for="SelectedStore" asp-items="Model.Stores" class="form-control"></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Create" class="btn btn-default" />
|
||||
<input type="submit" value="Create" class="btn btn-primary" />
|
||||
</div>
|
||||
</form>
|
||||
<a asp-action="ListApps">Back to the app list</a>
|
||||
|
@ -21,9 +21,9 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<a asp-action="CreateApp" class="btn btn-success" role="button"><span class="glyphicon glyphicon-plus"></span> Create a new app</a>
|
||||
<table class="table">
|
||||
<thead class="thead-inverse">
|
||||
<a asp-action="CreateApp" class="btn btn-primary" role="button"><span class="fa fa-plus"></span> Create a new app</a>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Store</th>
|
||||
<th>Name</th>
|
||||
@ -32,11 +32,11 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(var app in Model.Apps)
|
||||
@foreach (var app in Model.Apps)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
@if(app.IsOwner)
|
||||
@if (app.IsOwner)
|
||||
{
|
||||
<span><a asp-action="UpdateStore" asp-controller="Stores" asp-route-storeId="@app.StoreId">@app.StoreName</a></span>
|
||||
}
|
||||
@ -48,7 +48,7 @@
|
||||
<td>@app.AppName</td>
|
||||
<td>@app.AppType</td>
|
||||
<td style="text-align:right">
|
||||
@if(app.IsOwner)
|
||||
@if (app.IsOwner)
|
||||
{
|
||||
<a asp-action="@app.UpdateAction" asp-controller="Apps" asp-route-appId="@app.Id">Settings</a><span> - </span>
|
||||
}
|
||||
|
@ -35,7 +35,7 @@
|
||||
<span asp-validation-for="Template" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" class="btn btn-success" />
|
||||
<input type="submit" class="btn btn-primary" />
|
||||
</div>
|
||||
</form>
|
||||
<a asp-action="ListApps">Back to the app list</a>
|
||||
|
@ -11,7 +11,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<link rel="stylesheet" href="~/vendor/bootstrap/css/bootstrap.css" />
|
||||
<link rel="stylesheet" href="~/vendor/bootstrap4/css/bootstrap.css" />
|
||||
</head>
|
||||
<body class="h-100">
|
||||
<div class="container d-flex h-100">
|
||||
@ -19,11 +19,11 @@
|
||||
<h1 class="mb-4">@Model.Title</h1>
|
||||
<form method="post">
|
||||
<div class="row">
|
||||
@foreach(var item in Model.Items)
|
||||
@foreach (var item in Model.Items)
|
||||
{
|
||||
<div class="col-sm-4 mb-3">
|
||||
<h3>@item.Title</h3>
|
||||
<button type="submit" name="choiceKey" class="btn btn-secondary" value="@item.Id">Buy for @item.Price.Formatted</button>
|
||||
<button type="submit" name="choiceKey" class="btn btn-primary" value="@item.Id">Buy for @item.Price.Formatted</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@ -42,7 +42,7 @@
|
||||
</div>*@
|
||||
</div>
|
||||
</div>
|
||||
<script src="~/vendor/bootstrap/js/bootstrap.js"></script>
|
||||
<script src="~/vendor/jquery/jquery.js"></script>
|
||||
<script src="~/vendor/bootstrap4/js/bootstrap.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -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">
|
||||
@ -156,7 +163,6 @@
|
||||
<div class="payment__details__instruction__open-wallet">
|
||||
<a class="payment__details__instruction__open-wallet__btn action-button" v-bind:href="srvModel.invoiceBitcoinUrl">
|
||||
<span>{{$t("Open in wallet")}}</span>
|
||||
<span class="glyphicon glyphicon-new-window"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -250,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">
|
||||
@ -296,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>
|
||||
|
@ -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 },
|
||||
|
@ -55,7 +55,7 @@
|
||||
<span asp-validation-for="StoreId" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Create" class="btn btn-default" />
|
||||
<input type="submit" value="Create" class="btn btn-primary" />
|
||||
</div>
|
||||
</form>
|
||||
<a asp-action="ListInvoices">Back to List</a>
|
||||
|
@ -35,7 +35,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h3>Information</h3>
|
||||
<table class="table">
|
||||
<table class="table table-sm">
|
||||
<tr>
|
||||
<th>Store</th>
|
||||
<td><a href="@Model.StoreLink">@Model.StoreName</a></td>
|
||||
@ -93,7 +93,7 @@
|
||||
|
||||
<div class="col-md-6">
|
||||
<h3>Buyer information</h3>
|
||||
<table class="table">
|
||||
<table class="table table-sm">
|
||||
<tr>
|
||||
<th>Name
|
||||
<th>
|
||||
@ -134,7 +134,7 @@
|
||||
</table>
|
||||
|
||||
<h3>Product information</h3>
|
||||
<table class="table">
|
||||
<table class="table table-sm">
|
||||
<tr>
|
||||
<th>Item code</th>
|
||||
<td>@Model.ProductInformation.ItemCode</td>
|
||||
@ -153,7 +153,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Paid summary</h3>
|
||||
<table class="table">
|
||||
<table class="table table-sm">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th style="white-space:nowrap;">Payment method</th>
|
||||
@ -181,7 +181,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Payments</h3>
|
||||
<table class="table">
|
||||
<table class="table table-sm">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th style="white-space:nowrap;">Payment method</th>
|
||||
@ -208,7 +208,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Addresses</h3>
|
||||
<table class="table">
|
||||
<table class="table table-sm">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th style="white-space:nowrap;">Payment method</th>
|
||||
@ -232,7 +232,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Events</h3>
|
||||
<table class="table">
|
||||
<table class="table table-sm">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
|
@ -18,8 +18,10 @@
|
||||
<hr class="primary">
|
||||
<p>Create, search or pay an invoice. (<a href="#help" data-toggle="collapse">Help</a>)</p>
|
||||
<div id="help" class="collapse text-left">
|
||||
<p>You can search for invoice Id, deposit address, price, order id, store id, any buyer information and any product information.</br>
|
||||
You can also apply filters to your search by searching for `filtername:value`, here is a list of supported filters</p>
|
||||
<p>
|
||||
You can search for invoice Id, deposit address, price, order id, store id, any buyer information and any product information.</br>
|
||||
You can also apply filters to your search by searching for `filtername:value`, here is a list of supported filters
|
||||
</p>
|
||||
<ul>
|
||||
<li><b>storeid:id</b> for filtering a specific store</li>
|
||||
<li><b>status:(expired|invalid|complete|confirmed|paid|new)</b> for filtering a specific status</li>
|
||||
@ -27,21 +29,25 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<form asp-action="SearchInvoice" method="post">
|
||||
<input asp-for="SearchTerm" class="form-control" />
|
||||
<div class="input-group">
|
||||
<input asp-for="SearchTerm" class="form-control" />
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary" title="Search invoice">
|
||||
<span class="fa fa-search"></span> Search
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<input type="hidden" asp-for="Count" />
|
||||
<span asp-validation-for="SearchTerm" class="text-danger"></span>
|
||||
<button type="submit" class="btn btn-default" title="Search invoice">
|
||||
<span class="glyphicon glyphicon-search"></span> Search
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<a asp-action="CreateInvoice" class="btn btn-success" role="button"><span class="glyphicon glyphicon-plus"></span> Create a new invoice</a>
|
||||
<table class="table">
|
||||
<thead class="thead-inverse">
|
||||
<a asp-action="CreateInvoice" class="btn btn-primary" role="button"><span class="fa fa-plus"></span> Create a new invoice</a>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>OrderId</th>
|
||||
@ -82,13 +88,13 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<td >@invoice.Status</td>
|
||||
<td>@invoice.Status</td>
|
||||
}
|
||||
<td style="text-align:right">@invoice.AmountCurrency</td>
|
||||
<td style="text-align:right">
|
||||
@if(invoice.Status == "new")
|
||||
{
|
||||
<a asp-action="Checkout" asp-route-invoiceId="@invoice.InvoiceId">Checkout</a> <span>-</span>
|
||||
<a asp-action="Checkout" asp-route-invoiceId="@invoice.InvoiceId">Checkout</a> <span>-</span>
|
||||
}<a asp-action="Invoice" asp-route-invoiceId="@invoice.InvoiceId">Details</a>
|
||||
</td>
|
||||
</tr>
|
||||
@ -134,7 +140,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-danger">Yes, make invoice Invalid</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
||||
<input asp-for="ConfirmPassword" class="form-control" />
|
||||
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default">Update password</button>
|
||||
<button type="submit" class="btn btn-primary">Update password</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<p>
|
||||
<span class="glyphicon glyphicon-warning-sign"></span>
|
||||
<span class="fa fa-warning"></span>
|
||||
<strong>This action only disables 2FA.</strong>
|
||||
</p>
|
||||
<p>
|
||||
|
@ -37,7 +37,7 @@
|
||||
<input asp-for="Code" class="form-control" autocomplete="off" />
|
||||
<span asp-validation-for="Code" class="text-danger"></span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default">Verify</button>
|
||||
<button type="submit" class="btn btn-primary">Verify</button>
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -5,23 +5,23 @@
|
||||
}
|
||||
|
||||
@Html.Partial("_StatusMessage", Model.StatusMessage)
|
||||
@if(Model.CurrentLogins?.Count > 0)
|
||||
@if (Model.CurrentLogins?.Count > 0)
|
||||
{
|
||||
<h4>Registered Logins</h4>
|
||||
<table class="table">
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<tbody>
|
||||
@foreach(var login in Model.CurrentLogins)
|
||||
@foreach (var login in Model.CurrentLogins)
|
||||
{
|
||||
<tr>
|
||||
<td>@login.LoginProvider</td>
|
||||
<td>
|
||||
@if(Model.ShowRemoveButton)
|
||||
@if (Model.ShowRemoveButton)
|
||||
{
|
||||
<form asp-action="RemoveLogin" method="post">
|
||||
<div>
|
||||
<input asp-for="@login.LoginProvider" name="LoginProvider" type="hidden" />
|
||||
<input asp-for="@login.ProviderKey" name="ProviderKey" type="hidden" />
|
||||
<button type="submit" class="btn btn-default" title="Remove this @login.LoginProvider login from your account">Remove</button>
|
||||
<button type="submit" class="btn btn-primary" title="Remove this @login.LoginProvider login from your account">Remove</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
@ -35,16 +35,16 @@
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
@if(Model.OtherLogins?.Count > 0)
|
||||
@if (Model.OtherLogins?.Count > 0)
|
||||
{
|
||||
<h4>Add another service to log in.</h4>
|
||||
<hr />
|
||||
<form asp-action="LinkLogin" method="post" class="form-horizontal">
|
||||
<div id="socialLoginList">
|
||||
<p>
|
||||
@foreach(var provider in Model.OtherLogins)
|
||||
@foreach (var provider in Model.OtherLogins)
|
||||
{
|
||||
<button type="submit" class="btn btn-default" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
|
||||
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<p>
|
||||
<span class="glyphicon glyphicon-warning-sign"></span>
|
||||
<span class="fa fa-warning"></span>
|
||||
<strong>Put these codes in a safe place.</strong>
|
||||
</p>
|
||||
<p>
|
||||
|
@ -25,7 +25,7 @@
|
||||
{
|
||||
<div class="input-group">
|
||||
<input asp-for="Email" class="form-control" />
|
||||
<span class="input-group-addon" aria-hidden="true"><span class="glyphicon glyphicon-ok text-success"></span></span>
|
||||
<span class="input-group-addon" aria-hidden="true"><span class="fa fa-check text-success"></span></span>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
@ -35,7 +35,7 @@
|
||||
}
|
||||
<span asp-validation-for="Email" class="text-danger"></span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default">Save</button>
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<p>
|
||||
<span class="glyphicon glyphicon-warning-sign"></span>
|
||||
<span class="fa fa-warning"></span>
|
||||
<strong>If you reset your authenticator key your authenticator app will not work until you reconfigure it.</strong>
|
||||
</p>
|
||||
<p>
|
||||
|
@ -24,7 +24,7 @@
|
||||
<input asp-for="ConfirmPassword" class="form-control" />
|
||||
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default">Set password</button>
|
||||
<button type="submit" class="btn btn-primary">Set password</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -29,19 +29,19 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<a asp-action="Disable2faWarning" class="btn btn-default">Disable 2FA</a>
|
||||
<a asp-action="GenerateRecoveryCodes" class="btn btn-default">Reset recovery codes</a>
|
||||
<a asp-action="Disable2faWarning" class="btn btn-primary">Disable 2FA</a>
|
||||
<a asp-action="GenerateRecoveryCodes" class="btn btn-primary">Reset recovery codes</a>
|
||||
}
|
||||
|
||||
<h5>Authenticator app</h5>
|
||||
@if(!Model.HasAuthenticator)
|
||||
{
|
||||
<a asp-action="EnableAuthenticator" class="btn btn-default">Add authenticator app</a>
|
||||
<a asp-action="EnableAuthenticator" class="btn btn-primary">Add authenticator app</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a asp-action="EnableAuthenticator" class="btn btn-default">Configure authenticator app</a>
|
||||
<a asp-action="ResetAuthenticatorWarning" class="btn btn-default">Reset authenticator key</a>
|
||||
<a asp-action="EnableAuthenticator" class="btn btn-primary">Configure authenticator app</a>
|
||||
<a asp-action="ResetAuthenticatorWarning" class="btn btn-primary">Reset authenticator key</a>
|
||||
}
|
||||
|
||||
@section Scripts {
|
||||
|
@ -4,13 +4,13 @@
|
||||
var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
|
||||
}
|
||||
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li class="@ManageNavPages.IndexNavClass(ViewContext)"><a asp-action="Index">Profile</a></li>
|
||||
<li class="@ManageNavPages.ChangePasswordNavClass(ViewContext)"><a asp-action="ChangePassword">Password</a></li>
|
||||
<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)
|
||||
{
|
||||
<li class="@ManageNavPages.ExternalLoginsNavClass(ViewContext)"><a asp-action="ExternalLogins">External logins</a></li>
|
||||
<a class="nav-link @ManageNavPages.ExternalLoginsNavClass(ViewContext)" asp-action="ExternalLogins">External logins</a>
|
||||
}
|
||||
<li class="@ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)"><a asp-action="TwoFactorAuthentication">Two-factor authentication</a></li>
|
||||
</ul>
|
||||
<a class="nav-link @ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)" asp-action="TwoFactorAuthentication">Two-factor authentication</a>
|
||||
</div>
|
||||
|
||||
|
@ -56,8 +56,8 @@
|
||||
<input asp-for="TestEmail" class="form-control" />
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-success" name="command" value="Save">Save</button>
|
||||
<button type="submit" class="btn btn-default" name="command" value="Test">Test</button>
|
||||
<button type="submit" class="btn btn-primary" name="command" value="Save">Save</button>
|
||||
<button type="submit" class="btn btn-primary" name="command" value="Test">Test</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,8 +9,8 @@
|
||||
@Html.Partial("_StatusMessage", Model.StatusMessage)
|
||||
|
||||
|
||||
<table class="table">
|
||||
<thead class="thead-inverse">
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
@ -18,7 +18,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(var user in Model.Users)
|
||||
@foreach (var user in Model.Users)
|
||||
{
|
||||
<tr>
|
||||
<td>@user.Name</td>
|
||||
|
@ -23,7 +23,7 @@
|
||||
<label asp-for="LockSubscription"></label>
|
||||
<input asp-for="LockSubscription" type="checkbox" class="form-check-inline" />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success" name="command" value="Save">Save</button>
|
||||
<button type="submit" class="btn btn-primary" name="command" value="Save">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
46
BTCPayServer/Views/Server/Rates.cshtml
Normal file
46
BTCPayServer/Views/Server/Rates.cshtml
Normal file
@ -0,0 +1,46 @@
|
||||
@model RatesViewModel
|
||||
@{
|
||||
ViewData["Title"] = ServerNavPages.Rates;
|
||||
ViewData.AddActivePage(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" />
|
||||
<span asp-validation-for="PrivateKey" class="text-danger"></span>
|
||||
<p class="form-text text-muted">You can find the information on <a 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>
|
||||
<button type="submit" class="btn btn-primary" name="command" value="Save">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
@ -14,12 +14,14 @@ namespace BTCPayServer.Views.Server
|
||||
|
||||
|
||||
public static string Users => "Users";
|
||||
public static string Rates => "Rates";
|
||||
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 RatesNavClass(ViewContext viewContext) => PageNavClass(viewContext, Rates);
|
||||
public static string PoliciesNavClass(ViewContext viewContext) => PageNavClass(viewContext, Policies);
|
||||
public static string HangfireNavClass(ViewContext viewContext) => PageNavClass(viewContext, Hangfire);
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
<label asp-for="IsAdmin"></label>
|
||||
<input asp-for="IsAdmin" type="checkbox" class="form-check" />
|
||||
</div>
|
||||
<button name="command" type="submit" class="btn btn-success" value="Save">Save</button>
|
||||
<button name="command" type="submit" class="btn btn-primary" value="Save">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,9 +1,10 @@
|
||||
@using BTCPayServer.Views.Server
|
||||
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li class="@ServerNavPages.UsersNavClass(ViewContext)"><a asp-action="Users">Users</a></li>
|
||||
<li class="@ServerNavPages.EmailsNavClass(ViewContext)"><a asp-action="Emails">Email server</a></li>
|
||||
<li class="@ServerNavPages.PoliciesNavClass(ViewContext)"><a asp-action="Policies">Policies</a></li>
|
||||
<li class="@ServerNavPages.HangfireNavClass(ViewContext)"><a href="~/hangfire" target="_blank">Hangfire</a></li>
|
||||
</ul>
|
||||
<div class="nav flex-column nav-pills">
|
||||
<a class="nav-link @ServerNavPages.UsersNavClass(ViewContext)" asp-action="Users">Users</a>
|
||||
<a class="nav-link @ServerNavPages.RatesNavClass(ViewContext)" asp-action="Rates">Rates</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>
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<form method="post">
|
||||
<button type="submit" class="btn btn-info btn-danger" title="Continue">@Model.Action</button>
|
||||
<button type="submit" class="btn btn-secondary btn-danger" title="Continue">@Model.Action</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -16,7 +16,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<form method="post">
|
||||
@if(!Model.Confirmation)
|
||||
@if (!Model.Confirmation)
|
||||
{
|
||||
<div class="form-group">
|
||||
<h5>Derivation Scheme</h5>
|
||||
@ -33,17 +33,17 @@
|
||||
<div id="ledger-info" class="form-text text-muted" style="display: none;">
|
||||
<span>A ledger wallet is detected, which account do you want to use?</span>
|
||||
<ul>
|
||||
@for(int i = 0; i < 4; i++)
|
||||
@for (int i = 0; i < 4; i++)
|
||||
{
|
||||
<li><a class="ledger-info-recommended" data-ledgeraccount="@i" href="#">Account @i (49'/<span class="ledger-info-cointype">0</span>'/@i')</a></li>
|
||||
<li><a class="ledger-info-recommended" data-ledgeraccount="@i" href="#">Account @i (<span>@Model.RootKeyPath.Derive(i, true)</span>)</a></li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<span>BTCPay format memo</span>
|
||||
<table class="table">
|
||||
<thead class="thead-inverse">
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Address type</th>
|
||||
<th>Example</th>
|
||||
@ -77,7 +77,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<button name="command" type="submit" class="btn btn-info">Continue</button>
|
||||
<button name="command" type="submit" class="btn btn-primary">Continue</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -88,15 +88,15 @@
|
||||
<input type="hidden" asp-for="Confirmation" />
|
||||
<input type="hidden" asp-for="DerivationScheme" />
|
||||
<div class="form-group">
|
||||
<table class="table">
|
||||
<thead class="thead-inverse">
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key path</th>
|
||||
<th>Address</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(var sample in Model.AddressSamples)
|
||||
@foreach (var sample in Model.AddressSamples)
|
||||
{
|
||||
<tr>
|
||||
<td>@sample.KeyPath</td>
|
||||
@ -116,7 +116,7 @@
|
||||
<input asp-for="HintAddress" class="form-control" />
|
||||
<span asp-validation-for="HintAddress" class="text-danger"></span>
|
||||
</div>
|
||||
<button name="command" type="submit" class="btn btn-success">Confirm</button>
|
||||
<button name="command" type="submit" class="btn btn-primary">Confirm</button>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
|
@ -46,8 +46,8 @@
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
<button name="command" type="submit" value="save" class="btn btn-success">Submit</button>
|
||||
<button name="command" type="submit" value="test" class="btn btn-info">Test connection</button>
|
||||
<button name="command" type="submit" value="save" class="btn btn-primary">Submit</button>
|
||||
<button name="command" type="submit" value="test" class="btn btn-secondary">Test connection</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -50,7 +50,7 @@
|
||||
<span asp-validation-for="OnChainMinValue" class="text-danger"></span>
|
||||
<p class="form-text text-muted">Example: 5.50 USD</p>
|
||||
</div>
|
||||
<button name="command" type="submit" class="btn btn-success" value="Save">Save</button>
|
||||
<button name="command" type="submit" class="btn btn-primary" value="Save">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -49,7 +49,7 @@
|
||||
{
|
||||
<input type="hidden" asp-for="StoreId" />}
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Request pairing" class="btn btn-default" />
|
||||
<input type="submit" value="Request pairing" class="btn btn-primary" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -8,9 +8,9 @@
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<p>You can allow a public key to access the API of this store</p>
|
||||
@Html.Partial("_StatusMessage", Model.StatusMessage)
|
||||
<a asp-action="CreateToken" class="btn btn-success" role="button"><span class="glyphicon glyphicon-plus"></span>Create a new token</a>
|
||||
<table class="table">
|
||||
<thead class="thead-inverse">
|
||||
<a asp-action="CreateToken" class="btn btn-primary" role="button"><span class="fa fa-plus"></span> Create a new token</a>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<th>SIN</th>
|
||||
@ -19,7 +19,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(var token in Model.Tokens)
|
||||
@foreach (var token in Model.Tokens)
|
||||
{
|
||||
<tr>
|
||||
<td>@token.Label</td>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-4"></div>
|
||||
<div class="col-md-4">
|
||||
<table class="table">
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<td style="text-align:right;">@Model.Label</td>
|
||||
@ -42,7 +42,7 @@
|
||||
<span asp-validation-for="SelectedStore" class="text-danger"></span>
|
||||
</div>
|
||||
<input type="hidden" name="pairingCode" value="@Model.Id" />
|
||||
<button type="submit" class="btn btn-info" title="Approve this pairing demand">Approve</button>
|
||||
<button type="submit" class="btn btn-secondary" title="Approve this pairing demand">Approve</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-4"></div>
|
||||
|
@ -17,8 +17,10 @@
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<h5>Users</h5>
|
||||
<span>Add access to your store to other users (Guest will not be able to see and modify the store settings)<br />
|
||||
Note that the user must have a registered account on this BTCPay Server.</span>
|
||||
<span>
|
||||
Add access to your store to other users (Guest will not be able to see and modify the store settings)<br />
|
||||
Note that the user must have a registered account on this BTCPay Server.
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<form method="post">
|
||||
@ -27,12 +29,12 @@
|
||||
<option value="@StoreRoles.Owner">Owner</option>
|
||||
<option value="@StoreRoles.Guest">Guest</option>
|
||||
</select>
|
||||
<button type="submit" role="button" class="form-control btn btn-success"><span class="glyphicon glyphicon-plus"></span>Add user</button>
|
||||
<button type="submit" role="button" class="form-control btn btn-primary"><span class="fa fa-plus"></span> Add user</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<table class="table">
|
||||
<thead class="thead-inverse">
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
@ -40,7 +42,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(var user in Model.Users)
|
||||
@foreach (var user in Model.Users)
|
||||
{
|
||||
<tr>
|
||||
<td>@user.Email</td>
|
||||
|
@ -72,8 +72,8 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<table class="table">
|
||||
<thead class="thead-inverse">
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Crypto</th>
|
||||
<th>Derivation Scheme</th>
|
||||
@ -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>
|
||||
}
|
||||
@ -108,8 +108,8 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<table class="table">
|
||||
<thead class="thead-inverse">
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Crypto</th>
|
||||
<th>Address</th>
|
||||
@ -117,7 +117,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(var scheme in Model.LightningNodes)
|
||||
@foreach (var scheme in Model.LightningNodes)
|
||||
{
|
||||
<tr>
|
||||
<td>@scheme.CryptoCode</td>
|
||||
@ -134,10 +134,10 @@
|
||||
<input asp-for="LightningDescriptionTemplate" class="form-control" />
|
||||
<span asp-validation-for="LightningDescriptionTemplate" class="text-danger"></span>
|
||||
<p class="form-text text-muted">
|
||||
Placeholder available are: {StoreName}, {ItemDescription} and {OrderId}
|
||||
Available placeholders are: {StoreName}, {ItemDescription} and {OrderId}
|
||||
</p>
|
||||
</div>
|
||||
<button name="command" type="submit" class="btn btn-success" value="Save">Save</button>
|
||||
<button name="command" type="submit" class="btn btn-primary" value="Save">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -20,13 +20,13 @@
|
||||
<li>Make sure you are running the Ledger Bitcoin or Litecoin app with version superior or equal to 1.2.4</li>
|
||||
<li>Use a browser supporting the <a href="https://www.yubico.com/support/knowledge-base/categories/articles/browsers-support-u2f/">U2F protocol</a></li>
|
||||
</ul>
|
||||
<p id="hw-loading"><span class="glyphicon glyphicon-question-sign" style="color:orange"></span> <span>Detecting hardware wallet...</span></p>
|
||||
<p id="hw-error" style="display:none;"><span class="glyphicon glyphicon-remove-sign" style="color:red;"></span> <span class="hw-label">An error happened</span></p>
|
||||
<p id="hw-success" style="display:none;"><span class="glyphicon glyphicon-ok-sign" style="color:green;"></span> <span class="hw-label">Detecting hardware wallet...</span></p>
|
||||
<p id="hw-loading"><span class="fa fa-question-circle" style="color:orange"></span> <span>Detecting hardware wallet...</span></p>
|
||||
<p id="hw-error" style="display:none;"><span class="fa fa-times-circle" style="color:red;"></span> <span class="hw-label">An error happened</span></p>
|
||||
<p id="hw-success" style="display:none;"><span class="fa fa-check-circle" style="color:green;"></span> <span class="hw-label">Detecting hardware wallet...</span></p>
|
||||
|
||||
<p id="check-loading" style="display:none;"><span class="glyphicon glyphicon-question-sign" style="color:orange"></span> <span class="check-label">Detecting hardware wallet...</span></p>
|
||||
<p id="check-error" style="display:none;"><span class="glyphicon glyphicon-remove-sign" style="color:red;"></span> <span class="check-label">An error happened</span></p>
|
||||
<p id="check-success" style="display:none;"><span class="glyphicon glyphicon-ok-sign" style="color:green;"></span> <span class="check-label">Detecting hardware wallet...</span></p>
|
||||
<p id="check-loading" style="display:none;"><span class="fa fa-question-circle" style="color:orange"></span> <span class="check-label">Detecting hardware wallet...</span></p>
|
||||
<p id="check-error" style="display:none;"><span class="fa fa-times-circle" style="color:red;"></span> <span class="check-label">An error happened</span></p>
|
||||
<p id="check-success" style="display:none;"><span class="fa fa-check-circle" style="color:green;"></span> <span class="check-label">Detecting hardware wallet...</span></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
@ -58,7 +58,7 @@
|
||||
<label>Subtract fees from amount</label>
|
||||
<input id="substract-checkbox" name="SubstractFees" class="form-check" type="checkbox" />
|
||||
</div>
|
||||
<button id="confirm-button" name="command" type="submit" class="btn btn-success">Confirm</button>
|
||||
<button id="confirm-button" name="command" type="submit" class="btn btn-primary">Confirm</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,10 +1,10 @@
|
||||
@using BTCPayServer.Views.Stores
|
||||
@inject SignInManager<ApplicationUser> SignInManager
|
||||
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li class="@StoreNavPages.IndexNavClass(ViewContext)"><a asp-action="UpdateStore">General settings</a></li>
|
||||
<li class="@StoreNavPages.CheckoutNavClass(ViewContext)"><a asp-action="CheckoutExperience">Checkout experience</a></li>
|
||||
<li class="@StoreNavPages.TokensNavClass(ViewContext)"><a asp-action="ListTokens">Access Tokens</a></li>
|
||||
<li class="@StoreNavPages.UsersNavClass(ViewContext)"><a asp-action="StoreUsers">Users</a></li>
|
||||
</ul>
|
||||
<div class="nav flex-column nav-pills">
|
||||
<a class="nav-link @StoreNavPages.IndexNavClass(ViewContext)" asp-action="UpdateStore">General settings</a>
|
||||
<a class="nav-link @StoreNavPages.CheckoutNavClass(ViewContext)" asp-action="CheckoutExperience">Checkout experience</a>
|
||||
<a class="nav-link @StoreNavPages.TokensNavClass(ViewContext)" asp-action="ListTokens">Access Tokens</a>
|
||||
<a class="nav-link @StoreNavPages.UsersNavClass(ViewContext)" asp-action="StoreUsers">Users</a>
|
||||
</div>
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
||||
<span asp-validation-for="Name" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Create" class="btn btn-default" />
|
||||
<input type="submit" value="Create" class="btn btn-primary" />
|
||||
</div>
|
||||
</form>
|
||||
<a asp-action="ListStores">Back to List</a>
|
||||
|
@ -21,9 +21,9 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<a asp-action="CreateStore" class="btn btn-success" role="button"><span class="glyphicon glyphicon-plus"></span> Create a new store</a>
|
||||
<table class="table">
|
||||
<thead class="thead-inverse">
|
||||
<a asp-action="CreateStore" class="btn btn-primary" role="button"><span class="fa fa-plus"></span> Create a new store</a>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Website</th>
|
||||
@ -32,28 +32,28 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(var store in Model.Stores)
|
||||
@foreach (var store in Model.Stores)
|
||||
{
|
||||
<tr>
|
||||
<td>@store.Name</td>
|
||||
<td>
|
||||
@if(!string.IsNullOrEmpty(store.WebSite))
|
||||
@if (!string.IsNullOrEmpty(store.WebSite))
|
||||
{
|
||||
<a href="@store.WebSite">@store.WebSite</a>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@for(int i = 0; i < store.Balances.Length; i++)
|
||||
@for (int i = 0; i < store.Balances.Length; i++)
|
||||
{
|
||||
<span>@store.Balances[i]</span>
|
||||
if(i != store.Balances.Length - 1)
|
||||
if (i != store.Balances.Length - 1)
|
||||
{
|
||||
<br />
|
||||
}
|
||||
}
|
||||
</td>
|
||||
<td style="text-align:right">
|
||||
@if(store.IsOwner)
|
||||
@if (store.IsOwner)
|
||||
{
|
||||
<a asp-action="UpdateStore" asp-controller="Stores" asp-route-storeId="@store.Id">Settings</a><span> - </span>
|
||||
}
|
||||
|
@ -2,10 +2,10 @@
|
||||
{
|
||||
"outputFileName": "wwwroot/bundles/main-bundle.min.css",
|
||||
"inputFiles": [
|
||||
"wwwroot/vendor/bootstrap/css/bootstrap.css",
|
||||
"wwwroot/vendor/bootstrap4/css/bootstrap.css",
|
||||
"wwwroot/vendor/magnific-popup/magnific-popup.css",
|
||||
"wwwroot/css/creative.css",
|
||||
"wwwroot/css/site.css",
|
||||
"wwwroot/vendor/font-awesome/css/font-awesome.css",
|
||||
"wwwroot/main/**/*.css",
|
||||
"wwwroot/vendor/animatecss/animate.css"
|
||||
]
|
||||
},
|
||||
@ -14,11 +14,11 @@
|
||||
"inputFiles": [
|
||||
"wwwroot/vendor/jquery/jquery.js",
|
||||
"wwwroot/vendor/popper/popper.js",
|
||||
"wwwroot/vendor/bootstrap/js/bootstrap.js",
|
||||
"wwwroot/vendor/bootstrap4/js/bootstrap.js",
|
||||
"wwwroot/vendor/jquery-easing/jquery.easing.js",
|
||||
"wwwroot/vendor/scrollreveal/scrollreveal.min.js",
|
||||
"wwwroot/vendor/magnific-popup/jquery.magnific-popup.js",
|
||||
"wwwroot/js/creative.js"
|
||||
"wwwroot/main/**/js/*.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -32,8 +32,7 @@
|
||||
"outputFileName": "wwwroot/bundles/checkout-bundle.min.css",
|
||||
"inputFiles": [
|
||||
"wwwroot/vendor/font-awesome/css/font-awesome.css",
|
||||
"wwwroot/css/css.css",
|
||||
"wwwroot/css/normalizer.css",
|
||||
"wwwroot/checkout/**/*.css",
|
||||
"wwwroot/vendor/jquery-prettydropdowns/prettydropdowns.css"
|
||||
]
|
||||
},
|
||||
@ -47,7 +46,7 @@
|
||||
"wwwroot/vendor/i18next/i18next.js",
|
||||
"wwwroot/vendor/i18next/vue-i18next.js",
|
||||
"wwwroot/vendor/jquery-prettydropdowns/jquery.prettydropdowns.js",
|
||||
"wwwroot/js/checkout/**/*.js"
|
||||
"wwwroot/checkout/**/*.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -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();
|
@ -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"
|
||||
};
|
@ -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"
|
||||
};
|
@ -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"
|
||||
};
|
@ -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"
|
||||
};
|
@ -47,5 +47,8 @@
|
||||
"Archived_Body": "ご注文に関わる詳細などでお困りの場合はお店の担当窓口へお問い合わせください。",
|
||||
// Lightning
|
||||
"BOLT 11 Invoice": "お支払いコード",
|
||||
"Node Info": "接続情報"
|
||||
"Node Info": "接続情報",
|
||||
//
|
||||
"txCount": "取引 {{count}} 個",
|
||||
"txCount_plural": "取引 {{count}} 個"
|
||||
};
|
@ -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"
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user