Compare commits
124 Commits
Author | SHA1 | Date | |
---|---|---|---|
ac8feceaf2 | |||
3d8c5195ae | |||
caecb26420 | |||
ecc8b3d9ed | |||
d313395751 | |||
273cf1adc9 | |||
5feb520843 | |||
17e914778d | |||
db24ab792f | |||
448cc06a11 | |||
0780df4fd7 | |||
04174b7431 | |||
b7c58c2083 | |||
cd75fd6842 | |||
370951a3bd | |||
2c08b0137b | |||
1eee31e9f1 | |||
01cf579530 | |||
f72705935a | |||
a29ab6b6b0 | |||
4784518235 | |||
0697b8bf86 | |||
5050b59014 | |||
665cf4c3b1 | |||
98e81ab0fd | |||
6ce70237fc | |||
4f23fc99a1 | |||
d7fccae452 | |||
d7a5021ed2 | |||
63ec832667 | |||
8d95b9fa04 | |||
b497d1871e | |||
c7cd029482 | |||
68f2cba60d | |||
5c4200b036 | |||
bc06114023 | |||
556082c4c9 | |||
6a46d02fc6 | |||
d75e5b8b12 | |||
a97ef2eee8 | |||
be33ebc168 | |||
789193a0c8 | |||
01792cf299 | |||
ff9265f721 | |||
8d61314852 | |||
1ce6ae8727 | |||
dec5dbc0d2 | |||
4e32dad1ea | |||
127ca7582f | |||
b98993f84b | |||
e35f074b66 | |||
ba3d13d56c | |||
ead67887ab | |||
437f27f107 | |||
8d41a8e98d | |||
7e6ab015a6 | |||
f8bc3a5081 | |||
dd1a93ee0e | |||
3cf3aa63f6 | |||
011dd5574f | |||
365911286b | |||
fe5347aa86 | |||
f22c8a72cd | |||
eeb522fe7d | |||
f9e40b209a | |||
20635ea3d6 | |||
7062705d6f | |||
58b994e043 | |||
640ff36fa2 | |||
39ec5242d7 | |||
1c50210e61 | |||
a1ffda0151 | |||
fd15348551 | |||
989c99c550 | |||
bcf97b1474 | |||
67abbed66a | |||
eb01e91e13 | |||
12ceb9e0bc | |||
ecf03f90aa | |||
1747414a57 | |||
3a02f16c6e | |||
a6ee337ed0 | |||
559f535257 | |||
2952ccc7fd | |||
f81ca1888d | |||
ed02e0f4d6 | |||
0a83f21af5 | |||
23a3c145ed | |||
4184c6c208 | |||
29c28b1841 | |||
de48fb4077 | |||
bcd79c5882 | |||
b8c513aa2b | |||
ad67f4ef18 | |||
2c0bcfc0ec | |||
0ba1072d54 | |||
f7fe855274 | |||
449738414b | |||
a34842585d | |||
eb882c2c46 | |||
ca65c6bd8f | |||
3770adb7d3 | |||
7fdf19ca22 | |||
4e776adb03 | |||
26db946392 | |||
d102c142b9 | |||
f7989541b9 | |||
b7f0ce18b3 | |||
e1dfbfe3b0 | |||
786d129452 | |||
a37a8e8fcd | |||
355989c278 | |||
af3dee95de | |||
ee4f83ddba | |||
c326998381 | |||
239a011e60 | |||
5ffe118159 | |||
dbe5c62d11 | |||
be1128a886 | |||
7c3ddf904c | |||
25208915eb | |||
52e0845fc5 | |||
daf1a0a4bc | |||
bc8978182e |
@ -1,14 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.2" />
|
||||
<PackageReference Include="xunit" Version="2.3.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||
</ItemGroup>
|
||||
|
@ -129,19 +129,19 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Exchange = "coinaverage",
|
||||
CurrencyPair = CurrencyPair.Parse("BTC_USD"),
|
||||
Value = 5000m
|
||||
BidAsk = new BidAsk(5000m)
|
||||
});
|
||||
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||
{
|
||||
Exchange = "coinaverage",
|
||||
CurrencyPair = CurrencyPair.Parse("BTC_CAD"),
|
||||
Value = 4500m
|
||||
BidAsk = new BidAsk(4500m)
|
||||
});
|
||||
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||
{
|
||||
Exchange = "coinaverage",
|
||||
CurrencyPair = CurrencyPair.Parse("LTC_USD"),
|
||||
Value = 500m
|
||||
BidAsk = new BidAsk(500m)
|
||||
});
|
||||
rateProvider.DirectProviders.Add("coinaverage", coinAverageMock);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM microsoft/dotnet:2.1.300-rc1-sdk-alpine3.7
|
||||
FROM microsoft/dotnet:2.1.300-sdk-alpine3.7
|
||||
WORKDIR /app
|
||||
# caches restore result by copying csproj file separately
|
||||
COPY BTCPayServer.Tests/BTCPayServer.Tests.csproj BTCPayServer.Tests/BTCPayServer.Tests.csproj
|
||||
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using BTCPayServer.Rating;
|
||||
using Xunit;
|
||||
using System.Globalization;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -35,9 +36,9 @@ namespace BTCPayServer.Tests
|
||||
rules.ToString());
|
||||
var tests = new[]
|
||||
{
|
||||
(Pair: "DOGE_USD", Expected: "bittrex(DOGE_BTC) * gdax(BTC_USD) * 1.1"),
|
||||
(Pair: "BTC_USD", Expected: "gdax(BTC_USD)"),
|
||||
(Pair: "BTC_CAD", Expected: "coinbase(BTC_CAD)"),
|
||||
(Pair: "DOGE_USD", Expected: "bittrex(DOGE_BTC) * gdax(BTC_USD) * 1.1"),
|
||||
(Pair: "DOGE_CAD", Expected: "bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1"),
|
||||
(Pair: "LTC_CAD", Expected: "coinaverage(LTC_CAD) * 1.02"),
|
||||
};
|
||||
@ -81,9 +82,9 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var tests2 = new[]
|
||||
{
|
||||
(Pair: "DOGE_USD", Expected: "bittrex(DOGE_BTC) * gdax(BTC_USD) * 1.1", ExpectedExchangeRates: "bittrex(DOGE_BTC),gdax(BTC_USD)"),
|
||||
(Pair: "BTC_USD", Expected: "gdax(BTC_USD)", ExpectedExchangeRates: "gdax(BTC_USD)"),
|
||||
(Pair: "BTC_CAD", Expected: "coinbase(BTC_CAD)", ExpectedExchangeRates: "coinbase(BTC_CAD)"),
|
||||
(Pair: "DOGE_USD", Expected: "bittrex(DOGE_BTC) * gdax(BTC_USD) * 1.1", ExpectedExchangeRates: "bittrex(DOGE_BTC),gdax(BTC_USD)"),
|
||||
(Pair: "DOGE_CAD", Expected: "bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1", ExpectedExchangeRates: "bittrex(DOGE_BTC),coinbase(BTC_CAD)"),
|
||||
(Pair: "LTC_CAD", Expected: "coinaverage(LTC_CAD) * 1.02", ExpectedExchangeRates: "coinaverage(LTC_CAD)"),
|
||||
};
|
||||
@ -94,12 +95,12 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(test.ExpectedExchangeRates, string.Join(',', rule.ExchangeRates.OfType<object>().ToArray()));
|
||||
}
|
||||
var rule2 = rules.GetRuleFor(CurrencyPair.Parse("DOGE_CAD"));
|
||||
rule2.ExchangeRates.SetRate("bittrex", CurrencyPair.Parse("DOGE_BTC"), 5000);
|
||||
rule2.ExchangeRates.SetRate("bittrex", CurrencyPair.Parse("DOGE_BTC"), new BidAsk(5000m));
|
||||
rule2.Reevaluate();
|
||||
Assert.True(rule2.HasError);
|
||||
Assert.Equal("5000 * ERR_RATE_UNAVAILABLE(coinbase, BTC_CAD) * 1.1", rule2.ToString(true));
|
||||
Assert.Equal("bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1", rule2.ToString(false));
|
||||
rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), 2000.4m);
|
||||
rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), new BidAsk(2000.4m));
|
||||
rule2.Reevaluate();
|
||||
Assert.False(rule2.HasError);
|
||||
Assert.Equal("5000 * 2000.4 * 1.1", rule2.ToString(true));
|
||||
@ -116,7 +117,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
rule2 = rules.GetRuleFor(CurrencyPair.Parse("DOGE_USD"));
|
||||
Assert.Equal("(2000 * (-3 + coinbase(BTC_CAD) + 50 - 5)) * 1.1", rule2.ToString());
|
||||
rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), 1000m);
|
||||
rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), new BidAsk(1000m));
|
||||
Assert.True(rule2.Reevaluate());
|
||||
Assert.Equal("(2000 * (-3 + 1000 + 50 - 5)) * 1.1", rule2.ToString(true));
|
||||
Assert.Equal((2000m * (-3m + 1000m + 50m - 5m)) * 1.1m, rule2.Value.Value);
|
||||
@ -124,11 +125,55 @@ namespace BTCPayServer.Tests
|
||||
// Test inverse
|
||||
rule2 = rules.GetRuleFor(CurrencyPair.Parse("USD_DOGE"));
|
||||
Assert.Equal("(1 / (2000 * (-3 + coinbase(BTC_CAD) + 50 - 5))) * 1.1", rule2.ToString());
|
||||
rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), 1000m);
|
||||
rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), new BidAsk(1000m));
|
||||
Assert.True(rule2.Reevaluate());
|
||||
Assert.Equal("(1 / (2000 * (-3 + 1000 + 50 - 5))) * 1.1", rule2.ToString(true));
|
||||
Assert.Equal(( 1.0m / (2000m * (-3m + 1000m + 50m - 5m))) * 1.1m, rule2.Value.Value);
|
||||
Assert.Equal((1.0m / (2000m * (-3m + 1000m + 50m - 5m))) * 1.1m, rule2.Value.Value);
|
||||
////////
|
||||
|
||||
// Make sure kraken is not converted to CurrencyPair
|
||||
builder = new StringBuilder();
|
||||
builder.AppendLine("BTC_USD = kraken(BTC_USD)");
|
||||
Assert.True(RateRules.TryParse(builder.ToString(), out rules));
|
||||
rule2 = rules.GetRuleFor(CurrencyPair.Parse("BTC_USD"));
|
||||
rule2.ExchangeRates.SetRate("kraken", CurrencyPair.Parse("BTC_USD"), new BidAsk(1000m));
|
||||
Assert.True(rule2.Reevaluate());
|
||||
|
||||
// Make sure can handle pairs
|
||||
builder = new StringBuilder();
|
||||
builder.AppendLine("BTC_USD = kraken(BTC_USD)");
|
||||
Assert.True(RateRules.TryParse(builder.ToString(), out rules));
|
||||
rule2 = rules.GetRuleFor(CurrencyPair.Parse("BTC_USD"));
|
||||
rule2.ExchangeRates.SetRate("kraken", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m));
|
||||
Assert.True(rule2.Reevaluate());
|
||||
Assert.Equal("(6000, 6100)", rule2.ToString(true));
|
||||
Assert.Equal(6000m, rule2.Value.Value);
|
||||
rule2 = rules.GetRuleFor(CurrencyPair.Parse("USD_BTC"));
|
||||
rule2.ExchangeRates.SetRate("kraken", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m));
|
||||
Assert.True(rule2.Reevaluate());
|
||||
Assert.Equal("1 / (6000, 6100)", rule2.ToString(true));
|
||||
Assert.Equal(1m / 6100m, rule2.Value.Value);
|
||||
|
||||
// Make sure the inverse has more priority than X_X or CDNT_X
|
||||
builder = new StringBuilder();
|
||||
builder.AppendLine("EUR_CDNT = 10");
|
||||
builder.AppendLine("CDNT_BTC = CDNT_EUR * EUR_BTC;");
|
||||
builder.AppendLine("CDNT_X = CDNT_BTC * BTC_X;");
|
||||
builder.AppendLine("X_X = coinaverage(X_X);");
|
||||
Assert.True(RateRules.TryParse(builder.ToString(), out rules));
|
||||
rule2 = rules.GetRuleFor(CurrencyPair.Parse("CDNT_EUR"));
|
||||
rule2.ExchangeRates.SetRate("coinaverage", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m));
|
||||
Assert.True(rule2.Reevaluate());
|
||||
Assert.Equal("1 / 10", rule2.ToString(false));
|
||||
|
||||
// Make sure an inverse can be solved on an exchange
|
||||
builder = new StringBuilder();
|
||||
builder.AppendLine("X_X = coinaverage(X_X);");
|
||||
Assert.True(RateRules.TryParse(builder.ToString(), out rules));
|
||||
rule2 = rules.GetRuleFor(CurrencyPair.Parse("USD_BTC"));
|
||||
rule2.ExchangeRates.SetRate("coinaverage", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m));
|
||||
Assert.True(rule2.Reevaluate());
|
||||
Assert.Equal($"({(1m / 6100m).ToString(CultureInfo.InvariantCulture)}, {(1m / 6000m).ToString(CultureInfo.InvariantCulture)})", rule2.ToString(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -185,99 +185,6 @@ namespace BTCPayServer.Tests
|
||||
|
||||
HttpClient _Http = new HttpClient();
|
||||
|
||||
class MockHttpRequest : HttpRequest
|
||||
{
|
||||
Uri serverUri;
|
||||
public MockHttpRequest(Uri serverUri)
|
||||
{
|
||||
this.serverUri = serverUri;
|
||||
}
|
||||
public override HttpContext HttpContext => throw new NotImplementedException();
|
||||
|
||||
public override string Method
|
||||
{
|
||||
get => throw new NotImplementedException();
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
public override string Scheme
|
||||
{
|
||||
get => serverUri.Scheme;
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
public override bool IsHttps
|
||||
{
|
||||
get => throw new NotImplementedException();
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
public override HostString Host
|
||||
{
|
||||
get => new HostString(serverUri.Host, serverUri.Port);
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
public override PathString PathBase
|
||||
{
|
||||
get => "";
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
public override PathString Path
|
||||
{
|
||||
get => throw new NotImplementedException();
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
public override QueryString QueryString
|
||||
{
|
||||
get => throw new NotImplementedException();
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
public override IQueryCollection Query
|
||||
{
|
||||
get => throw new NotImplementedException();
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
public override string Protocol
|
||||
{
|
||||
get => throw new NotImplementedException();
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override IHeaderDictionary Headers => throw new NotImplementedException();
|
||||
|
||||
public override IRequestCookieCollection Cookies
|
||||
{
|
||||
get => throw new NotImplementedException();
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
public override long? ContentLength
|
||||
{
|
||||
get => throw new NotImplementedException();
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
public override string ContentType
|
||||
{
|
||||
get => throw new NotImplementedException();
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
public override Stream Body
|
||||
{
|
||||
get => throw new NotImplementedException();
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override bool HasFormContentType => throw new NotImplementedException();
|
||||
|
||||
public override IFormCollection Form
|
||||
{
|
||||
get => throw new NotImplementedException();
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public BTCPayServerTester PayTester
|
||||
{
|
||||
get; set;
|
||||
|
@ -36,6 +36,7 @@ using BTCPayServer.Services.Stores;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Validation;
|
||||
using ExchangeSharp;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
@ -48,6 +49,27 @@ namespace BTCPayServer.Tests
|
||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanHandleUriValidation()
|
||||
{
|
||||
var attribute = new UriAttribute();
|
||||
Assert.True(attribute.IsValid("http://localhost"));
|
||||
Assert.True(attribute.IsValid("http://localhost:1234"));
|
||||
Assert.True(attribute.IsValid("https://localhost"));
|
||||
Assert.True(attribute.IsValid("https://127.0.0.1"));
|
||||
Assert.True(attribute.IsValid("http://127.0.0.1"));
|
||||
Assert.True(attribute.IsValid("http://127.0.0.1:1234"));
|
||||
Assert.True(attribute.IsValid("http://gozo.com"));
|
||||
Assert.True(attribute.IsValid("https://gozo.com"));
|
||||
Assert.True(attribute.IsValid("https://gozo.com:1234"));
|
||||
Assert.True(attribute.IsValid("https://gozo.com:1234/test.css"));
|
||||
Assert.True(attribute.IsValid("https://gozo.com:1234/test.png"));
|
||||
Assert.False(attribute.IsValid("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud e"));
|
||||
Assert.False(attribute.IsValid(2));
|
||||
Assert.False(attribute.IsValid("http://"));
|
||||
Assert.False(attribute.IsValid("httpdsadsa.com"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanCalculateCryptoDue2()
|
||||
{
|
||||
@ -265,7 +287,7 @@ namespace BTCPayServer.Tests
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
|
||||
|
||||
// Set tolerance to 50%
|
||||
var stores = user.GetController<StoresController>();
|
||||
var vm = Assert.IsType<StoreViewModel>(Assert.IsType<ViewResult>(stores.UpdateStore()).Model);
|
||||
@ -296,6 +318,22 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RoundupCurrenciesCorrectly()
|
||||
{
|
||||
foreach (var test in new[]
|
||||
{
|
||||
(0.0005m, "$0.0005 (USD)"),
|
||||
(0.001m, "$0.001 (USD)"),
|
||||
(0.01m, "$0.01 (USD)"),
|
||||
(0.1m, "$0.10 (USD)"),
|
||||
})
|
||||
{
|
||||
var actual = InvoiceController.FormatCurrency(test.Item1, "USD", new CurrencyNameTable());
|
||||
Assert.Equal(test.Item2, actual);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanPayUsingBIP70()
|
||||
{
|
||||
@ -305,6 +343,7 @@ namespace BTCPayServer.Tests
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
Assert.True(user.BitPay.TestAccess(Facade.Merchant));
|
||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Buyer = new Buyer() { email = "test@fwf.com" },
|
||||
@ -427,7 +466,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanSendLightningPayment2()
|
||||
public void CanSendLightningPaymentCLightning()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
@ -459,7 +498,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanSendLightningPayment()
|
||||
public void CanSendLightningPaymentCharge()
|
||||
{
|
||||
|
||||
using (var tester = ServerTester.Create())
|
||||
@ -617,7 +656,7 @@ namespace BTCPayServer.Tests
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
|
||||
var cashCow = tester.ExplorerNode;
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
|
||||
var firstPayment = invoice.CryptoInfo[0].TotalDue - Money.Satoshis(10);
|
||||
@ -732,8 +771,30 @@ namespace BTCPayServer.Tests
|
||||
Assert.False(user.BitPay.TestAccess(Facade.Merchant));
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
|
||||
Assert.True(user.BitPay.TestAccess(Facade.Merchant));
|
||||
|
||||
// Test request pairing code client side
|
||||
var storeController = user.GetController<StoresController>();
|
||||
storeController.CreateToken(new CreateTokenViewModel()
|
||||
{
|
||||
Facade = Facade.Merchant.ToString(),
|
||||
Label = "test2",
|
||||
StoreId = user.StoreId
|
||||
}).GetAwaiter().GetResult();
|
||||
Assert.NotNull(storeController.GeneratedPairingCode);
|
||||
|
||||
|
||||
var k = new Key();
|
||||
var bitpay = new Bitpay(k, tester.PayTester.ServerUri);
|
||||
bitpay.AuthorizeClient(new PairingCode(storeController.GeneratedPairingCode)).Wait();
|
||||
Assert.True(bitpay.TestAccess(Facade.Merchant));
|
||||
Assert.True(bitpay.TestAccess(Facade.PointOfSale));
|
||||
// Same with new instance
|
||||
bitpay = new Bitpay(k, tester.PayTester.ServerUri);
|
||||
Assert.True(bitpay.TestAccess(Facade.Merchant));
|
||||
Assert.True(bitpay.TestAccess(Facade.PointOfSale));
|
||||
|
||||
// Can generate API Key
|
||||
var repo = tester.PayTester.GetService<TokenRepository>();
|
||||
Assert.Empty(repo.GetLegacyAPIKeys(user.StoreId).GetAwaiter().GetResult());
|
||||
@ -875,6 +936,11 @@ namespace BTCPayServer.Tests
|
||||
|
||||
Assert.Single(invoice.CryptoInfo);
|
||||
Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode);
|
||||
Assert.True(invoice.PaymentCodes.ContainsKey("LTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("LTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies["LTC"].Enabled);
|
||||
Assert.True(invoice.PaymentSubtotals.ContainsKey("LTC"));
|
||||
Assert.True(invoice.PaymentTotals.ContainsKey("LTC"));
|
||||
var cashCow = tester.LTCExplorerNode;
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
|
||||
var firstPayment = Money.Coins(0.1m);
|
||||
@ -1009,6 +1075,16 @@ namespace BTCPayServer.Tests
|
||||
Assert.Single(checkout.AvailableCryptos);
|
||||
Assert.Equal("BTC", checkout.CryptoCode);
|
||||
|
||||
Assert.Single(invoice.PaymentCodes);
|
||||
Assert.Single(invoice.SupportedTransactionCurrencies);
|
||||
Assert.Single(invoice.SupportedTransactionCurrencies);
|
||||
Assert.Single(invoice.PaymentSubtotals);
|
||||
Assert.Single(invoice.PaymentTotals);
|
||||
Assert.True(invoice.PaymentCodes.ContainsKey("BTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("BTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies["BTC"].Enabled);
|
||||
Assert.True(invoice.PaymentSubtotals.ContainsKey("BTC"));
|
||||
Assert.True(invoice.PaymentTotals.ContainsKey("BTC"));
|
||||
//////////////////////
|
||||
|
||||
// Retry now with LTC enabled
|
||||
@ -1057,6 +1133,18 @@ namespace BTCPayServer.Tests
|
||||
checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, "LTC").GetAwaiter().GetResult()).Value;
|
||||
Assert.Equal(2, checkout.AvailableCryptos.Count);
|
||||
Assert.Equal("LTC", checkout.CryptoCode);
|
||||
|
||||
|
||||
Assert.Equal(2, invoice.PaymentCodes.Count());
|
||||
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count());
|
||||
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count());
|
||||
Assert.Equal(2, invoice.PaymentSubtotals.Count());
|
||||
Assert.Equal(2, invoice.PaymentTotals.Count());
|
||||
Assert.True(invoice.PaymentCodes.ContainsKey("LTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("LTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies["LTC"].Enabled);
|
||||
Assert.True(invoice.PaymentSubtotals.ContainsKey("LTC"));
|
||||
Assert.True(invoice.PaymentTotals.ContainsKey("LTC"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1197,7 +1285,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal("orange", vmview.Items[1].Title);
|
||||
Assert.Equal(10.0m, vmview.Items[1].Price.Value);
|
||||
Assert.Equal("$5.00", vmview.Items[0].Price.Formatted);
|
||||
Assert.IsType<RedirectResult>(apps.ViewPointOfSale(appId, 0, "orange").Result);
|
||||
Assert.IsType<RedirectResult>(apps.ViewPointOfSale(appId, 0, null, null, null, null, "orange").Result);
|
||||
var invoice = user.BitPay.GetInvoices().First();
|
||||
Assert.Equal(10.00m, invoice.Price);
|
||||
Assert.Equal("CAD", invoice.Currency);
|
||||
@ -1260,6 +1348,8 @@ namespace BTCPayServer.Tests
|
||||
var repo = tester.PayTester.GetService<InvoiceRepository>();
|
||||
var ctx = tester.PayTester.GetService<ApplicationDbContextFactory>().CreateContext();
|
||||
Assert.Equal(0, invoice.CryptoInfo[0].TxCount);
|
||||
Assert.True(invoice.MinerFees.ContainsKey("BTC"));
|
||||
Assert.Equal(100m, invoice.MinerFees["BTC"].SatoshiPerBytes);
|
||||
Eventually(() =>
|
||||
{
|
||||
var textSearchResult = tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
@ -1399,10 +1489,10 @@ namespace BTCPayServer.Tests
|
||||
var quadri = new QuadrigacxRateProvider();
|
||||
var rates = quadri.GetRatesAsync().GetAwaiter().GetResult();
|
||||
Assert.NotEmpty(rates);
|
||||
Assert.NotEqual(0.0m, rates.First().Value);
|
||||
Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_CAD")).Value);
|
||||
Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_USD")).Value);
|
||||
Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("LTC_CAD")).Value);
|
||||
Assert.NotEqual(0.0m, rates.First().BidAsk.Bid);
|
||||
Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_CAD")).Bid);
|
||||
Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_USD")).Bid);
|
||||
Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("LTC_CAD")).Bid);
|
||||
Assert.Null(rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("LTC_USD")));
|
||||
}
|
||||
|
||||
@ -1411,7 +1501,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
var provider = new BTCPayNetworkProvider(NetworkType.Mainnet);
|
||||
var factory = CreateBTCPayRateFactory(provider);
|
||||
|
||||
|
||||
foreach (var result in factory
|
||||
.DirectProviders
|
||||
.Select(p => (ExpectedName: p.Key, ResultAsync: p.Value.GetRatesAsync()))
|
||||
@ -1423,11 +1513,11 @@ namespace BTCPayServer.Tests
|
||||
Assert.NotEmpty(exchangeRates.ByExchange[result.ExpectedName]);
|
||||
|
||||
// This check if the currency pair is using right currency pair
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
e => ( e.CurrencyPair == new CurrencyPair("BTC", "USD") ||
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
e => (e.CurrencyPair == new CurrencyPair("BTC", "USD") ||
|
||||
e.CurrencyPair == new CurrencyPair("BTC", "EUR") ||
|
||||
e.CurrencyPair == new CurrencyPair("BTC", "USDT"))
|
||||
&& e.Value > 1.0m // 1BTC will always be more than 1USD
|
||||
&& e.BidAsk.Bid > 1.0m // 1BTC will always be more than 1USD
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1470,7 +1560,6 @@ namespace BTCPayServer.Tests
|
||||
RateRules.TryParse("X_X = coinaverage(X_X);", out var rateRules);
|
||||
|
||||
var factory = CreateBTCPayRateFactory(provider);
|
||||
factory.DirectProviders.Clear();
|
||||
factory.CacheSpan = TimeSpan.FromSeconds(10);
|
||||
|
||||
var fetchedRate = factory.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules).GetAwaiter().GetResult();
|
||||
|
@ -46,7 +46,7 @@ services:
|
||||
- lightning-charged
|
||||
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:1.0.2.6
|
||||
image: nicolasdorier/nbxplorer:1.0.2.8
|
||||
ports:
|
||||
- "32838:32838"
|
||||
expose:
|
||||
@ -89,7 +89,7 @@ services:
|
||||
- "bitcoin_datadir:/data"
|
||||
|
||||
customer_lightningd:
|
||||
image: nicolasdorier/clightning:0.0.0.14-dev
|
||||
image: nicolasdorier/clightning:v0.6-dev
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
LIGHTNINGD_OPT: |
|
||||
@ -112,7 +112,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
lightning-charged:
|
||||
image: shesek/lightning-charge:0.3.9
|
||||
image: shesek/lightning-charge:0.3.15
|
||||
environment:
|
||||
NETWORK: regtest
|
||||
API_TOKEN: foiewnccewuify
|
||||
@ -131,7 +131,7 @@ services:
|
||||
- merchant_lightningd
|
||||
|
||||
merchant_lightningd:
|
||||
image: nicolasdorier/clightning:0.0.0.14-dev
|
||||
image: nicolasdorier/clightning:v0.6-dev
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
LIGHTNINGD_OPT: |
|
||||
|
34
BTCPayServer/BTCPayNetworkProvider.Feathercoin.cs
Normal file
34
BTCPayServer/BTCPayNetworkProvider.Feathercoin.cs
Normal file
@ -0,0 +1,34 @@
|
||||
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 InitFeathercoin()
|
||||
{
|
||||
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("FTC");
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://explorer.feathercoin.com/tx/{0}" : "https://explorer.feathercoin.com/tx/{0}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "feathercoin",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"FTC_X = FTC_BTC * BTC_X",
|
||||
"FTC_BTC = bittrex(FTC_BTC)"
|
||||
},
|
||||
CryptoImagePath = "imlegacy/feathercoin.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("8'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
33
BTCPayServer/BTCPayNetworkProvider.Groestlcoin.cs
Normal file
33
BTCPayServer/BTCPayNetworkProvider.Groestlcoin.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public partial class BTCPayNetworkProvider
|
||||
{
|
||||
public void InitGroestlcoin()
|
||||
{
|
||||
|
||||
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("GRS");
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://chainz.cryptoid.info/grs/tx.dws?{0}.htm" : "https://chainz.cryptoid.info/grs-test/tx.dws?{0}.htm",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "groestlcoin",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"GRS_X = GRS_BTC * BTC_X",
|
||||
"GRS_BTC = bittrex(GRS_BTC)"
|
||||
},
|
||||
CryptoImagePath = "imlegacy/groestlcoin.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("17'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
35
BTCPayServer/BTCPayNetworkProvider.Monacoin.cs
Normal file
35
BTCPayServer/BTCPayNetworkProvider.Monacoin.cs
Normal file
@ -0,0 +1,35 @@
|
||||
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 InitMonacoin()
|
||||
{
|
||||
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("MONA");
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://mona.insight.monaco-ex.org/insight/tx/{0}" : "https://testnet-mona.insight.monaco-ex.org/insight/tx/{0}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "monacoin",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"MONA_X = MONA_BTC * BTC_X",
|
||||
"MONA_BTC = zaif(MONA_BTC)"
|
||||
},
|
||||
CryptoImagePath = "imlegacy/monacoin.png",
|
||||
LightningImagePath = "imlegacy/mona-lightning.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("22'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
34
BTCPayServer/BTCPayNetworkProvider.Polis.cs
Normal file
34
BTCPayServer/BTCPayNetworkProvider.Polis.cs
Normal file
@ -0,0 +1,34 @@
|
||||
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 InitPolis()
|
||||
{
|
||||
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("POLIS");
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://insight.polispay.org/tx/{0}" : "https://insight.polispay.org/tx/{0}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "polis",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"POLIS_X = POLIS_BTC * BTC_X",
|
||||
"POLIS_BTC = cryptopia(POLIS_BTC)"
|
||||
},
|
||||
CryptoImagePath = "imlegacy/polis.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1997'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
34
BTCPayServer/BTCPayNetworkProvider.Ufo.cs
Normal file
34
BTCPayServer/BTCPayNetworkProvider.Ufo.cs
Normal file
@ -0,0 +1,34 @@
|
||||
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 InitUfo()
|
||||
{
|
||||
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("UFO");
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://chainz.cryptoid.info/ufo/tx.dws?{0}" : "https://chainz.cryptoid.info/ufo/tx.dws?{0}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "ufo",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"UFO_X = UFO_BTC * BTC_X",
|
||||
"UFO_BTC = coinexchange(UFO_BTC)"
|
||||
},
|
||||
CryptoImagePath = "imlegacy/ufo.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("202'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -49,6 +49,11 @@ namespace BTCPayServer
|
||||
InitLitecoin();
|
||||
InitDogecoin();
|
||||
InitBitcoinGold();
|
||||
InitMonacoin();
|
||||
InitPolis();
|
||||
InitFeathercoin();
|
||||
InitGroestlcoin();
|
||||
InitUfo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<Version>1.0.2.14</Version>
|
||||
<Version>1.0.2.37</Version>
|
||||
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
@ -37,24 +37,24 @@
|
||||
<PackageReference Include="Hangfire.PostgreSql" Version="1.4.8.2" />
|
||||
<PackageReference Include="LedgerWallet" Version="1.0.1.36" />
|
||||
<PackageReference Include="Meziantou.AspNetCore.BundleTagHelpers" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0-rc1-final" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
|
||||
<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.1.7" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.20" />
|
||||
<PackageReference Include="NBitcoin" Version="4.1.1.18" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.29" />
|
||||
<PackageReference Include="DBreeze" Version="1.87.0" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="1.0.2.8" />
|
||||
<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" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.1-rc1" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="1.0.2.11" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.2" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.3" />
|
||||
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.14" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.1.0" />
|
||||
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
|
||||
<PackageReference Include="Text.Analyzers" Version="2.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.0-rc1-final" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version=" 2.1.0-rc1-final" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version=" 2.1.0" PrivateAssets="All" />
|
||||
<PackageReference Include="YamlDotNet" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -12,7 +12,8 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
[BitpayAPIConstraint]
|
||||
[Authorize(AuthenticationSchemes = Security.Policies.BitpayAuthentication)]
|
||||
[BitpayAPIConstraint(true)]
|
||||
public class AccessTokenController : Controller
|
||||
{
|
||||
TokenRepository _TokenRepository;
|
||||
@ -30,6 +31,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[Route("tokens")]
|
||||
[AllowAnonymous]
|
||||
public async Task<DataWrapper<List<PairingCodeResponse>>> Tokens([FromBody] TokenRequest request)
|
||||
{
|
||||
PairingCodeEntity pairingEntity = null;
|
||||
@ -53,7 +55,7 @@ namespace BTCPayServer.Controllers
|
||||
else
|
||||
{
|
||||
var sin = this.User.GetSIN() ?? request.Id;
|
||||
if (string.IsNullOrEmpty(request.Id) || !NBitpayClient.Extensions.BitIdExtensions.ValidateSIN(request.Id))
|
||||
if (string.IsNullOrEmpty(sin) || !NBitpayClient.Extensions.BitIdExtensions.ValidateSIN(sin))
|
||||
throw new BitpayHttpException(400, "'id' property is required, alternatively, use BitId");
|
||||
|
||||
pairingEntity = await _TokenRepository.GetPairingAsync(request.PairingCode);
|
||||
@ -77,6 +79,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
new PairingCodeResponse()
|
||||
{
|
||||
Policies = new Newtonsoft.Json.Linq.JArray(),
|
||||
PairingCode = pairingEntity.Id,
|
||||
PairingExpiration = pairingEntity.Expiration,
|
||||
DateCreated = pairingEntity.CreatedTime,
|
||||
|
@ -17,6 +17,8 @@ using YamlDotNet.RepresentationModel;
|
||||
using System.IO;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -57,15 +59,56 @@ namespace BTCPayServer.Controllers
|
||||
var app = await GetOwnedApp(appId, AppType.PointOfSale);
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
|
||||
var settings = app.GetSettings<PointOfSaleSettings>();
|
||||
return View(new UpdatePointOfSaleViewModel() { Title = settings.Title, ShowCustomAmount = settings.ShowCustomAmount, Currency = settings.Currency, Template = settings.Template });
|
||||
var vm = new UpdatePointOfSaleViewModel()
|
||||
{
|
||||
Title = settings.Title,
|
||||
ShowCustomAmount = settings.ShowCustomAmount,
|
||||
Currency = settings.Currency,
|
||||
Template = settings.Template
|
||||
};
|
||||
if (HttpContext?.Request != null)
|
||||
{
|
||||
var appUrl = HttpContext.Request.GetAbsoluteRoot().WithTrailingSlash() + $"apps/{appId}/pos";
|
||||
var encoder = HtmlEncoder.Default;
|
||||
if (settings.ShowCustomAmount)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.AppendLine($"<form method=\"POST\" action=\"{encoder.Encode(appUrl)}\">");
|
||||
builder.AppendLine($" <input type=\"hidden\" name=\"amount\" value=\"100\" />");
|
||||
builder.AppendLine($" <input type=\"hidden\" name=\"email\" value=\"customer@example.com\" />");
|
||||
builder.AppendLine($" <input type=\"hidden\" name=\"orderId\" value=\"CustomOrderId\" />");
|
||||
builder.AppendLine($" <input type=\"hidden\" name=\"notificationUrl\" value=\"https://example.com/callbacks\" />");
|
||||
builder.AppendLine($" <input type=\"hidden\" name=\"redirectUrl\" value=\"https://example.com/thanksyou\" />");
|
||||
builder.AppendLine($" <button type=\"submit\">Buy now</button>");
|
||||
builder.AppendLine($"</form>");
|
||||
vm.Example1 = builder.ToString();
|
||||
}
|
||||
try
|
||||
{
|
||||
var items = Parse(settings.Template, settings.Currency);
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine($"<form method=\"POST\" action=\"{encoder.Encode(appUrl)}\">");
|
||||
builder.AppendLine($" <input type=\"hidden\" name=\"email\" value=\"customer@example.com\" />");
|
||||
builder.AppendLine($" <input type=\"hidden\" name=\"orderId\" value=\"CustomOrderId\" />");
|
||||
builder.AppendLine($" <input type=\"hidden\" name=\"notificationUrl\" value=\"https://example.com/callbacks\" />");
|
||||
builder.AppendLine($" <input type=\"hidden\" name=\"redirectUrl\" value=\"https://example.com/thanksyou\" />");
|
||||
builder.AppendLine($" <button type=\"submit\" name=\"choiceKey\" value=\"{items[0].Id}\">Buy now</button>");
|
||||
builder.AppendLine($"</form>");
|
||||
vm.Example2 = builder.ToString();
|
||||
}
|
||||
catch { }
|
||||
vm.InvoiceUrl = appUrl + "invoices/SkdsDghkdP3D3qkj7bLq3";
|
||||
}
|
||||
|
||||
vm.ExampleCallback = "{\n \"id\":\"SkdsDghkdP3D3qkj7bLq3\",\n \"url\":\"https://btcpay.example.com/invoice?id=SkdsDghkdP3D3qkj7bLq3\",\n \"status\":\"paid\",\n \"price\":10,\n \"currency\":\"EUR\",\n \"invoiceTime\":1520373130312,\n \"expirationTime\":1520374030312,\n \"currentTime\":1520373179327,\n \"exceptionStatus\":false,\n \"buyerFields\":{\n \"buyerEmail\":\"customer@example.com\",\n \"buyerNotify\":false\n },\n \"paymentSubtotals\": {\n \"BTC\":114700\n },\n \"paymentTotals\": {\n \"BTC\":118400\n },\n \"transactionCurrency\": \"BTC\",\n \"amountPaid\": \"1025900\",\n \"exchangeRates\": {\n \"BTC\": {\n \"EUR\": 8721.690715789999,\n \"USD\": 10817.99\n }\n }\n}";
|
||||
return View(vm);
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("{appId}/settings/pos")]
|
||||
public async Task<IActionResult> UpdatePointOfSale(string appId, UpdatePointOfSaleViewModel vm)
|
||||
{
|
||||
if (_Currencies.GetCurrencyData(vm.Currency) == null)
|
||||
if (_Currencies.GetCurrencyData(vm.Currency, false) == null)
|
||||
ModelState.AddModelError(nameof(vm.Currency), "Invalid currency");
|
||||
try
|
||||
{
|
||||
@ -102,8 +145,9 @@ namespace BTCPayServer.Controllers
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
var settings = app.GetSettings<PointOfSaleSettings>();
|
||||
var currency = _Currencies.GetCurrencyData(settings.Currency);
|
||||
var currency = _Currencies.GetCurrencyData(settings.Currency, false);
|
||||
double step = currency == null ? 1 : Math.Pow(10, -(currency.Divisibility));
|
||||
|
||||
return View(new ViewPointOfSaleViewModel()
|
||||
{
|
||||
Title = settings.Title,
|
||||
@ -163,7 +207,13 @@ namespace BTCPayServer.Controllers
|
||||
[HttpPost]
|
||||
[Route("{appId}/pos")]
|
||||
[IgnoreAntiforgeryToken]
|
||||
public async Task<IActionResult> ViewPointOfSale(string appId, decimal amount, string choiceKey)
|
||||
public async Task<IActionResult> ViewPointOfSale(string appId,
|
||||
decimal amount,
|
||||
string email,
|
||||
string orderId,
|
||||
string notificationUrl,
|
||||
string redirectUrl,
|
||||
string choiceKey)
|
||||
{
|
||||
var app = await GetApp(appId, AppType.PointOfSale);
|
||||
if (string.IsNullOrEmpty(choiceKey) && amount <= 0)
|
||||
@ -173,7 +223,7 @@ namespace BTCPayServer.Controllers
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
var settings = app.GetSettings<PointOfSaleSettings>();
|
||||
if(string.IsNullOrEmpty(choiceKey) && !settings.ShowCustomAmount)
|
||||
if (string.IsNullOrEmpty(choiceKey) && !settings.ShowCustomAmount)
|
||||
{
|
||||
return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId });
|
||||
}
|
||||
@ -190,16 +240,22 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!settings.ShowCustomAmount)
|
||||
return NotFound();
|
||||
price = amount;
|
||||
title = settings.Title;
|
||||
}
|
||||
|
||||
var store = await GetStore(app);
|
||||
var invoice = await _InvoiceController.CreateInvoiceCore(new NBitpayClient.Invoice()
|
||||
{
|
||||
ItemDesc = title,
|
||||
Currency = settings.Currency,
|
||||
Price = price,
|
||||
BuyerEmail = email,
|
||||
OrderId = orderId,
|
||||
NotificationURL = notificationUrl,
|
||||
RedirectURL = redirectUrl,
|
||||
FullNotifications = true
|
||||
}, store, HttpContext.Request.GetAbsoluteRoot());
|
||||
return Redirect(invoice.Data.Url);
|
||||
}
|
||||
|
@ -14,5 +14,24 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
return View("Home");
|
||||
}
|
||||
|
||||
public IActionResult About()
|
||||
{
|
||||
ViewData["Message"] = "Your application description page.";
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult Contact()
|
||||
{
|
||||
ViewData["Message"] = "Your contact page.";
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult Error()
|
||||
{
|
||||
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
[EnableCors("BitpayAPI")]
|
||||
[BitpayAPIConstraint]
|
||||
[Authorize(Policies.CanUseStore.Key)]
|
||||
[Authorize(Policies.CanUseStore.Key, AuthenticationSchemes = Policies.BitpayAuthentication)]
|
||||
public class InvoiceControllerAPI : Controller
|
||||
{
|
||||
private InvoiceController _InvoiceController;
|
||||
|
@ -78,7 +78,7 @@ namespace BTCPayServer.Controllers
|
||||
var wallet = _WalletProvider.GetWallet(network);
|
||||
if (wallet == null)
|
||||
return NotFound();
|
||||
var payment = PaymentMessage.Load(Request.Body);
|
||||
var payment = PaymentMessage.Load(Request.Body, network.NBitcoinNetwork);
|
||||
var unused = wallet.BroadcastTransactionsAsync(payment.Transactions);
|
||||
await _InvoiceRepository.AddRefundsAsync(invoiceId, payment.RefundTo.Select(p => new TxOut(p.Amount, p.Script)).ToArray(), network.NBitcoinNetwork);
|
||||
return new PaymentAckActionResult(payment.CreateACK(invoiceId + " is currently processing, thanks for your purchase..."));
|
||||
|
@ -51,7 +51,7 @@ namespace BTCPayServer.Controllers
|
||||
StoreLink = Url.Action(nameof(StoresController.UpdateStore), "Stores", new { storeId = store.Id }),
|
||||
Id = invoice.Id,
|
||||
Status = invoice.Status,
|
||||
TransactionSpeed = invoice.SpeedPolicy == SpeedPolicy.HighSpeed ? "high" :
|
||||
TransactionSpeed = invoice.SpeedPolicy == SpeedPolicy.HighSpeed ? "high" :
|
||||
invoice.SpeedPolicy == SpeedPolicy.MediumSpeed ? "medium" :
|
||||
invoice.SpeedPolicy == SpeedPolicy.LowMediumSpeed ? "low-medium" :
|
||||
"low",
|
||||
@ -61,7 +61,7 @@ namespace BTCPayServer.Controllers
|
||||
MonitoringDate = invoice.MonitoringExpiration,
|
||||
OrderId = invoice.OrderId,
|
||||
BuyerInformation = invoice.BuyerInformation,
|
||||
Fiat = FormatCurrency((decimal)dto.Price, dto.Currency),
|
||||
Fiat = FormatCurrency((decimal)dto.Price, dto.Currency, _CurrencyNameTable),
|
||||
NotificationUrl = invoice.NotificationURL,
|
||||
RedirectUrl = invoice.RedirectURL,
|
||||
ProductInformation = invoice.ProductInformation,
|
||||
@ -85,7 +85,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
cryptoPayment.Address = onchainMethod.DepositAddress;
|
||||
}
|
||||
cryptoPayment.Rate = FormatCurrency(data);
|
||||
cryptoPayment.Rate = ExchangeRate(data);
|
||||
cryptoPayment.PaymentUrl = cryptoInfo.PaymentUrls.BIP21;
|
||||
model.CryptoPayments.Add(cryptoPayment);
|
||||
}
|
||||
@ -242,15 +242,16 @@ namespace BTCPayServer.Controllers
|
||||
CustomCSSLink = storeBlob.CustomCSS?.AbsoluteUri,
|
||||
CustomLogoLink = storeBlob.CustomLogo?.AbsoluteUri,
|
||||
BtcAddress = paymentMethodDetails.GetPaymentDestination(),
|
||||
OrderAmount = (accounting.TotalDue - accounting.NetworkFee).ToString(),
|
||||
BtcDue = accounting.Due.ToString(),
|
||||
OrderAmount = (accounting.TotalDue - accounting.NetworkFee).ToString(),
|
||||
OrderAmountFiat = OrderAmountFromInvoice(network.CryptoCode, invoice.ProductInformation),
|
||||
CustomerEmail = invoice.RefundMail,
|
||||
RequiresRefundEmail = storeBlob.RequiresRefundEmail,
|
||||
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
|
||||
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
|
||||
MaxTimeMinutes = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalMinutes,
|
||||
ItemDesc = invoice.ProductInformation.ItemDesc,
|
||||
Rate = FormatCurrency(paymentMethod),
|
||||
Rate = ExchangeRate(paymentMethod),
|
||||
MerchantRefLink = invoice.RedirectURL ?? "/",
|
||||
StoreName = store.StoreName,
|
||||
InvoiceBitcoinUrl = paymentMethodId.PaymentType == PaymentTypes.BTCLike ? cryptoInfo.PaymentUrls.BIP21 :
|
||||
@ -288,14 +289,45 @@ namespace BTCPayServer.Controllers
|
||||
return (paymentMethodId.PaymentType == PaymentTypes.BTCLike ? Url.Content(network.CryptoImagePath) : Url.Content(network.LightningImagePath));
|
||||
}
|
||||
|
||||
private string FormatCurrency(PaymentMethod paymentMethod)
|
||||
private string OrderAmountFromInvoice(string cryptoCode, ProductInformation productInformation)
|
||||
{
|
||||
// if invoice source currency is the same as currently display currency, no need for "order amount from invoice"
|
||||
if (cryptoCode == productInformation.Currency)
|
||||
return null;
|
||||
|
||||
return FormatCurrency(productInformation.Price, productInformation.Currency, _CurrencyNameTable);
|
||||
}
|
||||
private string ExchangeRate(PaymentMethod paymentMethod)
|
||||
{
|
||||
string currency = paymentMethod.ParentEntity.ProductInformation.Currency;
|
||||
return FormatCurrency(paymentMethod.Rate, currency);
|
||||
return FormatCurrency(paymentMethod.Rate, currency, _CurrencyNameTable);
|
||||
}
|
||||
public string FormatCurrency(decimal price, string currency)
|
||||
|
||||
public static string FormatCurrency(decimal price, string currency, CurrencyNameTable currencies)
|
||||
{
|
||||
return price.ToString("C", _CurrencyNameTable.GetCurrencyProvider(currency)) + $" ({currency})";
|
||||
var provider = currencies.GetNumberFormatInfo(currency, true);
|
||||
var currencyData = currencies.GetCurrencyData(currency, true);
|
||||
var divisibility = currencyData.Divisibility;
|
||||
while (true)
|
||||
{
|
||||
var rounded = decimal.Round(price, divisibility, MidpointRounding.AwayFromZero);
|
||||
if ((Math.Abs(rounded - price) / price) < 0.001m)
|
||||
{
|
||||
price = rounded;
|
||||
break;
|
||||
}
|
||||
divisibility++;
|
||||
}
|
||||
if (divisibility != provider.CurrencyDecimalDigits)
|
||||
{
|
||||
provider = (NumberFormatInfo)provider.Clone();
|
||||
provider.CurrencyDecimalDigits = divisibility;
|
||||
}
|
||||
|
||||
if (currencyData.Crypto)
|
||||
return price.ToString("C", provider);
|
||||
else
|
||||
return price.ToString("C", provider) + $" ({currency})";
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@ -324,7 +356,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
leases.Add(_EventAggregator.Subscribe<Events.InvoiceDataChangedEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId)));
|
||||
leases.Add(_EventAggregator.Subscribe<Events.InvoiceNewAddressEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId)));
|
||||
leases.Add(_EventAggregator.Subscribe<Events.InvoiceEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId)));
|
||||
leases.Add(_EventAggregator.Subscribe<Events.InvoiceEvent>(async o => await NotifySocket(webSocket, o.Invoice.Id, invoiceId)));
|
||||
while (true)
|
||||
{
|
||||
var message = await webSocket.ReceiveAsync(DummyBuffer, default(CancellationToken));
|
||||
@ -393,7 +425,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
Status = invoice.Status + (invoice.ExceptionStatus == null ? string.Empty : $" ({invoice.ExceptionStatus})"),
|
||||
ShowCheckout = invoice.Status == "new",
|
||||
Date = (DateTimeOffset.UtcNow - invoice.InvoiceTime).Prettify() + " ago",
|
||||
Date = invoice.InvoiceTime,
|
||||
InvoiceId = invoice.Id,
|
||||
OrderId = invoice.OrderId ?? string.Empty,
|
||||
RedirectUrl = invoice.RedirectURL ?? string.Empty,
|
||||
@ -430,7 +462,7 @@ namespace BTCPayServer.Controllers
|
||||
var stores = await _StoreRepository.GetStoresByUserId(GetUserId());
|
||||
model.Stores = new SelectList(stores, nameof(StoreData.Id), nameof(StoreData.StoreName), model.StoreId);
|
||||
var store = stores.FirstOrDefault(s => s.Id == model.StoreId);
|
||||
if(store == null)
|
||||
if (store == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.StoreId), "Store not found");
|
||||
}
|
||||
@ -503,8 +535,11 @@ namespace BTCPayServer.Controllers
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> InvalidatePaidInvoice(string invoiceId)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if (invoice == null)
|
||||
return NotFound();
|
||||
await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoiceId);
|
||||
_EventAggregator.Publish(new InvoiceEvent(invoiceId, 1008, "invoice_markedInvalid"));
|
||||
_EventAggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1008, "invoice_markedInvalid"));
|
||||
return RedirectToAction(nameof(ListInvoices));
|
||||
}
|
||||
|
||||
|
@ -109,6 +109,9 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
entity.ProductInformation = Map<Invoice, ProductInformation>(invoice);
|
||||
entity.RedirectURL = invoice.RedirectURL ?? store.StoreWebsite;
|
||||
if (!Uri.IsWellFormedUriString(entity.RedirectURL, UriKind.Absolute))
|
||||
entity.RedirectURL = null;
|
||||
|
||||
entity.Status = "new";
|
||||
entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);
|
||||
|
||||
@ -141,32 +144,26 @@ namespace BTCPayServer.Controllers
|
||||
PaymentMethod: CreatePaymentMethodAsync(fetchingByCurrencyPair, o.Handler, o.SupportedPaymentMethod, o.Network, entity, store)))
|
||||
.ToList();
|
||||
|
||||
List<string> paymentMethodErrors = new List<string>();
|
||||
List<string> invoiceLogs = new List<string>();
|
||||
List<ISupportedPaymentMethod> supported = new List<ISupportedPaymentMethod>();
|
||||
var paymentMethods = new PaymentMethodDictionary();
|
||||
|
||||
foreach(var pair in fetchingByCurrencyPair)
|
||||
foreach (var pair in fetchingByCurrencyPair)
|
||||
{
|
||||
var rateResult = await pair.Value;
|
||||
bool hasError = false;
|
||||
if(rateResult.Errors.Count != 0)
|
||||
invoiceLogs.Add($"{pair.Key}: The rating rule is {rateResult.Rule}");
|
||||
invoiceLogs.Add($"{pair.Key}: The evaluated rating rule is {rateResult.EvaluatedRule}");
|
||||
if (rateResult.Errors.Count != 0)
|
||||
{
|
||||
var allRateRuleErrors = string.Join(", ", rateResult.Errors.ToArray());
|
||||
paymentMethodErrors.Add($"{pair.Key}: Rate rule error ({allRateRuleErrors})");
|
||||
hasError = true;
|
||||
invoiceLogs.Add($"{pair.Key}: Rate rule error ({allRateRuleErrors})");
|
||||
}
|
||||
if(rateResult.ExchangeExceptions.Count != 0)
|
||||
if (rateResult.ExchangeExceptions.Count != 0)
|
||||
{
|
||||
foreach(var ex in rateResult.ExchangeExceptions)
|
||||
foreach (var ex in rateResult.ExchangeExceptions)
|
||||
{
|
||||
paymentMethodErrors.Add($"{pair.Key}: Exception reaching exchange {ex.ExchangeName} ({ex.Exception.Message})");
|
||||
invoiceLogs.Add($"{pair.Key}: Exception reaching exchange {ex.ExchangeName} ({ex.Exception.Message})");
|
||||
}
|
||||
hasError = true;
|
||||
}
|
||||
if(hasError)
|
||||
{
|
||||
paymentMethodErrors.Add($"{pair.Key}: The rule is {rateResult.Rule}");
|
||||
paymentMethodErrors.Add($"{pair.Key}: Evaluated rule is {rateResult.EvaluatedRule}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,11 +179,11 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
catch (PaymentMethodUnavailableException ex)
|
||||
{
|
||||
paymentMethodErrors.Add($"{o.SupportedPaymentMethod.PaymentId.CryptoCode} ({o.SupportedPaymentMethod.PaymentId.PaymentType}): Payment method unavailable ({ex.Message})");
|
||||
invoiceLogs.Add($"{o.SupportedPaymentMethod.PaymentId.CryptoCode} ({o.SupportedPaymentMethod.PaymentId.PaymentType}): Payment method unavailable ({ex.Message})");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
paymentMethodErrors.Add($"{o.SupportedPaymentMethod.PaymentId.CryptoCode} ({o.SupportedPaymentMethod.PaymentId.PaymentType}): Unexpected exception ({ex.ToString()})");
|
||||
invoiceLogs.Add($"{o.SupportedPaymentMethod.PaymentId.CryptoCode} ({o.SupportedPaymentMethod.PaymentId.PaymentType}): Unexpected exception ({ex.ToString()})");
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,7 +191,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
StringBuilder errors = new StringBuilder();
|
||||
errors.AppendLine("No payment method available for this store");
|
||||
foreach (var error in paymentMethodErrors)
|
||||
foreach (var error in invoiceLogs)
|
||||
{
|
||||
errors.AppendLine(error);
|
||||
}
|
||||
@ -204,9 +201,9 @@ namespace BTCPayServer.Controllers
|
||||
entity.SetSupportedPaymentMethods(supported);
|
||||
entity.SetPaymentMethods(paymentMethods);
|
||||
entity.PosData = invoice.PosData;
|
||||
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, paymentMethodErrors, _NetworkProvider);
|
||||
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, invoiceLogs, _NetworkProvider);
|
||||
|
||||
_EventAggregator.Publish(new Events.InvoiceEvent(entity, 1001, "invoice_created"));
|
||||
_EventAggregator.Publish(new Events.InvoiceEvent(entity.EntityToDTO(_NetworkProvider), 1001, "invoice_created"));
|
||||
var resp = entity.EntityToDTO(_NetworkProvider);
|
||||
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ namespace BTCPayServer.Controllers
|
||||
CryptoCode = r.Pair.Left,
|
||||
Code = r.Pair.Right,
|
||||
CurrencyPair = r.Pair.ToString(),
|
||||
Name = _CurrencyNameTable.GetCurrencyData(r.Pair.Right)?.Name,
|
||||
Name = _CurrencyNameTable.GetCurrencyData(r.Pair.Right, true).Name,
|
||||
Value = r.Value.Value
|
||||
}).Where(n => n.Name != null).ToArray());
|
||||
}
|
||||
|
@ -53,8 +53,7 @@ namespace BTCPayServer.Controllers
|
||||
ExplorerClientProvider explorerProvider,
|
||||
IFeeProviderFactory feeRateProvider,
|
||||
LanguageService langService,
|
||||
IHostingEnvironment env,
|
||||
CoinAverageSettings coinAverage)
|
||||
IHostingEnvironment env)
|
||||
{
|
||||
_RateFactory = rateFactory;
|
||||
_Dashboard = dashboard;
|
||||
@ -72,9 +71,7 @@ namespace BTCPayServer.Controllers
|
||||
_ServiceProvider = serviceProvider;
|
||||
_BtcpayServerOptions = btcpayServerOptions;
|
||||
_BTCPayEnv = btcpayEnv;
|
||||
_CoinAverage = coinAverage;
|
||||
}
|
||||
CoinAverageSettings _CoinAverage;
|
||||
NBXplorerDashboard _Dashboard;
|
||||
BTCPayServerOptions _BtcpayServerOptions;
|
||||
BTCPayServerEnvironment _BTCPayEnv;
|
||||
@ -316,7 +313,7 @@ namespace BTCPayServer.Controllers
|
||||
Action = "Continue",
|
||||
Title = "Rate rule scripting",
|
||||
Description = scripting ?
|
||||
"This action will mofify your current rate sources. Are you sure to turn on rate rules scripting? (Advanced users)"
|
||||
"This action will modify your current rate sources. Are you sure to turn on rate rules scripting? (Advanced users)"
|
||||
: "This action will delete your rate script. Are you sure to turn off rate rules scripting?",
|
||||
ButtonClass = "btn-primary"
|
||||
});
|
||||
@ -518,7 +515,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
private CoinAverageExchange[] GetSupportedExchanges()
|
||||
{
|
||||
return _CoinAverage.AvailableExchanges
|
||||
return _RateFactory.GetSupportedExchanges()
|
||||
.Select(c => c.Value)
|
||||
.OrderBy(s => s.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
@ -21,7 +21,7 @@ namespace BTCPayServer
|
||||
return false;
|
||||
|
||||
var currency = match.Groups.Last().Value.ToUpperInvariant();
|
||||
var currencyData = _CurrencyTable.GetCurrencyData(currency);
|
||||
var currencyData = _CurrencyTable.GetCurrencyData(currency, false);
|
||||
if (currencyData == null)
|
||||
return false;
|
||||
v = Math.Round(v, currencyData.Divisibility);
|
||||
|
@ -6,6 +6,11 @@ using System.Threading.Tasks;
|
||||
using Hangfire;
|
||||
using Hangfire.MemoryStorage;
|
||||
using Hangfire.PostgreSql;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations;
|
||||
using JetBrains.Annotations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations.Operations;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
@ -31,12 +36,56 @@ namespace BTCPayServer.Data
|
||||
return new ApplicationDbContext(builder.Options);
|
||||
}
|
||||
|
||||
class CustomNpgsqlMigrationsSqlGenerator : NpgsqlMigrationsSqlGenerator
|
||||
{
|
||||
public CustomNpgsqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies) : base(dependencies)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Generate(NpgsqlCreateDatabaseOperation operation, IModel model, MigrationCommandListBuilder builder)
|
||||
{
|
||||
builder
|
||||
.Append("CREATE DATABASE ")
|
||||
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name));
|
||||
|
||||
// POSTGRES gotcha: Indexed Text column (even if PK) are not used if we are not using C locale
|
||||
builder
|
||||
.Append(" TEMPLATE ")
|
||||
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("template0"));
|
||||
|
||||
builder
|
||||
.Append(" LC_CTYPE ")
|
||||
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("C"));
|
||||
|
||||
builder
|
||||
.Append(" LC_COLLATE ")
|
||||
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("C"));
|
||||
|
||||
builder
|
||||
.Append(" ENCODING ")
|
||||
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("UTF8"));
|
||||
|
||||
if (operation.Tablespace != null)
|
||||
{
|
||||
builder
|
||||
.Append(" TABLESPACE ")
|
||||
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Tablespace));
|
||||
}
|
||||
|
||||
builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
|
||||
|
||||
EndStatement(builder, suppressTransaction: true);
|
||||
}
|
||||
}
|
||||
|
||||
public void ConfigureBuilder(DbContextOptionsBuilder builder)
|
||||
{
|
||||
if (_Type == DatabaseType.Sqlite)
|
||||
builder.UseSqlite(_ConnectionString);
|
||||
else if (_Type == DatabaseType.Postgres)
|
||||
builder.UseNpgsql(_ConnectionString);
|
||||
builder
|
||||
.UseNpgsql(_ConnectionString)
|
||||
.ReplaceService<IMigrationsSqlGenerator, CustomNpgsqlMigrationsSqlGenerator>();
|
||||
}
|
||||
|
||||
public void ConfigureHangfireBuilder(IGlobalConfiguration builder)
|
||||
|
@ -72,7 +72,7 @@ namespace BTCPayServer
|
||||
}
|
||||
try
|
||||
{
|
||||
var data = Encoders.Base58Check.DecodeData(parts[i]);
|
||||
var data = Network.GetBase58CheckEncoder().DecodeData(parts[i]);
|
||||
if (data.Length < 4)
|
||||
continue;
|
||||
var prefix = Utils.ToUInt32(data, false);
|
||||
@ -80,7 +80,7 @@ namespace BTCPayServer
|
||||
for (int ii = 0; ii < 4; ii++)
|
||||
data[ii] = standardPrefix[ii];
|
||||
|
||||
var derivationScheme = new BitcoinExtPubKey(Encoders.Base58Check.EncodeData(data), Network).ToString();
|
||||
var derivationScheme = new BitcoinExtPubKey(Network.GetBase58CheckEncoder().EncodeData(data), Network).ToString();
|
||||
electrumMapping.TryGetValue(prefix, out string[] labels);
|
||||
if (labels != null)
|
||||
{
|
||||
|
@ -8,24 +8,20 @@ namespace BTCPayServer.Events
|
||||
{
|
||||
public class InvoiceEvent
|
||||
{
|
||||
public InvoiceEvent(InvoiceEntity invoice, int code, string name) : this(invoice.Id, code, name)
|
||||
public InvoiceEvent(Models.InvoiceResponse invoice, int code, string name)
|
||||
{
|
||||
|
||||
}
|
||||
public InvoiceEvent(string invoiceId, int code, string name)
|
||||
{
|
||||
InvoiceId = invoiceId;
|
||||
Invoice = invoice;
|
||||
EventCode = code;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string InvoiceId { get; set; }
|
||||
public Models.InvoiceResponse Invoice { get; set; }
|
||||
public int EventCode { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Invoice {InvoiceId} new event: {Name} ({EventCode})";
|
||||
return $"Invoice {Invoice.Id} new event: {Name} ({EventCode})";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,28 +36,6 @@ namespace BTCPayServer
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static string Prettify(this TimeSpan timeSpan)
|
||||
{
|
||||
if (timeSpan.TotalMinutes < 1)
|
||||
{
|
||||
return $"{(int)timeSpan.TotalSeconds} second{Plural((int)timeSpan.TotalSeconds)}";
|
||||
}
|
||||
if (timeSpan.TotalHours < 1)
|
||||
{
|
||||
return $"{(int)timeSpan.TotalMinutes} minute{Plural((int)timeSpan.TotalMinutes)}";
|
||||
}
|
||||
if (timeSpan.Days < 1)
|
||||
{
|
||||
return $"{(int)timeSpan.TotalHours} hour{Plural((int)timeSpan.TotalHours)}";
|
||||
}
|
||||
return $"{(int)timeSpan.TotalDays} day{Plural((int)timeSpan.TotalDays)}";
|
||||
}
|
||||
|
||||
private static string Plural(int totalDays)
|
||||
{
|
||||
return totalDays > 1 ? "s" : string.Empty;
|
||||
}
|
||||
|
||||
public static string PrettyPrint(this TimeSpan expiration)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
@ -198,7 +198,11 @@ namespace BTCPayServer.HostedServices
|
||||
PosData = dto.PosData,
|
||||
Price = dto.Price,
|
||||
Status = dto.Status,
|
||||
BuyerFields = invoice.RefundMail == null ? null : new Newtonsoft.Json.Linq.JObject() { new JProperty("buyerEmail", invoice.RefundMail) }
|
||||
BuyerFields = invoice.RefundMail == null ? null : new Newtonsoft.Json.Linq.JObject() { new JProperty("buyerEmail", invoice.RefundMail) },
|
||||
PaymentSubtotals = dto.PaymentSubtotals,
|
||||
PaymentTotals = dto.PaymentTotals,
|
||||
AmountPaid = dto.AmountPaid,
|
||||
ExchangeRates = dto.ExchangeRates
|
||||
};
|
||||
|
||||
// We keep backward compatibility with bitpay by passing BTC info to the notification
|
||||
@ -304,7 +308,7 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
leases.Add(_EventAggregator.Subscribe<InvoiceEvent>(async e =>
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, e.InvoiceId);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, e.Invoice.Id);
|
||||
List<Task> tasks = new List<Task>();
|
||||
|
||||
// Awaiting this later help make sure invoices should arrive in order
|
||||
|
@ -66,10 +66,10 @@ namespace BTCPayServer.HostedServices
|
||||
context.MarkDirty();
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
|
||||
context.Events.Add(new InvoiceEvent(invoice, 1004, "invoice_expired"));
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1004, "invoice_expired"));
|
||||
invoice.Status = "expired";
|
||||
if(invoice.ExceptionStatus == "paidPartial")
|
||||
context.Events.Add(new InvoiceEvent(invoice, 2000, "invoice_expiredPaidPartial"));
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 2000, "invoice_expiredPaidPartial"));
|
||||
}
|
||||
|
||||
var payments = invoice.GetPayments().Where(p => p.Accounted).ToArray();
|
||||
@ -84,7 +84,7 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
if (invoice.Status == "new")
|
||||
{
|
||||
context.Events.Add(new InvoiceEvent(invoice, 1003, "invoice_paidInFull"));
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1003, "invoice_paidInFull"));
|
||||
invoice.Status = "paid";
|
||||
invoice.ExceptionStatus = accounting.Paid > accounting.TotalDue ? "paidOver" : null;
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
@ -93,7 +93,7 @@ namespace BTCPayServer.HostedServices
|
||||
else if (invoice.Status == "expired" && invoice.ExceptionStatus != "paidLate")
|
||||
{
|
||||
invoice.ExceptionStatus = "paidLate";
|
||||
context.Events.Add(new InvoiceEvent(invoice, 1009, "invoice_paidAfterExpiration"));
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1009, "invoice_paidAfterExpiration"));
|
||||
context.MarkDirty();
|
||||
}
|
||||
}
|
||||
@ -139,14 +139,14 @@ namespace BTCPayServer.HostedServices
|
||||
(confirmedAccounting.Paid < accounting.MinimumTotalDue))
|
||||
{
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
context.Events.Add(new InvoiceEvent(invoice, 1013, "invoice_failedToConfirm"));
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1013, "invoice_failedToConfirm"));
|
||||
invoice.Status = "invalid";
|
||||
context.MarkDirty();
|
||||
}
|
||||
else if (confirmedAccounting.Paid >= accounting.MinimumTotalDue)
|
||||
{
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
context.Events.Add(new InvoiceEvent(invoice, 1005, "invoice_confirmed"));
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1005, "invoice_confirmed"));
|
||||
invoice.Status = "confirmed";
|
||||
context.MarkDirty();
|
||||
}
|
||||
@ -157,7 +157,7 @@ namespace BTCPayServer.HostedServices
|
||||
var completedAccounting = paymentMethod.Calculate(p => p.GetCryptoPaymentData().PaymentCompleted(p, network));
|
||||
if (completedAccounting.Paid >= accounting.MinimumTotalDue)
|
||||
{
|
||||
context.Events.Add(new InvoiceEvent(invoice, 1006, "invoice_completed"));
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1006, "invoice_completed"));
|
||||
invoice.Status = "complete";
|
||||
context.MarkDirty();
|
||||
}
|
||||
@ -249,13 +249,13 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
if (b.Name == "invoice_created")
|
||||
{
|
||||
Watch(b.InvoiceId);
|
||||
await Wait(b.InvoiceId);
|
||||
Watch(b.Invoice.Id);
|
||||
await Wait(b.Invoice.Id);
|
||||
}
|
||||
|
||||
if (b.Name == "invoice_receivedPayment")
|
||||
{
|
||||
Watch(b.InvoiceId);
|
||||
Watch(b.Invoice.Id);
|
||||
}
|
||||
}));
|
||||
return Task.CompletedTask;
|
||||
|
@ -117,7 +117,6 @@ namespace BTCPayServer.Hosting
|
||||
services.AddSingleton<IHostedService, InvoiceWatcher>();
|
||||
services.AddSingleton<IHostedService, RatesHostedService>();
|
||||
services.AddTransient<IConfigureOptions<MvcOptions>, BTCPayClaimsFilter>();
|
||||
services.AddTransient<IConfigureOptions<MvcOptions>, BitpayClaimsFilter>();
|
||||
|
||||
services.TryAddSingleton<ExplorerClientProvider>();
|
||||
services.TryAddSingleton<Bitpay>(o =>
|
||||
@ -137,6 +136,7 @@ namespace BTCPayServer.Hosting
|
||||
// bundling
|
||||
|
||||
services.AddAuthorization(o => Policies.AddBTCPayPolicies(o));
|
||||
BitpayAuthentication.AddAuthentication(services);
|
||||
|
||||
services.AddBundles();
|
||||
services.AddTransient<BundleOptions>(provider =>
|
||||
|
@ -20,5 +20,9 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
|
||||
[Display(Name = "User can input custom amount")]
|
||||
public bool ShowCustomAmount { get; set; }
|
||||
public string Example1 { get; internal set; }
|
||||
public string Example2 { get; internal set; }
|
||||
public string ExampleCallback { get; internal set; }
|
||||
public string InvoiceUrl { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
11
BTCPayServer/Models/ErrorViewModel.cs
Normal file
11
BTCPayServer/Models/ErrorViewModel.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer.Models
|
||||
{
|
||||
public class ErrorViewModel
|
||||
{
|
||||
public string RequestId { get; set; }
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
}
|
||||
}
|
@ -224,6 +224,29 @@ namespace BTCPayServer.Models
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[JsonProperty("paymentSubtotals")]
|
||||
public Dictionary<string, long> PaymentSubtotals { get; set; }
|
||||
|
||||
[JsonProperty("paymentTotals")]
|
||||
public Dictionary<string, long> PaymentTotals { get; set; }
|
||||
|
||||
[JsonProperty("amountPaid", DefaultValueHandling = DefaultValueHandling.Include)]
|
||||
public long AmountPaid { get; set; }
|
||||
|
||||
[JsonProperty("minerFees")]
|
||||
public Dictionary<string, NBitpayClient.MinerFeeInfo> MinerFees { get; set; }
|
||||
|
||||
[JsonProperty("exchangeRates")]
|
||||
public Dictionary<string, Dictionary<string, decimal>> ExchangeRates { get; set; }
|
||||
|
||||
[JsonProperty("supportedTransactionCurrencies")]
|
||||
public Dictionary<string, NBitpayClient.InvoiceSupportedTransactionCurrency> SupportedTransactionCurrencies { get; set; }
|
||||
|
||||
[JsonProperty("addresses")]
|
||||
public Dictionary<string, string> Addresses { get; set; }
|
||||
[JsonProperty("paymentCodes")]
|
||||
public Dictionary<string, NBitpayClient.InvoicePaymentUrls> PaymentCodes { get; set; }
|
||||
}
|
||||
public class Flags
|
||||
{
|
||||
@ -233,4 +256,5 @@ namespace BTCPayServer.Models
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Validation;
|
||||
|
||||
namespace BTCPayServer.Models.InvoicingModels
|
||||
{
|
||||
@ -52,8 +53,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
get; set;
|
||||
}
|
||||
|
||||
|
||||
[Url]
|
||||
[Uri]
|
||||
public string NotificationUrl
|
||||
{
|
||||
get; set;
|
||||
|
@ -33,10 +33,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
|
||||
public class InvoiceModel
|
||||
{
|
||||
public string Date
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public DateTimeOffset Date { get; set; }
|
||||
|
||||
public string OrderId { get; set; }
|
||||
public string RedirectUrl { get; set; }
|
||||
|
@ -37,6 +37,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
public string TimeLeft { get; set; }
|
||||
public string Rate { get; set; }
|
||||
public string OrderAmount { get; set; }
|
||||
public string OrderAmountFiat { get; set; }
|
||||
public string InvoiceBitcoinUrl { get; set; }
|
||||
public string InvoiceBitcoinUrlQR { get; set; }
|
||||
public int TxCount { get; set; }
|
||||
|
@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Validation;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
||||
namespace BTCPayServer.Models.StoreViewModels
|
||||
@ -42,10 +43,10 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
public string OnChainMinValue { get; set; }
|
||||
|
||||
[Display(Name = "Link to a custom CSS stylesheet")]
|
||||
[Url]
|
||||
[Uri]
|
||||
public string CustomCSS { get; set; }
|
||||
[Display(Name = "Link to a custom logo")]
|
||||
[Url]
|
||||
[Uri]
|
||||
public string CustomLogo { get; set; }
|
||||
|
||||
[Display(Name = "Custom HTML title to display on Checkout page")]
|
||||
|
@ -44,7 +44,7 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
public string ScriptTest { get; set; }
|
||||
public CoinAverageExchange[] AvailableExchanges { get; set; }
|
||||
|
||||
[Display(Name = "Multiply the rate by ...")]
|
||||
[Display(Name = "Multiply the rate by... (Setting to 1.01 would apply a discount of 1% to the purchase)")]
|
||||
[Range(0.01, 10.0)]
|
||||
public double RateMultiplier
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Validation;
|
||||
using BTCPayServer.Validations;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using System;
|
||||
@ -34,7 +35,7 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Url]
|
||||
[Uri]
|
||||
[Display(Name = "Store Website")]
|
||||
[MaxLength(500)]
|
||||
public string StoreWebsite
|
||||
|
@ -3,6 +3,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Models
|
||||
{
|
||||
@ -44,6 +45,9 @@ namespace BTCPayServer.Models
|
||||
|
||||
public class PairingCodeResponse
|
||||
{
|
||||
[JsonProperty(PropertyName = "policies")]
|
||||
public JArray Policies { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "pairingCode")]
|
||||
public string PairingCode
|
||||
{
|
||||
|
@ -161,7 +161,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
{
|
||||
var payment = await _InvoiceRepository.AddPayment(invoice.Id, DateTimeOffset.UtcNow, paymentData, network.CryptoCode);
|
||||
if(payment != null)
|
||||
await ReceivedPayment(wallet, invoice.Id, payment, evt.DerivationStrategy);
|
||||
await ReceivedPayment(wallet, invoice, payment, evt.DerivationStrategy);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -332,7 +332,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin.Timestamp, paymentData, network.CryptoCode).ConfigureAwait(false);
|
||||
alreadyAccounted.Add(coin.Coin.Outpoint);
|
||||
if (payment != null)
|
||||
invoice = await ReceivedPayment(wallet, invoice.Id, payment, strategy);
|
||||
invoice = await ReceivedPayment(wallet, invoice, payment, strategy);
|
||||
totalPayment++;
|
||||
}
|
||||
}
|
||||
@ -346,10 +346,10 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
private async Task<InvoiceEntity> ReceivedPayment(BTCPayWallet wallet, string invoiceId, PaymentEntity payment, DerivationStrategyBase strategy)
|
||||
private async Task<InvoiceEntity> ReceivedPayment(BTCPayWallet wallet, InvoiceEntity invoice, PaymentEntity payment, DerivationStrategyBase strategy)
|
||||
{
|
||||
var paymentData = (BitcoinLikePaymentData)payment.GetCryptoPaymentData();
|
||||
var invoice = (await UpdatePaymentStates(wallet, invoiceId));
|
||||
invoice = (await UpdatePaymentStates(wallet, invoice.Id));
|
||||
var paymentMethod = invoice.GetPaymentMethod(wallet.Network, PaymentTypes.BTCLike, _ExplorerClients.NetworkProviders);
|
||||
if (paymentMethod != null &&
|
||||
paymentMethod.GetPaymentMethodDetails() is BitcoinLikeOnChainPaymentMethod btc &&
|
||||
@ -358,13 +358,13 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
{
|
||||
var address = await wallet.ReserveAddressAsync(strategy);
|
||||
btc.DepositAddress = address.ToString();
|
||||
await _InvoiceRepository.NewAddress(invoiceId, btc, wallet.Network);
|
||||
_Aggregator.Publish(new InvoiceNewAddressEvent(invoiceId, address.ToString(), wallet.Network));
|
||||
await _InvoiceRepository.NewAddress(invoice.Id, btc, wallet.Network);
|
||||
_Aggregator.Publish(new InvoiceNewAddressEvent(invoice.Id, address.ToString(), wallet.Network));
|
||||
paymentMethod.SetPaymentMethodDetails(btc);
|
||||
invoice.SetPaymentMethod(paymentMethod);
|
||||
}
|
||||
wallet.InvalidateCache(strategy);
|
||||
_Aggregator.Publish(new InvoiceEvent(invoiceId, 1002, "invoice_receivedPayment"));
|
||||
_Aggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1002, "invoice_receivedPayment"));
|
||||
return invoice;
|
||||
}
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
|
@ -156,7 +156,7 @@ namespace BTCPayServer.Payments.Lightning.Charge
|
||||
|
||||
async Task<LightningInvoice> ILightningInvoiceClient.CreateInvoice(LightMoney amount, string description, TimeSpan expiry, CancellationToken cancellation)
|
||||
{
|
||||
var invoice = await CreateInvoiceAsync(new CreateInvoiceRequest() { Amount = amount, Expiry = expiry, Description = description ?? "" });
|
||||
var invoice = await CreateInvoiceAsync(new CreateInvoiceRequest() { Amount = amount, Expiry = expiry, Description = description ?? "" }, cancellation);
|
||||
return new LightningInvoice() { Id = invoice.Id, Amount = amount, BOLT11 = invoice.PayReq, Status = "unpaid" };
|
||||
}
|
||||
|
||||
|
@ -36,17 +36,25 @@ namespace BTCPayServer.Payments.Lightning
|
||||
expiry = TimeSpan.FromSeconds(1);
|
||||
|
||||
LightningInvoice lightningInvoice = null;
|
||||
try
|
||||
|
||||
string description = storeBlob.LightningDescriptionTemplate;
|
||||
description = description.Replace("{StoreName}", store.StoreName ?? "", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{ItemDescription}", invoice.ProductInformation.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{OrderId}", invoice.OrderId ?? "", StringComparison.OrdinalIgnoreCase);
|
||||
using (var cts = new CancellationTokenSource(5000))
|
||||
{
|
||||
string description = storeBlob.LightningDescriptionTemplate;
|
||||
description = description.Replace("{StoreName}", store.StoreName ?? "", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{ItemDescription}", invoice.ProductInformation.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{OrderId}", invoice.OrderId ?? "", StringComparison.OrdinalIgnoreCase);
|
||||
lightningInvoice = await client.CreateInvoice(new LightMoney(due, LightMoneyUnit.BTC), description, expiry);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException($"Impossible to create lightning invoice ({ex.Message})", ex);
|
||||
try
|
||||
{
|
||||
lightningInvoice = await client.CreateInvoice(new LightMoney(due, LightMoneyUnit.BTC), description, expiry, cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException) when (cts.IsCancellationRequested)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException($"The lightning node did not replied in a timely maner");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException($"Impossible to create lightning invoice ({ex.Message})", ex);
|
||||
}
|
||||
}
|
||||
var nodeInfo = await test;
|
||||
return new LightningLikePaymentMethodDetails()
|
||||
@ -62,34 +70,36 @@ namespace BTCPayServer.Payments.Lightning
|
||||
if (!_Dashboard.IsFullySynched(network.CryptoCode, out var summary))
|
||||
throw new PaymentMethodUnavailableException($"Full node not available");
|
||||
|
||||
var cts = new CancellationTokenSource(5000);
|
||||
var client = _LightningClientFactory.CreateClient(supportedPaymentMethod, network);
|
||||
LightningNodeInformation info = null;
|
||||
try
|
||||
using (var cts = new CancellationTokenSource(5000))
|
||||
{
|
||||
info = await client.GetInfo(cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException) when (cts.IsCancellationRequested)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException($"The lightning node did not replied in a timely maner");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException($"Error while connecting to the API ({ex.Message})");
|
||||
}
|
||||
var client = _LightningClientFactory.CreateClient(supportedPaymentMethod, network);
|
||||
LightningNodeInformation info = null;
|
||||
try
|
||||
{
|
||||
info = await client.GetInfo(cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException) when (cts.IsCancellationRequested)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException($"The lightning node did not replied in a timely maner");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException($"Error while connecting to the API ({ex.Message})");
|
||||
}
|
||||
|
||||
if (info.Address == null)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException($"No lightning node public address has been configured");
|
||||
}
|
||||
if (info.Address == null)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException($"No lightning node public address has been configured");
|
||||
}
|
||||
|
||||
var blocksGap = Math.Abs(info.BlockHeight - summary.Status.ChainHeight);
|
||||
if (blocksGap > 10)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException($"The lightning is not synched ({blocksGap} blocks)");
|
||||
}
|
||||
var blocksGap = Math.Abs(info.BlockHeight - summary.Status.ChainHeight);
|
||||
if (blocksGap > 10)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException($"The lightning is not synched ({blocksGap} blocks)");
|
||||
}
|
||||
|
||||
return new NodeInfo(info.NodeId, info.Address, info.P2PPort);
|
||||
return new NodeInfo(info.NodeId, info.Address, info.P2PPort);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task TestConnection(NodeInfo nodeInfo, CancellationToken cancellation)
|
||||
|
@ -46,7 +46,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
{
|
||||
if (inv.Name == "invoice_created")
|
||||
{
|
||||
await EnsureListening(inv.InvoiceId, false);
|
||||
await EnsureListening(inv.Invoice.Id, false);
|
||||
}
|
||||
}));
|
||||
|
||||
@ -189,8 +189,12 @@ namespace BTCPayServer.Payments.Lightning
|
||||
BOLT11 = notification.BOLT11,
|
||||
Amount = notification.Amount
|
||||
}, network.CryptoCode, accounted: true);
|
||||
if(payment != null)
|
||||
_Aggregator.Publish(new InvoiceEvent(listenedInvoice.InvoiceId, 1002, "invoice_receivedPayment"));
|
||||
if (payment != null)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, listenedInvoice.InvoiceId);
|
||||
if(invoice != null)
|
||||
_Aggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1002, "invoice_receivedPayment"));
|
||||
}
|
||||
}
|
||||
|
||||
List<Task> _ListeningLightning = new List<Task>();
|
||||
|
@ -41,9 +41,9 @@ namespace BTCPayServer.Rating
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rate.Value.HasValue)
|
||||
if (rate.BidAsk != null)
|
||||
{
|
||||
_AllRates[key].Value = rate.Value;
|
||||
_AllRates[key].BidAsk = rate.BidAsk;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -58,39 +58,175 @@ namespace BTCPayServer.Rating
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public void SetRate(string exchangeName, CurrencyPair currencyPair, decimal value)
|
||||
public void SetRate(string exchangeName, CurrencyPair currencyPair, BidAsk bidAsk)
|
||||
{
|
||||
if (ByExchange.TryGetValue(exchangeName, out var rates))
|
||||
{
|
||||
var rate = rates.FirstOrDefault(r => r.CurrencyPair == currencyPair);
|
||||
if (rate != null)
|
||||
rate.Value = value;
|
||||
if(rate != null)
|
||||
{
|
||||
rate.BidAsk = bidAsk;
|
||||
}
|
||||
var invPair = currencyPair.Inverse();
|
||||
var invRate = rates.FirstOrDefault(r => r.CurrencyPair == invPair);
|
||||
if (invRate != null)
|
||||
{
|
||||
invRate.BidAsk = bidAsk?.Inverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
public decimal? GetRate(string exchangeName, CurrencyPair currencyPair)
|
||||
public BidAsk GetRate(string exchangeName, CurrencyPair currencyPair)
|
||||
{
|
||||
if (currencyPair.Left == currencyPair.Right)
|
||||
return 1.0m;
|
||||
return BidAsk.One;
|
||||
if (ByExchange.TryGetValue(exchangeName, out var rates))
|
||||
{
|
||||
var rate = rates.FirstOrDefault(r => r.CurrencyPair == currencyPair);
|
||||
if (rate != null)
|
||||
return rate.Value;
|
||||
return rate.BidAsk;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public class BidAsk
|
||||
{
|
||||
|
||||
private readonly static BidAsk _One = new BidAsk(1.0m);
|
||||
public static BidAsk One
|
||||
{
|
||||
get
|
||||
{
|
||||
return _One;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly static BidAsk _Zero = new BidAsk(0.0m);
|
||||
public static BidAsk Zero
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Zero;
|
||||
}
|
||||
}
|
||||
public BidAsk(decimal bid, decimal ask)
|
||||
{
|
||||
if (bid > ask)
|
||||
throw new ArgumentException("the bid should be lower than ask", nameof(bid));
|
||||
_Ask = ask;
|
||||
_Bid = bid;
|
||||
}
|
||||
public BidAsk(decimal v) : this(v, v)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private readonly decimal _Bid;
|
||||
public decimal Bid
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Bid;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private readonly decimal _Ask;
|
||||
public decimal Ask
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Ask;
|
||||
}
|
||||
}
|
||||
public BidAsk Inverse()
|
||||
{
|
||||
return new BidAsk(1.0m / Ask, 1.0m / Bid);
|
||||
}
|
||||
|
||||
public static BidAsk operator +(BidAsk a, BidAsk b)
|
||||
{
|
||||
return new BidAsk(a.Bid + b.Bid, a.Ask + b.Ask);
|
||||
}
|
||||
|
||||
public static BidAsk operator +(BidAsk a)
|
||||
{
|
||||
return new BidAsk(a.Bid, a.Ask);
|
||||
}
|
||||
|
||||
public static BidAsk operator -(BidAsk a)
|
||||
{
|
||||
return new BidAsk(-a.Bid, -a.Ask);
|
||||
}
|
||||
|
||||
public static BidAsk operator *(BidAsk a, BidAsk b)
|
||||
{
|
||||
return new BidAsk(a.Bid * b.Bid, a.Ask * b.Ask);
|
||||
}
|
||||
|
||||
public static BidAsk operator /(BidAsk a, BidAsk b)
|
||||
{
|
||||
// This one is tricky.
|
||||
// BTC_EUR = (6000, 6100)
|
||||
// Implicit rule give
|
||||
// EUR_BTC = 1 / BTC_EUR
|
||||
// Or
|
||||
// EUR_BTC = (1, 1) / BTC_EUR
|
||||
// Naive calculation would give us ( 1/6000, 1/6100) = (0.000166, 0.000163)
|
||||
// However, this is an invalid BidAsk!!! because 0.000166 > 0.000163
|
||||
// So instead, we need to calculate (1/6100, 1/6000)
|
||||
return new BidAsk(a.Bid / b.Ask, a.Ask / b.Bid);
|
||||
}
|
||||
|
||||
public static BidAsk operator -(BidAsk a, BidAsk b)
|
||||
{
|
||||
return new BidAsk(a.Bid - b.Bid, a.Ask - b.Ask);
|
||||
}
|
||||
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
BidAsk item = obj as BidAsk;
|
||||
if (item == null)
|
||||
return false;
|
||||
return Bid == item.Bid && Ask == item.Ask;
|
||||
}
|
||||
public static bool operator ==(BidAsk a, BidAsk b)
|
||||
{
|
||||
if (System.Object.ReferenceEquals(a, b))
|
||||
return true;
|
||||
if (((object)a == null) || ((object)b == null))
|
||||
return false;
|
||||
return a.Bid == b.Bid && a.Ask == b.Ask;
|
||||
}
|
||||
|
||||
public static bool operator !=(BidAsk a, BidAsk b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return ToString().GetHashCode(StringComparison.InvariantCulture);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (Bid == Ask)
|
||||
return Bid.ToString(CultureInfo.InvariantCulture);
|
||||
return $"({Bid.ToString(CultureInfo.InvariantCulture)} , {Ask.ToString(CultureInfo.InvariantCulture)})";
|
||||
}
|
||||
}
|
||||
public class ExchangeRate
|
||||
{
|
||||
public string Exchange { get; set; }
|
||||
public CurrencyPair CurrencyPair { get; set; }
|
||||
public decimal? Value { get; set; }
|
||||
public BidAsk BidAsk { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (Value == null)
|
||||
if (BidAsk == null)
|
||||
return $"{Exchange}({CurrencyPair})";
|
||||
return $"{Exchange}({CurrencyPair}) == {Value.Value.ToString(CultureInfo.InvariantCulture)}";
|
||||
return $"{Exchange}({CurrencyPair}) == {BidAsk.ToString()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ namespace BTCPayServer.Rating
|
||||
UnsupportedOperator,
|
||||
MissingArgument,
|
||||
DivideByZero,
|
||||
InvalidNegative,
|
||||
PreprocessError,
|
||||
RateUnavailable,
|
||||
InvalidExchangeName,
|
||||
@ -139,7 +140,7 @@ namespace BTCPayServer.Rating
|
||||
}
|
||||
return new RateRule(this, currencyPair, candidate);
|
||||
}
|
||||
|
||||
|
||||
public ExpressionSyntax FindBestCandidate(CurrencyPair p)
|
||||
{
|
||||
var invP = p.Inverse();
|
||||
@ -147,9 +148,9 @@ namespace BTCPayServer.Rating
|
||||
foreach (var pair in new[]
|
||||
{
|
||||
(Pair: p, Priority: 0, Inverse: false),
|
||||
(Pair: new CurrencyPair(p.Left, "X"), Priority: 1, Inverse: false),
|
||||
(Pair: new CurrencyPair("X", p.Right), Priority: 1, Inverse: false),
|
||||
(Pair: invP, Priority: 2, Inverse: true),
|
||||
(Pair: invP, Priority: 1, Inverse: true),
|
||||
(Pair: new CurrencyPair(p.Left, "X"), Priority: 2, Inverse: false),
|
||||
(Pair: new CurrencyPair("X", p.Right), Priority: 2, Inverse: false),
|
||||
(Pair: new CurrencyPair(invP.Left, "X"), Priority: 3, Inverse: true),
|
||||
(Pair: new CurrencyPair("X", invP.Right), Priority: 3, Inverse: true),
|
||||
(Pair: new CurrencyPair("X", "X"), Priority: 4, Inverse: false)
|
||||
@ -216,8 +217,7 @@ namespace BTCPayServer.Rating
|
||||
}
|
||||
else
|
||||
{
|
||||
var token = SyntaxFactory.ParseToken(rate.Value.ToString(CultureInfo.InvariantCulture));
|
||||
return SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, token);
|
||||
return RateRules.CreateExpression(rate.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -225,7 +225,7 @@ namespace BTCPayServer.Rating
|
||||
|
||||
class CalculateWalker : CSharpSyntaxWalker
|
||||
{
|
||||
public Stack<decimal> Values = new Stack<decimal>();
|
||||
public Stack<BidAsk> Values = new Stack<BidAsk>();
|
||||
public List<RateRulesErrors> Errors = new List<RateRulesErrors>();
|
||||
|
||||
public override void VisitPrefixUnaryExpression(PrefixUnaryExpressionSyntax node)
|
||||
@ -254,7 +254,15 @@ namespace BTCPayServer.Rating
|
||||
switch (node.Kind())
|
||||
{
|
||||
case SyntaxKind.UnaryMinusExpression:
|
||||
Values.Push(-Values.Pop());
|
||||
var v = Values.Pop();
|
||||
if(v.Bid == v.Ask)
|
||||
{
|
||||
Values.Push(-v);
|
||||
}
|
||||
else
|
||||
{
|
||||
Errors.Add(RateRulesErrors.InvalidNegative);
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.UnaryPlusExpression:
|
||||
Values.Push(+Values.Pop());
|
||||
@ -299,7 +307,7 @@ namespace BTCPayServer.Rating
|
||||
Values.Push(a * b);
|
||||
break;
|
||||
case SyntaxKind.DivideExpression:
|
||||
if (b == decimal.Zero)
|
||||
if (a.Ask == decimal.Zero || b.Ask == decimal.Zero)
|
||||
{
|
||||
Errors.Add(RateRulesErrors.DivideByZero);
|
||||
}
|
||||
@ -309,19 +317,48 @@ namespace BTCPayServer.Rating
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.SubtractExpression:
|
||||
Values.Push(a - b);
|
||||
if (b.Bid == b.Ask)
|
||||
{
|
||||
Values.Push(a - b);
|
||||
}
|
||||
else
|
||||
{
|
||||
Errors.Add(RateRulesErrors.InvalidNegative);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException("Should never happen");
|
||||
}
|
||||
}
|
||||
|
||||
Stack<decimal> _TupleValues = null;
|
||||
public override void VisitTupleExpression(TupleExpressionSyntax node)
|
||||
{
|
||||
_TupleValues = new Stack<decimal>();
|
||||
base.VisitTupleExpression(node);
|
||||
if(_TupleValues.Count != 2)
|
||||
{
|
||||
Errors.Add(RateRulesErrors.MissingArgument);
|
||||
}
|
||||
else
|
||||
{
|
||||
var ask = _TupleValues.Pop();
|
||||
var bid = _TupleValues.Pop();
|
||||
Values.Push(new BidAsk(bid, ask));
|
||||
}
|
||||
_TupleValues = null;
|
||||
}
|
||||
|
||||
public override void VisitLiteralExpression(LiteralExpressionSyntax node)
|
||||
{
|
||||
switch (node.Kind())
|
||||
{
|
||||
case SyntaxKind.NumericLiteralExpression:
|
||||
Values.Push(decimal.Parse(node.ToString(), CultureInfo.InvariantCulture));
|
||||
var v = decimal.Parse(node.ToString(), CultureInfo.InvariantCulture);
|
||||
if (_TupleValues == null)
|
||||
Values.Push(new BidAsk(v));
|
||||
else
|
||||
_TupleValues.Push(v);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -347,17 +384,23 @@ namespace BTCPayServer.Rating
|
||||
class FlattenExpressionRewriter : CSharpSyntaxRewriter
|
||||
{
|
||||
RateRules parent;
|
||||
CurrencyPair pair;
|
||||
int nested = 0;
|
||||
public FlattenExpressionRewriter(RateRules parent, CurrencyPair pair)
|
||||
{
|
||||
Context.Push(pair);
|
||||
this.pair = pair;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public ExchangeRates ExchangeRates = new ExchangeRates();
|
||||
public Stack<CurrencyPair> Context { get; set; } = new Stack<CurrencyPair>();
|
||||
bool IsInvocation;
|
||||
public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node)
|
||||
{
|
||||
if (IsInvocation)
|
||||
{
|
||||
Errors.Add(RateRulesErrors.InvalidCurrencyIdentifier);
|
||||
return RateRules.CreateExpression($"ERR_INVALID_CURRENCY_PAIR({node.ToString()})");
|
||||
}
|
||||
IsInvocation = true;
|
||||
_ExchangeName = node.Expression.ToString();
|
||||
var result = base.VisitInvocationExpression(node);
|
||||
@ -365,18 +408,27 @@ namespace BTCPayServer.Rating
|
||||
return result;
|
||||
}
|
||||
|
||||
bool IsArgumentList;
|
||||
public override SyntaxNode VisitArgumentList(ArgumentListSyntax node)
|
||||
{
|
||||
IsArgumentList = true;
|
||||
var result = base.VisitArgumentList(node);
|
||||
IsArgumentList = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
string _ExchangeName = null;
|
||||
|
||||
public List<RateRulesErrors> Errors = new List<RateRulesErrors>();
|
||||
const int MaxNestedCount = 8;
|
||||
public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
|
||||
{
|
||||
if (CurrencyPair.TryParse(node.Identifier.ValueText, out var currentPair))
|
||||
if (
|
||||
(!IsInvocation || IsArgumentList) &&
|
||||
CurrencyPair.TryParse(node.Identifier.ValueText, out var currentPair))
|
||||
{
|
||||
var ctx = Context.Peek();
|
||||
|
||||
var replacedPair = new CurrencyPair(left: currentPair.Left == "X" ? ctx.Left : currentPair.Left,
|
||||
right: currentPair.Right == "X" ? ctx.Right : currentPair.Right);
|
||||
var replacedPair = new CurrencyPair(left: currentPair.Left == "X" ? pair.Left : currentPair.Left,
|
||||
right: currentPair.Right == "X" ? pair.Right : currentPair.Right);
|
||||
if (IsInvocation) // eg. replace bittrex(BTC_X) to bittrex(BTC_USD)
|
||||
{
|
||||
ExchangeRates.Add(new ExchangeRate() { CurrencyPair = replacedPair, Exchange = _ExchangeName });
|
||||
@ -385,13 +437,13 @@ namespace BTCPayServer.Rating
|
||||
else // eg. replace BTC_X to BTC_USD, then replace by the expression for BTC_USD
|
||||
{
|
||||
var bestCandidate = parent.FindBestCandidate(replacedPair);
|
||||
if (Context.Count > MaxNestedCount)
|
||||
if (nested > MaxNestedCount)
|
||||
{
|
||||
Errors.Add(RateRulesErrors.TooMuchNestedCalls);
|
||||
return RateRules.CreateExpression($"ERR_TOO_MUCH_NESTED_CALLS({replacedPair})");
|
||||
}
|
||||
Context.Push(replacedPair);
|
||||
var replaced = Visit(bestCandidate);
|
||||
var innerFlatten = CreateNewContext(replacedPair);
|
||||
var replaced = innerFlatten.Visit(bestCandidate);
|
||||
if (replaced is ExpressionSyntax expression)
|
||||
{
|
||||
var hasBinaryOps = new HasBinaryOperations();
|
||||
@ -401,7 +453,6 @@ namespace BTCPayServer.Rating
|
||||
replaced = SyntaxFactory.ParenthesizedExpression(expression);
|
||||
}
|
||||
}
|
||||
Context.Pop();
|
||||
if (Errors.Contains(RateRulesErrors.TooMuchNestedCalls))
|
||||
{
|
||||
return RateRules.CreateExpression($"ERR_TOO_MUCH_NESTED_CALLS({replacedPair})");
|
||||
@ -411,16 +462,37 @@ namespace BTCPayServer.Rating
|
||||
}
|
||||
return base.VisitIdentifierName(node);
|
||||
}
|
||||
|
||||
private FlattenExpressionRewriter CreateNewContext(CurrencyPair pair)
|
||||
{
|
||||
return new FlattenExpressionRewriter(parent, pair)
|
||||
{
|
||||
Errors = Errors,
|
||||
nested = nested + 1,
|
||||
ExchangeRates = ExchangeRates,
|
||||
};
|
||||
}
|
||||
}
|
||||
private SyntaxNode expression;
|
||||
FlattenExpressionRewriter flatten;
|
||||
|
||||
public RateRule(RateRules parent, CurrencyPair currencyPair, SyntaxNode candidate)
|
||||
{
|
||||
_CurrencyPair = currencyPair;
|
||||
flatten = new FlattenExpressionRewriter(parent, currencyPair);
|
||||
this.expression = flatten.Visit(candidate);
|
||||
}
|
||||
|
||||
|
||||
private readonly CurrencyPair _CurrencyPair;
|
||||
public CurrencyPair CurrencyPair
|
||||
{
|
||||
get
|
||||
{
|
||||
return _CurrencyPair;
|
||||
}
|
||||
}
|
||||
|
||||
public ExchangeRates ExchangeRates
|
||||
{
|
||||
get
|
||||
@ -452,7 +524,7 @@ namespace BTCPayServer.Rating
|
||||
Errors.AddRange(calculate.Errors);
|
||||
return false;
|
||||
}
|
||||
_Value = calculate.Values.Pop();
|
||||
_Value = calculate.Values.Pop().Bid;
|
||||
_EvaluatedNode = result;
|
||||
return true;
|
||||
}
|
||||
|
247
BTCPayServer/Security/BitpayAuthentication.cs
Normal file
247
BTCPayServer/Security/BitpayAuthentication.cs
Normal file
@ -0,0 +1,247 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Authentication;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitpayClient;
|
||||
using NBitpayClient.Extensions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using BTCPayServer.Logging;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BTCPayServer.Security
|
||||
{
|
||||
public class BitpayAuthentication
|
||||
{
|
||||
public class BitpayAuthOptions : AuthenticationSchemeOptions
|
||||
{
|
||||
|
||||
}
|
||||
class BitpayAuthHandler : AuthenticationHandler<BitpayAuthOptions>
|
||||
{
|
||||
StoreRepository _StoreRepository;
|
||||
TokenRepository _TokenRepository;
|
||||
public BitpayAuthHandler(
|
||||
TokenRepository tokenRepository,
|
||||
StoreRepository storeRepository,
|
||||
IOptionsMonitor<BitpayAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
|
||||
{
|
||||
_TokenRepository = tokenRepository;
|
||||
_StoreRepository = storeRepository;
|
||||
}
|
||||
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
if (Context.Request.HttpContext.GetIsBitpayAPI())
|
||||
{
|
||||
List<Claim> claims = new List<Claim>();
|
||||
var bitpayAuth = Context.Request.HttpContext.GetBitpayAuth();
|
||||
string storeId = null;
|
||||
// Careful, those are not the opposite. failedAuth says if a the tentative failed.
|
||||
// successAuth, ensure that at least one succeed.
|
||||
var failedAuth = false;
|
||||
var successAuth = false;
|
||||
if (!string.IsNullOrEmpty(bitpayAuth.Signature) && !string.IsNullOrEmpty(bitpayAuth.Id))
|
||||
{
|
||||
var result = await CheckBitId(Context.Request.HttpContext, bitpayAuth.Signature, bitpayAuth.Id, claims);
|
||||
storeId = result.StoreId;
|
||||
failedAuth = !result.SuccessAuth;
|
||||
successAuth = result.SuccessAuth;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(bitpayAuth.Authorization))
|
||||
{
|
||||
storeId = await CheckLegacyAPIKey(Context.Request.HttpContext, bitpayAuth.Authorization);
|
||||
if (storeId == null)
|
||||
{
|
||||
Logs.PayServer.LogDebug("API key check failed");
|
||||
failedAuth = true;
|
||||
}
|
||||
successAuth = storeId != null;
|
||||
}
|
||||
|
||||
if (failedAuth)
|
||||
{
|
||||
return AuthenticateResult.Fail("Invalid credentials");
|
||||
}
|
||||
|
||||
if (successAuth)
|
||||
{
|
||||
if (storeId != null)
|
||||
{
|
||||
claims.Add(new Claim(Policies.CanUseStore.Key, storeId));
|
||||
var store = await _StoreRepository.FindStore(storeId);
|
||||
Context.Request.HttpContext.SetStoreData(store);
|
||||
}
|
||||
return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, Policies.BitpayAuthentication)), Policies.BitpayAuthentication));
|
||||
}
|
||||
}
|
||||
return AuthenticateResult.NoResult();
|
||||
}
|
||||
|
||||
private async Task<(string StoreId, bool SuccessAuth)> CheckBitId(HttpContext httpContext, string sig, string id, List<Claim> claims)
|
||||
{
|
||||
httpContext.Request.EnableRewind();
|
||||
|
||||
string storeId = null;
|
||||
string body = string.Empty;
|
||||
if (httpContext.Request.ContentLength != 0 && httpContext.Request.Body != null)
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(httpContext.Request.Body, Encoding.UTF8, true, 1024, true))
|
||||
{
|
||||
body = reader.ReadToEnd();
|
||||
}
|
||||
httpContext.Request.Body.Position = 0;
|
||||
}
|
||||
|
||||
var url = httpContext.Request.GetEncodedUrl();
|
||||
try
|
||||
{
|
||||
var key = new PubKey(id);
|
||||
if (BitIdExtensions.CheckBitIDSignature(key, sig, url, body))
|
||||
{
|
||||
var sin = key.GetBitIDSIN();
|
||||
claims.Add(new Claim(Claims.SIN, sin));
|
||||
|
||||
string token = null;
|
||||
if (httpContext.Request.Query.TryGetValue("token", out var tokenValues))
|
||||
{
|
||||
token = tokenValues[0];
|
||||
}
|
||||
|
||||
if (token == null && !String.IsNullOrEmpty(body) && httpContext.Request.Method == "POST")
|
||||
{
|
||||
try
|
||||
{
|
||||
token = JObject.Parse(body)?.Property("token")?.Value?.Value<string>();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (token != null)
|
||||
{
|
||||
var bitToken = await GetTokenPermissionAsync(sin, token);
|
||||
if (bitToken == null)
|
||||
{
|
||||
return (null, false);
|
||||
}
|
||||
storeId = bitToken.StoreId;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (FormatException) { }
|
||||
return (storeId, true);
|
||||
}
|
||||
|
||||
private async Task<string> CheckLegacyAPIKey(HttpContext httpContext, string auth)
|
||||
{
|
||||
var splitted = auth.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (splitted.Length != 2 || !splitted[0].Equals("Basic", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string apiKey = null;
|
||||
try
|
||||
{
|
||||
apiKey = Encoders.ASCII.EncodeData(Encoders.Base64.DecodeData(splitted[1]));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return await _TokenRepository.GetStoreIdFromAPIKey(apiKey);
|
||||
}
|
||||
|
||||
private async Task<BitTokenEntity> GetTokenPermissionAsync(string sin, string expectedToken)
|
||||
{
|
||||
var actualTokens = (await _TokenRepository.GetTokens(sin)).ToArray();
|
||||
actualTokens = actualTokens.SelectMany(t => GetCompatibleTokens(t)).ToArray();
|
||||
|
||||
var actualToken = actualTokens.FirstOrDefault(a => a.Value.Equals(expectedToken, StringComparison.Ordinal));
|
||||
if (expectedToken == null || actualToken == null)
|
||||
{
|
||||
Logs.PayServer.LogDebug($"No token found for facade {Facade.Merchant} for SIN {sin}");
|
||||
return null;
|
||||
}
|
||||
return actualToken;
|
||||
}
|
||||
|
||||
private IEnumerable<BitTokenEntity> GetCompatibleTokens(BitTokenEntity token)
|
||||
{
|
||||
if (token.Facade == Facade.Merchant.ToString())
|
||||
{
|
||||
yield return token.Clone(Facade.User);
|
||||
yield return token.Clone(Facade.PointOfSale);
|
||||
}
|
||||
if (token.Facade == Facade.PointOfSale.ToString())
|
||||
{
|
||||
yield return token.Clone(Facade.User);
|
||||
}
|
||||
yield return token;
|
||||
}
|
||||
|
||||
private bool IsBitpayAPI(HttpContext httpContext, bool bitpayAuth)
|
||||
{
|
||||
if (!httpContext.Request.Path.HasValue)
|
||||
return false;
|
||||
|
||||
var isJson = (httpContext.Request.ContentType ?? string.Empty).StartsWith("application/json", StringComparison.OrdinalIgnoreCase);
|
||||
var path = httpContext.Request.Path.Value;
|
||||
if (
|
||||
bitpayAuth &&
|
||||
path == "/invoices" &&
|
||||
httpContext.Request.Method == "POST" &&
|
||||
isJson)
|
||||
return true;
|
||||
|
||||
if (
|
||||
bitpayAuth &&
|
||||
path == "/invoices" &&
|
||||
httpContext.Request.Method == "GET")
|
||||
return true;
|
||||
|
||||
if (
|
||||
path.StartsWith("/invoices/", StringComparison.OrdinalIgnoreCase) &&
|
||||
httpContext.Request.Method == "GET" &&
|
||||
(isJson || httpContext.Request.Query.ContainsKey("token")))
|
||||
return true;
|
||||
|
||||
if (path.Equals("/rates", StringComparison.OrdinalIgnoreCase) &&
|
||||
httpContext.Request.Method == "GET")
|
||||
return true;
|
||||
|
||||
if (
|
||||
path.Equals("/tokens", StringComparison.Ordinal) &&
|
||||
(httpContext.Request.Method == "GET" || httpContext.Request.Method == "POST"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
internal static void AddAuthentication(IServiceCollection services, Action<BitpayAuthOptions> bitpayAuth = null)
|
||||
{
|
||||
bitpayAuth = bitpayAuth ?? new Action<BitpayAuthOptions>((o) => { });
|
||||
services.AddAuthentication().AddScheme<BitpayAuthOptions, BitpayAuthHandler>(Policies.BitpayAuthentication, bitpayAuth);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,196 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Authentication;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitpayClient;
|
||||
using NBitpayClient.Extensions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using BTCPayServer.Logging;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
|
||||
namespace BTCPayServer.Security
|
||||
{
|
||||
public class BitpayClaimsFilter : IAsyncAuthorizationFilter, IConfigureOptions<MvcOptions>
|
||||
{
|
||||
UserManager<ApplicationUser> _UserManager;
|
||||
StoreRepository _StoreRepository;
|
||||
TokenRepository _TokenRepository;
|
||||
|
||||
public BitpayClaimsFilter(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
TokenRepository tokenRepository,
|
||||
StoreRepository storeRepository)
|
||||
{
|
||||
_UserManager = userManager;
|
||||
_StoreRepository = storeRepository;
|
||||
_TokenRepository = tokenRepository;
|
||||
}
|
||||
|
||||
void IConfigureOptions<MvcOptions>.Configure(MvcOptions options)
|
||||
{
|
||||
options.Filters.Add(typeof(BitpayClaimsFilter));
|
||||
}
|
||||
|
||||
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
|
||||
{
|
||||
var principal = context.HttpContext.User;
|
||||
if (context.HttpContext.GetIsBitpayAPI())
|
||||
{
|
||||
var bitpayAuth = context.HttpContext.GetBitpayAuth();
|
||||
string storeId = null;
|
||||
var failedAuth = false;
|
||||
if (!string.IsNullOrEmpty(bitpayAuth.Signature) && !string.IsNullOrEmpty(bitpayAuth.Id))
|
||||
{
|
||||
storeId = await CheckBitId(context.HttpContext, bitpayAuth.Signature, bitpayAuth.Id);
|
||||
if (!context.HttpContext.User.Claims.Any(c => c.Type == Claims.SIN))
|
||||
{
|
||||
Logs.PayServer.LogDebug("BitId signature check failed");
|
||||
failedAuth = true;
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(bitpayAuth.Authorization))
|
||||
{
|
||||
storeId = await CheckLegacyAPIKey(context.HttpContext, bitpayAuth.Authorization);
|
||||
if (storeId == null)
|
||||
{
|
||||
Logs.PayServer.LogDebug("API key check failed");
|
||||
failedAuth = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (storeId != null)
|
||||
{
|
||||
var identity = ((ClaimsIdentity)context.HttpContext.User.Identity);
|
||||
identity.AddClaim(new Claim(Policies.CanUseStore.Key, storeId));
|
||||
var store = await _StoreRepository.FindStore(storeId);
|
||||
context.HttpContext.SetStoreData(store);
|
||||
}
|
||||
else if (failedAuth)
|
||||
{
|
||||
throw new BitpayHttpException(401, "Invalid credentials");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> CheckBitId(HttpContext httpContext, string sig, string id)
|
||||
{
|
||||
httpContext.Request.EnableRewind();
|
||||
|
||||
string storeId = null;
|
||||
string body = string.Empty;
|
||||
if (httpContext.Request.ContentLength != 0 && httpContext.Request.Body != null)
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(httpContext.Request.Body, Encoding.UTF8, true, 1024, true))
|
||||
{
|
||||
body = reader.ReadToEnd();
|
||||
}
|
||||
httpContext.Request.Body.Position = 0;
|
||||
}
|
||||
|
||||
var url = httpContext.Request.GetEncodedUrl();
|
||||
try
|
||||
{
|
||||
var key = new PubKey(id);
|
||||
if (BitIdExtensions.CheckBitIDSignature(key, sig, url, body))
|
||||
{
|
||||
var sin = key.GetBitIDSIN();
|
||||
var identity = ((ClaimsIdentity)httpContext.User.Identity);
|
||||
identity.AddClaim(new Claim(Claims.SIN, sin));
|
||||
|
||||
string token = null;
|
||||
if (httpContext.Request.Query.TryGetValue("token", out var tokenValues))
|
||||
{
|
||||
token = tokenValues[0];
|
||||
}
|
||||
|
||||
if (token == null && !String.IsNullOrEmpty(body) && httpContext.Request.Method == "POST")
|
||||
{
|
||||
try
|
||||
{
|
||||
token = JObject.Parse(body)?.Property("token")?.Value?.Value<string>();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (token != null)
|
||||
{
|
||||
var bitToken = await GetTokenPermissionAsync(sin, token);
|
||||
if (bitToken == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
storeId = bitToken.StoreId;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (FormatException) { }
|
||||
return storeId;
|
||||
}
|
||||
|
||||
private async Task<string> CheckLegacyAPIKey(HttpContext httpContext, string auth)
|
||||
{
|
||||
var splitted = auth.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (splitted.Length != 2 || !splitted[0].Equals("Basic", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string apiKey = null;
|
||||
try
|
||||
{
|
||||
apiKey = Encoders.ASCII.EncodeData(Encoders.Base64.DecodeData(splitted[1]));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return await _TokenRepository.GetStoreIdFromAPIKey(apiKey);
|
||||
}
|
||||
|
||||
private async Task<BitTokenEntity> GetTokenPermissionAsync(string sin, string expectedToken)
|
||||
{
|
||||
var actualTokens = (await _TokenRepository.GetTokens(sin)).ToArray();
|
||||
actualTokens = actualTokens.SelectMany(t => GetCompatibleTokens(t)).ToArray();
|
||||
|
||||
var actualToken = actualTokens.FirstOrDefault(a => a.Value.Equals(expectedToken, StringComparison.Ordinal));
|
||||
if (expectedToken == null || actualToken == null)
|
||||
{
|
||||
Logs.PayServer.LogDebug($"No token found for facade {Facade.Merchant} for SIN {sin}");
|
||||
return null;
|
||||
}
|
||||
return actualToken;
|
||||
}
|
||||
|
||||
private IEnumerable<BitTokenEntity> GetCompatibleTokens(BitTokenEntity token)
|
||||
{
|
||||
if (token.Facade == Facade.Merchant.ToString())
|
||||
{
|
||||
yield return token.Clone(Facade.User);
|
||||
yield return token.Clone(Facade.PointOfSale);
|
||||
}
|
||||
if (token.Facade == Facade.PointOfSale.ToString())
|
||||
{
|
||||
yield return token.Clone(Facade.User);
|
||||
}
|
||||
yield return token;
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ namespace BTCPayServer.Security
|
||||
{
|
||||
public static class Policies
|
||||
{
|
||||
public const string BitpayAuthentication = "Bitpay.Auth";
|
||||
public const string CookieAuthentication = "Identity.Application";
|
||||
public static AuthorizationOptions AddBTCPayPolicies(this AuthorizationOptions options)
|
||||
{
|
||||
|
@ -12,6 +12,8 @@ using NBXplorer.Models;
|
||||
using NBXplorer;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using BTCPayServer.Payments;
|
||||
using NBitpayClient;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
|
||||
namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
@ -336,19 +338,34 @@ namespace BTCPayServer.Services.Invoices
|
||||
ExpirationTime = ExpirationTime,
|
||||
Status = Status,
|
||||
Currency = ProductInformation.Currency,
|
||||
Flags = new Flags() { Refundable = Refundable }
|
||||
Flags = new Flags() { Refundable = Refundable },
|
||||
PaymentSubtotals = new Dictionary<string, long>(),
|
||||
PaymentTotals = new Dictionary<string, long>(),
|
||||
SupportedTransactionCurrencies = new Dictionary<string, InvoiceSupportedTransactionCurrency>(),
|
||||
Addresses = new Dictionary<string, string>(),
|
||||
PaymentCodes = new Dictionary<string, InvoicePaymentUrls>(),
|
||||
ExchangeRates = new Dictionary<string, Dictionary<string, decimal>>()
|
||||
};
|
||||
|
||||
dto.Url = ServerUrl.WithTrailingSlash() + $"invoice?id=" + Id;
|
||||
dto.CryptoInfo = new List<NBitpayClient.InvoiceCryptoInfo>();
|
||||
dto.MinerFees = new Dictionary<string, MinerFeeInfo>();
|
||||
foreach (var info in this.GetPaymentMethods(networkProvider))
|
||||
{
|
||||
var accounting = info.Calculate();
|
||||
var cryptoInfo = new NBitpayClient.InvoiceCryptoInfo();
|
||||
cryptoInfo.CryptoCode = info.GetId().CryptoCode;
|
||||
var subtotalPrice = accounting.TotalDue - accounting.NetworkFee;
|
||||
var cryptoCode = info.GetId().CryptoCode;
|
||||
var address = info.GetPaymentMethodDetails()?.GetPaymentDestination();
|
||||
var exrates = new Dictionary<string, decimal>
|
||||
{
|
||||
{ ProductInformation.Currency, cryptoInfo.Rate }
|
||||
};
|
||||
|
||||
cryptoInfo.CryptoCode = cryptoCode;
|
||||
cryptoInfo.PaymentType = info.GetId().PaymentType.ToString();
|
||||
cryptoInfo.Rate = info.Rate;
|
||||
cryptoInfo.Price = (accounting.TotalDue - accounting.NetworkFee).ToString();
|
||||
cryptoInfo.Price = subtotalPrice.ToString();
|
||||
|
||||
cryptoInfo.Due = accounting.Due.ToString();
|
||||
cryptoInfo.Paid = accounting.Paid.ToString();
|
||||
@ -357,17 +374,19 @@ namespace BTCPayServer.Services.Invoices
|
||||
cryptoInfo.TxCount = accounting.TxCount;
|
||||
cryptoInfo.CryptoPaid = accounting.CryptoPaid.ToString();
|
||||
|
||||
cryptoInfo.Address = info.GetPaymentMethodDetails()?.GetPaymentDestination();
|
||||
cryptoInfo.ExRates = new Dictionary<string, decimal>
|
||||
{
|
||||
{ ProductInformation.Currency, cryptoInfo.Rate }
|
||||
};
|
||||
cryptoInfo.Address = address;
|
||||
|
||||
cryptoInfo.ExRates = exrates;
|
||||
var paymentId = info.GetId();
|
||||
var scheme = info.Network.UriScheme;
|
||||
cryptoInfo.Url = ServerUrl.WithTrailingSlash() + $"i/{paymentId}/{Id}";
|
||||
|
||||
if (paymentId.PaymentType == PaymentTypes.BTCLike)
|
||||
{
|
||||
var minerInfo = new MinerFeeInfo();
|
||||
minerInfo.TotalFee = accounting.NetworkFee.Satoshi;
|
||||
minerInfo.SatoshiPerBytes = ((BitcoinLikeOnChainPaymentMethod)info.GetPaymentMethodDetails()).FeeRate.GetFee(1).Satoshi;
|
||||
dto.MinerFees.TryAdd(paymentId.CryptoCode, minerInfo);
|
||||
var cryptoSuffix = cryptoInfo.CryptoCode == "BTC" ? "" : "/" + cryptoInfo.CryptoCode;
|
||||
cryptoInfo.PaymentUrls = new NBitpayClient.InvoicePaymentUrls()
|
||||
{
|
||||
@ -377,7 +396,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
BIP21 = $"{scheme}:{cryptoInfo.Address}?amount={cryptoInfo.Due}",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (paymentId.PaymentType == PaymentTypes.LightningLike)
|
||||
{
|
||||
cryptoInfo.PaymentUrls = new NBitpayClient.InvoicePaymentUrls()
|
||||
@ -396,10 +415,23 @@ namespace BTCPayServer.Services.Invoices
|
||||
dto.BTCDue = cryptoInfo.Due;
|
||||
dto.PaymentUrls = cryptoInfo.PaymentUrls;
|
||||
}
|
||||
|
||||
#pragma warning restore CS0618
|
||||
dto.CryptoInfo.Add(cryptoInfo);
|
||||
|
||||
dto.PaymentCodes.Add(paymentId.ToString(), cryptoInfo.PaymentUrls);
|
||||
dto.PaymentSubtotals.Add(paymentId.ToString(), subtotalPrice.Satoshi);
|
||||
dto.PaymentTotals.Add(paymentId.ToString(), accounting.TotalDue.Satoshi);
|
||||
dto.SupportedTransactionCurrencies.TryAdd(cryptoCode, new InvoiceSupportedTransactionCurrency()
|
||||
{
|
||||
Enabled = true
|
||||
});
|
||||
dto.Addresses.Add(paymentId.ToString(), address);
|
||||
dto.ExchangeRates.TryAdd(cryptoCode, exrates);
|
||||
}
|
||||
|
||||
//dto.AmountPaid dto.MinerFees & dto.TransactionCurrency are not supported by btcpayserver as we have multi currency payment support per invoice
|
||||
|
||||
Populate(ProductInformation, dto);
|
||||
Populate(BuyerInformation, dto);
|
||||
|
||||
|
@ -112,7 +112,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
invoice.StoreId = storeId;
|
||||
using (var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
context.Invoices.Add(new InvoiceData()
|
||||
context.Invoices.Add(new Data.InvoiceData()
|
||||
{
|
||||
StoreDataId = storeId,
|
||||
Id = invoice.Id,
|
||||
@ -267,7 +267,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
using (var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
var invoiceData = await context.FindAsync<InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||
if (invoiceData == null)
|
||||
return;
|
||||
var invoiceEntity = ToObject<InvoiceEntity>(invoiceData.Blob, null);
|
||||
@ -307,7 +307,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
using (var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
var invoiceData = await context.FindAsync<InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||
if (invoiceData == null)
|
||||
return;
|
||||
invoiceData.Status = status;
|
||||
@ -320,7 +320,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
using (var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
var invoiceData = await context.FindAsync<InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||
if (invoiceData?.Status != "paid")
|
||||
return;
|
||||
invoiceData.Status = "invalid";
|
||||
@ -331,7 +331,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
using (var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
IQueryable<InvoiceData> query =
|
||||
IQueryable<Data.InvoiceData> query =
|
||||
context
|
||||
.Invoices
|
||||
.Include(o => o.Payments)
|
||||
@ -351,7 +351,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
}
|
||||
|
||||
private InvoiceEntity ToEntity(InvoiceData invoice)
|
||||
private InvoiceEntity ToEntity(Data.InvoiceData invoice)
|
||||
{
|
||||
var entity = ToObject<InvoiceEntity>(invoice.Blob, null);
|
||||
#pragma warning disable CS0618
|
||||
@ -386,7 +386,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
using (var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
IQueryable<InvoiceData> query = context
|
||||
IQueryable<Data.InvoiceData> query = context
|
||||
.Invoices
|
||||
.Include(o => o.Payments)
|
||||
.Include(o => o.RefundAddresses);
|
||||
|
@ -55,7 +55,7 @@ namespace BTCPayServer.Services.Mails
|
||||
public SmtpClient CreateSmtpClient()
|
||||
{
|
||||
SmtpClient client = new SmtpClient(Server, Port.Value);
|
||||
client.EnableSsl = true;
|
||||
client.EnableSsl = EnableSSL;
|
||||
client.UseDefaultCredentials = false;
|
||||
client.Credentials = new NetworkCredential(Login, Password);
|
||||
client.DeliveryMethod = SmtpDeliveryMethod.Network;
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Rating;
|
||||
using ExchangeSharp;
|
||||
@ -35,7 +36,6 @@ namespace BTCPayServer.Services.Rates
|
||||
}
|
||||
IMemoryCache _Cache;
|
||||
private IOptions<MemoryCacheOptions> _CacheOptions;
|
||||
|
||||
public IMemoryCache Cache
|
||||
{
|
||||
get
|
||||
@ -68,10 +68,12 @@ namespace BTCPayServer.Services.Rates
|
||||
DirectProviders.Add("bittrex", new ExchangeSharpRateProvider("bittrex", new ExchangeBittrexAPI(), true));
|
||||
DirectProviders.Add("poloniex", new ExchangeSharpRateProvider("poloniex", new ExchangePoloniexAPI(), false));
|
||||
DirectProviders.Add("hitbtc", new ExchangeSharpRateProvider("hitbtc", new ExchangeHitbtcAPI(), false));
|
||||
DirectProviders.Add("cryptopia", new ExchangeSharpRateProvider("cryptopia", new ExchangeCryptopiaAPI(), false));
|
||||
|
||||
// Handmade providers
|
||||
DirectProviders.Add("bitpay", new BitpayRateProvider(new NBitpayClient.Bitpay(new NBitcoin.Key(), new Uri("https://bitpay.com/"))));
|
||||
DirectProviders.Add(QuadrigacxRateProvider.QuadrigacxName, new QuadrigacxRateProvider());
|
||||
DirectProviders.Add(CoinAverageRateProvider.CoinAverageName, new CoinAverageRateProvider() { Exchange = CoinAverageRateProvider.CoinAverageName, Authenticator = _CoinAverageSettings });
|
||||
|
||||
// Those exchanges make multiple requests when calling GetTickers so we remove them
|
||||
//DirectProviders.Add("kraken", new ExchangeSharpRateProvider("kraken", new ExchangeKrakenAPI(), true));
|
||||
@ -82,6 +84,20 @@ namespace BTCPayServer.Services.Rates
|
||||
//DirectProviders.Add("bitstamp", new ExchangeSharpRateProvider("bitstamp", new ExchangeBitstampAPI()));
|
||||
}
|
||||
|
||||
public CoinAverageExchanges GetSupportedExchanges()
|
||||
{
|
||||
CoinAverageExchanges exchanges = new CoinAverageExchanges();
|
||||
foreach (var exchange in _CoinAverageSettings.AvailableExchanges)
|
||||
{
|
||||
exchanges.Add(exchange.Value);
|
||||
}
|
||||
|
||||
// Add other exchanges supported here
|
||||
exchanges.Add(new CoinAverageExchange(CoinAverageRateProvider.CoinAverageName, "Coin Average"));
|
||||
exchanges.Add(new CoinAverageExchange("cryptopia", "Cryptopia"));
|
||||
|
||||
return exchanges;
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, IRateProvider> _DirectProviders = new Dictionary<string, IRateProvider>();
|
||||
public Dictionary<string, IRateProvider> DirectProviders
|
||||
|
@ -24,7 +24,7 @@ namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
return new ExchangeRates((await _Bitpay.GetRatesAsync().ConfigureAwait(false))
|
||||
.AllRates
|
||||
.Select(r => new ExchangeRate() { Exchange = BitpayName, CurrencyPair = new CurrencyPair("BTC", r.Code), Value = r.Value })
|
||||
.Select(r => new ExchangeRate() { Exchange = BitpayName, CurrencyPair = new CurrencyPair("BTC", r.Code), BidAsk = new BidAsk(r.Value) })
|
||||
.ToList());
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ namespace BTCPayServer.Services.Rates
|
||||
public const string CoinAverageName = "coinaverage";
|
||||
public CoinAverageRateProvider()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
static HttpClient _Client = new HttpClient();
|
||||
|
||||
@ -69,10 +69,31 @@ namespace BTCPayServer.Services.Rates
|
||||
|
||||
public ICoinAverageAuthenticator Authenticator { get; set; }
|
||||
|
||||
private bool TryToDecimal(JProperty p, out decimal v)
|
||||
private bool TryToBidAsk(JProperty p, out BidAsk bidAsk)
|
||||
{
|
||||
JToken token = p.Value[Exchange == CoinAverageName ? "last" : "bid"];
|
||||
return decimal.TryParse(token.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out v);
|
||||
bidAsk = null;
|
||||
if (Exchange == CoinAverageName)
|
||||
{
|
||||
JToken last = p.Value["last"];
|
||||
if (!decimal.TryParse(last.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v) ||
|
||||
v <= 0)
|
||||
return false;
|
||||
bidAsk = new BidAsk(v);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
JToken bid = p.Value["bid"];
|
||||
JToken ask = p.Value["ask"];
|
||||
if (bid == null || ask == null ||
|
||||
!decimal.TryParse(bid.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v1) ||
|
||||
!decimal.TryParse(ask.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v2) ||
|
||||
v1 > v2 ||
|
||||
v1 <= 0 || v2 <= 0)
|
||||
return false;
|
||||
bidAsk = new BidAsk(v1, v2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ExchangeRates> GetRatesAsync()
|
||||
@ -108,10 +129,10 @@ namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
ExchangeRate exchangeRate = new ExchangeRate();
|
||||
exchangeRate.Exchange = Exchange;
|
||||
if (!TryToDecimal(prop, out decimal value))
|
||||
if (!TryToBidAsk(prop, out var value))
|
||||
continue;
|
||||
exchangeRate.Value = value;
|
||||
if(CurrencyPair.TryParse(prop.Name, out var pair))
|
||||
exchangeRate.BidAsk = value;
|
||||
if (CurrencyPair.TryParse(prop.Name, out var pair))
|
||||
{
|
||||
exchangeRate.CurrencyPair = pair;
|
||||
exchangeRates.Add(exchangeRate);
|
||||
|
@ -43,12 +43,11 @@ namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
public CoinAverageExchanges()
|
||||
{
|
||||
Add(new CoinAverageExchange(CoinAverageRateProvider.CoinAverageName, "Coin Average"));
|
||||
}
|
||||
|
||||
public void Add(CoinAverageExchange exchange)
|
||||
{
|
||||
Add(exchange.Name, exchange);
|
||||
TryAdd(exchange.Name, exchange);
|
||||
}
|
||||
}
|
||||
public class CoinAverageSettings : ICoinAverageAuthenticator
|
||||
|
@ -31,6 +31,7 @@ namespace BTCPayServer.Services.Rates
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
public bool Crypto { get; set; }
|
||||
}
|
||||
public class CurrencyNameTable
|
||||
{
|
||||
@ -40,6 +41,27 @@ namespace BTCPayServer.Services.Rates
|
||||
}
|
||||
|
||||
static Dictionary<string, IFormatProvider> _CurrencyProviders = new Dictionary<string, IFormatProvider>();
|
||||
|
||||
public NumberFormatInfo GetNumberFormatInfo(string currency, bool useFallback)
|
||||
{
|
||||
var data = GetCurrencyProvider(currency);
|
||||
if (data is NumberFormatInfo nfi)
|
||||
return nfi;
|
||||
if (data is CultureInfo ci)
|
||||
return ci.NumberFormat;
|
||||
if (!useFallback)
|
||||
return null;
|
||||
return CreateFallbackCurrencyFormatInfo(currency);
|
||||
}
|
||||
|
||||
private NumberFormatInfo CreateFallbackCurrencyFormatInfo(string currency)
|
||||
{
|
||||
var usd = GetNumberFormatInfo("USD", false);
|
||||
var currencyInfo = (NumberFormatInfo)usd.Clone();
|
||||
currencyInfo.CurrencySymbol = currency;
|
||||
return currencyInfo;
|
||||
}
|
||||
|
||||
public IFormatProvider GetCurrencyProvider(string currency)
|
||||
{
|
||||
lock (_CurrencyProviders)
|
||||
@ -54,7 +76,11 @@ namespace BTCPayServer.Services.Rates
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
AddCurrency(_CurrencyProviders, "BTC", 8, "BTC");
|
||||
|
||||
foreach (var network in new BTCPayNetworkProvider(NetworkType.Mainnet).GetAll())
|
||||
{
|
||||
AddCurrency(_CurrencyProviders, network.CryptoCode, 8, network.CryptoCode);
|
||||
}
|
||||
}
|
||||
return _CurrencyProviders.TryGet(currency);
|
||||
}
|
||||
@ -106,13 +132,38 @@ namespace BTCPayServer.Services.Rates
|
||||
info.Symbol = splitted[3];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var network in new BTCPayNetworkProvider(NetworkType.Mainnet).GetAll())
|
||||
{
|
||||
dico.TryAdd(network.CryptoCode, new CurrencyData()
|
||||
{
|
||||
Code = network.CryptoCode,
|
||||
Divisibility = 8,
|
||||
Name = network.CryptoCode,
|
||||
Crypto = true
|
||||
});
|
||||
}
|
||||
|
||||
return dico.Values.ToArray();
|
||||
}
|
||||
|
||||
public CurrencyData GetCurrencyData(string currency)
|
||||
public CurrencyData GetCurrencyData(string currency, bool useFallback)
|
||||
{
|
||||
CurrencyData result;
|
||||
_Currencies.TryGetValue(currency.ToUpperInvariant(), out result);
|
||||
if(!_Currencies.TryGetValue(currency.ToUpperInvariant(), out result))
|
||||
{
|
||||
if(useFallback)
|
||||
{
|
||||
var usd = GetCurrencyData("USD", false);
|
||||
result = new CurrencyData()
|
||||
{
|
||||
Code = currency,
|
||||
Crypto = true,
|
||||
Name = currency,
|
||||
Divisibility = usd.Divisibility
|
||||
};
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ namespace BTCPayServer.Services.Rates
|
||||
var rate = new ExchangeRate();
|
||||
rate.CurrencyPair = pair;
|
||||
rate.Exchange = _ExchangeName;
|
||||
rate.Value = ticker.Value.Bid;
|
||||
rate.BidAsk = new BidAsk(ticker.Value.Bid, ticker.Value.Ask);
|
||||
return rate;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
|
@ -14,13 +14,19 @@ namespace BTCPayServer.Services.Rates
|
||||
public const string QuadrigacxName = "quadrigacx";
|
||||
static HttpClient _Client = new HttpClient();
|
||||
|
||||
private bool TryToDecimal(JObject p, out decimal v)
|
||||
private bool TryToBidAsk(JObject p, out BidAsk v)
|
||||
{
|
||||
v = 0.0m;
|
||||
JToken token = p.Property("bid")?.Value;
|
||||
if (token == null)
|
||||
v = null;
|
||||
JToken bid = p.Property("bid")?.Value;
|
||||
JToken ask = p.Property("ask")?.Value;
|
||||
if (bid == null || ask == null)
|
||||
return false;
|
||||
return decimal.TryParse(token.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out v);
|
||||
if (!decimal.TryParse(bid.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v1) ||
|
||||
!decimal.TryParse(bid.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v2) ||
|
||||
v1 <= 0m || v2 <= 0m || v1 > v2)
|
||||
return false;
|
||||
v = new BidAsk(v1, v2);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<ExchangeRates> GetRatesAsync()
|
||||
@ -37,9 +43,9 @@ namespace BTCPayServer.Services.Rates
|
||||
continue;
|
||||
rate.CurrencyPair = pair;
|
||||
rate.Exchange = QuadrigacxName;
|
||||
if (!TryToDecimal((JObject)prop.Value, out var v))
|
||||
if (!TryToBidAsk((JObject)prop.Value, out var v))
|
||||
continue;
|
||||
rate.Value = v;
|
||||
rate.BidAsk = v;
|
||||
exchangeRates.Add(rate);
|
||||
}
|
||||
return exchangeRates;
|
||||
|
23
BTCPayServer/Validation/UriAttribute.cs
Normal file
23
BTCPayServer/Validation/UriAttribute.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
|
||||
namespace BTCPayServer.Validation
|
||||
{
|
||||
//from https://stackoverflow.com/a/47196738/275504
|
||||
public class UriAttribute : ValidationAttribute
|
||||
{
|
||||
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
|
||||
{
|
||||
var str = value == null ? null : Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
Uri uri;
|
||||
bool valid = string.IsNullOrWhiteSpace(str) || Uri.TryCreate(str, UriKind.Absolute, out uri);
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
return new ValidationResult(ErrorMessage);
|
||||
}
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<partial name="_StatusMessage" for="@("Thank you for confirming your email.")" />
|
||||
<partial name="_StatusMessage" model="@("Thank you for confirming your email.")" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -38,6 +38,31 @@
|
||||
<textarea asp-for="Template" rows="20" cols="40" class="form-control"></textarea>
|
||||
<span asp-validation-for="Template" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<h5>Host button externally</h5>
|
||||
<p>You can host point of sale buttons in an external website with the following code.</p>
|
||||
@if(Model.Example1 != null)
|
||||
{
|
||||
<span>For anything with a custom amount</span>
|
||||
<pre><code class="html">@Model.Example1</code></pre>
|
||||
}
|
||||
@if(Model.Example2 != null)
|
||||
{
|
||||
<span>For a specific item of your template</span>
|
||||
<pre><code class="html">@Model.Example2</code></pre>
|
||||
}
|
||||
<p>A <code>POST</code> callback will be sent to notification with the following form will be sent to <code>notificationUrl</code> once the enough is paid and once again once there is enough confirmations to the payment:</p>
|
||||
<pre><code class="json">@Model.ExampleCallback</code></pre>
|
||||
<p><strong>Never</strong> trust anything but <code>id</code>, <strong>ignore</strong> the other fields completely, an attacker can spoof those, they are present only for backward compatibility reason:</p>
|
||||
<p>
|
||||
<ul>
|
||||
<li><strong>Build the invoice's url by yourself</strong> do not trust the <code>url</code> field, this can be spoofed to use attacker's server.</li>
|
||||
<li>Send a <code>GET</code> request to the invoice's url with <code>Content-Type: application/json</code></li>
|
||||
<li>Verify that the <code>orderId</code> is from your backend, that the <code>price</code> is correct and that <code>status</code> is either <code>confirmed</code> or <code>complete</code></li>
|
||||
<li>You can then ship your order</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" class="btn btn-primary" />
|
||||
</div>
|
||||
@ -47,3 +72,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@section Scripts {
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
|
||||
<script>hljs.initHighlightingOnLoad();</script>
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,7 @@
|
||||
<span>{{ srvModel.btcDue }} {{ srvModel.cryptoCode }}</span>
|
||||
</div>
|
||||
|
||||
<div class="single-item-order__right__ex-rate">
|
||||
<div class="single-item-order__right__ex-rate" v-if="srvModel.orderAmountFiat">
|
||||
1 {{ srvModel.cryptoCode }} = {{ srvModel.rate }}
|
||||
</div>
|
||||
</div>
|
||||
@ -87,6 +87,12 @@
|
||||
<div class="line-items__item__label">{{$t("Order Amount")}}</div>
|
||||
<div class="line-items__item__value">{{srvModel.orderAmount}} {{ srvModel.cryptoCode }}</div>
|
||||
</div>
|
||||
<div class="line-items__item line-items_fiatvalue" v-if="srvModel.orderAmountFiat">
|
||||
<div class="line-items__item__label"> </div>
|
||||
<div class="line-items__item__value single-item-order__right__ex-rate">
|
||||
{{srvModel.orderAmountFiat}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="line-items__item">
|
||||
<div class="line-items__item__label">
|
||||
<span>{{$t("Network Cost")}}</span>
|
||||
@ -133,7 +139,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div adjust-height="" class="payment-box">
|
||||
<div class="payment-box">
|
||||
<div class="bp-view payment manual-flow enter-contact-email active" id="emailAddressView">
|
||||
<form class="manual__step-one refund-address-form contact-email-form" id="emailAddressForm" name="emailAddressForm" novalidate="">
|
||||
<div class="manual__step-one__header">
|
||||
|
@ -53,15 +53,15 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Created date</th>
|
||||
<td>@Model.CreatedDate</td>
|
||||
<td>@Model.CreatedDate.ToBrowserDate()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Expiration date</th>
|
||||
<td>@Model.ExpirationDate</td>
|
||||
<td>@Model.ExpirationDate.ToBrowserDate()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Monitoring date</th>
|
||||
<td>@Model.MonitoringDate</td>
|
||||
<td>@Model.MonitoringDate.ToBrowserDate()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Transaction speed</th>
|
||||
@ -289,7 +289,7 @@
|
||||
@foreach(var evt in Model.Events)
|
||||
{
|
||||
<tr>
|
||||
<td>@evt.Timestamp</td>
|
||||
<td>@evt.Timestamp.ToBrowserDate()</td>
|
||||
<td>@evt.Message</td>
|
||||
</tr>
|
||||
}
|
||||
|
@ -66,7 +66,7 @@
|
||||
@foreach(var invoice in Model.Invoices)
|
||||
{
|
||||
<tr>
|
||||
<td>@invoice.Date</td>
|
||||
<td>@invoice.Date.ToTimeAgo()</td>
|
||||
<td>
|
||||
@if(invoice.RedirectUrl != string.Empty)
|
||||
{
|
||||
|
@ -40,7 +40,7 @@
|
||||
<table class="table table-sm">
|
||||
<tr>
|
||||
<th>Quota period</th>
|
||||
<td>@Model.RateLimits.TotalPeriod.Prettify()</td>
|
||||
<td>@Model.RateLimits.TotalPeriod.TimeString()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Requests quota</th>
|
||||
@ -48,7 +48,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Quota reset in</th>
|
||||
<td>@Model.RateLimits.CounterReset.Prettify()</td>
|
||||
<td>@Model.RateLimits.CounterReset.TimeString()</td>
|
||||
</tr>
|
||||
</table>
|
||||
}
|
||||
|
22
BTCPayServer/Views/Shared/Error.cshtml
Normal file
22
BTCPayServer/Views/Shared/Error.cshtml
Normal file
@ -0,0 +1,22 @@
|
||||
@model ErrorViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Error";
|
||||
}
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if(Model != null && Model.ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Development environment should not be enabled in deployed applications</strong>, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>, and restarting the application.
|
||||
</p>
|
@ -10,8 +10,8 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Some of your nodes are still synching...<br />
|
||||
BTCPay Server will not work correctly until it is over.
|
||||
Some of your nodes are still synchronizing...<br />
|
||||
BTCPay Server will not create invoices with those cryptocurrencies.
|
||||
</p>
|
||||
@foreach (var line in dashboard.GetAll())
|
||||
{
|
||||
@ -47,12 +47,12 @@
|
||||
}
|
||||
else if (line.Status.BitcoinStatus.IsSynched)
|
||||
{
|
||||
<li>The node is synched (Height: @line.Status.BitcoinStatus.Headers)</li>
|
||||
<li>The node is synchronized (Height: @line.Status.BitcoinStatus.Headers)</li>
|
||||
@if (line.Status.BitcoinStatus.IsSynched &&
|
||||
line.Status.SyncHeight.HasValue &&
|
||||
line.Status.SyncHeight.Value < line.Status.BitcoinStatus.Headers)
|
||||
{
|
||||
<li>NBXplorer is synching... (Height: @line.Status.SyncHeight.Value)</li>
|
||||
<li>NBXplorer is synchronizing... (Height: @line.Status.SyncHeight.Value)</li>
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -16,7 +16,7 @@
|
||||
<div class="alert alert-warning alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<p>
|
||||
<span>A connection to a lightning charge node or clightning unix socket is required to generate lignting network enabled invoices. <br /></span>
|
||||
<span>A connection to a lightning charge node or clightning unix socket is required to generate lightning network enabled invoices. <br /></span>
|
||||
<span>This is experimental and not advised for production so keep in mind:</span>
|
||||
</p>
|
||||
<ul>
|
||||
|
@ -96,7 +96,7 @@
|
||||
<div class="form-group">
|
||||
<h5>Lightning nodes (Experimental)</h5>
|
||||
<p>
|
||||
<span>A connection to a lightning charge node is required to generate lignting network enabled invoices.<br /></span>
|
||||
<span>A connection to a lightning charge node is required to generate lightning network enabled invoices.<br /></span>
|
||||
<span>This is experimental and not advised for production.</span>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -17,7 +17,7 @@
|
||||
If your Ledger wallet is not detected:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Make sure you are running the Ledger Bitcoin or Litecoin app with version superior or equal to 1.2.4</li>
|
||||
<li>Make sure you are running the Ledger 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="fa fa-question-circle" style="color:orange"></span> <span>Detecting hardware wallet...</span></p>
|
||||
|
@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
|
||||
namespace BTCPayServer.Views
|
||||
@ -22,5 +24,39 @@ namespace BTCPayServer.Views
|
||||
var activePage = (T)viewData[ACTIVE_PAGE_KEY];
|
||||
return page.Equals(activePage) ? "active" : null;
|
||||
}
|
||||
|
||||
public static HtmlString ToBrowserDate(this DateTimeOffset date)
|
||||
{
|
||||
var hello = date.ToString("o", CultureInfo.InvariantCulture);
|
||||
return new HtmlString($"<span class='localizeDate'>{hello}</span>");
|
||||
}
|
||||
|
||||
public static string ToTimeAgo(this DateTimeOffset date)
|
||||
{
|
||||
var formatted = (DateTimeOffset.UtcNow - date).TimeString() + " ago";
|
||||
return formatted;
|
||||
}
|
||||
|
||||
public static string TimeString(this TimeSpan timeSpan)
|
||||
{
|
||||
if (timeSpan.TotalMinutes < 1)
|
||||
{
|
||||
return $"{(int)timeSpan.TotalSeconds} second{Plural((int)timeSpan.TotalSeconds)}";
|
||||
}
|
||||
if (timeSpan.TotalHours < 1)
|
||||
{
|
||||
return $"{(int)timeSpan.TotalMinutes} minute{Plural((int)timeSpan.TotalMinutes)}";
|
||||
}
|
||||
if (timeSpan.Days < 1)
|
||||
{
|
||||
return $"{(int)timeSpan.TotalHours} hour{Plural((int)timeSpan.TotalHours)}";
|
||||
}
|
||||
return $"{(int)timeSpan.TotalDays} day{Plural((int)timeSpan.TotalDays)}";
|
||||
}
|
||||
|
||||
private static string Plural(int totalDays)
|
||||
{
|
||||
return totalDays > 1 ? "s" : string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,8 @@
|
||||
"wwwroot/vendor/jquery-easing/jquery.easing.js",
|
||||
"wwwroot/vendor/scrollreveal/scrollreveal.min.js",
|
||||
"wwwroot/vendor/magnific-popup/jquery.magnific-popup.js",
|
||||
"wwwroot/vendor/bootstrap4-creativestart/*.js"
|
||||
"wwwroot/vendor/bootstrap4-creativestart/*.js",
|
||||
"wwwroot/main/**/*.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -10328,6 +10328,7 @@ All mobile class names should be prefixed by m- */
|
||||
.wrong-email .payment-tabs {
|
||||
pointer-events: none;
|
||||
margin-top: -2.95rem;
|
||||
z-index: -1;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@ -10412,10 +10413,6 @@ All mobile class names should be prefixed by m- */
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
.payment-tabs {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.single-item-order {
|
||||
z-index: 2;
|
||||
}
|
||||
@ -11146,31 +11143,13 @@ language-selector {
|
||||
|
||||
line-items {
|
||||
background: #FBFBFB;
|
||||
height: 25px;
|
||||
border-top: 0;
|
||||
border-top: 1px solid rgba(238, 238, 238, 0.5);
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
transition: height 250ms ease;
|
||||
display: none;
|
||||
}
|
||||
|
||||
line-items.expanded {
|
||||
height: 120px;
|
||||
border-top: 1px solid rgba(238, 238, 238, 0.5);
|
||||
}
|
||||
|
||||
line-items.expanded.paid-over {
|
||||
height: 295px;
|
||||
}
|
||||
|
||||
line-items.expanded.paid-partial-expired, line-items.expanded.paid-full {
|
||||
height: 272px;
|
||||
}
|
||||
|
||||
line-items .line-items {
|
||||
padding: 1rem;
|
||||
padding: 10px 1rem;
|
||||
color: #565D6E;
|
||||
}
|
||||
|
||||
@ -11198,6 +11177,10 @@ line-items {
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
line-items .line-items_fiatvalue {
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
line-items .line-items__item__label {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
|
@ -237,8 +237,12 @@ $(document).ready(function () {
|
||||
});
|
||||
|
||||
// Expand Line-Items
|
||||
var lineItemsExpanded = false;
|
||||
$(".buyerTotalLine").click(function () {
|
||||
$("line-items").toggleClass("expanded");
|
||||
lineItemsExpanded ? $("line-items").slideUp() : $("line-items").slideDown();
|
||||
lineItemsExpanded = !lineItemsExpanded;
|
||||
|
||||
$(".buyerTotalLine").toggleClass("expanded");
|
||||
$(".single-item-order__right__btc-price__chevron").toggleClass("expanded");
|
||||
});
|
||||
|
@ -35,8 +35,8 @@ const locales_de = {
|
||||
"What happened?": "Was ist passiert?",
|
||||
"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 Wallet verfügbar. Abhängig von Ihrer Wallet, kann dies 48-72 Stunden dauern.",
|
||||
"InvoiceExpired_Body_2": "Wenn Sie versucht haben, eine Zahlung zu senden, wurde sie vom Netzwerk noch nicht akzeptiert. Wir haben Ihre Gelder noch nicht erhalten.",
|
||||
"InvoiceExpired_Body_3": "Wenn die Transaktion vom 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}}",
|
||||
|
@ -35,8 +35,8 @@ const locales_en = {
|
||||
"What happened?": "What happened?",
|
||||
"InvoiceExpired_Body_1": "This invoice has expired. An invoice is only valid for {{maxTimeMinutes}} minutes. \
|
||||
You can return to {{storeName}} if you would like to submit your payment again.",
|
||||
"InvoiceExpired_Body_2": "If you tried to send a payment, it has not yet been accepted by the Bitcoin network. We have not yet received your funds.",
|
||||
"InvoiceExpired_Body_3": "If the transaction is not accepted by the Bitcoin network, the funds will be spendable again in your wallet. Depending on your wallet, this may take 48-72 hours.",
|
||||
"InvoiceExpired_Body_2": "If you tried to send a payment, it has not yet been accepted by the network. We have not yet received your funds.",
|
||||
"InvoiceExpired_Body_3": "If the transaction is not accepted by the network, the funds will be spendable again in your wallet. Depending on your wallet, this may take 48-72 hours.",
|
||||
"Invoice ID": "Invoice ID",
|
||||
"Order ID": "Order ID",
|
||||
"Return to StoreName": "Return to {{storeName}}",
|
||||
|
@ -5,45 +5,45 @@ const locales_es = {
|
||||
"Awaiting Payment...": "En espera de pago...",
|
||||
"Pay with": "Pagar con",
|
||||
"Contact and Refund Email": "Contacto y correo electrónico de reembolso",
|
||||
"Contact_Body": "Por favor provea una dirección de correo electrónico a continuación. Nos pondremos en contacto con usted en esta dirección si hay un problema con su pago.",
|
||||
"Your email": "Su correo electrónico",
|
||||
"Contact_Body": "Por favor indica una dirección de correo electrónico a continuación. Nos pondremos en contacto contigo en esta dirección si hay algún problema con tu pago.",
|
||||
"Your email": "Tu correo electrónico",
|
||||
"Continue": "Continuar",
|
||||
"Please enter a valid email address": "Por favor entre un correo electrónico válido",
|
||||
"Please enter a valid email address": "Por favor ingresa un correo electrónico válido",
|
||||
"Order Amount": "Total del pedido",
|
||||
"Network Cost": "Costo de la red",
|
||||
"Already Paid": "Ya pagado",
|
||||
"Due": "Debido",
|
||||
"Already Paid": "Ya has pagado",
|
||||
"Due": "Aún debes",
|
||||
// Tabs
|
||||
"Scan": "Escanear",
|
||||
"Copy": "Copiar",
|
||||
"Conversion": "Conversión",
|
||||
// Scan tab
|
||||
"Open in wallet": "Abrir en billetera",
|
||||
"Open in wallet": "Abrir en la billetera",
|
||||
// Copy tab
|
||||
"CompletePay_Body": "Para completar su pago, envíe {{btcDue}} {{cryptoCode}} a la siguiente dirección.",
|
||||
"CompletePay_Body": "Para completar tu pago, envía {{btcDue}} {{cryptoCode}} a la siguiente dirección:",
|
||||
"Amount": "Cantidad",
|
||||
"Address": "Dirección",
|
||||
"Copied": "Copiado",
|
||||
// Conversion tab
|
||||
"ConversionTab_BodyTop": "Puede pagar {{btcDue}} {{cryptoCode}} usando altcoins que no sean los que el comerciante soporta directamente.",
|
||||
"ConversionTab_BodyDesc": "Este servicio es provisto por terceros. Tenga en cuenta que no tenemos control sobre cómo los proveedores enviarán sus fondos. La factura solo se marcará como abonada una vez que se reciban los fondos en la cadena de bloques de {{cryptoCode}} .",
|
||||
"ConversionTab_BodyTop": "Puedes pagar {{btcDue}} {{cryptoCode}} usando Altcoins que este comercio no soporta directamente.",
|
||||
"ConversionTab_BodyDesc": "Este servicio es provisto por terceros. Ten en cuenta que no tenemos control sobre cómo estos terceros envían los fondos. La factura solo se marcará como pagada una vez se reciban los fondos en la cadena de bloques de {{cryptoCode}} .",
|
||||
"Shapeshift_Button_Text": "Pagar con Altcoins",
|
||||
"ConversionTab_Lightning": "No hay proveedores de conversión disponibles para los pagos de Lightning Network.",
|
||||
// Invoice expired
|
||||
"Invoice expiring soon...": "La factura expira pronto...",
|
||||
"Invoice expired": "La factura expiró",
|
||||
"What happened?": "¿Qué sucedió?",
|
||||
"InvoiceExpired_Body_1": "Esta factura ha expirado. Una factura solo es válida por {{maxTimeMinutes}} minutos. Puede volver a {{storeName}} si desea volver a enviar su pago.",
|
||||
"InvoiceExpired_Body_2": "Si intentó enviar un pago, aún no ha sido aceptado por la red de Bitcoin. Todavía no hemos recibido sus fondos.",
|
||||
"InvoiceExpired_Body_3": "Si la transacción no es aceptada por la red de Bitcoin, los fondos se podrán gastar nuevamente en su billetera. Dependiendo de su billetera, esto puede tomar 48-72 horas.",
|
||||
"Invoice ID": "ID de factura",
|
||||
"Order ID": "ID de pedido",
|
||||
"InvoiceExpired_Body_1": "Esta factura ha expirado. Una factura solo es válida por {{maxTimeMinutes}} minutos. Puedes regresar a {{storeName}} si deseas volver a enviar tu pago.",
|
||||
"InvoiceExpired_Body_2": "Si intentaste enviar un pago, aún no ha sido aceptado por la red de Bitcoin. Todavía no hemos recibido tus fondos.",
|
||||
"InvoiceExpired_Body_3": "Si la transacción no es aceptada por la red de Bitcoin, los fondos se podrán gastar nuevamente en tu billetera. Dependiendo de tu billetera, esto puede tomar 48-72 horas.",
|
||||
"Invoice ID": "ID de la factura",
|
||||
"Order ID": "ID del pedido",
|
||||
"Return to StoreName": "Regresar a {{storeName}}",
|
||||
// Invoice paid
|
||||
"This invoice has been paid": "Esta factura ha sido pagada",
|
||||
// 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",
|
||||
"Archived_Body": "Por favor, comunícate con la tienda para obtener información de tu pedido o asistencia",
|
||||
// Lightning
|
||||
"BOLT 11 Invoice": "Factura BOLT 11",
|
||||
"Node Info": "Información del nodo",
|
||||
|
@ -26,7 +26,7 @@ const locales_is = {
|
||||
"Copied": "Afritað",
|
||||
// Conversion tab
|
||||
"ConversionTab_BodyTop": "Þú getur borgað {{btcDue}} {{cryptoCode}} með altcoins.",
|
||||
"ConversionTab_BodyDesc": "Þessi þjónusta er veitt af þriðja aðila. Mundu að við höfum ekki stjórn á því hvað þeir gera við peningana. Reikningurinn verður aðeins móttekinn þegar {{cryptoCode}} greiðslan hefur verið staðfest á Bitcoin netinu.",
|
||||
"ConversionTab_BodyDesc": "Þessi þjónusta er veitt af þriðja aðila. Mundu að við höfum ekki stjórn á því hvað þeir gera við peningana. Reikningurinn verður aðeins móttekinn þegar {{cryptoCode}} greiðslan hefur verið staðfest á netinu.",
|
||||
"Shapeshift_Button_Text": "Borga með Altcoins",
|
||||
"ConversionTab_Lightning": "Engir viðskiptaveitendur eru í boði fyrir Lightning Network greiðslur.",
|
||||
// Invoice expired
|
||||
@ -36,7 +36,7 @@ const locales_is = {
|
||||
"InvoiceExpired_Body_1": "Þessi reikningur er útrunnin. Reikningurinn er aðeins gildur í {{maxTimeMinutes}} mínútur. \
|
||||
Þú getur farið aftur á {{storeName}} ef þú vilt reyna aftur.",
|
||||
"InvoiceExpired_Body_2": "Ef þú reyndir að senda greiðslu, þá hefur hún ekki verið samþykkt.",
|
||||
"InvoiceExpired_Body_3": "Ef viðskiptin eru ekki samþykkt af Bitcoin netinu verða fjármunirnir aðgengilegar aftur í veskinu þínu. Það fer eftir veskinu þínu og getur tekið 48-72 klukkustundir.",
|
||||
"InvoiceExpired_Body_3": "Ef viðskiptin eru ekki samþykkt af netinu verða fjármunirnir aðgengilegar aftur í veskinu þínu. Það fer eftir veskinu þínu og getur tekið 48-72 klukkustundir.",
|
||||
"Invoice ID": "Innheimtu ID",
|
||||
"Order ID": "Pöntun ID",
|
||||
"Return to StoreName": "Fara aftur á {{storeName}}",
|
||||
|
BIN
BTCPayServer/wwwroot/imlegacy/feathercoin.png
Normal file
BIN
BTCPayServer/wwwroot/imlegacy/feathercoin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 147 KiB |
BIN
BTCPayServer/wwwroot/imlegacy/groestlcoin.png
Normal file
BIN
BTCPayServer/wwwroot/imlegacy/groestlcoin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 93 KiB |
986
BTCPayServer/wwwroot/imlegacy/mona-lightning.svg
Normal file
986
BTCPayServer/wwwroot/imlegacy/mona-lightning.svg
Normal file
@ -0,0 +1,986 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 200 200" style="enable-background:new 0 0 200 200;" xml:space="preserve">
|
||||
<g id="レイヤー_2" style="display:none;">
|
||||
|
||||
<image style="display:inline;overflow:visible;" width="440" height="702" xlink:href="data:image/jpeg;base64,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
|
||||
vMbj+hZ/xdEhClN+KRt47w47w47w47w47w47w47w47w47w47w47w47w47w47w47w47w47w47w47w
|
||||
47w47waIyb6C1UpJBBmae5juO47juO47n+n/AHHcED16CMdxuG4bhuG4bhuG4bhuG4bhuG4bhuG4
|
||||
bhuG4bv+mlrSguU0OU0OU0OU0OU0OU0OU0OU0OU0OU0Cktmf0vcdy+rMu42/p9v++pam3/bb3+qL
|
||||
+yY6e5rQnar+5fUK0SnY0+f9/wD8PqCMghSkhaCMKPv9WRmR/wDdaS6tA5Lw5Lw5Lw5Lw5Lw5Lw5
|
||||
Lw5Lw5Lw5Lw5Lw5Lw5Lw5Lw5Lw5LwjrUtH6Uzq8Q8Jgm+4S0agTINvsnpif4/wBKZ1EpRDuY7mO5
|
||||
juY7n1RP8f6S20LHGZHGZHGZHGZHGZHGZHGZHGZHGZHGZHGZHGZHGZHGZHGZHGZCEJQX/RJpMv6I
|
||||
bWs1x3UF9OWjhElJ6tbURmDVudSSXPpiPsDWRkGZBJSclpCTMzP/AKE//9oACAEBAAEFAF+oGYNQ
|
||||
NY8hDyDyEPIQ8hDyEPIQ8hDyEPIQ8hDyEPIQ8hDyEPIQ8hDyEPIQ8hDyEPIQ8hDyEPIQ8hDyEPIQ
|
||||
8hDyEPIQ8hDyEPIQ8hDyEPIQ8hDyEPIQ8g8hAlgldx361+ozCldgt3sFSCIHJIcohyiHKIcohyiH
|
||||
KIcohyiHKIcohyiHKIcohyiHKIcohyiHKIcohyiHKIcohyiHKIcohyiHKIcohyiHKIcohyiHKIco
|
||||
hyiHKIcohyiHKIcogUkgh8jCHO4SoF1L9SjDq+wsJTpOeV0x5HBvWN6xvWN6xvWN6xvWN6xvWN6x
|
||||
vWN6xvWN6xvWN6xvWN6xvWN6xvWN6xvWN6xvWN6xvWN6xvWN6xvWN6xvWN6xvWN6xvWN6xvWPIse
|
||||
RwE88Qr5C3Gm1dwR9S/U4YkK7FLPu9T1zdhIm45JZC0LQr9pr1dksq7knTpX6nNJJ/2fPu5i3vJF
|
||||
5Kh2LRQbli1gtwZJIWZElRg0qIGhZESVGDIyHY/6afsEIxHMI0LpX6ndJWj3rxb3jiYa7yyvERiW
|
||||
tbijKX4HXHWpm9a5CZj5KN1xuI4hb7co0OIJDbhrWa1/Xw9Y+iNC6V+p3SVo968W94/MTDun4cC5
|
||||
ZlxHojq0IeZddb8yXkFJTHZQopam4zMlRKalpWCWkon7BD1j6I0LpX6ndJWj3rxb3jtCy7MduK2v
|
||||
ROnPTnv2iHrH0RoXSv1O6StHvXBnvwHZdnMln+1Q9Y+iNC6V+p3SVophbim4TriviZI+Jkj4mSPi
|
||||
ZI+Jkj4mSPiZI+Jkj4mSPiZI+Jkj4mSPiZI+Jkj4mSPiZI+Jkj4mSPiZI+Jkj4mSPiZI+Jkj4mSP
|
||||
iZI+Jkj4mSPiZI+Jkj4mSPiZI+Jkj4mSPiZI+Jkj4mSPiZI+Jkj4mSPiZI+Jkj4mSPiZIbYXHdj6
|
||||
I0LpX6ndJWjekX/M6bhIjvE8giMzdjutr8bhjxObUxVrM2VbTbcI1NuICIqlINh4leNweF4G24k/
|
||||
E6RojPLWpJoV9GcdZNNx3HENsOOK4jm51lTX9JPu4+iNC6V+p3SVo3pHVtc58fbDQokxdhOm+0tJ
|
||||
vsEG1t7Eut72loSTTyNsklFH8zaI7LyVkh1AbfT3bfbIidIltvI7La7qU12L6JCk8Rt+ObaUbl+V
|
||||
rdINJNCT7uPojQulfqd0laMR5DqI0SWl3iOd+O+OO+OO+OO+OO+OO+OO+ENSUKcTMdHHfHHfG2X4
|
||||
+O+OO+OO+OO+OO+OO+OO+OO+OO+OO+OO+OO+OO+OO+OO+OO+OO+OO+OO+OO+OO+OO+OO+OO+OO+O
|
||||
O+OO+OO+OO+OO+JaFImR9EaF0r9TukrTH/Y/tl19zj6I0LpX6ndJWmP+x/bLr7nH0RoXSv1O6StM
|
||||
f9j9ZKe48eO/JJ+dIkx0yHpPnXYKTB5qkwEy5a65b8jhJXJfisP2DjMR95w3rR5t+U9PbWt6Wb0d
|
||||
by2xdfc4+iNC6V+p3SVpj/sQ/LkxrBu1sFMIly5Ev6iUzyI7TMvyy2HZEeQzI851ajaRWOeCPCea
|
||||
riYWUMmH0wHmX24kNt5ptyJNM3oq1k+xITIjpfS2Lr7nH0RoXSv1O6StMf8AYhyHGceTVQEtlXQ0
|
||||
l9M8+0whbzbbXyEbxlYRjabcQ6gJnxlKZkNPpTOYUCsIxoZebfbObH2tSGnmm5LThHPipMpsc3m3
|
||||
EuELr7nH0RoXSv1O6StMf9j9VLbW5HntrcitRn0xHkPNOqbdcjRY7kdlmO4tuIl1kRWpLKTYdQw8
|
||||
04pmNHmNRZTZIhPtSPIbMhL7TchQhEZoF19zj6I0LpX6ndJWmP8AsfpTMiPuXf8AXMiMiIiIGRKL
|
||||
+l19zj6I0LpX6ndJWmP+x+lWk1E2ky+muvucfRGhdK/U7pK0j3UuCj8nsR+T2I/J7Efk9iPyexH5
|
||||
PYj8nsR+T2I/J7Efk9iPyexH5PYj8nsR+T2I/J7Efk9iPyexH5PYj8nsR+T2I/J7EQsgnSJe0xtM
|
||||
bTG0xtMbTG0xtMbTG0xtMbTG0xtMbTG0xtMbTG0xtMbTG0xdf2s4+iNC6V+p3SVpj3sf2y7+6R9E
|
||||
aF0r9TukrTHvY/oTJXGQ3MWlCLCIporKH4Y8tiT9Gcx0wuZHbDkyM0rlMBKiUn9C7+6R9EaF0r9T
|
||||
ukrTHvY/oTkOOMogyzZe5z8duBK7MsOpsPokJlxyW3JQtKXWH3Yqk/pXf3SPojQulfqd0laY97H9
|
||||
h2p3eNvf+jd/dI+iNC6V+p3SVpj6klB8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY
|
||||
8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8jY8
|
||||
jY8jY8jY8jYujI7OPojQulfqd0laUiO8Pxjxjxjxjxjxjxjxjxjxjxjxjxjxjxjxjxjxjxjxjxjx
|
||||
jxjxjxjxjxjxjxjxjxjxjxjxjxjxjxjxjxjxjxjxjxjxi1LtYx9EaF0r9TukrSi9l+2W/wBxj6I0
|
||||
LpX6ndJWlF7L9st/uMfRGhdK/U7pK0ovZdNnN+Pr2c5t5DX5lejH8oTbtTcusGpUn+RpURxiY05A
|
||||
scmYYbavTk21rffHWkWzlv24mX9cw1WXDc+LJ/kaVEca/kx15xGe2Tki6yt+qKpzZqXNavTk20m+
|
||||
22N5mkummUmSR7OqaylSm6DKnbaeVnWnGgZpIsLO3vygHbZDFg1CMm5D1hmcyNbfmV6I2bTl2PVb
|
||||
/cY+iNC6V+p3SVpRey6cl/19+3sanCMRym+sr3EIzMt/IHcgxcrLJcHtZCMzSuroqGUdDJoMgguX
|
||||
FJmSU4NkVxbWF1byVTGsVk1MJcjJa6PGQ7ZN1uLY0kUv++ZRXvKm22ORcphYtj+SUr0rKIFTd3tr
|
||||
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
|
||||
8+pIh/44oN9HTL/qIb4bSJwsnHjSQ/2lA9QS6HUjSTU/ULCj0xASlKVko+npyJ3208AiScUpF5S7
|
||||
/kNrYhweP5hSTuQwyiXoxr7HnLCsMZW8DZtEQaMZXIiOnLFOY3YaR8yg9rOLiyws7SEWr6UJxAHr
|
||||
MBhcWDi+9QkamIfZ+BFCjKGGOKwsPhwQAoEOr0zh6g40LIHrz9I3O93BACgs9xf/2gAIAQMCBj8A
|
||||
22wtaFjGIjuKqb1U3qpvVTeqm9VN6qb1U3qpvVTeqm9VN6qb1U3qpvVTeqm9VN6qb1U3qpvUSbfT
|
||||
2f1BQwnCTM2oRslMVkLFv3buKqrKsCmB/tsMPt7P6goYKiZKFo/9N+Gipw5K0Oh3bFD7ey0Q5cLD
|
||||
hk3BZJLJJZJLJJZJLJJZJLJJZJLJJZJLJJZJLJJZJLJJZJLJJZJLJJZJKIIYgdpicLoE12unvfil
|
||||
RVNyqblU3KpuVTcqm5VNyqblU3KpuQFtvds9fyOcU72cx4qwiXx2p98vgpNYQK/NYx6e75LFvodp
|
||||
ie5kGr1DaeCERSK8ZfDaWNCrGnE1H9FiBsAtBtkF3Cm1uLP9V44Szus3ILNyCzcgs3ILNyCzcgs3
|
||||
ILNyCzcgs3ILNyCzcgs3ILNyCzcgs3IJ5Wl2/Dh59qw0Lf2VpCNrMWRtHpLIOat/L5oknc/w+faP
|
||||
3H8OHn2q7mVfbUqvaP3H8MYg7LLzKy8ysvMrLzKy8ysvMrLzKy8ysvMrLzKy8ysvMrLzKy8ysvMr
|
||||
LzKaIYe5Vo9jRDpzGzut2g3IPmDDz3+zE7PEl+9GEi/oEji4ng25SAoJHaGk5an9fYenMYoFEdKN
|
||||
p3syc7/cX//aAAgBAQEGPwA+U/rg+U+Dvxvxvxvxvxvxvxvxvxvxvxvxvxvxvxvxvxvxvxvxvxvx
|
||||
vxvxvxvxvxvxvxvxvxvxvxvxvxvxvxvxvxvxvxv/ALJvKflCIxUUqabMbXbrOO0es47R68do9eO0
|
||||
evHaPXjtHrx2j147R68do9eO0evHaPXjtHrx2j147R68do9eO0evHaPXjtHrx2j147R68do9eO0e
|
||||
vHaPXjtHrx2j147R68do9eO0evHaPXjtHrx2j147R68do9eO0evHaPXjtHrx2j147R68do9eO0ev
|
||||
HaPXjtHrONkjdZwC5qQaV/sG8p+Q4r82HjlZlVFzcNK7wOkHBe3POQdHpYKupVhvBFP1VT58Dw28
|
||||
p+Q4OJvd/wDmGJoz6yENsU9Gwbjgu8JB3VIoR5Gxyo3zqRXbvHzHFQpI8dMbAT5BjaCPKMVKkDx0
|
||||
xUAnyDFDs+Xb+oPpwPDbyn5Dg4m93/5hiVbuuQvspuJoN+Da2K5SuwvSgXyDBdyWY7STvxb8hiq5
|
||||
ehgu2p+fCKrFWOXmUNAW6cTPIxdYczKrGorWgxV2Lqe0hNQRhDEzIC7bj5MW5bbM5Iqd5XZQnAkj
|
||||
FOQchI6R0HAvT2FFZF/xjcPpwXO9jX9Qjw28p+Q4OJvd/wDmGJpWjEgDba7xsG7HPgIWanaG+viY
|
||||
YMUwow3HoI8eIaSqpRaMGrWtT4hiEK2YRAKX8e2uJCx9VIWBI8R6cZ5JVaMbaLWrfNuwqwtkOdiV
|
||||
HiNKYe4lbPKBRM23acNFKqIkgILKoFD0HZh468RcED5gD+oh4beU/IcHE3u//MMS3V1J6tjUKNmy
|
||||
nScGKzQOw6F2LX5z045stKgUAG4D9VDw28p+Q4ODLCFJYZSGBIpv6CMeufh/dXYvV+rB4beU/IcF
|
||||
gRTdtwFBWp8ZP1Y7SdZ+7jtJ1n7uO0nWfu47SdZ+7jtJ1n7uO0nWfu47SdZ+7jtJ1n7uO0nWfu47
|
||||
SdZ+7jtJ1n7uO0nWfu47SdZ+7jtJ1n7uO0nWfu47SdZ+7jtJ1n7uO0nWfu47SdZ+7jtJ1n7uO0nW
|
||||
fu47SdZ+7jtJ1n7uO0nWfu47SdZ+7jtJ1n7uO0nWfu47SdZ+7jtJ1n7uO0nWfu47SdZ+7jtJ1n7u
|
||||
O0nWfu47SdZ+7jtJ1n7uO0nWfu47SdZ+7jtJ1n7uO0nWfu47SdZ+7jtJ1n7uO0nWfu47SdZ+7jlu
|
||||
QTv2bsDw28p+Q4PlwPp82CYgC/QDitKMDRh4jgAbzgIRmYioy7cCinbsGw4zZGyjeaGmBk21XMSQ
|
||||
QPJhSAxZq8OU9GACjAncCDtxxqV8oIwr50UNuDEg+bBXISV30BOBwnbsGw7cezbqOAChBO6oO3GU
|
||||
o2Y9FDXATKVJ28QIwVYUI2H9E5pK5dmwGp24aQUCrvrilMuytW2CnjwFBUhhUMDw0GASQwbcy7R8
|
||||
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
|
||||
x2Yvsn72OzF9k/ex2Yvsn72OzF9k/ex2Yvsn72OzF9k/exDA4jCyOFJCmtCejix2j+z6sdo/s+rH
|
||||
aP7Pqx2j+z6sdo/s+rHaP7Pqx2j+z6sdo/s+rHaP7Pqx2j+z6sdo/s+rHaP7Pqx2j+z6sdo/s+rH
|
||||
aP7Pqx2j+z6sdo/s+rHaP7Pqx2j+z6sdo/s+rHaP7Pqx2j+z6sDbX1a+dsDw28p+Q4b3reZf1aPd
|
||||
r52wPDbyn5Dhvet5l/sVKpneRgiKTlGZvG22gxIbyP4flULMCXjIP7r5Vr5KYeXOVWPth1ZGFd3A
|
||||
4DbejZh5y5RIyA+dHRlJ3VVlDfsw3JYkoaMrKyMK7uFwD+hu0UOeKM5WbNRiRvyrQ1p5cAOxBIDU
|
||||
yk0B6WoDT6cZXY1pm4VZuHx8IOG49iqHJ29k9OAw3EVH0/2I92vnbA8NvKfkOG963mX+xyrClwhP
|
||||
rYXpxL/hzELXy4lEcTRRBo3gtpXDmqHMwqGcKD4q4laOA28hyhVzJzWUHi4lLKPm24uSIXRZWhKL
|
||||
LLzXoh4qszv58XEzLSORIwjVG0rmrs+n9Dkijiz5mLJJUBRm/eBIOz5sSlY+bz1AzAgBWAptzHdj
|
||||
lJGZmECqaEDbX/ERsxawgjMwySgdK9o/2Q92vnbA8NvKfkOG963mX9RZ6DMRQtTbTHMyjPSmagrT
|
||||
xV/sh7tfO2B4beU/IcNUgesbefmXHaHWMdodYx2h1jHaHWMdodYx2h1jHaHWMdodYx2h1jHaHWMd
|
||||
odYx2h1jHaHWMdodYx2h1jHaHWMdodYx2h1jHaHWMdodYx2h1jHaHWMdodYx2h1jHaHWMdodYx2h
|
||||
1jHaHWMdodYx2h1jHaHWMdodYx2h1jHaHWMdodYx2h1jHaHWMdodYx2h1jHaHWMdodYx2h1jHaHW
|
||||
MdodYwCDX1a+dsDw28p+Q4b3jeYfq0e7XznA8NvKfkOG943mH6tHu185wPDbyn5DhveN5h+rR7tf
|
||||
OcDw28p+Q4b3jeYeFcXuTmfDxtJy65c2UVpWhp1YWa37u3EsT7VkRnZW6NjLb0x/+s3fXJ/8fF5L
|
||||
c2408WJAl5klQK5s2YsiZcuXpxJHYaNNqNopHKvIGZopRTaVKQuuw7NhxyrvRZLeSmbJLKUah6aN
|
||||
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
|
||||
/9k=" transform="matrix(1 0 0 1 -440.3528 -197.892)">
|
||||
</image>
|
||||
</g>
|
||||
<g id="レイヤー_3" style="display:none;">
|
||||
<g style="display:inline;">
|
||||
<path style="fill:#FFFFFF;" d="M57.285,130.792"/>
|
||||
<g>
|
||||
|
||||
<rect x="120.103" y="145.331" transform="matrix(-0.4854 0.8743 -0.8743 -0.4854 306.1181 110.4958)" style="fill:#FFFFFF;" width="0.879" height="0"/>
|
||||
<path style="fill:#FFFFFF;" d="M100.587,15.843c-9.32,0-18.292,1.513-26.689,4.299L48.676,2.633l-2.814,3.538l19.23,17.454
|
||||
C35.928,37.093,15.64,66.615,15.64,100.79c0,46.84,38.107,84.947,84.947,84.947c10.025,0,19.648-1.751,28.585-4.954l-6.518-4.546
|
||||
c-7.004,2.052-14.408,3.158-22.067,3.158c-43.343,0-78.605-35.262-78.605-78.605c0-32.587,19.933-60.604,48.246-72.503
|
||||
c0.605-0.254,0.605-0.254,0,0l48.901,44.386l-26.995,54.239l29.651,26.416l-1.028-8.381l-0.427,0.769l0.427-0.769l-2.656-21.652
|
||||
l26.874-53.809L80.567,24.773c6.394-1.685,13.104-2.587,20.02-2.587c43.343,0,78.605,35.262,78.605,78.605
|
||||
c0,32.035-19.265,59.652-46.817,71.883l5.155,4.601c28.385-13.766,48.004-42.876,48.004-76.484
|
||||
C185.534,53.95,147.427,15.843,100.587,15.843z"/>
|
||||
</g>
|
||||
<path style="fill:#FFFFFF;" d="M137.53,177.275l-5.155-4.601c-0.785,0.348-0.785,0.348,0,0l-50.419-45.002l27.016-54.397
|
||||
L79.224,46.49l-0.111,0.238l4.563,31.512v0l-25.961,52.71l64.938,45.287l6.518,4.546l25.202,17.576l2.804-3.548L137.53,177.275z"
|
||||
/>
|
||||
</g>
|
||||
<g style="display:inline;">
|
||||
<path style="fill:#FFFFFF;" d="M57.285,130.792"/>
|
||||
<g>
|
||||
|
||||
<rect x="120.103" y="145.331" transform="matrix(-0.4854 0.8743 -0.8743 -0.4854 306.1181 110.4958)" style="fill:#FFFFFF;" width="0.879" height="0"/>
|
||||
<path style="fill:#FFFFFF;" d="M100.587,15.843c-9.32,0-18.292,1.513-26.689,4.299L48.676,2.633l-2.814,3.538l19.23,17.454
|
||||
C35.928,37.093,15.64,66.615,15.64,100.79c0,46.84,38.107,84.947,84.947,84.947c10.025,0,19.648-1.751,28.585-4.954l-6.518-4.546
|
||||
c-7.004,2.052-14.408,3.158-22.067,3.158c-43.343,0-78.605-35.262-78.605-78.605c0-32.587,19.933-60.604,48.246-72.503
|
||||
c0.605-0.254,0.605-0.254,0,0l48.901,44.386l-26.995,54.239l29.651,26.416l-1.028-8.381l-0.427,0.769l0.427-0.769l-2.656-21.652
|
||||
l26.874-53.809L80.567,24.773c6.394-1.685,13.104-2.587,20.02-2.587c43.343,0,78.605,35.262,78.605,78.605
|
||||
c0,32.035-19.265,59.652-46.817,71.883l5.155,4.601c28.385-13.766,48.004-42.876,48.004-76.484
|
||||
C185.534,53.95,147.427,15.843,100.587,15.843z"/>
|
||||
</g>
|
||||
<path style="fill:#FFFFFF;" d="M137.53,177.275l-5.155-4.601c-0.785,0.348-0.785,0.348,0,0l-50.419-45.002l27.016-54.397
|
||||
L79.224,46.49l-0.111,0.238l4.563,31.512v0l-25.961,52.71l64.938,45.287l6.518,4.546l25.202,17.576l2.804-3.548L137.53,177.275z"
|
||||
/>
|
||||
</g>
|
||||
<g style="display:inline;">
|
||||
<path style="fill:#FFFFFF;" d="M57.285,130.792"/>
|
||||
<g>
|
||||
<circle style="fill:#FFFFFF;" cx="102" cy="100" r="81.667"/>
|
||||
|
||||
<rect x="120.103" y="145.331" transform="matrix(-0.4854 0.8743 -0.8743 -0.4854 306.1181 110.4958)" style="fill:#FFFFFF;" width="0.879" height="0"/>
|
||||
<path style="fill:#FAD31A;" d="M100.587,15.843c-9.32,0-18.292,1.513-26.689,4.299L48.676,2.633l-2.814,3.538l19.23,17.454
|
||||
C35.928,37.093,15.64,66.615,15.64,100.79c0,46.84,38.107,84.947,84.947,84.947c10.025,0,19.648-1.751,28.585-4.954l-6.518-4.546
|
||||
c-7.004,2.052-14.408,3.158-22.067,3.158c-43.343,0-78.605-35.262-78.605-78.605c0-32.587,19.933-60.604,48.246-72.503
|
||||
c0.605-0.254,0.605-0.254,0,0l48.901,44.386l-26.995,54.239l29.651,26.416l-1.028-8.381l-0.427,0.769l0.427-0.769l-2.656-21.652
|
||||
l26.874-53.809L80.567,24.773c6.394-1.685,13.104-2.587,20.02-2.587c43.343,0,78.605,35.262,78.605,78.605
|
||||
c0,32.035-19.265,59.652-46.817,71.883l5.155,4.601c28.385-13.766,48.004-42.876,48.004-76.484
|
||||
C185.534,53.95,147.427,15.843,100.587,15.843z"/>
|
||||
</g>
|
||||
<path style="fill:#F7B421;" d="M137.53,177.275l-5.155-4.601c-0.785,0.348-0.785,0.348,0,0l-50.419-45.002l27.016-54.397
|
||||
L79.224,46.49l-0.111,0.238l4.563,31.512v0l-25.961,52.71l64.938,45.287l6.518,4.546l25.202,17.576l2.804-3.548L137.53,177.275z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="レイヤー_3のコピー_3">
|
||||
<g>
|
||||
<path style="fill:#FFFFFF;" d="M57.285,130.792"/>
|
||||
<g>
|
||||
|
||||
<rect x="120.103" y="145.331" transform="matrix(-0.4854 0.8743 -0.8743 -0.4854 306.1181 110.4958)" style="fill:#FFFFFF;" width="0.879" height="0"/>
|
||||
<path style="fill:#FFFFFF;" d="M100.587,15.843c-9.32,0-18.292,1.513-26.689,4.299L48.676,2.633l-2.814,3.538l19.23,17.454
|
||||
C35.928,37.093,15.64,66.615,15.64,100.79c0,46.84,38.107,84.947,84.947,84.947c10.025,0,19.648-1.751,28.585-4.954l-6.518-4.546
|
||||
c-7.004,2.052-14.408,3.158-22.067,3.158c-43.343,0-78.605-35.262-78.605-78.605c0-32.587,19.933-60.604,48.246-72.503
|
||||
c0.605-0.254,0.605-0.254,0,0l48.901,44.386l-26.995,54.239l29.651,26.416l-1.028-8.381l-0.427,0.769l0.427-0.769l-2.656-21.652
|
||||
l26.874-53.809L80.567,24.773c6.394-1.685,13.104-2.587,20.02-2.587c43.343,0,78.605,35.262,78.605,78.605
|
||||
c0,32.035-19.265,59.652-46.817,71.883l5.155,4.601c28.385-13.766,48.004-42.876,48.004-76.484
|
||||
C185.534,53.95,147.427,15.843,100.587,15.843z"/>
|
||||
</g>
|
||||
<path style="fill:#FFFFFF;" d="M137.53,177.275l-5.155-4.601c-0.785,0.348-0.785,0.348,0,0l-50.419-45.002l27.016-54.397
|
||||
L79.224,46.49l-0.111,0.238l4.563,31.512v0l-25.961,52.71l64.938,45.287l6.518,4.546l25.202,17.576l2.804-3.548L137.53,177.275z"
|
||||
/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#FFFFFF;" d="M57.285,130.792"/>
|
||||
<g>
|
||||
|
||||
<rect x="120.103" y="145.331" transform="matrix(-0.4854 0.8743 -0.8743 -0.4854 306.1181 110.4958)" style="fill:#FFFFFF;" width="0.879" height="0"/>
|
||||
<path style="fill:#FFFFFF;" d="M100.587,15.843c-9.32,0-18.292,1.513-26.689,4.299L48.676,2.633l-2.814,3.538l19.23,17.454
|
||||
C35.928,37.093,15.64,66.615,15.64,100.79c0,46.84,38.107,84.947,84.947,84.947c10.025,0,19.648-1.751,28.585-4.954l-6.518-4.546
|
||||
c-7.004,2.052-14.408,3.158-22.067,3.158c-43.343,0-78.605-35.262-78.605-78.605c0-32.587,19.933-60.604,48.246-72.503
|
||||
c0.605-0.254,0.605-0.254,0,0l48.901,44.386l-26.995,54.239l29.651,26.416l-1.028-8.381l-0.427,0.769l0.427-0.769l-2.656-21.652
|
||||
l26.874-53.809L80.567,24.773c6.394-1.685,13.104-2.587,20.02-2.587c43.343,0,78.605,35.262,78.605,78.605
|
||||
c0,32.035-19.265,59.652-46.817,71.883l5.155,4.601c28.385-13.766,48.004-42.876,48.004-76.484
|
||||
C185.534,53.95,147.427,15.843,100.587,15.843z"/>
|
||||
</g>
|
||||
<path style="fill:#FFFFFF;" d="M137.53,177.275l-5.155-4.601c-0.785,0.348-0.785,0.348,0,0l-50.419-45.002l27.016-54.397
|
||||
L79.224,46.49l-0.111,0.238l4.563,31.512v0l-25.961,52.71l64.938,45.287l6.518,4.546l25.202,17.576l2.804-3.548L137.53,177.275z"
|
||||
/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#FFFFFF;" d="M57.285,130.792"/>
|
||||
<g>
|
||||
<circle style="fill:#FFFFFF;" cx="102" cy="100" r="81.667"/>
|
||||
|
||||
<rect x="120.103" y="145.331" transform="matrix(-0.4854 0.8743 -0.8743 -0.4854 306.1181 110.4958)" style="fill:#FFFFFF;" width="0.879" height="0"/>
|
||||
<path style="fill:#DECDA9;" d="M100.587,15.843c-9.32,0-18.292,1.513-26.689,4.299L48.676,2.633l-2.814,3.538l19.23,17.454
|
||||
C35.928,37.093,15.64,66.615,15.64,100.79c0,46.84,38.107,84.947,84.947,84.947c10.025,0,19.648-1.751,28.585-4.954l-6.518-4.546
|
||||
c-7.004,2.052-14.408,3.158-22.067,3.158c-43.343,0-78.605-35.262-78.605-78.605c0-32.587,19.933-60.604,48.246-72.503
|
||||
c0.605-0.254,0.605-0.254,0,0l48.901,44.386l-26.995,54.239l29.651,26.416l-1.028-8.381l-0.427,0.769l0.427-0.769l-2.656-21.652
|
||||
l26.874-53.809L80.567,24.773c6.394-1.685,13.104-2.587,20.02-2.587c43.343,0,78.605,35.262,78.605,78.605
|
||||
c0,32.035-19.265,59.652-46.817,71.883l5.155,4.601c28.385-13.766,48.004-42.876,48.004-76.484
|
||||
C185.534,53.95,147.427,15.843,100.587,15.843z"/>
|
||||
</g>
|
||||
<path style="fill:#DEC6A9;" d="M137.53,177.275l-5.155-4.601c-0.785,0.348-0.785,0.348,0,0l-50.419-45.002l27.016-54.397
|
||||
L79.224,46.49l-0.111,0.238l4.563,31.512v0l-25.961,52.71l64.938,45.287l6.518,4.546l25.202,17.576l2.804-3.548L137.53,177.275z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="レイヤー_3のコピー" style="display:none;">
|
||||
<g style="display:inline;">
|
||||
<path style="fill:#FFFFFF;" d="M57.285,130.792"/>
|
||||
<g>
|
||||
|
||||
<rect x="120.103" y="145.331" transform="matrix(-0.4854 0.8743 -0.8743 -0.4854 306.1181 110.4958)" style="fill:#FFFFFF;" width="0.879" height="0"/>
|
||||
<path style="fill:#FFFFFF;" d="M100.587,15.843c-9.32,0-18.292,1.513-26.689,4.299L48.676,2.633l-2.814,3.538l19.23,17.454
|
||||
C35.928,37.093,15.64,66.615,15.64,100.79c0,46.84,38.107,84.947,84.947,84.947c10.025,0,19.648-1.751,28.585-4.954l-6.518-4.546
|
||||
c-7.004,2.052-14.408,3.158-22.067,3.158c-43.343,0-78.605-35.262-78.605-78.605c0-32.587,19.933-60.604,48.246-72.503
|
||||
c0.605-0.254,0.605-0.254,0,0l48.901,44.386l-26.995,54.239l29.651,26.416l-1.028-8.381l-0.427,0.769l0.427-0.769l-2.656-21.652
|
||||
l26.874-53.809L80.567,24.773c6.394-1.685,13.104-2.587,20.02-2.587c43.343,0,78.605,35.262,78.605,78.605
|
||||
c0,32.035-19.265,59.652-46.817,71.883l5.155,4.601c28.385-13.766,48.004-42.876,48.004-76.484
|
||||
C185.534,53.95,147.427,15.843,100.587,15.843z"/>
|
||||
</g>
|
||||
<path style="fill:#FFFFFF;" d="M137.53,177.275l-5.155-4.601c-0.785,0.348-0.785,0.348,0,0l-50.419-45.002l27.016-54.397
|
||||
L79.224,46.49l-0.111,0.238l4.563,31.512v0l-25.961,52.71l64.938,45.287l6.518,4.546l25.202,17.576l2.804-3.548L137.53,177.275z"
|
||||
/>
|
||||
</g>
|
||||
<g style="display:inline;">
|
||||
<path style="fill:#FFFFFF;" d="M57.285,130.792"/>
|
||||
<g>
|
||||
|
||||
<rect x="120.103" y="145.331" transform="matrix(-0.4854 0.8743 -0.8743 -0.4854 306.1181 110.4958)" style="fill:#FFFFFF;" width="0.879" height="0"/>
|
||||
<path style="fill:#FFFFFF;" d="M100.587,15.843c-9.32,0-18.292,1.513-26.689,4.299L48.676,2.633l-2.814,3.538l19.23,17.454
|
||||
C35.928,37.093,15.64,66.615,15.64,100.79c0,46.84,38.107,84.947,84.947,84.947c10.025,0,19.648-1.751,28.585-4.954l-6.518-4.546
|
||||
c-7.004,2.052-14.408,3.158-22.067,3.158c-43.343,0-78.605-35.262-78.605-78.605c0-32.587,19.933-60.604,48.246-72.503
|
||||
c0.605-0.254,0.605-0.254,0,0l48.901,44.386l-26.995,54.239l29.651,26.416l-1.028-8.381l-0.427,0.769l0.427-0.769l-2.656-21.652
|
||||
l26.874-53.809L80.567,24.773c6.394-1.685,13.104-2.587,20.02-2.587c43.343,0,78.605,35.262,78.605,78.605
|
||||
c0,32.035-19.265,59.652-46.817,71.883l5.155,4.601c28.385-13.766,48.004-42.876,48.004-76.484
|
||||
C185.534,53.95,147.427,15.843,100.587,15.843z"/>
|
||||
</g>
|
||||
<path style="fill:#FFFFFF;" d="M137.53,177.275l-5.155-4.601c-0.785,0.348-0.785,0.348,0,0l-50.419-45.002l27.016-54.397
|
||||
L79.224,46.49l-0.111,0.238l4.563,31.512v0l-25.961,52.71l64.938,45.287l6.518,4.546l25.202,17.576l2.804-3.548L137.53,177.275z"
|
||||
/>
|
||||
</g>
|
||||
<g style="display:inline;">
|
||||
<path style="fill:#B2B4B6;" d="M57.285,130.792"/>
|
||||
<g>
|
||||
<circle style="fill:#FFFFFF;" cx="102" cy="100" r="81.667"/>
|
||||
|
||||
<rect x="120.103" y="145.331" transform="matrix(-0.4854 0.8743 -0.8743 -0.4854 306.1181 110.4958)" style="fill:#B2B4B6;" width="0.879" height="0"/>
|
||||
<path style="fill:#B2B4B6;" d="M100.587,15.843c-9.32,0-18.292,1.513-26.689,4.299L48.676,2.633l-2.814,3.538l19.23,17.454
|
||||
C35.928,37.093,15.64,66.615,15.64,100.79c0,46.84,38.107,84.947,84.947,84.947c10.025,0,19.648-1.751,28.585-4.954l-6.518-4.546
|
||||
c-7.004,2.052-14.408,3.158-22.067,3.158c-43.343,0-78.605-35.262-78.605-78.605c0-32.587,19.933-60.604,48.246-72.503
|
||||
c0.605-0.254,0.605-0.254,0,0l48.901,44.386l-26.995,54.239l29.651,26.416l-1.028-8.381l-0.427,0.769l0.427-0.769l-2.656-21.652
|
||||
l26.874-53.809L80.567,24.773c6.394-1.685,13.104-2.587,20.02-2.587c43.343,0,78.605,35.262,78.605,78.605
|
||||
c0,32.035-19.265,59.652-46.817,71.883l5.155,4.601c28.385-13.766,48.004-42.876,48.004-76.484
|
||||
C185.534,53.95,147.427,15.843,100.587,15.843z"/>
|
||||
</g>
|
||||
<path style="fill:#8C8D8E;" d="M137.53,177.275l-5.155-4.601c-0.785,0.348-0.785,0.348,0,0l-50.419-45.002l27.016-54.397
|
||||
L79.224,46.49l-0.111,0.238l4.563,31.512v0l-25.961,52.71l64.938,45.287l6.518,4.546l25.202,17.576l2.804-3.548L137.53,177.275z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="レイヤー_3のコピー_2" style="display:none;">
|
||||
<g style="display:inline;">
|
||||
<path style="fill:#FFFFFF;" d="M57.285,130.792"/>
|
||||
<g>
|
||||
|
||||
<rect x="120.103" y="145.331" transform="matrix(-0.4854 0.8743 -0.8743 -0.4854 306.1181 110.4958)" style="fill:#FFFFFF;" width="0.879" height="0"/>
|
||||
<path style="fill:#FFFFFF;" d="M100.587,15.843c-9.32,0-18.292,1.513-26.689,4.299L48.676,2.633l-2.814,3.538l19.23,17.454
|
||||
C35.928,37.093,15.64,66.615,15.64,100.79c0,46.84,38.107,84.947,84.947,84.947c10.025,0,19.648-1.751,28.585-4.954l-6.518-4.546
|
||||
c-7.004,2.052-14.408,3.158-22.067,3.158c-43.343,0-78.605-35.262-78.605-78.605c0-32.587,19.933-60.604,48.246-72.503
|
||||
c0.605-0.254,0.605-0.254,0,0l48.901,44.386l-26.995,54.239l29.651,26.416l-1.028-8.381l-0.427,0.769l0.427-0.769l-2.656-21.652
|
||||
l26.874-53.809L80.567,24.773c6.394-1.685,13.104-2.587,20.02-2.587c43.343,0,78.605,35.262,78.605,78.605
|
||||
c0,32.035-19.265,59.652-46.817,71.883l5.155,4.601c28.385-13.766,48.004-42.876,48.004-76.484
|
||||
C185.534,53.95,147.427,15.843,100.587,15.843z"/>
|
||||
</g>
|
||||
<path style="fill:#FFFFFF;" d="M137.53,177.275l-5.155-4.601c-0.785,0.348-0.785,0.348,0,0l-50.419-45.002l27.016-54.397
|
||||
L79.224,46.49l-0.111,0.238l4.563,31.512v0l-25.961,52.71l64.938,45.287l6.518,4.546l25.202,17.576l2.804-3.548L137.53,177.275z"
|
||||
/>
|
||||
</g>
|
||||
<g style="display:inline;">
|
||||
<path style="fill:#FFFFFF;" d="M57.285,130.792"/>
|
||||
<g>
|
||||
|
||||
<rect x="120.103" y="145.331" transform="matrix(-0.4854 0.8743 -0.8743 -0.4854 306.1181 110.4958)" style="fill:#FFFFFF;" width="0.879" height="0"/>
|
||||
<path style="fill:#FFFFFF;" d="M100.587,15.843c-9.32,0-18.292,1.513-26.689,4.299L48.676,2.633l-2.814,3.538l19.23,17.454
|
||||
C35.928,37.093,15.64,66.615,15.64,100.79c0,46.84,38.107,84.947,84.947,84.947c10.025,0,19.648-1.751,28.585-4.954l-6.518-4.546
|
||||
c-7.004,2.052-14.408,3.158-22.067,3.158c-43.343,0-78.605-35.262-78.605-78.605c0-32.587,19.933-60.604,48.246-72.503
|
||||
c0.605-0.254,0.605-0.254,0,0l48.901,44.386l-26.995,54.239l29.651,26.416l-1.028-8.381l-0.427,0.769l0.427-0.769l-2.656-21.652
|
||||
l26.874-53.809L80.567,24.773c6.394-1.685,13.104-2.587,20.02-2.587c43.343,0,78.605,35.262,78.605,78.605
|
||||
c0,32.035-19.265,59.652-46.817,71.883l5.155,4.601c28.385-13.766,48.004-42.876,48.004-76.484
|
||||
C185.534,53.95,147.427,15.843,100.587,15.843z"/>
|
||||
</g>
|
||||
<path style="fill:#FFFFFF;" d="M137.53,177.275l-5.155-4.601c-0.785,0.348-0.785,0.348,0,0l-50.419-45.002l27.016-54.397
|
||||
L79.224,46.49l-0.111,0.238l4.563,31.512v0l-25.961,52.71l64.938,45.287l6.518,4.546l25.202,17.576l2.804-3.548L137.53,177.275z"
|
||||
/>
|
||||
</g>
|
||||
<g style="display:inline;">
|
||||
<path style="fill:#B2B4B6;" d="M57.285,130.792"/>
|
||||
<g>
|
||||
<circle style="fill:#FFFFFF;" cx="102" cy="100" r="81.667"/>
|
||||
|
||||
<rect x="120.103" y="145.331" transform="matrix(-0.4854 0.8743 -0.8743 -0.4854 306.1181 110.4958)" style="fill:#B2B4B6;" width="0.879" height="0"/>
|
||||
<path style="fill:#D9DADB;" d="M100.587,15.843c-9.32,0-18.292,1.513-26.689,4.299L48.676,2.633l-2.814,3.538l19.23,17.454
|
||||
C35.928,37.093,15.64,66.615,15.64,100.79c0,46.84,38.107,84.947,84.947,84.947c10.025,0,19.648-1.751,28.585-4.954l-6.518-4.546
|
||||
c-7.004,2.052-14.408,3.158-22.067,3.158c-43.343,0-78.605-35.262-78.605-78.605c0-32.587,19.933-60.604,48.246-72.503
|
||||
c0.605-0.254,0.605-0.254,0,0l48.901,44.386l-26.995,54.239l29.651,26.416l-1.028-8.381l-0.427,0.769l0.427-0.769l-2.656-21.652
|
||||
l26.874-53.809L80.567,24.773c6.394-1.685,13.104-2.587,20.02-2.587c43.343,0,78.605,35.262,78.605,78.605
|
||||
c0,32.035-19.265,59.652-46.817,71.883l5.155,4.601c28.385-13.766,48.004-42.876,48.004-76.484
|
||||
C185.534,53.95,147.427,15.843,100.587,15.843z"/>
|
||||
</g>
|
||||
<path style="fill:#B2B4B6;" d="M137.53,177.275l-5.155-4.601c-0.785,0.348-0.785,0.348,0,0l-50.419-45.002l27.016-54.397
|
||||
L79.224,46.49l-0.111,0.238l4.563,31.512v0l-25.961,52.71l64.938,45.287l6.518,4.546l25.202,17.576l2.804-3.548L137.53,177.275z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 73 KiB |
BIN
BTCPayServer/wwwroot/imlegacy/monacoin.png
Normal file
BIN
BTCPayServer/wwwroot/imlegacy/monacoin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 132 KiB |
BIN
BTCPayServer/wwwroot/imlegacy/polis.png
Normal file
BIN
BTCPayServer/wwwroot/imlegacy/polis.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
BTCPayServer/wwwroot/imlegacy/ufo.png
Normal file
BIN
BTCPayServer/wwwroot/imlegacy/ufo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
9
BTCPayServer/wwwroot/main/site.js
Normal file
9
BTCPayServer/wwwroot/main/site.js
Normal file
@ -0,0 +1,9 @@
|
||||
$(function () {
|
||||
$(".localizeDate").each(function (index) {
|
||||
var serverDate = $(this).text();
|
||||
var localDate = new Date(serverDate);
|
||||
|
||||
var dateString = localDate.toLocaleDateString() + " " + localDate.toLocaleTimeString();
|
||||
$(this).text(dateString);
|
||||
});
|
||||
});
|
@ -1,4 +1,4 @@
|
||||
FROM microsoft/dotnet:2.1.300-rc1-sdk-alpine3.7 AS builder
|
||||
FROM microsoft/dotnet:2.1.300-sdk-alpine3.7 AS builder
|
||||
WORKDIR /source
|
||||
COPY BTCPayServer/BTCPayServer.csproj BTCPayServer.csproj
|
||||
# Cache some dependencies
|
||||
@ -6,7 +6,7 @@ RUN dotnet restore
|
||||
COPY BTCPayServer/. .
|
||||
RUN dotnet publish --output /app/ --configuration Release
|
||||
|
||||
FROM microsoft/dotnet:2.1.0-rc1-aspnetcore-runtime-alpine3.7
|
||||
FROM microsoft/dotnet:2.1.0-aspnetcore-runtime-alpine3.7
|
||||
|
||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT false
|
||||
RUN apk add --no-cache icu-libs
|
||||
|
@ -5,4 +5,4 @@
|
||||
<add key="aspnetcidev" value="https://dotnet.myget.org/F/aspnetcore-ci-dev/api/v3/index.json"/>
|
||||
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json"/>
|
||||
</packageSources>
|
||||
</configuration>
|
||||
</configuration>
|
||||
|
15
README.md
15
README.md
@ -20,6 +20,19 @@ This solution is for you if:
|
||||
* You want to become a payment processor yourself and offer a BTCPay hosted solution to merchants
|
||||
* You want a way to support currencies other than those offered by Bitpay
|
||||
|
||||
## We support altcoins!
|
||||
|
||||
In addition to Bitcoin, we support the following crypto currencies:
|
||||
|
||||
* BGold
|
||||
* Dogecoin
|
||||
* Feathercoin
|
||||
* Groestlcoin
|
||||
* Litecoin
|
||||
* Monacoin
|
||||
* Polis
|
||||
* UFO
|
||||
|
||||
## Documentation
|
||||
|
||||
Please check out our [complete documentation](https://github.com/btcpayserver/btcpayserver-doc) for more details.
|
||||
@ -30,7 +43,7 @@ You can also checkout [The Merchants Guide to accepting Bitcoin directly with no
|
||||
|
||||
While the documentation advise using docker-compose, you may want to build yourself outside of development purpose.
|
||||
|
||||
First install .NET Core SDK 2.1 as specified by [Microsoft website](https://www.microsoft.com/net/download/dotnet-core/sdk-2.1.300-rc1).
|
||||
First install .NET Core SDK 2.1 as specified by [Microsoft website](https://www.microsoft.com/net/download/dotnet-core).
|
||||
|
||||
On Powershell:
|
||||
```
|
||||
|
Reference in New Issue
Block a user