Compare commits
88 Commits
Author | SHA1 | Date | |
---|---|---|---|
9db77e6351 | |||
5bc1eaec9f | |||
81c9ce7284 | |||
caa6978d80 | |||
af22d6a4e3 | |||
0eabb3c37c | |||
a63ed4d3b4 | |||
968c820702 | |||
3061b4dfd2 | |||
ed4de612dd | |||
d4bdd5fd9c | |||
87b316ec23 | |||
9c99ffae57 | |||
d0f585df9d | |||
bac2db5cda | |||
c35bf2f483 | |||
2e04c5e39c | |||
9dcf16e819 | |||
361d494cde | |||
4d7015294e | |||
5f16fb4668 | |||
4bf2228675 | |||
2ba823f192 | |||
27fa2d5b69 | |||
47ef7661d8 | |||
3f6ff25322 | |||
f56c23009a | |||
e80593fb7b | |||
57324345ac | |||
73e280157d | |||
70d1d0d230 | |||
94e0048a3b | |||
9db5c0f375 | |||
2bb24282d2 | |||
998472e463 | |||
63ff46a768 | |||
660f43e3b7 | |||
0ba96aa4b8 | |||
d85247d2ad | |||
9ca85ed365 | |||
93113fd871 | |||
d5ae79c38c | |||
7cf07b27e3 | |||
bb0f986b0c | |||
2c2a85327f | |||
7bf03e497b | |||
7a4dee3d38 | |||
7b27d6f0bb | |||
83dc95a0a7 | |||
d60889f952 | |||
8c9952973d | |||
00673bdb7f | |||
d039890a9b | |||
41e88c07fe | |||
67c5027b16 | |||
a341d4f800 | |||
3ad1834439 | |||
4b492eae85 | |||
f0ff47af8d | |||
991826b686 | |||
22d59a1ed7 | |||
475ea68696 | |||
864e84706a | |||
9c93e76eeb | |||
94be2b46d5 | |||
4b4d0d2d19 | |||
0d06cf63b7 | |||
7b24c02d51 | |||
becf488714 | |||
e89e8226e4 | |||
a533a96598 | |||
27321c0919 | |||
058472d325 | |||
b5c9a03052 | |||
07dad3affa | |||
8afc103ae7 | |||
591d7b4b80 | |||
2162afc78e | |||
25e226d219 | |||
8472bfe90d | |||
93645b2fbe | |||
d53c987f2e | |||
682693a9f0 | |||
e836faf792 | |||
6e27233be8 | |||
9209984a2f | |||
1477630c78 | |||
580494fea7 |
@ -7,23 +7,16 @@ jobs:
|
||||
- checkout
|
||||
|
||||
test:
|
||||
machine: true
|
||||
machine:
|
||||
docker_layer_caching: true
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
command: |
|
||||
lsb_release -a
|
||||
wget -q https://packages.microsoft.com/config/ubuntu/14.04/packages-microsoft-prod.deb
|
||||
sudo dpkg -i packages-microsoft-prod.deb
|
||||
sudo apt-get install apt-transport-https
|
||||
sudo apt-get update
|
||||
sudo apt-get install dotnet-sdk-2.1
|
||||
dotnet --info
|
||||
dotnet build /p:TreatWarningsAsErrors=true
|
||||
cd BTCPayServer.Tests
|
||||
dotnet test --filter Fast=Fast
|
||||
docker-compose up -d dev
|
||||
dotnet test --filter Integration=Integration
|
||||
docker-compose down --v
|
||||
docker-compose build
|
||||
docker-compose run tests
|
||||
|
||||
# publish jobs require $DOCKERHUB_REPO, $DOCKERHUB_USER, $DOCKERHUB_PASS defined
|
||||
publish_docker_linuxamd64:
|
||||
|
@ -1,12 +1,17 @@
|
||||
FROM microsoft/dotnet:2.1.403-sdk-alpine3.7
|
||||
WORKDIR /app
|
||||
# caches restore result by copying csproj file separately
|
||||
COPY BTCPayServer.Tests/BTCPayServer.Tests.csproj BTCPayServer.Tests/BTCPayServer.Tests.csproj
|
||||
FROM microsoft/dotnet:2.1.500-sdk-alpine3.7 AS builder
|
||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT false
|
||||
RUN apk add --no-cache icu-libs
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
ENV LANG en_US.UTF-8
|
||||
|
||||
# This should be removed soon https://github.com/dotnet/corefx/issues/30003
|
||||
RUN apk add --no-cache curl
|
||||
|
||||
WORKDIR /source
|
||||
COPY BTCPayServer/BTCPayServer.csproj BTCPayServer/BTCPayServer.csproj
|
||||
|
||||
WORKDIR /app/BTCPayServer.Tests
|
||||
RUN dotnet restore
|
||||
# copies the rest of your code
|
||||
COPY . ../.
|
||||
|
||||
ENTRYPOINT ["dotnet", "test"]
|
||||
COPY BTCPayServer.Tests/BTCPayServer.Tests.csproj BTCPayServer.Tests/BTCPayServer.Tests.csproj
|
||||
RUN dotnet restore BTCPayServer.Tests/BTCPayServer.Tests.csproj
|
||||
COPY . .
|
||||
RUN dotnet build
|
||||
WORKDIR /source/BTCPayServer.Tests
|
||||
ENTRYPOINT ["./docker-entrypoint.sh"]
|
||||
|
@ -1,4 +1,4 @@
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using System.Linq;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
@ -48,6 +48,7 @@ using BTCPayServer.Models.ServerViewModels;
|
||||
using BTCPayServer.Security;
|
||||
using NBXplorer.Models;
|
||||
using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel;
|
||||
using NBitpayClient.Extensions;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -339,69 +340,22 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
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)"),
|
||||
(0.0005m, "$0.0005 (USD)", "USD"),
|
||||
(0.001m, "$0.001 (USD)", "USD"),
|
||||
(0.01m, "$0.01 (USD)", "USD"),
|
||||
(0.1m, "$0.10 (USD)", "USD"),
|
||||
(0.1m, "0,10 € (EUR)", "EUR"),
|
||||
(1000m, "¥1,000 (JPY)", "JPY"),
|
||||
(1000.0001m, "₹ 1,000.00 (INR)", "INR")
|
||||
})
|
||||
{
|
||||
var actual = new CurrencyNameTable().DisplayFormatCurrency(test.Item1, "USD");
|
||||
var actual = new CurrencyNameTable().DisplayFormatCurrency(test.Item1, test.Item3);
|
||||
actual = actual.Replace("¥", "¥"); // Hack so JPY test pass on linux as well
|
||||
Assert.Equal(test.Item2, actual);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public void CanPayUsingBIP70()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
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" },
|
||||
Price = 5000.0m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
//RedirectURL = redirect + "redirect",
|
||||
//NotificationURL = CallbackUri + "/notification",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
Assert.False(invoice.Refundable);
|
||||
|
||||
var url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP72);
|
||||
var request = url.GetPaymentRequest();
|
||||
var payment = request.CreatePayment();
|
||||
|
||||
Transaction tx = new Transaction();
|
||||
tx.Outputs.AddRange(request.Details.Outputs.Select(o => new TxOut(o.Amount, o.Script)));
|
||||
var cashCow = tester.ExplorerNode;
|
||||
tx = cashCow.FundRawTransaction(tx).Transaction;
|
||||
tx = cashCow.SignRawTransaction(tx);
|
||||
|
||||
payment.Transactions.Add(tx);
|
||||
|
||||
payment.RefundTo.Add(new PaymentOutput(Money.Coins(1.0m), new Key().ScriptPubKey));
|
||||
var ack = payment.SubmitPayment();
|
||||
Assert.NotNull(ack);
|
||||
|
||||
Eventually(() =>
|
||||
{
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("paid", localInvoice.Status);
|
||||
Assert.True(localInvoice.Refundable);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Timeout = 60 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanSetLightningServer()
|
||||
{
|
||||
@ -453,7 +407,7 @@ namespace BTCPayServer.Tests
|
||||
await ProcessLightningPayment(LightningConnectionType.Charge);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Timeout = 60 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanSendLightningPaymentLnd()
|
||||
{
|
||||
@ -934,6 +888,17 @@ namespace BTCPayServer.Tests
|
||||
var result = client.SendAsync(message).GetAwaiter().GetResult();
|
||||
result.EnsureSuccessStatusCode();
|
||||
/////////////////////
|
||||
|
||||
// Have error 403 with bad signature
|
||||
client = new HttpClient();
|
||||
HttpRequestMessage mess = new HttpRequestMessage(HttpMethod.Get, tester.PayTester.ServerUri.AbsoluteUri + "tokens");
|
||||
mess.Content = new StringContent(string.Empty, Encoding.UTF8, "application/json");
|
||||
mess.Headers.Add("x-signature", "3045022100caa123193afc22ef93d9c6b358debce6897c09dd9869fe6fe029c9cb43623fac022000b90c65c50ba8bbbc6ebee8878abe5659e17b9f2e1b27d95eda4423da5608fe");
|
||||
mess.Headers.Add("x-identity", "04b4d82095947262dd70f94c0a0e005ec3916e3f5f2181c176b8b22a52db22a8c436c4703f43a9e8884104854a11e1eb30df8fdf116e283807a1f1b8fe4c182b99");
|
||||
mess.Method = HttpMethod.Get;
|
||||
result = client.SendAsync(mess).GetAwaiter().GetResult();
|
||||
Assert.Equal(System.Net.HttpStatusCode.Unauthorized, result.StatusCode);
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
@ -1397,7 +1362,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Timeout = 60 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanSetPaymentMethodLimits()
|
||||
{
|
||||
@ -1506,6 +1471,44 @@ donation:
|
||||
Assert.NotNull(donationInvoice);
|
||||
Assert.Equal("CAD", donationInvoice.Currency);
|
||||
Assert.Equal("donation", donationInvoice.ItemDesc);
|
||||
|
||||
foreach (var test in new[]
|
||||
{
|
||||
(Code: "EUR", ExpectedSymbol: "€", ExpectedDecimalSeparator: ",", ExpectedDivisibility: 2, ExpectedThousandSeparator: "\xa0", ExpectedPrefixed: false, ExpectedSymbolSpace: true),
|
||||
(Code: "INR", ExpectedSymbol: "₹", ExpectedDecimalSeparator: ".", ExpectedDivisibility: 2, ExpectedThousandSeparator: ",", ExpectedPrefixed: true, ExpectedSymbolSpace: true),
|
||||
(Code: "JPY", ExpectedSymbol: "¥", ExpectedDecimalSeparator: ".", ExpectedDivisibility: 0, ExpectedThousandSeparator: ",", ExpectedPrefixed: true, ExpectedSymbolSpace: false),
|
||||
(Code: "BTC", ExpectedSymbol: "BTC", ExpectedDecimalSeparator: ".", ExpectedDivisibility: 8, ExpectedThousandSeparator: ",", ExpectedPrefixed: false, ExpectedSymbolSpace: true),
|
||||
})
|
||||
{
|
||||
Logs.Tester.LogInformation($"Testing for {test.Code}");
|
||||
vmpos = Assert.IsType<UpdatePointOfSaleViewModel>(Assert.IsType<ViewResult>(apps.UpdatePointOfSale(appId).Result).Model);
|
||||
vmpos.Title = "hello";
|
||||
vmpos.Currency = test.Item1;
|
||||
vmpos.ButtonText = "{0} Purchase";
|
||||
vmpos.CustomButtonText = "Nicolas Sexy Hair";
|
||||
vmpos.CustomTipText = "Wanna tip?";
|
||||
vmpos.Template = @"
|
||||
apple:
|
||||
price: 1000.0
|
||||
title: good apple
|
||||
orange:
|
||||
price: 10.0
|
||||
donation:
|
||||
price: 1.02
|
||||
custom: true
|
||||
";
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(appId, vmpos).Result);
|
||||
publicApps = user.GetController<AppsPublicController>();
|
||||
vmview = Assert.IsType<ViewPointOfSaleViewModel>(Assert.IsType<ViewResult>(publicApps.ViewPointOfSale(appId).Result).Model);
|
||||
Assert.Equal(test.Code, vmview.CurrencyCode);
|
||||
Assert.Equal(test.ExpectedSymbol, vmview.CurrencySymbol.Replace("¥", "¥")); // Hack so JPY test pass on linux as well);
|
||||
Assert.Equal(test.ExpectedSymbol, vmview.CurrencyInfo.CurrencySymbol.Replace("¥", "¥")); // Hack so JPY test pass on linux as well);
|
||||
Assert.Equal(test.ExpectedDecimalSeparator, vmview.CurrencyInfo.DecimalSeparator);
|
||||
Assert.Equal(test.ExpectedThousandSeparator, vmview.CurrencyInfo.ThousandSeparator);
|
||||
Assert.Equal(test.ExpectedPrefixed, vmview.CurrencyInfo.Prefixed);
|
||||
Assert.Equal(test.ExpectedDivisibility, vmview.CurrencyInfo.Divisibility);
|
||||
Assert.Equal(test.ExpectedSymbolSpace, vmview.CurrencyInfo.SymbolSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1619,10 +1622,9 @@ donation:
|
||||
var jsonResultPaid = user.GetController<InvoiceController>().Export("json").GetAwaiter().GetResult();
|
||||
var paidresult = Assert.IsType<ContentResult>(jsonResultPaid);
|
||||
Assert.Equal("application/json", paidresult.ContentType);
|
||||
Assert.Contains("\"ItemDesc\": \"Some \\\", description\"", paidresult.Content);
|
||||
Assert.Contains("\"FiatPrice\": 500.0", paidresult.Content);
|
||||
Assert.Contains("\"InvoiceItemDesc\": \"Some \\\", description\"", paidresult.Content);
|
||||
Assert.Contains("\"InvoicePrice\": 500.0", paidresult.Content);
|
||||
Assert.Contains("\"ConversionRate\": 5000.0", paidresult.Content);
|
||||
Assert.Contains("\"PaymentDue\": \"0.10020000 BTC\"", paidresult.Content);
|
||||
Assert.Contains($"\"InvoiceId\": \"{invoice.Id}\",", paidresult.Content);
|
||||
});
|
||||
|
||||
@ -1687,14 +1689,9 @@ donation:
|
||||
var paidresult = Assert.IsType<ContentResult>(exportResultPaid);
|
||||
Assert.Equal("application/csv", paidresult.ContentType);
|
||||
Assert.Contains($",\"orderId\",\"{invoice.Id}\",", paidresult.Content);
|
||||
Assert.Contains($",\"OnChain\",\"0.10020000 BTC\",\"0.10009990 BTC\",\"0.00000000 BTC\",\"5000.0\",\"500.0\"", paidresult.Content);
|
||||
Assert.Contains($",\"USD\",\"\",\"Some ``, description\",\"new\"", paidresult.Content);
|
||||
Assert.Contains($",\"OnChain\",\"0.1000999\",\"BTC\",\"5000.0\",\"500.0\"", paidresult.Content);
|
||||
Assert.Contains($",\"USD\",\"\",\"Some ``, description\",\"new (paidPartial)\"", paidresult.Content);
|
||||
});
|
||||
|
||||
/*
|
||||
ReceivedDate,StoreId,OrderId,InvoiceId,CreatedDate,ExpirationDate,MonitoringDate,PaymentId,CryptoCode,Destination,PaymentType,PaymentDue,PaymentPaid,PaymentOverpaid,ConversionRate,FiatPrice,FiatCurrency,ItemCode,ItemDesc,Status
|
||||
"11/30/2018 10:28:42 AM","7AagXzWdWhLLR3Zar25YLiw2uHAJDzVT4oXGKC9bBCis","orderId","GxtJsWbgxxAXXoCurqyeK6","11/30/2018 10:28:40 AM","11/30/2018 10:43:40 AM","11/30/2018 11:43:40 AM","ec0341537f565d213bc64caa352fbbf9e0deb31cab1f91bccf89db0dd1604457-0","BTC","mqWghCp9RVw8fNgQMLjawyKStxpGfWBk1L","OnChain","0.10020000 BTC","0.10009990 BTC","0.00000000 BTC","5000.0","500.0","USD","","Some ``, description","new"
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -1801,7 +1798,7 @@ ReceivedDate,StoreId,OrderId,InvoiceId,CreatedDate,ExpirationDate,MonitoringDate
|
||||
Assert.True(IsMapped(invoice, ctx));
|
||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||
|
||||
var invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult();
|
||||
var invoiceEntity = repo.GetInvoice(invoice.Id, true).GetAwaiter().GetResult();
|
||||
Assert.Single(invoiceEntity.HistoricalAddresses);
|
||||
Assert.Null(invoiceEntity.HistoricalAddresses[0].UnAssigned);
|
||||
|
||||
@ -1819,7 +1816,7 @@ ReceivedDate,StoreId,OrderId,InvoiceId,CreatedDate,ExpirationDate,MonitoringDate
|
||||
Assert.True(IsMapped(invoice, ctx));
|
||||
Assert.True(IsMapped(localInvoice, ctx));
|
||||
|
||||
invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult();
|
||||
invoiceEntity = repo.GetInvoice(invoice.Id, true).GetAwaiter().GetResult();
|
||||
var historical1 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.GetAddress() == invoice.BitcoinAddress);
|
||||
Assert.NotNull(historical1.UnAssigned);
|
||||
var historical2 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.GetAddress() == localInvoice.BitcoinAddress);
|
||||
|
@ -36,8 +36,6 @@ namespace BTCPayServer.Tests
|
||||
Task.WaitAll(langs.Select(async l =>
|
||||
{
|
||||
bool isSourceLang = l == "en";
|
||||
if (l == "no")
|
||||
return;
|
||||
var j = await client.GetTransifexAsync($"https://www.transifex.com/api/2/project/btcpayserver/resource/enjson/translation/{l}/");
|
||||
if(!isSourceLang)
|
||||
{
|
||||
@ -56,8 +54,12 @@ namespace BTCPayServer.Tests
|
||||
var langFile = Path.Combine(langsDir, langCode + ".json");
|
||||
var jobj = JObject.Parse(content);
|
||||
jobj["code"] = langCode;
|
||||
|
||||
if ((string)jobj["currentLanguage"] == "English" && !isSourceLang)
|
||||
return; // Not translated
|
||||
if ((string)jobj["currentLanguage"] == "disable")
|
||||
return; // Not translated
|
||||
|
||||
jobj.AddFirst(new JProperty("NOTICE_WARN", "THIS CODE HAS BEEN AUTOMATICALLY GENERATED FROM TRANSIFEX, IF YOU WISH TO HELP TRANSLATION COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/"));
|
||||
if (isSourceLang)
|
||||
{
|
||||
|
@ -19,10 +19,10 @@ services:
|
||||
TESTS_MYSQL: User ID=root;Host=mysql;Port=3306;Database=btcpayserver
|
||||
TESTS_PORT: 80
|
||||
TESTS_HOSTNAME: tests
|
||||
TEST_MERCHANTLIGHTNINGD: "type=clightning;server=/etc/merchant_lightningd_datadir/lightning-rpc"
|
||||
TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=/etc/customer_lightningd_datadir/lightning-rpc"
|
||||
TEST_MERCHANTCHARGE: "type=charge;server=https://lightning-charged:9112/;api-token=foiewnccewuify;allowinsecure=true"
|
||||
TEST_MERCHANTLND: "type=lnd-rest;server=https://lnd:lnd@127.0.0.1:53280/;allowinsecure=true"
|
||||
TEST_MERCHANTLIGHTNINGD: "type=clightning;server=unix://etc/merchant_lightningd_datadir/lightning-rpc"
|
||||
TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=unix://etc/customer_lightningd_datadir/lightning-rpc"
|
||||
TEST_MERCHANTCHARGE: "type=charge;server=http://lightning-charged:9112/;api-token=foiewnccewuify"
|
||||
TEST_MERCHANTLND: "https://lnd:lnd@merchant_lnd:8080/"
|
||||
TESTS_INCONTAINER: "true"
|
||||
expose:
|
||||
- "80"
|
||||
@ -36,7 +36,7 @@ services:
|
||||
|
||||
# The dev container is not actually used, it is just handy to run `docker-compose up dev` to start all services
|
||||
dev:
|
||||
image: nicolasdorier/docker-bitcoin:0.17.0
|
||||
image: btcpayserver/bitcoin:0.17.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_EXTRA_ARGS: |
|
||||
@ -53,7 +53,7 @@ services:
|
||||
- merchant_lnd
|
||||
|
||||
devlnd:
|
||||
image: nicolasdorier/docker-bitcoin:0.17.0
|
||||
image: btcpayserver/bitcoin:0.17.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_EXTRA_ARGS: |
|
||||
@ -93,12 +93,13 @@ services:
|
||||
- bitcoind
|
||||
- litecoind
|
||||
|
||||
|
||||
bitcoind:
|
||||
image: nicolasdorier/docker-bitcoin:0.17.0
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/bitcoin:0.17.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_EXTRA_ARGS: |
|
||||
deprecatedrpc=signrawtransaction
|
||||
BITCOIN_EXTRA_ARGS: |-
|
||||
rpcuser=ceiwHEbqWI83
|
||||
rpcpassword=DwubwWsoo3
|
||||
rpcport=43782
|
||||
@ -106,9 +107,9 @@ services:
|
||||
whitelist=0.0.0.0/0
|
||||
zmqpubrawblock=tcp://0.0.0.0:28332
|
||||
zmqpubrawtx=tcp://0.0.0.0:28333
|
||||
deprecatedrpc=signrawtransaction
|
||||
ports:
|
||||
- "43782:43782"
|
||||
- "28332:28332"
|
||||
expose:
|
||||
- "43782" # RPC
|
||||
- "39388" # P2P
|
||||
@ -118,7 +119,7 @@ services:
|
||||
- "bitcoin_datadir:/data"
|
||||
|
||||
customer_lightningd:
|
||||
image: nicolasdorier/clightning:v0.6.2-3-dev
|
||||
image: btcpayserver/lightning:v0.6.2-dev
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
@ -144,7 +145,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
lightning-charged:
|
||||
image: shesek/lightning-charge:0.4.3
|
||||
image: shesek/lightning-charge:0.4.6-standalone
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NETWORK: regtest
|
||||
@ -164,7 +165,7 @@ services:
|
||||
- merchant_lightningd
|
||||
|
||||
merchant_lightningd:
|
||||
image: nicolasdorier/clightning:v0.6.2-3-dev
|
||||
image: btcpayserver/lightning:v0.6.2-dev
|
||||
stop_signal: SIGKILL
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
@ -188,13 +189,13 @@ services:
|
||||
- bitcoind
|
||||
|
||||
litecoind:
|
||||
image: nicolasdorier/docker-litecoin:0.15.1
|
||||
restart: unless-stopped
|
||||
image: nicolasdorier/docker-litecoin:0.16.3
|
||||
environment:
|
||||
BITCOIN_EXTRA_ARGS: |
|
||||
BITCOIN_EXTRA_ARGS: |-
|
||||
rpcuser=ceiwHEbqWI83
|
||||
rpcpassword=DwubwWsoo3
|
||||
regtest=1
|
||||
server=1
|
||||
rpcport=43782
|
||||
port=39388
|
||||
whitelist=0.0.0.0/0
|
||||
@ -221,13 +222,16 @@ services:
|
||||
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:0.5-beta-2
|
||||
image: btcpayserver/lnd:v0.5.1-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
LND_ENVIRONMENT: "regtest"
|
||||
LND_EXPLORERURL: "http://nbxplorer:32838/"
|
||||
LND_EXTRA_ARGS: |
|
||||
restlisten=0.0.0.0:8080
|
||||
rpclisten=127.0.0.1:10008
|
||||
rpclisten=0.0.0.0:10009
|
||||
bitcoin.node=bitcoind
|
||||
bitcoind.rpchost=bitcoind:43782
|
||||
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
|
||||
@ -248,13 +252,16 @@ services:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:0.5-beta-2
|
||||
image: btcpayserver/lnd:v0.5.1-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
LND_ENVIRONMENT: "regtest"
|
||||
LND_EXPLORERURL: "http://nbxplorer:32838/"
|
||||
LND_EXTRA_ARGS: |
|
||||
restlisten=0.0.0.0:8080
|
||||
rpclisten=127.0.0.1:10008
|
||||
rpclisten=0.0.0.0:10009
|
||||
bitcoin.node=bitcoind
|
||||
bitcoind.rpchost=bitcoind:43782
|
||||
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
|
||||
|
5
BTCPayServer.Tests/docker-entrypoint.sh
Executable file
5
BTCPayServer.Tests/docker-entrypoint.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
dotnet test --filter Fast=Fast --no-build
|
||||
dotnet test --filter Integration=Integration --no-build -v n
|
@ -1,3 +1,5 @@
|
||||
{
|
||||
"parallelizeTestCollections": false
|
||||
}
|
||||
"parallelizeTestCollections": false,
|
||||
"longRunningTestSeconds": 60,
|
||||
"diagnosticMessages": true
|
||||
}
|
||||
|
@ -242,6 +242,8 @@ namespace BTCPayServer.Authentication
|
||||
using (var ctx = _Factory.CreateContext())
|
||||
{
|
||||
var token = await ctx.PairedSINData.FindAsync(tokenId);
|
||||
if (token == null)
|
||||
return null;
|
||||
return CreateTokenEntity(token);
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ namespace BTCPayServer
|
||||
"GRS_BTC = bittrex(GRS_BTC)"
|
||||
},
|
||||
CryptoImagePath = "imlegacy/groestlcoin.png",
|
||||
LightningImagePath = "imlegacy/groestlcoin-lightning.png",
|
||||
LightningImagePath = "imlegacy/groestlcoin-lightning.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("17'") : new KeyPath("1'")
|
||||
});
|
||||
|
@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<Version>1.0.3.24</Version>
|
||||
<Version>1.0.3.35</Version>
|
||||
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
@ -33,7 +33,7 @@
|
||||
<EmbeddedResource Include="Currencies.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.0.2" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.0.4" />
|
||||
<PackageReference Include="BuildBundlerMinifier" Version="2.7.385" />
|
||||
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.5.3" />
|
||||
<PackageReference Include="Hangfire" Version="1.6.20" />
|
||||
@ -46,7 +46,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NBitcoin" Version="4.1.1.72" />
|
||||
<PackageReference Include="NBitcoin" Version="4.1.1.73" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.30" />
|
||||
<PackageReference Include="DBreeze" Version="1.92.0" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="2.0.0.1" />
|
||||
@ -136,7 +136,10 @@
|
||||
<Content Update="Views\Home\BitpayTranslator.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\LndRestServices.cshtml">
|
||||
<Content Update="Views\Server\LightningChargeServices.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\SparkServices.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\SSHService.cshtml">
|
||||
@ -154,7 +157,7 @@
|
||||
<Content Update="Views\Public\PayButtonHandle.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\LndGrpcServices.cshtml">
|
||||
<Content Update="Views\Server\LndServices.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\Maintenance.cshtml">
|
||||
|
@ -37,8 +37,8 @@ namespace BTCPayServer.Configuration
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public string LogFile
|
||||
{
|
||||
get;
|
||||
@ -68,7 +68,7 @@ namespace BTCPayServer.Configuration
|
||||
public static LogEventLevel GetDebugLogLevel(IConfiguration configuration)
|
||||
{
|
||||
var raw = configuration.GetValue("debugloglevel", nameof(LogEventLevel.Debug));
|
||||
return (LogEventLevel)Enum.Parse(typeof(LogEventLevel), raw, true);
|
||||
return (LogEventLevel)Enum.Parse(typeof(LogEventLevel), raw, true);
|
||||
}
|
||||
|
||||
public void LoadArgs(IConfiguration conf)
|
||||
@ -137,10 +137,49 @@ namespace BTCPayServer.Configuration
|
||||
|
||||
externalLnd<ExternalLndGrpc>($"{net.CryptoCode}.external.lnd.grpc", "lnd-grpc");
|
||||
externalLnd<ExternalLndRest>($"{net.CryptoCode}.external.lnd.rest", "lnd-rest");
|
||||
|
||||
var spark = conf.GetOrDefault<string>($"{net.CryptoCode}.external.spark", string.Empty);
|
||||
if (spark.Length != 0)
|
||||
{
|
||||
if (!SparkConnectionString.TryParse(spark, out var connectionString))
|
||||
{
|
||||
throw new ConfigException($"Invalid setting {net.CryptoCode}.external.spark, " + Environment.NewLine +
|
||||
$"Valid example: 'server=https://btcpay.example.com/spark/btc/;cookiefile=/etc/clightning_bitcoin_spark/.cookie'");
|
||||
}
|
||||
ExternalServicesByCryptoCode.Add(net.CryptoCode, new ExternalSpark(connectionString));
|
||||
}
|
||||
|
||||
var charge = conf.GetOrDefault<string>($"{net.CryptoCode}.external.charge", string.Empty);
|
||||
if (charge.Length != 0)
|
||||
{
|
||||
if (!LightningConnectionString.TryParse(charge, false, out var chargeConnectionString, out var chargeError))
|
||||
LightningConnectionString.TryParse("type=charge;" + charge, false, out chargeConnectionString, out chargeError);
|
||||
|
||||
if(chargeConnectionString == null || chargeConnectionString.ConnectionType != LightningConnectionType.Charge)
|
||||
{
|
||||
throw new ConfigException($"Invalid setting {net.CryptoCode}.external.charge, " + Environment.NewLine +
|
||||
$"lightning charge server: 'type=charge;server=https://charge.example.com;api-token=2abdf302...'" + Environment.NewLine +
|
||||
$"lightning charge server: 'type=charge;server=https://charge.example.com;cookiefilepath=/root/.charge/.cookie'" + Environment.NewLine +
|
||||
chargeError ?? string.Empty);
|
||||
}
|
||||
ExternalServicesByCryptoCode.Add(net.CryptoCode, new ExternalCharge(chargeConnectionString));
|
||||
}
|
||||
}
|
||||
|
||||
Logs.Configuration.LogInformation("Supported chains: " + String.Join(',', supportedChains.ToArray()));
|
||||
|
||||
var services = conf.GetOrDefault<string>("externalservices", null);
|
||||
if (services != null)
|
||||
{
|
||||
foreach (var service in services.Split(new[] { ';', ',' })
|
||||
.Select(p => p.Split(':'))
|
||||
.Where(p => p.Length == 2)
|
||||
.Select(p => (Name: p[0], Link: p[1])))
|
||||
{
|
||||
ExternalServices.AddOrReplace(service.Name, service.Link);
|
||||
}
|
||||
}
|
||||
|
||||
PostgresConnectionString = conf.GetOrDefault<string>("postgres", null);
|
||||
MySQLConnectionString = conf.GetOrDefault<string>("mysql", null);
|
||||
BundleJsCss = conf.GetOrDefault<bool>("bundlejscss", true);
|
||||
@ -248,6 +287,8 @@ namespace BTCPayServer.Configuration
|
||||
|
||||
public string RootPath { get; set; }
|
||||
public Dictionary<string, LightningConnectionString> InternalLightningByCryptoCode { get; set; } = new Dictionary<string, LightningConnectionString>();
|
||||
public Dictionary<string, string> ExternalServices { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
public ExternalServices ExternalServicesByCryptoCode { get; set; } = new ExternalServices();
|
||||
|
||||
public BTCPayNetworkProvider NetworkProvider { get; set; }
|
||||
|
@ -33,6 +33,7 @@ namespace BTCPayServer.Configuration
|
||||
app.Option("--postgres", $"Connection string to a PostgreSQL database (default: SQLite)", CommandOptionType.SingleValue);
|
||||
app.Option("--mysql", $"Connection string to a MySQL database (default: SQLite)", CommandOptionType.SingleValue);
|
||||
app.Option("--externalurl", $"The expected external URL of this service, to use if BTCPay is behind a reverse proxy (default: empty, use the incoming HTTP request to figure out)", CommandOptionType.SingleValue);
|
||||
app.Option("--externalservices", $"Links added to external services inside Server Settings / Services under the format service1:path2;service2:path2.(default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--bundlejscss", $"Bundle JavaScript and CSS files for better performance (default: true)", CommandOptionType.SingleValue);
|
||||
app.Option("--rootpath", "The root path in the URL to access BTCPay (default: /)", CommandOptionType.SingleValue);
|
||||
app.Option("--sshconnection", "SSH server to manage BTCPay under the form user@server:port (default: root@externalhost or empty)", CommandOptionType.SingleValue);
|
||||
@ -49,6 +50,8 @@ namespace BTCPayServer.Configuration
|
||||
app.Option($"--{crypto}explorercookiefile", $"Path to the cookie file (default: {network.NBXplorerNetwork.DefaultSettings.DefaultCookieFile})", CommandOptionType.SingleValue);
|
||||
app.Option($"--{crypto}lightning", $"Easy configuration of lightning for the server administrator: Must be a UNIX socket of c-lightning (lightning-rpc) or URL to a charge server (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option($"--{crypto}externallndgrpc", $"The LND gRPC configuration BTCPay will expose to easily connect to the internal lnd wallet from Zap wallet (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option($"--{crypto}externalspark", $"Show spark information in Server settings / Server. The connection string to spark server (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option($"--{crypto}externalcharge", $"Show lightning charge information in Server settings/Server. The connection string to charge server (default: empty)", CommandOptionType.SingleValue);
|
||||
}
|
||||
return app;
|
||||
}
|
||||
|
19
BTCPayServer/Configuration/External/ExternalCharge.cs
vendored
Normal file
19
BTCPayServer/Configuration/External/ExternalCharge.cs
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Lightning;
|
||||
|
||||
namespace BTCPayServer.Configuration.External
|
||||
{
|
||||
public class ExternalCharge : ExternalService
|
||||
{
|
||||
public ExternalCharge(LightningConnectionString connectionString)
|
||||
{
|
||||
if (connectionString == null)
|
||||
throw new ArgumentNullException(nameof(connectionString));
|
||||
ConnectionString = connectionString;
|
||||
}
|
||||
public LightningConnectionString ConnectionString { get; }
|
||||
}
|
||||
}
|
@ -8,28 +8,23 @@ namespace BTCPayServer.Configuration.External
|
||||
{
|
||||
public abstract class ExternalLnd : ExternalService
|
||||
{
|
||||
public ExternalLnd(LightningConnectionString connectionString, LndTypes type)
|
||||
public ExternalLnd(LightningConnectionString connectionString, string type)
|
||||
{
|
||||
ConnectionString = connectionString;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public LndTypes Type { get; set; }
|
||||
public string Type { get; set; }
|
||||
public LightningConnectionString ConnectionString { get; set; }
|
||||
}
|
||||
|
||||
public enum LndTypes
|
||||
{
|
||||
gRPC, Rest
|
||||
}
|
||||
|
||||
public class ExternalLndGrpc : ExternalLnd
|
||||
{
|
||||
public ExternalLndGrpc(LightningConnectionString connectionString) : base(connectionString, LndTypes.gRPC) { }
|
||||
public ExternalLndGrpc(LightningConnectionString connectionString) : base(connectionString, "lnd-grpc") { }
|
||||
}
|
||||
|
||||
public class ExternalLndRest : ExternalLnd
|
||||
{
|
||||
public ExternalLndRest(LightningConnectionString connectionString) : base(connectionString, LndTypes.Rest) { }
|
||||
public ExternalLndRest(LightningConnectionString connectionString) : base(connectionString, "lnd-rest") { }
|
||||
}
|
||||
}
|
||||
|
19
BTCPayServer/Configuration/External/ExternalSpark.cs
vendored
Normal file
19
BTCPayServer/Configuration/External/ExternalSpark.cs
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Configuration.External
|
||||
{
|
||||
public class ExternalSpark : ExternalService
|
||||
{
|
||||
public SparkConnectionString ConnectionString { get; }
|
||||
|
||||
public ExternalSpark(SparkConnectionString connectionString)
|
||||
{
|
||||
if (connectionString == null)
|
||||
throw new ArgumentNullException(nameof(connectionString));
|
||||
ConnectionString = connectionString;
|
||||
}
|
||||
}
|
||||
}
|
47
BTCPayServer/Configuration/SparkConnectionString.cs
Normal file
47
BTCPayServer/Configuration/SparkConnectionString.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Configuration
|
||||
{
|
||||
public class SparkConnectionString
|
||||
{
|
||||
public Uri Server { get; private set; }
|
||||
public string CookeFile { get; private set; }
|
||||
|
||||
public static bool TryParse(string str, out SparkConnectionString result)
|
||||
{
|
||||
if (str == null)
|
||||
throw new ArgumentNullException(nameof(str));
|
||||
|
||||
result = null;
|
||||
var resultTemp = new SparkConnectionString();
|
||||
foreach(var kv in str.Split(';')
|
||||
.Select(part => part.Split('='))
|
||||
.Where(kv => kv.Length == 2))
|
||||
{
|
||||
switch (kv[0].ToLowerInvariant())
|
||||
{
|
||||
case "server":
|
||||
if (resultTemp.Server != null)
|
||||
return false;
|
||||
if (!Uri.IsWellFormedUriString(kv[1], UriKind.Absolute))
|
||||
return false;
|
||||
resultTemp.Server = new Uri(kv[1], UriKind.Absolute);
|
||||
break;
|
||||
case "cookiefile":
|
||||
case "cookiefilepath":
|
||||
if (resultTemp.CookeFile != null)
|
||||
return false;
|
||||
resultTemp.CookeFile = kv[1];
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
result = resultTemp;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Apps;
|
||||
@ -32,31 +33,33 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpGet]
|
||||
[Route("/apps/{appId}/pos")]
|
||||
[XFrameOptionsAttribute(null)]
|
||||
public async Task<IActionResult> ViewPointOfSale(string appId)
|
||||
{
|
||||
var app = await _AppsHelper.GetApp(appId, AppType.PointOfSale);
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
var settings = app.GetSettings<PointOfSaleSettings>();
|
||||
var currency = _AppsHelper.GetCurrencyData(settings.Currency, false);
|
||||
double step = currency == null ? 1 : Math.Pow(10, -(currency.Divisibility));
|
||||
|
||||
var numberFormatInfo = _AppsHelper.Currencies.GetNumberFormatInfo(currency.Code) ?? _AppsHelper.Currencies.GetNumberFormatInfo("USD");
|
||||
var numberFormatInfo = _AppsHelper.Currencies.GetNumberFormatInfo(settings.Currency) ?? _AppsHelper.Currencies.GetNumberFormatInfo("USD");
|
||||
double step = Math.Pow(10, -(numberFormatInfo.CurrencyDecimalDigits));
|
||||
|
||||
return View(new ViewPointOfSaleViewModel()
|
||||
{
|
||||
Title = settings.Title,
|
||||
Step = step.ToString(CultureInfo.InvariantCulture),
|
||||
EnableShoppingCart = settings.EnableShoppingCart,
|
||||
ShowCustomAmount = settings.ShowCustomAmount,
|
||||
CurrencyCode = currency.Code,
|
||||
CurrencySymbol = currency.Symbol,
|
||||
CurrencyCode = settings.Currency,
|
||||
CurrencySymbol = numberFormatInfo.CurrencySymbol,
|
||||
CurrencyInfo = new ViewPointOfSaleViewModel.CurrencyInfoData()
|
||||
{
|
||||
CurrencySymbol = string.IsNullOrEmpty(currency.Symbol) ? currency.Code : currency.Symbol,
|
||||
Divisibility = currency.Divisibility,
|
||||
CurrencySymbol = string.IsNullOrEmpty(numberFormatInfo.CurrencySymbol) ? settings.Currency : numberFormatInfo.CurrencySymbol,
|
||||
Divisibility = numberFormatInfo.CurrencyDecimalDigits,
|
||||
DecimalSeparator = numberFormatInfo.CurrencyDecimalSeparator,
|
||||
ThousandSeparator = numberFormatInfo.NumberGroupSeparator,
|
||||
Prefixed = new[] { 0, 2 }.Contains(numberFormatInfo.CurrencyPositivePattern)
|
||||
Prefixed = new[] { 0, 2 }.Contains(numberFormatInfo.CurrencyPositivePattern),
|
||||
SymbolSpace = new[] { 2, 3 }.Contains(numberFormatInfo.CurrencyPositivePattern)
|
||||
},
|
||||
Items = _AppsHelper.Parse(settings.Template, settings.Currency),
|
||||
ButtonText = settings.ButtonText,
|
||||
|
@ -50,6 +50,7 @@ namespace BTCPayServer.Controllers
|
||||
try
|
||||
{
|
||||
BitcoinUrlBuilder urlBuilder = new BitcoinUrlBuilder(vm.BitpayLink);
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (!urlBuilder.PaymentRequestUrl.DnsSafeHost.EndsWith("bitpay.com", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new Exception("This tool only work with bitpay");
|
||||
@ -57,6 +58,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
var client = HttpClientFactory.CreateClient();
|
||||
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, urlBuilder.PaymentRequestUrl);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/payment-request"));
|
||||
var result = await client.SendAsync(request);
|
||||
// {"network":"main","currency":"BTC","requiredFeeRate":29.834,"outputs":[{"amount":255900,"address":"1PgPo5d4swD6pKfCgoXtoW61zqTfX9H7tj"}],"time":"2018-12-03T14:39:47.162Z","expires":"2018-12-03T14:54:47.162Z","memo":"Payment request for BitPay invoice HHfG8cprRMzZG6MErCqbjv for merchant VULTR Holdings LLC","paymentUrl":"https://bitpay.com/i/HHfG8cprRMzZG6MErCqbjv","paymentId":"HHfG8cprRMzZG6MErCqbjv"}
|
||||
|
@ -40,16 +40,18 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpGet]
|
||||
[Route("invoices/{id}")]
|
||||
[AllowAnonymous]
|
||||
public async Task<DataWrapper<InvoiceResponse>> GetInvoice(string id, string token)
|
||||
public async Task<DataWrapper<InvoiceResponse>> GetInvoice(string id)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, id);
|
||||
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
InvoiceId = id,
|
||||
StoreId = new[] { HttpContext.GetStoreData().Id }
|
||||
})).FirstOrDefault();
|
||||
if (invoice == null)
|
||||
throw new BitpayHttpException(404, "Object not found");
|
||||
var resp = invoice.EntityToDTO(_NetworkProvider);
|
||||
return new DataWrapper<InvoiceResponse>(resp);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("invoices")]
|
||||
public async Task<DataWrapper<InvoiceResponse[]>> GetInvoices(
|
||||
|
@ -1,117 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Payments;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Payment;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class InvoiceController
|
||||
{
|
||||
[HttpGet]
|
||||
[Route("i/{invoiceId}/{cryptoCode?}")]
|
||||
[AcceptMediaTypeConstraint("application/bitcoin-paymentrequest")]
|
||||
public async Task<IActionResult> GetInvoiceRequest(string invoiceId, string cryptoCode = null)
|
||||
{
|
||||
if (cryptoCode == null)
|
||||
cryptoCode = "BTC";
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
var network = _NetworkProvider.GetNetwork(cryptoCode);
|
||||
var paymentMethodId = new PaymentMethodId(cryptoCode, Payments.PaymentTypes.BTCLike);
|
||||
if (invoice == null || invoice.IsExpired() || network == null || !invoice.Support(paymentMethodId))
|
||||
return NotFound();
|
||||
|
||||
var dto = invoice.EntityToDTO(_NetworkProvider);
|
||||
var paymentMethod = dto.CryptoInfo.First(c => c.GetpaymentMethodId() == paymentMethodId);
|
||||
PaymentRequest request = new PaymentRequest
|
||||
{
|
||||
DetailsVersion = 1
|
||||
};
|
||||
request.Details.Expires = invoice.ExpirationTime;
|
||||
request.Details.Memo = invoice.ProductInformation.ItemDesc;
|
||||
request.Details.Network = network.NBitcoinNetwork;
|
||||
request.Details.Outputs.Add(new PaymentOutput() { Amount = paymentMethod.Due, Script = BitcoinAddress.Create(paymentMethod.Address, network.NBitcoinNetwork).ScriptPubKey });
|
||||
request.Details.MerchantData = Encoding.UTF8.GetBytes(invoice.Id);
|
||||
request.Details.Time = DateTimeOffset.UtcNow;
|
||||
request.Details.PaymentUrl = new Uri(invoice.ServerUrl.WithTrailingSlash() + ($"i/{invoice.Id}"), UriKind.Absolute);
|
||||
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
if (store == null)
|
||||
throw new BitpayHttpException(401, "Unknown store");
|
||||
|
||||
if (store.StoreCertificate != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
request.Sign(store.StoreCertificate, PKIType.X509SHA256);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logs.PayServer.LogWarning(ex, "Error while signing payment request");
|
||||
}
|
||||
}
|
||||
|
||||
return new PaymentRequestActionResult(request);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("i/{invoiceId}", Order = 99)]
|
||||
[Route("i/{invoiceId}/{cryptoCode}", Order = 99)]
|
||||
[MediaTypeConstraint("application/bitcoin-payment")]
|
||||
public async Task<IActionResult> PostPayment(string invoiceId, string cryptoCode = null)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if (cryptoCode == null)
|
||||
cryptoCode = "BTC";
|
||||
var network = _NetworkProvider.GetNetwork(cryptoCode);
|
||||
if (network == null || invoice == null || invoice.IsExpired() || !invoice.Support(new PaymentMethodId(cryptoCode, Payments.PaymentTypes.BTCLike)))
|
||||
return NotFound();
|
||||
|
||||
var wallet = _WalletProvider.GetWallet(network);
|
||||
if (wallet == null)
|
||||
return NotFound();
|
||||
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..."));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class PaymentRequestActionResult : IActionResult
|
||||
{
|
||||
PaymentRequest req;
|
||||
public PaymentRequestActionResult(PaymentRequest req)
|
||||
{
|
||||
this.req = req;
|
||||
}
|
||||
public Task ExecuteResultAsync(ActionContext context)
|
||||
{
|
||||
context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary";
|
||||
context.HttpContext.Response.ContentType = "application/bitcoin-paymentrequest";
|
||||
req.WriteTo(context.HttpContext.Response.Body);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
public class PaymentAckActionResult : IActionResult
|
||||
{
|
||||
PaymentACK req;
|
||||
public PaymentAckActionResult(PaymentACK req)
|
||||
{
|
||||
this.req = req;
|
||||
}
|
||||
public Task ExecuteResultAsync(ActionContext context)
|
||||
{
|
||||
context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary";
|
||||
context.HttpContext.Response.ContentType = "application/bitcoin-paymentack";
|
||||
req.WriteTo(context.HttpContext.Response.Body);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Changelly;
|
||||
@ -30,11 +31,13 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
[HttpGet]
|
||||
[Route("invoices/{invoiceId}")]
|
||||
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
|
||||
public async Task<IActionResult> Invoice(string invoiceId)
|
||||
{
|
||||
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
InvoiceId = invoiceId,
|
||||
UserId = GetUserId(),
|
||||
IncludeAddresses = true,
|
||||
IncludeEvents = true
|
||||
})).FirstOrDefault();
|
||||
@ -48,7 +51,7 @@ namespace BTCPayServer.Controllers
|
||||
StoreName = store.StoreName,
|
||||
StoreLink = Url.Action(nameof(StoresController.UpdateStore), "Stores", new { storeId = store.Id }),
|
||||
Id = invoice.Id,
|
||||
Status = invoice.Status,
|
||||
State = invoice.GetInvoiceState().ToString(),
|
||||
TransactionSpeed = invoice.SpeedPolicy == SpeedPolicy.HighSpeed ? "high" :
|
||||
invoice.SpeedPolicy == SpeedPolicy.MediumSpeed ? "medium" :
|
||||
invoice.SpeedPolicy == SpeedPolicy.LowMediumSpeed ? "low-medium" :
|
||||
@ -100,7 +103,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var m = new InvoiceDetailsModel.Payment();
|
||||
m.Crypto = payment.GetPaymentMethodId().CryptoCode;
|
||||
m.DepositAddress = onChainPaymentData.Output.ScriptPubKey.GetDestinationAddress(paymentNetwork.NBitcoinNetwork);
|
||||
m.DepositAddress = onChainPaymentData.GetDestination(paymentNetwork);
|
||||
|
||||
int confirmationCount = 0;
|
||||
if ((onChainPaymentData.ConfirmationCount < paymentNetwork.MaxTrackedConfirmation && payment.Accounted)
|
||||
@ -209,7 +212,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
private async Task<PaymentModel> GetInvoiceModel(string invoiceId, string paymentMethodIdStr)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
||||
if (invoice == null)
|
||||
return null;
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
@ -298,7 +301,9 @@ namespace BTCPayServer.Controllers
|
||||
throw new NotSupportedException(),
|
||||
TxCount = accounting.TxRequired,
|
||||
BtcPaid = accounting.Paid.ToString(),
|
||||
Status = invoice.Status,
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Status = invoice.StatusString,
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
NetworkFee = paymentMethodDetails.GetTxFee(),
|
||||
IsMultiCurrency = invoice.GetPayments().Select(p => p.GetPaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1,
|
||||
ChangellyEnabled = changelly != null,
|
||||
@ -369,8 +374,8 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (!HttpContext.WebSockets.IsWebSocketRequest)
|
||||
return NotFound();
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if (invoice == null || invoice.Status == "complete" || invoice.Status == "invalid" || invoice.Status == "expired")
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
||||
if (invoice == null || invoice.Status == InvoiceStatus.Complete || invoice.Status == InvoiceStatus.Invalid || invoice.Status == InvoiceStatus.Expired)
|
||||
return NotFound();
|
||||
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
||||
CompositeDisposable leases = new CompositeDisposable();
|
||||
@ -437,15 +442,18 @@ namespace BTCPayServer.Controllers
|
||||
var list = await ListInvoicesProcess(searchTerm, skip, count);
|
||||
foreach (var invoice in list)
|
||||
{
|
||||
var state = invoice.GetInvoiceState();
|
||||
model.Invoices.Add(new InvoiceModel()
|
||||
{
|
||||
Status = invoice.Status + (invoice.ExceptionStatus == null ? string.Empty : $" ({invoice.ExceptionStatus})"),
|
||||
ShowCheckout = invoice.Status == "new",
|
||||
Status = state.ToString(),
|
||||
ShowCheckout = invoice.Status == InvoiceStatus.New,
|
||||
Date = invoice.InvoiceTime,
|
||||
InvoiceId = invoice.Id,
|
||||
OrderId = invoice.OrderId ?? string.Empty,
|
||||
RedirectUrl = invoice.RedirectURL ?? string.Empty,
|
||||
AmountCurrency = $"{invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture)} {invoice.ProductInformation.Currency}"
|
||||
AmountCurrency = $"{invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture)} {invoice.ProductInformation.Currency}",
|
||||
CanMarkInvalid = state.CanMarkInvalid(),
|
||||
CanMarkComplete = state.CanMarkComplete()
|
||||
});
|
||||
}
|
||||
return View(model);
|
||||
@ -476,7 +484,7 @@ namespace BTCPayServer.Controllers
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> Export(string format, string searchTerm = null)
|
||||
{
|
||||
var model = new InvoiceExport();
|
||||
var model = new InvoiceExport(_NetworkProvider);
|
||||
|
||||
var invoices = await ListInvoicesProcess(searchTerm, 0, int.MaxValue);
|
||||
var res = model.Process(invoices, format);
|
||||
@ -585,17 +593,60 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("invoices/invalidatepaid")]
|
||||
[HttpGet]
|
||||
[Route("invoices/{invoiceId}/changestate/{newState}")]
|
||||
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> InvalidatePaidInvoice(string invoiceId)
|
||||
public IActionResult ChangeInvoiceState(string invoiceId, string newState)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if (newState == "invalid")
|
||||
{
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Action = "Make invoice invalid",
|
||||
Title = "Change invoice state",
|
||||
Description = $"You will transition the state of this invoice to \"invalid\", do you want to continue?",
|
||||
});
|
||||
}
|
||||
else if (newState == "complete")
|
||||
{
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Action = "Make invoice complete",
|
||||
Title = "Change invoice state",
|
||||
Description = $"You will transition the state of this invoice to \"complete\", do you want to continue?",
|
||||
ButtonClass = "btn-primary"
|
||||
});
|
||||
}
|
||||
else
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("invoices/{invoiceId}/changestate/{newState}")]
|
||||
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> ChangeInvoiceStateConfirm(string invoiceId, string newState)
|
||||
{
|
||||
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
InvoiceId = invoiceId,
|
||||
UserId = GetUserId()
|
||||
})).FirstOrDefault();
|
||||
if (invoice == null)
|
||||
return NotFound();
|
||||
await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoiceId);
|
||||
_EventAggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1008, "invoice_markedInvalid"));
|
||||
if (newState == "invalid")
|
||||
{
|
||||
await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoiceId);
|
||||
_EventAggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1008, "invoice_markedInvalid"));
|
||||
StatusMessage = "Invoice marked invalid";
|
||||
}
|
||||
else if(newState == "complete")
|
||||
{
|
||||
await _InvoiceRepository.UpdatePaidInvoiceToComplete(invoiceId);
|
||||
_EventAggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 2008, "invoice_markedComplete"));
|
||||
StatusMessage = "Invoice marked complete";
|
||||
}
|
||||
return RedirectToAction(nameof(ListInvoices));
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ namespace BTCPayServer.Controllers
|
||||
if (!Uri.IsWellFormedUriString(entity.RedirectURL, UriKind.Absolute))
|
||||
entity.RedirectURL = null;
|
||||
|
||||
entity.Status = "new";
|
||||
entity.Status = InvoiceStatus.New;
|
||||
entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);
|
||||
|
||||
HashSet<CurrencyPair> currencyPairsToFetch = new HashSet<CurrencyPair>();
|
||||
|
57
BTCPayServer/Controllers/Macaroons.cs
Normal file
57
BTCPayServer/Controllers/Macaroons.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public class Macaroons
|
||||
{
|
||||
public class Macaroon
|
||||
{
|
||||
public Macaroon(byte[] bytes)
|
||||
{
|
||||
Bytes = bytes;
|
||||
Hex = NBitcoin.DataEncoders.Encoders.Hex.EncodeData(bytes);
|
||||
}
|
||||
|
||||
public string Hex { get; set; }
|
||||
public byte[] Bytes { get; set; }
|
||||
}
|
||||
public static async Task<Macaroons> GetFromDirectoryAsync(string directoryPath)
|
||||
{
|
||||
if (directoryPath == null)
|
||||
throw new ArgumentNullException(nameof(directoryPath));
|
||||
Macaroons macaroons = new Macaroons();
|
||||
if (!Directory.Exists(directoryPath))
|
||||
return macaroons;
|
||||
foreach(var file in Directory.GetFiles(directoryPath, "*.macaroon"))
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (Path.GetFileName(file))
|
||||
{
|
||||
case "admin.macaroon":
|
||||
macaroons.AdminMacaroon = new Macaroon(await File.ReadAllBytesAsync(file));
|
||||
break;
|
||||
case "readonly.macaroon":
|
||||
macaroons.ReadonlyMacaroon = new Macaroon(await File.ReadAllBytesAsync(file));
|
||||
break;
|
||||
case "invoice.macaroon":
|
||||
macaroons.InvoiceMacaroon = new Macaroon(await File.ReadAllBytesAsync(file));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
return macaroons;
|
||||
}
|
||||
public Macaroon ReadonlyMacaroon { get; set; }
|
||||
|
||||
public Macaroon InvoiceMacaroon { get; set; }
|
||||
public Macaroon AdminMacaroon { get; set; }
|
||||
}
|
||||
}
|
@ -46,6 +46,7 @@ namespace BTCPayServer.Controllers
|
||||
RateFetcher rateProviderFactory,
|
||||
SettingsRepository settingsRepository,
|
||||
NBXplorerDashboard dashBoard,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
LightningConfigurationProvider lnConfigProvider,
|
||||
Services.Stores.StoreRepository storeRepository)
|
||||
{
|
||||
@ -53,6 +54,7 @@ namespace BTCPayServer.Controllers
|
||||
_UserManager = userManager;
|
||||
_SettingsRepository = settingsRepository;
|
||||
_dashBoard = dashBoard;
|
||||
HttpClientFactory = httpClientFactory;
|
||||
_RateProviderFactory = rateProviderFactory;
|
||||
_StoreRepository = storeRepository;
|
||||
_LnConfigProvider = lnConfigProvider;
|
||||
@ -395,6 +397,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public IHttpClientFactory HttpClientFactory { get; }
|
||||
|
||||
[Route("server/emails")]
|
||||
public async Task<IActionResult> Emails()
|
||||
@ -431,16 +434,126 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
Crypto = cryptoCode,
|
||||
Type = grpcService.Type,
|
||||
Action = nameof(LndServices),
|
||||
Index = i++,
|
||||
});
|
||||
}
|
||||
i = 0;
|
||||
foreach (var sparkService in _Options.ExternalServicesByCryptoCode.GetServices<ExternalSpark>(cryptoCode))
|
||||
{
|
||||
result.LNDServices.Add(new ServicesViewModel.LNDServiceViewModel()
|
||||
{
|
||||
Crypto = cryptoCode,
|
||||
Type = "Spark server",
|
||||
Action = nameof(SparkServices),
|
||||
Index = i++,
|
||||
});
|
||||
}
|
||||
foreach (var chargeService in _Options.ExternalServicesByCryptoCode.GetServices<ExternalCharge>(cryptoCode))
|
||||
{
|
||||
result.LNDServices.Add(new ServicesViewModel.LNDServiceViewModel()
|
||||
{
|
||||
Crypto = cryptoCode,
|
||||
Type = "Lightning charge server",
|
||||
Action = nameof(LightningChargeServices),
|
||||
Index = i++,
|
||||
});
|
||||
}
|
||||
}
|
||||
result.HasSSH = _Options.SSHSettings != null;
|
||||
foreach(var externalService in _Options.ExternalServices)
|
||||
{
|
||||
result.ExternalServices.Add(new ServicesViewModel.ExternalService()
|
||||
{
|
||||
Name = externalService.Key,
|
||||
Link = this.Request.GetRelativePath(externalService.Value)
|
||||
});
|
||||
}
|
||||
if(_Options.SSHSettings != null)
|
||||
{
|
||||
result.ExternalServices.Add(new ServicesViewModel.ExternalService()
|
||||
{
|
||||
Name = "SSH",
|
||||
Link = this.Url.Action(nameof(SSHService))
|
||||
});
|
||||
}
|
||||
return View(result);
|
||||
}
|
||||
|
||||
[Route("server/services/lnd-grpc/{cryptoCode}/{index}")]
|
||||
public IActionResult LndGrpcServices(string cryptoCode, int index, uint? nonce)
|
||||
[Route("server/services/lightning-charge/{cryptoCode}/{index}")]
|
||||
public async Task<IActionResult> LightningChargeServices(string cryptoCode, int index, bool showQR = false)
|
||||
{
|
||||
if (!_dashBoard.IsFullySynched(cryptoCode, out var unusud))
|
||||
{
|
||||
StatusMessage = $"Error: {cryptoCode} is not fully synched";
|
||||
return RedirectToAction(nameof(Services));
|
||||
}
|
||||
var lightningCharge = _Options.ExternalServicesByCryptoCode.GetServices<ExternalCharge>(cryptoCode).Select(c => c.ConnectionString).FirstOrDefault();
|
||||
if (lightningCharge == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
ChargeServiceViewModel vm = new ChargeServiceViewModel();
|
||||
vm.Uri = lightningCharge.ToUri(false).AbsoluteUri;
|
||||
vm.APIToken = lightningCharge.Password;
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(vm.APIToken) && lightningCharge.CookieFilePath != null)
|
||||
{
|
||||
if (lightningCharge.CookieFilePath != "fake")
|
||||
vm.APIToken = await System.IO.File.ReadAllTextAsync(lightningCharge.CookieFilePath);
|
||||
else
|
||||
vm.APIToken = "fake";
|
||||
}
|
||||
var builder = new UriBuilder(lightningCharge.ToUri(false));
|
||||
builder.UserName = "api-token";
|
||||
builder.Password = vm.APIToken;
|
||||
vm.AuthenticatedUri = builder.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = $"Error: {ex.Message}";
|
||||
return RedirectToAction(nameof(Services));
|
||||
}
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[Route("server/services/spark/{cryptoCode}/{index}")]
|
||||
public async Task<IActionResult> SparkServices(string cryptoCode, int index, bool showQR = false)
|
||||
{
|
||||
if (!_dashBoard.IsFullySynched(cryptoCode, out var unusud))
|
||||
{
|
||||
StatusMessage = $"Error: {cryptoCode} is not fully synched";
|
||||
return RedirectToAction(nameof(Services));
|
||||
}
|
||||
var spark = _Options.ExternalServicesByCryptoCode.GetServices<ExternalSpark>(cryptoCode).Select(c => c.ConnectionString).FirstOrDefault();
|
||||
if(spark == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
SparkServicesViewModel vm = new SparkServicesViewModel();
|
||||
vm.ShowQR = showQR;
|
||||
try
|
||||
{
|
||||
var cookie = (spark.CookeFile == "fake"
|
||||
? "fake:fake:fake" // If we are testing, it should not crash
|
||||
: await System.IO.File.ReadAllTextAsync(spark.CookeFile)).Split(':');
|
||||
if (cookie.Length >= 3)
|
||||
{
|
||||
vm.SparkLink = $"{spark.Server.AbsoluteUri}?access-key={cookie[2]}";
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
StatusMessage = $"Error: {ex.Message}";
|
||||
return RedirectToAction(nameof(Services));
|
||||
}
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[Route("server/services/lnd/{cryptoCode}/{index}")]
|
||||
public async Task<IActionResult> LndServices(string cryptoCode, int index, uint? nonce)
|
||||
{
|
||||
if (!_dashBoard.IsFullySynched(cryptoCode, out var unusud))
|
||||
{
|
||||
@ -451,9 +564,19 @@ namespace BTCPayServer.Controllers
|
||||
if (external == null)
|
||||
return NotFound();
|
||||
var model = new LndGrpcServicesViewModel();
|
||||
if (external.ConnectionType == LightningConnectionType.LndGRPC)
|
||||
{
|
||||
model.Host = $"{external.BaseUri.DnsSafeHost}:{external.BaseUri.Port}";
|
||||
model.SSL = external.BaseUri.Scheme == "https";
|
||||
model.ConnectionType = "GRPC";
|
||||
model.GRPCSSLCipherSuites = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256";
|
||||
}
|
||||
else if(external.ConnectionType == LightningConnectionType.LndREST)
|
||||
{
|
||||
model.Uri = external.BaseUri.AbsoluteUri;
|
||||
model.ConnectionType = "REST";
|
||||
}
|
||||
|
||||
model.Host = $"{external.BaseUri.DnsSafeHost}:{external.BaseUri.Port}";
|
||||
model.SSL = external.BaseUri.Scheme == "https";
|
||||
if (external.CertificateThumbprint != null)
|
||||
{
|
||||
model.CertificateThumbprint = Encoders.Hex.EncodeData(external.CertificateThumbprint);
|
||||
@ -462,10 +585,14 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
model.Macaroon = Encoders.Hex.EncodeData(external.Macaroon);
|
||||
}
|
||||
var macaroons = external.MacaroonDirectoryPath == null ? null : await Macaroons.GetFromDirectoryAsync(external.MacaroonDirectoryPath);
|
||||
model.AdminMacaroon = macaroons?.AdminMacaroon?.Hex;
|
||||
model.InvoiceMacaroon = macaroons?.InvoiceMacaroon?.Hex;
|
||||
model.ReadonlyMacaroon = macaroons?.ReadonlyMacaroon?.Hex;
|
||||
|
||||
if (nonce != null)
|
||||
{
|
||||
var configKey = GetConfigKey("lnd-grpc", cryptoCode, index, nonce.Value);
|
||||
var configKey = GetConfigKey("lnd", cryptoCode, index, nonce.Value);
|
||||
var lnConfig = _LnConfigProvider.GetConfig(configKey);
|
||||
if (lnConfig != null)
|
||||
{
|
||||
@ -492,29 +619,46 @@ namespace BTCPayServer.Controllers
|
||||
return Json(conf);
|
||||
}
|
||||
|
||||
[Route("server/services/lnd-grpc/{cryptoCode}/{index}")]
|
||||
[Route("server/services/lnd/{cryptoCode}/{index}")]
|
||||
[HttpPost]
|
||||
public IActionResult LndGrpcServicesPost(string cryptoCode, int index)
|
||||
public async Task<IActionResult> LndServicesPost(string cryptoCode, int index)
|
||||
{
|
||||
var external = GetExternalLndConnectionString(cryptoCode, index);
|
||||
if (external == null)
|
||||
return NotFound();
|
||||
LightningConfigurations confs = new LightningConfigurations();
|
||||
LightningConfiguration conf = new LightningConfiguration();
|
||||
conf.Type = "grpc";
|
||||
conf.ChainType = _Options.NetworkType.ToString();
|
||||
conf.CryptoCode = cryptoCode;
|
||||
conf.Host = external.BaseUri.DnsSafeHost;
|
||||
conf.Port = external.BaseUri.Port;
|
||||
conf.SSL = external.BaseUri.Scheme == "https";
|
||||
conf.Macaroon = external.Macaroon == null ? null : Encoders.Hex.EncodeData(external.Macaroon);
|
||||
conf.CertificateThumbprint = external.CertificateThumbprint == null ? null : Encoders.Hex.EncodeData(external.CertificateThumbprint);
|
||||
confs.Configurations.Add(conf);
|
||||
var macaroons = external.MacaroonDirectoryPath == null ? null : await Macaroons.GetFromDirectoryAsync(external.MacaroonDirectoryPath);
|
||||
if (external.ConnectionType == LightningConnectionType.LndGRPC)
|
||||
{
|
||||
LightningConfiguration grpcConf = new LightningConfiguration();
|
||||
grpcConf.Type = "grpc";
|
||||
grpcConf.Host = external.BaseUri.DnsSafeHost;
|
||||
grpcConf.Port = external.BaseUri.Port;
|
||||
grpcConf.SSL = external.BaseUri.Scheme == "https";
|
||||
confs.Configurations.Add(grpcConf);
|
||||
}
|
||||
else if (external.ConnectionType == LightningConnectionType.LndREST)
|
||||
{
|
||||
var restconf = new LNDRestConfiguration();
|
||||
restconf.Type = "lnd-rest";
|
||||
restconf.Uri = external.BaseUri.AbsoluteUri;
|
||||
confs.Configurations.Add(restconf);
|
||||
}
|
||||
else
|
||||
throw new NotSupportedException(external.ConnectionType.ToString());
|
||||
var commonConf = (LNDConfiguration)confs.Configurations[confs.Configurations.Count - 1];
|
||||
commonConf.ChainType = _Options.NetworkType.ToString();
|
||||
commonConf.CryptoCode = cryptoCode;
|
||||
commonConf.Macaroon = external.Macaroon == null ? null : Encoders.Hex.EncodeData(external.Macaroon);
|
||||
commonConf.CertificateThumbprint = external.CertificateThumbprint == null ? null : Encoders.Hex.EncodeData(external.CertificateThumbprint);
|
||||
commonConf.AdminMacaroon = macaroons?.AdminMacaroon?.Hex;
|
||||
commonConf.ReadonlyMacaroon = macaroons?.ReadonlyMacaroon?.Hex;
|
||||
commonConf.InvoiceMacaroon = macaroons?.InvoiceMacaroon?.Hex;
|
||||
|
||||
var nonce = RandomUtils.GetUInt32();
|
||||
var configKey = GetConfigKey("lnd-grpc", cryptoCode, index, nonce);
|
||||
var configKey = GetConfigKey("lnd", cryptoCode, index, nonce);
|
||||
_LnConfigProvider.KeepConfig(configKey, confs);
|
||||
return RedirectToAction(nameof(LndGrpcServices), new { cryptoCode = cryptoCode, nonce = nonce });
|
||||
return RedirectToAction(nameof(LndServices), new { cryptoCode = cryptoCode, nonce = nonce });
|
||||
}
|
||||
|
||||
private LightningConnectionString GetExternalLndConnectionString(string cryptoCode, int index)
|
||||
@ -539,28 +683,6 @@ namespace BTCPayServer.Controllers
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
[Route("server/services/lnd-rest/{cryptoCode}/{index}")]
|
||||
public IActionResult LndRestServices(string cryptoCode, int index, uint? nonce)
|
||||
{
|
||||
if (!_dashBoard.IsFullySynched(cryptoCode, out var unusud))
|
||||
{
|
||||
StatusMessage = $"Error: {cryptoCode} is not fully synched";
|
||||
return RedirectToAction(nameof(Services));
|
||||
}
|
||||
var external = GetExternalLndConnectionString(cryptoCode, index);
|
||||
if (external == null)
|
||||
return NotFound();
|
||||
var model = new LndRestServicesViewModel();
|
||||
|
||||
model.BaseApiUrl = external.BaseUri.ToString();
|
||||
if (external.CertificateThumbprint != null)
|
||||
model.CertificateThumbprint = Encoders.Hex.EncodeData(external.CertificateThumbprint);
|
||||
if (external.Macaroon != null)
|
||||
model.Macaroon = Encoders.Hex.EncodeData(external.Macaroon);
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[Route("server/services/ssh")]
|
||||
public IActionResult SSHService(bool downloadKeyFile = false)
|
||||
{
|
||||
|
@ -46,7 +46,7 @@ namespace BTCPayServer.Controllers
|
||||
string storeId,
|
||||
string cryptoCode,
|
||||
string command,
|
||||
int account = 0)
|
||||
string keyPath = "")
|
||||
{
|
||||
if (!HttpContext.WebSockets.IsWebSocketRequest)
|
||||
return NotFound();
|
||||
@ -67,7 +67,10 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
if (command == "getxpub")
|
||||
{
|
||||
var getxpubResult = await hw.GetExtPubKey(network, account, normalOperationTimeout.Token);
|
||||
var k = KeyPath.Parse(keyPath);
|
||||
if (k.Indexes.Length == 0)
|
||||
throw new FormatException("Invalid key path");
|
||||
var getxpubResult = await hw.GetExtPubKey(network, k, normalOperationTimeout.Token);
|
||||
result = getxpubResult;
|
||||
}
|
||||
}
|
||||
@ -171,7 +174,8 @@ namespace BTCPayServer.Controllers
|
||||
if (strategy != null)
|
||||
await wallet.TrackAsync(strategy.DerivationStrategyBase);
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, strategy);
|
||||
storeBlob.SetExcluded(paymentMethodId, willBeExcluded);
|
||||
storeBlob.SetExcluded(paymentMethodId, willBeExcluded);
|
||||
storeBlob.SetWalletKeyPathRoot(paymentMethodId, vm.KeyPath == null ? null : KeyPath.Parse(vm.KeyPath));
|
||||
store.SetStoreBlob(storeBlob);
|
||||
}
|
||||
catch
|
||||
|
@ -410,8 +410,8 @@ namespace BTCPayServer.Controllers
|
||||
return NotFound();
|
||||
|
||||
var cryptoCode = walletId.CryptoCode;
|
||||
var storeBlob = (await Repository.FindStore(walletId.StoreId, GetUserId()));
|
||||
var derivationScheme = GetPaymentMethod(walletId, storeBlob).DerivationStrategyBase;
|
||||
var storeData = (await Repository.FindStore(walletId.StoreId, GetUserId()));
|
||||
var derivationScheme = GetPaymentMethod(walletId, storeData).DerivationStrategyBase;
|
||||
|
||||
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
||||
|
||||
@ -476,15 +476,6 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
catch { throw new FormatException("Invalid value for subtract fees"); }
|
||||
}
|
||||
if (command == "getinfo")
|
||||
{
|
||||
var strategy = GetDirectDerivationStrategy(derivationScheme);
|
||||
if (strategy == null || await hw.GetKeyPath(network, strategy, normalOperationTimeout.Token) == null)
|
||||
{
|
||||
throw new Exception($"This store is not configured to use this ledger");
|
||||
}
|
||||
result = new GetInfoResult();
|
||||
}
|
||||
if (command == "test")
|
||||
{
|
||||
result = await hw.Test(normalOperationTimeout.Token);
|
||||
@ -514,10 +505,20 @@ namespace BTCPayServer.Controllers
|
||||
throw new ArgumentOutOfRangeException(nameof(element.amount), "The amount should be above zero");
|
||||
}
|
||||
|
||||
var foundKeyPath = await hw.GetKeyPath(network, strategy, normalOperationTimeout.Token);
|
||||
if (foundKeyPath == null)
|
||||
var storeBlob = storeData.GetStoreBlob();
|
||||
var paymentId = new Payments.PaymentMethodId(cryptoCode, Payments.PaymentTypes.BTCLike);
|
||||
var foundKeyPath = storeBlob.GetWalletKeyPathRoot(paymentId);
|
||||
// Some deployment have the wallet root key path saved in the store blob
|
||||
// If it does, we only have to make 1 call to the hw to check if it can sign the given strategy,
|
||||
if (foundKeyPath == null || !await hw.CanSign(network, strategy, foundKeyPath, normalOperationTimeout.Token))
|
||||
{
|
||||
throw new HardwareWalletException($"This store is not configured to use this ledger");
|
||||
// If the saved wallet key path is not present or incorrect, let's scan the wallet to see if it can sign strategy
|
||||
foundKeyPath = await hw.FindKeyPath(network, strategy, normalOperationTimeout.Token);
|
||||
if (foundKeyPath == null)
|
||||
throw new HardwareWalletException($"This store is not configured to use this ledger");
|
||||
storeBlob.SetWalletKeyPathRoot(paymentId, foundKeyPath);
|
||||
storeData.SetStoreBlob(storeBlob);
|
||||
await Repository.UpdateStore(storeData);
|
||||
}
|
||||
|
||||
TransactionBuilder builder = network.NBitcoinNetwork.CreateTransactionBuilder();
|
||||
|
@ -81,5 +81,10 @@ namespace BTCPayServer.Data
|
||||
get; set;
|
||||
}
|
||||
public List<PendingInvoiceData> PendingInvoices { get; set; }
|
||||
|
||||
public Services.Invoices.InvoiceState GetInvoiceState()
|
||||
{
|
||||
return new Services.Invoices.InvoiceState(Status, ExceptionStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -366,6 +366,23 @@ namespace BTCPayServer.Data
|
||||
|
||||
[Obsolete("Use GetExcludedPaymentMethods instead")]
|
||||
public string[] ExcludedPaymentMethods { get; set; }
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
public void SetWalletKeyPathRoot(PaymentMethodId paymentMethodId, KeyPath keyPath)
|
||||
{
|
||||
if (keyPath == null)
|
||||
WalletKeyPathRoots.Remove(paymentMethodId.ToString());
|
||||
else
|
||||
WalletKeyPathRoots.AddOrReplace(paymentMethodId.ToString().ToLowerInvariant(), keyPath.ToString());
|
||||
}
|
||||
public KeyPath GetWalletKeyPathRoot(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
if (WalletKeyPathRoots.TryGetValue(paymentMethodId.ToString().ToLowerInvariant(), out var k))
|
||||
return KeyPath.Parse(k);
|
||||
return null;
|
||||
}
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
[Obsolete("Use SetWalletKeyPathRoot/GetWalletKeyPathRoot instead")]
|
||||
public Dictionary<string, string> WalletKeyPathRoots { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
public IPaymentFilter GetExcludedPaymentMethods()
|
||||
{
|
||||
|
@ -11,23 +11,14 @@ namespace BTCPayServer.Events
|
||||
public InvoiceDataChangedEvent(InvoiceEntity invoice)
|
||||
{
|
||||
InvoiceId = invoice.Id;
|
||||
Status = invoice.Status;
|
||||
ExceptionStatus = invoice.ExceptionStatus;
|
||||
State = invoice.GetInvoiceState();
|
||||
}
|
||||
public string InvoiceId { get; set; }
|
||||
public string Status { get; internal set; }
|
||||
public string ExceptionStatus { get; internal set; }
|
||||
public string InvoiceId { get; }
|
||||
public InvoiceState State { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (string.IsNullOrEmpty(ExceptionStatus) || ExceptionStatus == "false")
|
||||
{
|
||||
return $"Invoice status is {Status}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"Invoice status is {Status} (Exception status: {ExceptionStatus})";
|
||||
}
|
||||
return $"Invoice status is {State}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -168,6 +168,41 @@ namespace BTCPayServer
|
||||
request.Path.ToUriComponent());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If 'toto' and RootPath is 'rootpath' returns '/rootpath/toto'
|
||||
/// If 'toto' and RootPath is empty returns '/toto'
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetRelativePath(this HttpRequest request, string path)
|
||||
{
|
||||
if (path.Length > 0 && path[0] != '/')
|
||||
path = $"/{path}";
|
||||
return string.Concat(
|
||||
request.PathBase.ToUriComponent(),
|
||||
path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If 'https://example.com/toto' returns 'https://example.com/toto'
|
||||
/// If 'toto' and RootPath is 'rootpath' returns '/rootpath/toto'
|
||||
/// If 'toto' and RootPath is empty returns '/toto'
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetRelativePathOrAbsolute(this HttpRequest request, string path)
|
||||
{
|
||||
if (Uri.TryCreate(path, UriKind.Absolute, out var unused))
|
||||
return path;
|
||||
if (path.Length > 0 && path[0] != '/')
|
||||
path = $"/{path}";
|
||||
return string.Concat(
|
||||
request.PathBase.ToUriComponent(),
|
||||
path);
|
||||
}
|
||||
|
||||
public static string GetAbsoluteUri(this HttpRequest request, string redirectUrl)
|
||||
{
|
||||
bool isRelative =
|
||||
|
@ -330,7 +330,7 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
leases.Add(_EventAggregator.Subscribe<InvoiceEvent>(async e =>
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, e.Invoice.Id);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(e.Invoice.Id);
|
||||
if (invoice == null)
|
||||
return;
|
||||
List<Task> tasks = new List<Task>();
|
||||
@ -345,6 +345,7 @@ namespace BTCPayServer.HostedServices
|
||||
e.Name == "invoice_paidInFull" ||
|
||||
e.Name == "invoice_failedToConfirm" ||
|
||||
e.Name == "invoice_markedInvalid" ||
|
||||
e.Name == "invoice_markedComplete" ||
|
||||
e.Name == "invoice_failedToConfirm" ||
|
||||
e.Name == "invoice_completed" ||
|
||||
e.Name == "invoice_expiredPaidPartial"
|
||||
|
@ -61,14 +61,14 @@ namespace BTCPayServer.HostedServices
|
||||
private async Task UpdateInvoice(UpdateInvoiceContext context)
|
||||
{
|
||||
var invoice = context.Invoice;
|
||||
if (invoice.Status == "new" && invoice.ExpirationTime < DateTimeOffset.UtcNow)
|
||||
if (invoice.Status == InvoiceStatus.New && invoice.ExpirationTime < DateTimeOffset.UtcNow)
|
||||
{
|
||||
context.MarkDirty();
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1004, "invoice_expired"));
|
||||
invoice.Status = "expired";
|
||||
if(invoice.ExceptionStatus == "paidPartial")
|
||||
invoice.Status = InvoiceStatus.Expired;
|
||||
if(invoice.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 2000, "invoice_expiredPaidPartial"));
|
||||
}
|
||||
|
||||
@ -78,57 +78,57 @@ namespace BTCPayServer.HostedServices
|
||||
if (paymentMethod == null)
|
||||
return;
|
||||
var network = _NetworkProvider.GetNetwork(paymentMethod.GetId().CryptoCode);
|
||||
if (invoice.Status == "new" || invoice.Status == "expired")
|
||||
if (invoice.Status == InvoiceStatus.New || invoice.Status == InvoiceStatus.Expired)
|
||||
{
|
||||
if (accounting.Paid >= accounting.MinimumTotalDue)
|
||||
{
|
||||
if (invoice.Status == "new")
|
||||
if (invoice.Status == InvoiceStatus.New)
|
||||
{
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1003, "invoice_paidInFull"));
|
||||
invoice.Status = "paid";
|
||||
invoice.ExceptionStatus = accounting.Paid > accounting.TotalDue ? "paidOver" : null;
|
||||
invoice.Status = InvoiceStatus.Paid;
|
||||
invoice.ExceptionStatus = accounting.Paid > accounting.TotalDue ? InvoiceExceptionStatus.PaidOver : InvoiceExceptionStatus.None;
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
context.MarkDirty();
|
||||
}
|
||||
else if (invoice.Status == "expired" && invoice.ExceptionStatus != "paidLate")
|
||||
else if (invoice.Status == InvoiceStatus.Expired && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidLate)
|
||||
{
|
||||
invoice.ExceptionStatus = "paidLate";
|
||||
invoice.ExceptionStatus = InvoiceExceptionStatus.PaidLate;
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1009, "invoice_paidAfterExpiration"));
|
||||
context.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
if (accounting.Paid < accounting.MinimumTotalDue && invoice.GetPayments().Count != 0 && invoice.ExceptionStatus != "paidPartial")
|
||||
if (accounting.Paid < accounting.MinimumTotalDue && invoice.GetPayments().Count != 0 && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidPartial)
|
||||
{
|
||||
invoice.ExceptionStatus = "paidPartial";
|
||||
invoice.ExceptionStatus = InvoiceExceptionStatus.PaidPartial;
|
||||
context.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
// Just make sure RBF did not cancelled a payment
|
||||
if (invoice.Status == "paid")
|
||||
if (invoice.Status == InvoiceStatus.Paid)
|
||||
{
|
||||
if (accounting.MinimumTotalDue <= accounting.Paid && accounting.Paid <= accounting.TotalDue && invoice.ExceptionStatus == "paidOver")
|
||||
if (accounting.MinimumTotalDue <= accounting.Paid && accounting.Paid <= accounting.TotalDue && invoice.ExceptionStatus == InvoiceExceptionStatus.PaidOver)
|
||||
{
|
||||
invoice.ExceptionStatus = null;
|
||||
invoice.ExceptionStatus = InvoiceExceptionStatus.None;
|
||||
context.MarkDirty();
|
||||
}
|
||||
|
||||
if (accounting.Paid > accounting.TotalDue && invoice.ExceptionStatus != "paidOver")
|
||||
if (accounting.Paid > accounting.TotalDue && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidOver)
|
||||
{
|
||||
invoice.ExceptionStatus = "paidOver";
|
||||
invoice.ExceptionStatus = InvoiceExceptionStatus.PaidOver;
|
||||
context.MarkDirty();
|
||||
}
|
||||
|
||||
if (accounting.Paid < accounting.MinimumTotalDue)
|
||||
{
|
||||
invoice.Status = "new";
|
||||
invoice.ExceptionStatus = accounting.Paid == Money.Zero ? null : "paidPartial";
|
||||
invoice.Status = InvoiceStatus.New;
|
||||
invoice.ExceptionStatus = accounting.Paid == Money.Zero ? InvoiceExceptionStatus.None : InvoiceExceptionStatus.PaidPartial;
|
||||
context.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
if (invoice.Status == "paid")
|
||||
if (invoice.Status == InvoiceStatus.Paid)
|
||||
{
|
||||
var confirmedAccounting = paymentMethod.Calculate(p => p.GetCryptoPaymentData().PaymentConfirmed(p, invoice.SpeedPolicy, network));
|
||||
|
||||
@ -140,25 +140,25 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1013, "invoice_failedToConfirm"));
|
||||
invoice.Status = "invalid";
|
||||
invoice.Status = InvoiceStatus.Invalid;
|
||||
context.MarkDirty();
|
||||
}
|
||||
else if (confirmedAccounting.Paid >= accounting.MinimumTotalDue)
|
||||
{
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1005, "invoice_confirmed"));
|
||||
invoice.Status = "confirmed";
|
||||
invoice.Status = InvoiceStatus.Confirmed;
|
||||
context.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
if (invoice.Status == "confirmed")
|
||||
if (invoice.Status == InvoiceStatus.Confirmed)
|
||||
{
|
||||
var completedAccounting = paymentMethod.Calculate(p => p.GetCryptoPaymentData().PaymentCompleted(p, network));
|
||||
if (completedAccounting.Paid >= accounting.MinimumTotalDue)
|
||||
{
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1006, "invoice_completed"));
|
||||
invoice.Status = "complete";
|
||||
invoice.Status = InvoiceStatus.Complete;
|
||||
context.MarkDirty();
|
||||
}
|
||||
}
|
||||
@ -208,7 +208,7 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
private async Task Wait(string invoiceId)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
||||
try
|
||||
{
|
||||
var delay = invoice.ExpirationTime - DateTimeOffset.UtcNow;
|
||||
@ -283,14 +283,14 @@ namespace BTCPayServer.HostedServices
|
||||
loopCount++;
|
||||
try
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, true);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId, true);
|
||||
if (invoice == null)
|
||||
break;
|
||||
var updateContext = new UpdateInvoiceContext(invoice);
|
||||
await UpdateInvoice(updateContext);
|
||||
if (updateContext.Dirty)
|
||||
{
|
||||
await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.Status, invoice.ExceptionStatus);
|
||||
await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.GetInvoiceState());
|
||||
updateContext.Events.Insert(0, new InvoiceDataChangedEvent(invoice));
|
||||
}
|
||||
|
||||
@ -299,8 +299,8 @@ namespace BTCPayServer.HostedServices
|
||||
_EventAggregator.Publish(evt, evt.GetType());
|
||||
}
|
||||
|
||||
if (invoice.Status == "complete" ||
|
||||
((invoice.Status == "invalid" || invoice.Status == "expired") && invoice.MonitoringExpiration < DateTimeOffset.UtcNow))
|
||||
if (invoice.Status == InvoiceStatus.Complete ||
|
||||
((invoice.Status == InvoiceStatus.Invalid || invoice.Status == InvoiceStatus.Expired) && invoice.MonitoringExpiration < DateTimeOffset.UtcNow))
|
||||
{
|
||||
if (await _InvoiceRepository.RemovePendingInvoice(invoice.Id))
|
||||
_EventAggregator.Publish(new InvoiceStopWatchedEvent(invoice.Id));
|
||||
|
@ -28,7 +28,8 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
public string CurrencySymbol { get; set; }
|
||||
public string ThousandSeparator { get; set; }
|
||||
public string DecimalSeparator { get; set; }
|
||||
public int Divisibility { get; internal set; }
|
||||
public int Divisibility { get; set; }
|
||||
public bool SymbolSpace { get; set; }
|
||||
}
|
||||
|
||||
public CurrencyInfoData CurrencyInfo { get; set; }
|
||||
|
@ -81,11 +81,11 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
public string BOLT11 { get; set; }
|
||||
}
|
||||
|
||||
public string Status
|
||||
public string State
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string StatusException { get; set; }
|
||||
public InvoiceExceptionStatus StatusException { get; set; }
|
||||
public DateTimeOffset CreatedDate
|
||||
{
|
||||
get; set;
|
||||
|
@ -46,6 +46,9 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public bool CanMarkComplete { get; set; }
|
||||
public bool CanMarkInvalid { get; set; }
|
||||
public bool CanMarkStatus => CanMarkComplete || CanMarkInvalid;
|
||||
public bool ShowCheckout { get; set; }
|
||||
public string ExceptionStatus { get; set; }
|
||||
public string AmountCurrency
|
||||
|
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Models.ServerViewModels
|
||||
{
|
||||
public class ChargeServiceViewModel
|
||||
{
|
||||
public string Uri { get; set; }
|
||||
public string APIToken { get; set; }
|
||||
public string AuthenticatedUri { get; set; }
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -10,8 +11,16 @@ namespace BTCPayServer.Models.ServerViewModels
|
||||
public string Host { get; set; }
|
||||
public bool SSL { get; set; }
|
||||
public string Macaroon { get; set; }
|
||||
public string AdminMacaroon { get; set; }
|
||||
public string ReadonlyMacaroon { get; set; }
|
||||
public string InvoiceMacaroon { get; set; }
|
||||
public string CertificateThumbprint { get; set; }
|
||||
[Display(Name = "GRPC SSL Cipher suite (GRPC_SSL_CIPHER_SUITES)")]
|
||||
public string GRPCSSLCipherSuites { get; set; }
|
||||
public string QRCode { get; set; }
|
||||
public string QRCodeLink { get; set; }
|
||||
[Display(Name = "REST Uri")]
|
||||
public string Uri { get; set; }
|
||||
public string ConnectionType { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
@ -11,11 +11,18 @@ namespace BTCPayServer.Models.ServerViewModels
|
||||
public class LNDServiceViewModel
|
||||
{
|
||||
public string Crypto { get; set; }
|
||||
public LndTypes Type { get; set; }
|
||||
public string Type { get; set; }
|
||||
public int Index { get; set; }
|
||||
public string Action { get; internal set; }
|
||||
}
|
||||
|
||||
public class ExternalService
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Link { get; set; }
|
||||
}
|
||||
|
||||
public List<LNDServiceViewModel> LNDServices { get; set; } = new List<LNDServiceViewModel>();
|
||||
public bool HasSSH { get; set; }
|
||||
public List<ExternalService> ExternalServices { get; set; } = new List<ExternalService>();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Models.ServerViewModels
|
||||
{
|
||||
public class SparkServicesViewModel
|
||||
{
|
||||
public string SparkLink { get; set; }
|
||||
public bool ShowQR { get; set; }
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
} = new List<(string KeyPath, string Address)>();
|
||||
|
||||
public string CryptoCode { get; set; }
|
||||
public string KeyPath { get; set; }
|
||||
[Display(Name = "Hint address")]
|
||||
public string HintAddress { get; set; }
|
||||
public bool Confirmation { get; set; }
|
||||
|
@ -78,5 +78,15 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public BitcoinAddress GetDestination(BTCPayNetwork network)
|
||||
{
|
||||
return Output.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork);
|
||||
}
|
||||
|
||||
string CryptoPaymentData.GetDestination(BTCPayNetwork network)
|
||||
{
|
||||
return GetDestination(network).ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -205,7 +205,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
|
||||
async Task<InvoiceEntity> UpdatePaymentStates(BTCPayWallet wallet, string invoiceId)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, false);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId, false);
|
||||
if (invoice == null)
|
||||
return null;
|
||||
List<PaymentEntity> updatedPaymentEntities = new List<PaymentEntity>();
|
||||
@ -315,7 +315,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
var invoices = await _InvoiceRepository.GetPendingInvoices();
|
||||
foreach (var invoiceId in invoices)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, true);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId, true);
|
||||
if (invoice == null)
|
||||
continue;
|
||||
var alreadyAccounted = GetAllBitcoinPaymentData(invoice).Select(p => p.Outpoint).ToHashSet();
|
||||
|
@ -16,6 +16,12 @@ namespace BTCPayServer.Payments.Lightning
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
public LightMoney Amount { get; set; }
|
||||
public string BOLT11 { get; set; }
|
||||
|
||||
public string GetDestination(BTCPayNetwork network)
|
||||
{
|
||||
return GetPaymentId();
|
||||
}
|
||||
|
||||
public string GetPaymentId()
|
||||
{
|
||||
return BOLT11;
|
||||
|
@ -73,7 +73,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
{
|
||||
if (Listening(invoiceId))
|
||||
return;
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
||||
foreach (var paymentMethod in invoice.GetPaymentMethods(_NetworkProvider)
|
||||
.Where(c => c.GetId().PaymentType == PaymentTypes.LightningLike))
|
||||
{
|
||||
@ -156,7 +156,8 @@ namespace BTCPayServer.Payments.Lightning
|
||||
if (notification.Id == listenedInvoice.PaymentMethodDetails.InvoiceId &&
|
||||
notification.BOLT11 == listenedInvoice.PaymentMethodDetails.BOLT11)
|
||||
{
|
||||
if (notification.Status == LightningInvoiceStatus.Paid && notification.PaidAt.HasValue)
|
||||
if (notification.Status == LightningInvoiceStatus.Paid &&
|
||||
notification.PaidAt.HasValue && notification.Amount != null)
|
||||
{
|
||||
await AddPayment(network, notification, listenedInvoice);
|
||||
if (DoneListening(listenedInvoice))
|
||||
@ -194,7 +195,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
}, network.CryptoCode, accounted: true);
|
||||
if (payment != null)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, listenedInvoice.InvoiceId);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(listenedInvoice.InvoiceId);
|
||||
if (invoice != null)
|
||||
_Aggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1002, "invoice_receivedPayment"));
|
||||
}
|
||||
|
@ -28,11 +28,14 @@
|
||||
"BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32838/",
|
||||
"BTCPAY_BTCLIGHTNING": "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify",
|
||||
"BTCPAY_BTCEXTERNALLNDGRPC": "type=lnd-grpc;server=https://lnd:lnd@127.0.0.1:53280/;allowinsecure=true",
|
||||
"BTCPAY_BTCEXTERNALLNDREST": "type=lnd-rest;server=https://lnd:lnd@127.0.0.1:53280/lnd-rest/btc/;allowinsecure=true;macaroonfilepath=D:\\admin.macaroon",
|
||||
"BTCPAY_BTCEXTERNALLNDREST": "type=lnd-rest;server=https://lnd:lnd@127.0.0.1:53280/lnd-rest/btc/;allowinsecure=true",
|
||||
"BTCPAY_BTCEXTERNALSPARK": "server=https://127.0.0.1:53280/spark/btc/;cookiefile=fake",
|
||||
"BTCPAY_BTCEXTERNALCHARGE": "server=https://127.0.0.1:53280/spark/btc/;cookiefilepath=fake",
|
||||
"BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/",
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"BTCPAY_CHAINS": "btc,ltc",
|
||||
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver"
|
||||
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver",
|
||||
"BTCPAY_EXTERNALSERVICES": "totoservice:totolink;"
|
||||
},
|
||||
"applicationUrl": "https://localhost:14142/"
|
||||
}
|
||||
|
@ -57,39 +57,37 @@ namespace BTCPayServer.Security
|
||||
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;
|
||||
|
||||
bool? success = null;
|
||||
if (!string.IsNullOrEmpty(bitpayAuth.Signature) && !string.IsNullOrEmpty(bitpayAuth.Id))
|
||||
{
|
||||
var result = await CheckBitId(Context.Request.HttpContext, bitpayAuth.Signature, bitpayAuth.Id, claims);
|
||||
storeId = result.StoreId;
|
||||
successAuth = result.SuccessAuth;
|
||||
failedAuth = !successAuth;
|
||||
success = result.SuccessAuth;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(bitpayAuth.Authorization))
|
||||
{
|
||||
storeId = await CheckLegacyAPIKey(Context.Request.HttpContext, bitpayAuth.Authorization);
|
||||
successAuth = storeId != null;
|
||||
failedAuth = !successAuth;
|
||||
success = storeId != null;
|
||||
}
|
||||
|
||||
if (failedAuth)
|
||||
if (success.HasValue)
|
||||
{
|
||||
return AuthenticateResult.Fail("Invalid credentials");
|
||||
}
|
||||
|
||||
if (successAuth)
|
||||
{
|
||||
if (storeId != null)
|
||||
if (success.Value)
|
||||
{
|
||||
claims.Add(new Claim(Policies.CanCreateInvoice.Key, storeId));
|
||||
var store = await _StoreRepository.FindStore(storeId);
|
||||
store.AdditionalClaims.AddRange(claims);
|
||||
Context.Request.HttpContext.SetStoreData(store);
|
||||
if (storeId != null)
|
||||
{
|
||||
claims.Add(new Claim(Policies.CanCreateInvoice.Key, storeId));
|
||||
var store = await _StoreRepository.FindStore(storeId);
|
||||
store.AdditionalClaims.AddRange(claims);
|
||||
Context.Request.HttpContext.SetStoreData(store);
|
||||
}
|
||||
return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, Policies.BitpayAuthentication)), Policies.BitpayAuthentication));
|
||||
}
|
||||
else
|
||||
{
|
||||
return AuthenticateResult.Fail("Invalid credentials");
|
||||
}
|
||||
return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, Policies.BitpayAuthentication)), Policies.BitpayAuthentication));
|
||||
}
|
||||
}
|
||||
return AuthenticateResult.NoResult();
|
||||
|
@ -89,20 +89,19 @@ namespace BTCPayServer.Services
|
||||
return new LedgerTestResult() { Success = true };
|
||||
}
|
||||
|
||||
public async Task<GetXPubResult> GetExtPubKey(BTCPayNetwork network, int account, CancellationToken cancellation)
|
||||
public async Task<GetXPubResult> GetExtPubKey(BTCPayNetwork network, KeyPath keyPath, CancellationToken cancellation)
|
||||
{
|
||||
if (network == null)
|
||||
throw new ArgumentNullException(nameof(network));
|
||||
|
||||
var segwit = network.NBitcoinNetwork.Consensus.SupportSegwit;
|
||||
var path = network.GetRootKeyPath().Derive(account, true);
|
||||
var pubkey = await GetExtPubKey(Ledger, network, path, false, cancellation);
|
||||
var pubkey = await GetExtPubKey(Ledger, network, keyPath, false, cancellation);
|
||||
var derivation = new DerivationStrategyFactory(network.NBitcoinNetwork).CreateDirectDerivationStrategy(pubkey, new DerivationStrategyOptions()
|
||||
{
|
||||
P2SH = segwit,
|
||||
Legacy = !segwit
|
||||
});
|
||||
return new GetXPubResult() { ExtPubKey = derivation.ToString(), KeyPath = path };
|
||||
return new GetXPubResult() { ExtPubKey = derivation.ToString(), KeyPath = keyPath };
|
||||
}
|
||||
|
||||
private static async Task<BitcoinExtPubKey> GetExtPubKey(LedgerClient ledger, BTCPayNetwork network, KeyPath account, bool onlyChaincode, CancellationToken cancellation)
|
||||
@ -129,7 +128,13 @@ namespace BTCPayServer.Services
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<KeyPath> GetKeyPath(BTCPayNetwork network, DirectDerivationStrategy directStrategy, CancellationToken cancellation)
|
||||
public async Task<bool> CanSign(BTCPayNetwork network, DirectDerivationStrategy strategy, KeyPath keyPath, CancellationToken cancellation)
|
||||
{
|
||||
var hwKey = await GetExtPubKey(Ledger, network, keyPath, true, cancellation);
|
||||
return hwKey.ExtPubKey.PubKey == strategy.Root.PubKey;
|
||||
}
|
||||
|
||||
public async Task<KeyPath> FindKeyPath(BTCPayNetwork network, DirectDerivationStrategy directStrategy, CancellationToken cancellation)
|
||||
{
|
||||
List<KeyPath> derivations = new List<KeyPath>();
|
||||
if (network.NBitcoinNetwork.Consensus.SupportSegwit)
|
||||
@ -164,7 +169,17 @@ namespace BTCPayServer.Services
|
||||
KeyPath changeKeyPath,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return await Ledger.SignTransactionAsync(signatureRequests, unsigned, changeKeyPath, cancellationToken);
|
||||
try
|
||||
{
|
||||
var signedTransaction = await Ledger.SignTransactionAsync(signatureRequests, unsigned, changeKeyPath, cancellationToken);
|
||||
if (signedTransaction == null)
|
||||
throw new Exception("The ledger failed to sign the transaction");
|
||||
return signedTransaction;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("The ledger failed to sign the transaction", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
@ -10,6 +11,12 @@ namespace BTCPayServer.Services.Invoices.Export
|
||||
{
|
||||
public class InvoiceExport
|
||||
{
|
||||
public BTCPayNetworkProvider Networks { get; }
|
||||
|
||||
public InvoiceExport(BTCPayNetworkProvider networks)
|
||||
{
|
||||
Networks = networks;
|
||||
}
|
||||
public string Process(InvoiceEntity[] invoices, string fileFormat)
|
||||
{
|
||||
var csvInvoices = new List<ExportInvoiceHolder>();
|
||||
@ -55,9 +62,7 @@ namespace BTCPayServer.Services.Invoices.Export
|
||||
var cryptoCode = payment.GetPaymentMethodId().CryptoCode;
|
||||
var pdata = payment.GetCryptoPaymentData();
|
||||
|
||||
var pmethod = invoice.GetPaymentMethod(payment.GetPaymentMethodId(), null);
|
||||
var accounting = pmethod.Calculate();
|
||||
var details = pmethod.GetPaymentMethodDetails();
|
||||
var pmethod = invoice.GetPaymentMethod(payment.GetPaymentMethodId(), Networks);
|
||||
|
||||
var target = new ExportInvoiceHolder
|
||||
{
|
||||
@ -65,23 +70,24 @@ namespace BTCPayServer.Services.Invoices.Export
|
||||
PaymentId = pdata.GetPaymentId(),
|
||||
CryptoCode = cryptoCode,
|
||||
ConversionRate = pmethod.Rate,
|
||||
PaymentType = details.GetPaymentType() == Payments.PaymentTypes.BTCLike ? "OnChain" : "OffChain",
|
||||
Destination = details.GetPaymentDestination(),
|
||||
PaymentDue = $"{accounting.MinimumTotalDue} {cryptoCode}",
|
||||
PaymentPaid = $"{accounting.CryptoPaid} {cryptoCode}",
|
||||
PaymentOverpaid = $"{accounting.OverpaidHelper} {cryptoCode}",
|
||||
|
||||
PaymentType = payment.GetPaymentMethodId().PaymentType == Payments.PaymentTypes.BTCLike ? "OnChain" : "OffChain",
|
||||
Destination = payment.GetCryptoPaymentData().GetDestination(Networks.GetNetwork(cryptoCode)),
|
||||
Paid = pdata.GetValue().ToString(CultureInfo.InvariantCulture),
|
||||
OrderId = invoice.OrderId,
|
||||
StoreId = invoice.StoreId,
|
||||
InvoiceId = invoice.Id,
|
||||
CreatedDate = invoice.InvoiceTime.UtcDateTime,
|
||||
ExpirationDate = invoice.ExpirationTime.UtcDateTime,
|
||||
MonitoringDate = invoice.MonitoringExpiration.UtcDateTime,
|
||||
Status = invoice.Status,
|
||||
ItemCode = invoice.ProductInformation?.ItemCode,
|
||||
ItemDesc = invoice.ProductInformation?.ItemDesc,
|
||||
FiatPrice = invoice.ProductInformation?.Price ?? 0,
|
||||
FiatCurrency = invoice.ProductInformation?.Currency,
|
||||
InvoiceCreatedDate = invoice.InvoiceTime.UtcDateTime,
|
||||
InvoiceExpirationDate = invoice.ExpirationTime.UtcDateTime,
|
||||
InvoiceMonitoringDate = invoice.MonitoringExpiration.UtcDateTime,
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
InvoiceFullStatus = invoice.GetInvoiceState().ToString(),
|
||||
InvoiceStatus = invoice.StatusString,
|
||||
InvoiceExceptionStatus = invoice.ExceptionStatusString,
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
InvoiceItemCode = invoice.ProductInformation.ItemCode,
|
||||
InvoiceItemDesc = invoice.ProductInformation.ItemDesc,
|
||||
InvoicePrice = invoice.ProductInformation.Price,
|
||||
InvoiceCurrency = invoice.ProductInformation.Currency,
|
||||
};
|
||||
|
||||
exportList.Add(target);
|
||||
@ -99,23 +105,23 @@ namespace BTCPayServer.Services.Invoices.Export
|
||||
public string StoreId { get; set; }
|
||||
public string OrderId { get; set; }
|
||||
public string InvoiceId { get; set; }
|
||||
public DateTime CreatedDate { get; set; }
|
||||
public DateTime ExpirationDate { get; set; }
|
||||
public DateTime MonitoringDate { get; set; }
|
||||
public DateTime InvoiceCreatedDate { get; set; }
|
||||
public DateTime InvoiceExpirationDate { get; set; }
|
||||
public DateTime InvoiceMonitoringDate { get; set; }
|
||||
|
||||
public string PaymentId { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string Destination { get; set; }
|
||||
public string PaymentType { get; set; }
|
||||
public string PaymentDue { get; set; }
|
||||
public string PaymentPaid { get; set; }
|
||||
public string PaymentOverpaid { get; set; }
|
||||
public string Paid { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public decimal ConversionRate { get; set; }
|
||||
|
||||
public decimal FiatPrice { get; set; }
|
||||
public string FiatCurrency { get; set; }
|
||||
public string ItemCode { get; set; }
|
||||
public string ItemDesc { get; set; }
|
||||
public string Status { get; set; }
|
||||
public decimal InvoicePrice { get; set; }
|
||||
public string InvoiceCurrency { get; set; }
|
||||
public string InvoiceItemCode { get; set; }
|
||||
public string InvoiceItemDesc { get; set; }
|
||||
public string InvoiceFullStatus { get; set; }
|
||||
public string InvoiceStatus { get; set; }
|
||||
public string InvoiceExceptionStatus { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -226,15 +226,23 @@ namespace BTCPayServer.Services.Invoices
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
public string Status
|
||||
[JsonIgnore]
|
||||
public InvoiceStatus Status
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string ExceptionStatus
|
||||
[JsonProperty(PropertyName = "status")]
|
||||
[Obsolete("Use Status instead")]
|
||||
public string StatusString => InvoiceState.ToString(Status);
|
||||
[JsonIgnore]
|
||||
public InvoiceExceptionStatus ExceptionStatus
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
[JsonProperty(PropertyName = "exceptionStatus")]
|
||||
[Obsolete("Use ExceptionStatus instead")]
|
||||
public string ExceptionStatusString => InvoiceState.ToString(ExceptionStatus);
|
||||
|
||||
[Obsolete("Use GetPayments instead")]
|
||||
public List<PaymentEntity> Payments
|
||||
@ -341,7 +349,10 @@ namespace BTCPayServer.Services.Invoices
|
||||
CurrentTime = DateTimeOffset.UtcNow,
|
||||
InvoiceTime = InvoiceTime,
|
||||
ExpirationTime = ExpirationTime,
|
||||
Status = Status,
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Status = StatusString,
|
||||
ExceptionStatus = ExceptionStatus == InvoiceExceptionStatus.None ? new JValue(false) : new JValue(ExceptionStatusString),
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
Currency = ProductInformation.Currency,
|
||||
Flags = new Flags() { Refundable = Refundable },
|
||||
PaymentSubtotals = new Dictionary<string, long>(),
|
||||
@ -395,9 +406,6 @@ namespace BTCPayServer.Services.Invoices
|
||||
var cryptoSuffix = cryptoInfo.CryptoCode == "BTC" ? "" : "/" + cryptoInfo.CryptoCode;
|
||||
cryptoInfo.PaymentUrls = new NBitpayClient.InvoicePaymentUrls()
|
||||
{
|
||||
BIP72 = $"{scheme}:{cryptoInfo.Address}?amount={cryptoInfo.Due}&r={ServerUrl.WithTrailingSlash() + ($"i/{Id}{cryptoSuffix}")}",
|
||||
BIP72b = $"{scheme}:?r={ServerUrl.WithTrailingSlash() + ($"i/{Id}{cryptoSuffix}")}",
|
||||
BIP73 = ServerUrl.WithTrailingSlash() + ($"i/{Id}{cryptoSuffix}"),
|
||||
BIP21 = $"{scheme}:{cryptoInfo.Address}?amount={cryptoInfo.Due}",
|
||||
};
|
||||
}
|
||||
@ -450,7 +458,6 @@ namespace BTCPayServer.Services.Invoices
|
||||
|
||||
dto.Token = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16)); //No idea what it is useful for
|
||||
dto.Guid = Guid.NewGuid().ToString();
|
||||
dto.ExceptionStatus = ExceptionStatus == null ? new JValue(false) : new JValue(ExceptionStatus);
|
||||
return dto;
|
||||
}
|
||||
|
||||
@ -529,6 +536,108 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
public InvoiceState GetInvoiceState()
|
||||
{
|
||||
return new InvoiceState(Status, ExceptionStatus);
|
||||
}
|
||||
}
|
||||
|
||||
public enum InvoiceStatus
|
||||
{
|
||||
New,
|
||||
Paid,
|
||||
Expired,
|
||||
Invalid,
|
||||
Complete,
|
||||
Confirmed
|
||||
}
|
||||
public enum InvoiceExceptionStatus
|
||||
{
|
||||
None,
|
||||
PaidLate,
|
||||
PaidPartial,
|
||||
Marked,
|
||||
Invalid,
|
||||
PaidOver
|
||||
}
|
||||
public class InvoiceState
|
||||
{
|
||||
static Dictionary<string, InvoiceStatus> _StringToInvoiceStatus;
|
||||
static Dictionary<InvoiceStatus, string> _InvoiceStatusToString;
|
||||
|
||||
static Dictionary<string, InvoiceExceptionStatus> _StringToExceptionStatus;
|
||||
static Dictionary<InvoiceExceptionStatus, string> _ExceptionStatusToString;
|
||||
|
||||
static InvoiceState()
|
||||
{
|
||||
_StringToInvoiceStatus = new Dictionary<string, InvoiceStatus>();
|
||||
_StringToInvoiceStatus.Add("paid", InvoiceStatus.Paid);
|
||||
_StringToInvoiceStatus.Add("expired", InvoiceStatus.Expired);
|
||||
_StringToInvoiceStatus.Add("invalid", InvoiceStatus.Invalid);
|
||||
_StringToInvoiceStatus.Add("complete", InvoiceStatus.Complete);
|
||||
_StringToInvoiceStatus.Add("new", InvoiceStatus.New);
|
||||
_StringToInvoiceStatus.Add("confirmed", InvoiceStatus.Confirmed);
|
||||
_InvoiceStatusToString = _StringToInvoiceStatus.ToDictionary(kv => kv.Value, kv => kv.Key);
|
||||
|
||||
_StringToExceptionStatus = new Dictionary<string, InvoiceExceptionStatus>();
|
||||
_StringToExceptionStatus.Add(string.Empty, InvoiceExceptionStatus.None);
|
||||
_StringToExceptionStatus.Add("paidPartial", InvoiceExceptionStatus.PaidPartial);
|
||||
_StringToExceptionStatus.Add("paidLate", InvoiceExceptionStatus.PaidLate);
|
||||
_StringToExceptionStatus.Add("paidOver", InvoiceExceptionStatus.PaidOver);
|
||||
_StringToExceptionStatus.Add("marked", InvoiceExceptionStatus.Marked);
|
||||
_ExceptionStatusToString = _StringToExceptionStatus.ToDictionary(kv => kv.Value, kv => kv.Key);
|
||||
_StringToExceptionStatus.Add("false", InvoiceExceptionStatus.None);
|
||||
}
|
||||
public InvoiceState(string status, string exceptionStatus)
|
||||
{
|
||||
Status = _StringToInvoiceStatus[status];
|
||||
ExceptionStatus = _StringToExceptionStatus[exceptionStatus ?? string.Empty];
|
||||
}
|
||||
public InvoiceState(InvoiceStatus status, InvoiceExceptionStatus exceptionStatus)
|
||||
{
|
||||
Status = status;
|
||||
ExceptionStatus = exceptionStatus;
|
||||
}
|
||||
|
||||
public InvoiceStatus Status { get; }
|
||||
public InvoiceExceptionStatus ExceptionStatus { get; }
|
||||
|
||||
public static string ToString(InvoiceStatus status)
|
||||
{
|
||||
return _InvoiceStatusToString[status];
|
||||
}
|
||||
|
||||
public static string ToString(InvoiceExceptionStatus exceptionStatus)
|
||||
{
|
||||
return _ExceptionStatusToString[exceptionStatus];
|
||||
}
|
||||
|
||||
public bool CanMarkComplete()
|
||||
{
|
||||
return (Status == InvoiceStatus.Paid) ||
|
||||
#pragma warning disable CA1305 // Specify IFormatProvider
|
||||
((Status == InvoiceStatus.New || Status == InvoiceStatus.Expired) && ExceptionStatus == InvoiceExceptionStatus.PaidPartial) ||
|
||||
((Status == InvoiceStatus.New || Status == InvoiceStatus.Expired) && ExceptionStatus == InvoiceExceptionStatus.PaidLate) ||
|
||||
(Status != InvoiceStatus.Complete && ExceptionStatus == InvoiceExceptionStatus.Marked) ||
|
||||
(Status == InvoiceStatus.Invalid);
|
||||
#pragma warning restore CA1305 // Specify IFormatProvider
|
||||
}
|
||||
|
||||
public bool CanMarkInvalid()
|
||||
{
|
||||
return (Status == InvoiceStatus.Paid) ||
|
||||
(Status == InvoiceStatus.New) ||
|
||||
#pragma warning disable CA1305 // Specify IFormatProvider
|
||||
((Status == InvoiceStatus.New || Status == InvoiceStatus.Expired) && ExceptionStatus == InvoiceExceptionStatus.PaidPartial) ||
|
||||
((Status == InvoiceStatus.New || Status == InvoiceStatus.Expired) && ExceptionStatus == InvoiceExceptionStatus.PaidLate) ||
|
||||
(Status != InvoiceStatus.Invalid && ExceptionStatus == InvoiceExceptionStatus.Marked);
|
||||
#pragma warning restore CA1305 // Specify IFormatProvider;
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return ToString(Status) + (ExceptionStatus == InvoiceExceptionStatus.None ? string.Empty : $" ({ToString(ExceptionStatus)})");
|
||||
}
|
||||
}
|
||||
|
||||
public class PaymentMethodAccounting
|
||||
@ -873,5 +982,6 @@ namespace BTCPayServer.Services.Invoices
|
||||
bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy, BTCPayNetwork network);
|
||||
|
||||
PaymentTypes GetPaymentType();
|
||||
string GetDestination(BTCPayNetwork network);
|
||||
}
|
||||
}
|
||||
|
@ -126,7 +126,9 @@ namespace BTCPayServer.Services.Invoices
|
||||
Created = invoice.InvoiceTime,
|
||||
Blob = ToBytes(invoice, null),
|
||||
OrderId = invoice.OrderId,
|
||||
Status = invoice.Status,
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Status = invoice.StatusString,
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
ItemCode = invoice.ProductInformation.ItemCode,
|
||||
CustomerEmail = invoice.RefundMail
|
||||
});
|
||||
@ -316,15 +318,15 @@ namespace BTCPayServer.Services.Invoices
|
||||
});
|
||||
}
|
||||
|
||||
public async Task UpdateInvoiceStatus(string invoiceId, string status, string exceptionStatus)
|
||||
public async Task UpdateInvoiceStatus(string invoiceId, InvoiceState invoiceState)
|
||||
{
|
||||
using (var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||
if (invoiceData == null)
|
||||
return;
|
||||
invoiceData.Status = status;
|
||||
invoiceData.ExceptionStatus = exceptionStatus;
|
||||
invoiceData.Status = InvoiceState.ToString(invoiceState.Status);
|
||||
invoiceData.ExceptionStatus = InvoiceState.ToString(invoiceState.ExceptionStatus);
|
||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@ -334,13 +336,26 @@ namespace BTCPayServer.Services.Invoices
|
||||
using (var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||
if (invoiceData?.Status != "paid")
|
||||
if (invoiceData == null || !invoiceData.GetInvoiceState().CanMarkInvalid())
|
||||
return;
|
||||
invoiceData.Status = "invalid";
|
||||
invoiceData.ExceptionStatus = "marked";
|
||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
public async Task<InvoiceEntity> GetInvoice(string storeId, string id, bool inludeAddressData = false)
|
||||
public async Task UpdatePaidInvoiceToComplete(string invoiceId)
|
||||
{
|
||||
using (var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||
if (invoiceData == null || !invoiceData.GetInvoiceState().CanMarkComplete())
|
||||
return;
|
||||
invoiceData.Status = "complete";
|
||||
invoiceData.ExceptionStatus = "marked";
|
||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
public async Task<InvoiceEntity> GetInvoice(string id, bool inludeAddressData = false)
|
||||
{
|
||||
using (var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
@ -353,9 +368,6 @@ namespace BTCPayServer.Services.Invoices
|
||||
query = query.Include(o => o.HistoricalAddressInvoices).Include(o => o.AddressInvoices);
|
||||
query = query.Where(i => i.Id == id);
|
||||
|
||||
if (storeId != null)
|
||||
query = query.Where(i => i.StoreDataId == storeId);
|
||||
|
||||
var invoice = await query.FirstOrDefaultAsync().ConfigureAwait(false);
|
||||
if (invoice == null)
|
||||
return null;
|
||||
@ -375,8 +387,9 @@ namespace BTCPayServer.Services.Invoices
|
||||
return paymentEntity;
|
||||
}).ToList();
|
||||
#pragma warning restore CS0618
|
||||
entity.ExceptionStatus = invoice.ExceptionStatus;
|
||||
entity.Status = invoice.Status;
|
||||
var state = invoice.GetInvoiceState();
|
||||
entity.ExceptionStatus = state.ExceptionStatus;
|
||||
entity.Status = state.Status;
|
||||
entity.RefundMail = invoice.CustomerEmail;
|
||||
entity.Refundable = invoice.RefundAddresses.Count != 0;
|
||||
if (invoice.HistoricalAddressInvoices != null)
|
||||
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
|
||||
namespace BTCPayServer.Services
|
||||
{
|
||||
@ -27,9 +28,9 @@ namespace BTCPayServer.Services
|
||||
|
||||
private void CleanExpired()
|
||||
{
|
||||
foreach(var item in _Map)
|
||||
foreach (var item in _Map)
|
||||
{
|
||||
if(item.Value.expiration < DateTimeOffset.UtcNow)
|
||||
if (item.Value.expiration < DateTimeOffset.UtcNow)
|
||||
{
|
||||
_Map.TryRemove(item.Key, out var unused);
|
||||
}
|
||||
@ -39,17 +40,28 @@ namespace BTCPayServer.Services
|
||||
|
||||
public class LightningConfigurations
|
||||
{
|
||||
public List<LightningConfiguration> Configurations { get; set; } = new List<LightningConfiguration>();
|
||||
public List<object> Configurations { get; set; } = new List<object>();
|
||||
}
|
||||
public class LightningConfiguration
|
||||
|
||||
public class LNDConfiguration
|
||||
{
|
||||
public string ChainType { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string CertificateThumbprint { get; set; }
|
||||
public string Macaroon { get; set; }
|
||||
public string AdminMacaroon { get; set; }
|
||||
public string ReadonlyMacaroon { get; set; }
|
||||
public string InvoiceMacaroon { get; set; }
|
||||
}
|
||||
public class LightningConfiguration : LNDConfiguration
|
||||
{
|
||||
public string Host { get; set; }
|
||||
public int Port { get; set; }
|
||||
public bool SSL { get; set; }
|
||||
public string CertificateThumbprint { get; set; }
|
||||
public string Macaroon { get; set; }
|
||||
}
|
||||
public class LNDRestConfiguration : LNDConfiguration
|
||||
{
|
||||
public string Uri { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -3,33 +3,39 @@
|
||||
ViewData["Title"] = "Reset password";
|
||||
}
|
||||
|
||||
<h2>@ViewData["Title"]</h2>
|
||||
<h4>Reset your password.</h4>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<form method="post">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<input asp-for="Code" type="hidden" />
|
||||
<div class="form-group">
|
||||
<label asp-for="Email"></label>
|
||||
<input asp-for="Email" class="form-control" />
|
||||
<span asp-validation-for="Email" class="text-danger"></span>
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Password"></label>
|
||||
<input asp-for="Password" class="form-control" />
|
||||
<span asp-validation-for="Password" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<form method="post">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<input asp-for="Code" type="hidden" />
|
||||
<div class="form-group">
|
||||
<label asp-for="Email"></label>
|
||||
<input asp-for="Email" class="form-control" />
|
||||
<span asp-validation-for="Email" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Password"></label>
|
||||
<input asp-for="Password" class="form-control" />
|
||||
<span asp-validation-for="Password" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="ConfirmPassword"></label>
|
||||
<input asp-for="ConfirmPassword" class="form-control" />
|
||||
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Reset</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="ConfirmPassword"></label>
|
||||
<input asp-for="ConfirmPassword" class="form-control" />
|
||||
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Reset</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
|
@ -3,6 +3,12 @@
|
||||
}
|
||||
|
||||
<h2>@ViewData["Title"]</h2>
|
||||
<p>
|
||||
Your password has been reset. Please <a asp-action="Login">click here to log in</a>.
|
||||
</p>
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
Your password has been reset. Please <a asp-action="Login">click here to log in</a>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -3,6 +3,26 @@
|
||||
ViewData["Title"] = "Update Point of Sale";
|
||||
}
|
||||
<section>
|
||||
<div class="modal" id="product-modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Product management</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Modal body text goes here.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="js-product-save btn btn-primary">Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
@ -58,9 +78,17 @@
|
||||
<input asp-for="CustomCSSLink" class="form-control" />
|
||||
<span asp-validation-for="CustomCSSLink" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label">Products</label>*
|
||||
<div class="mb-3">
|
||||
<a class="js-product-add btn btn-secondary" href="#" data-toggle="modal" data-target="#product-modal"><i class="fa fa-plus fa-fw"></i> Add Product</a>
|
||||
</div>
|
||||
<div class="js-products bg-light row p-3">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Template" class="control-label"></label>*
|
||||
<textarea asp-for="Template" rows="20" cols="40" class="form-control"></textarea>
|
||||
<textarea asp-for="Template" rows="10" cols="40" class="js-product-template form-control"></textarea>
|
||||
<span asp-validation-for="Template" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@ -101,5 +129,56 @@
|
||||
<link rel="stylesheet" href="~/vendor/highlightjs/default.min.css">
|
||||
<script src="~/vendor/highlightjs/highlight.min.js"></script>
|
||||
<script>hljs.initHighlightingOnLoad();</script>
|
||||
|
||||
<script id="template-product-item" type="text/template">
|
||||
<div class="col-sm-4 col-md-3 mb-3">
|
||||
<div class="card">
|
||||
{image}
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">{title}</h6>
|
||||
<a href="#" class="js-product-edit btn btn-primary" data-toggle="modal" data-target="#product-modal">Edit</a>
|
||||
<a href="#" class="js-product-remove btn btn-danger"><i class="fa fa-trash"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="template-product-content" type="text/template">
|
||||
<div class="mb-3">
|
||||
<input class="js-product-id" type="hidden" name="id" value="{id}">
|
||||
<input class="js-product-index" type="hidden" name="index" value="{index}">
|
||||
<div class="form-row">
|
||||
<div class="col-sm-6">
|
||||
<label>Title</label>*
|
||||
<input type="text" class="js-product-title form-control mb-2" value="{title}" autofocus />
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<label>Price</label>*
|
||||
<input type="text" class="js-product-price form-control mb-2" value="{price}" />
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<label>Custom price</label>
|
||||
<select class="js-product-custom form-control">
|
||||
{custom}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="col">
|
||||
<label>Image</label>
|
||||
<input type="text" class="js-product-image form-control mb-2" value="{image}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="col">
|
||||
<label>Description</label>
|
||||
<textarea rows="3" cols="40" class="js-product-description form-control mb-2">{description}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script src="~/products/js/products.js"></script>
|
||||
<script src="~/products/js/products.jquery.js"></script>
|
||||
}
|
||||
|
||||
|
@ -9,8 +9,8 @@
|
||||
<div class="header-content-inner text-white">
|
||||
<h1>Welcome to BTCPay Server</h1>
|
||||
<hr />
|
||||
<p>BTCPay Server is a free and open source server for merchants wanting to accept Bitcoin for their business. The API is compatible with Bitpay service to allow seamless migration.</p>
|
||||
<a style="background-color: #fff;color: #222;display:inline-block;text-align: center;white-space: nowrap;vertical-align: middle;user-select: none;line-height: 1.25;font-size: 1rem;text-decoration:none;font-weight: 700; text-transform: uppercase;border: none;border-radius: 300px;padding: 15px 30px;" href="https://github.com/btcpayserver/btcpayserver-doc">Getting started</a>
|
||||
<p>BTCPay Server is a free and open source server for merchants wanting to accept Bitcoin for their business.</p>
|
||||
<a style="background-color: #fff;color: #222;display:inline-block;text-align: center;white-space: nowrap;vertical-align: middle;user-select: none;line-height: 1.25;font-size: 1rem;text-decoration:none;font-weight: 700; text-transform: uppercase;border: none;border-radius: 300px;padding: 15px 30px;" href="https://docs.btcpayserver.org">Getting started</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
@ -54,6 +54,10 @@
|
||||
<th>Id</th>
|
||||
<td>@Model.Id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>State</th>
|
||||
<td>@Model.State</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Created date</th>
|
||||
<td>@Model.CreatedDate.ToBrowserDate()</td>
|
||||
@ -70,14 +74,6 @@
|
||||
<th>Transaction speed</th>
|
||||
<td>@Model.TransactionSpeed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<td>@Model.Status</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status Exception</th>
|
||||
<td>@Model.StatusException</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Refund email</th>
|
||||
<td><a href="mailto:@Model.RefundEmail">@Model.RefundEmail</a></td>
|
||||
@ -221,7 +217,7 @@
|
||||
<th class="text-right">Rate</th>
|
||||
<th class="text-right">Paid</th>
|
||||
<th class="text-right">Due</th>
|
||||
@if (Model.StatusException == "paidOver")
|
||||
@if (Model.StatusException == InvoiceExceptionStatus.PaidOver)
|
||||
{
|
||||
<th class="text-right">Overpaid</th>
|
||||
}
|
||||
@ -236,7 +232,7 @@
|
||||
<td class="text-right">@payment.Rate</td>
|
||||
<td class="text-right">@payment.Paid</td>
|
||||
<td class="text-right">@payment.Due</td>
|
||||
@if (Model.StatusException == "paidOver")
|
||||
@if (Model.StatusException == InvoiceExceptionStatus.PaidOver)
|
||||
{
|
||||
<td class="text-right">@payment.Overpaid</td>
|
||||
}
|
||||
|
@ -99,23 +99,7 @@
|
||||
}
|
||||
</td>
|
||||
<td>@invoice.InvoiceId</td>
|
||||
@if (invoice.Status == "paid")
|
||||
{
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
<a class="dropdown-toggle dropdown-toggle-split" style="cursor: pointer;" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
@invoice.Status <span class="sr-only">Toggle Dropdown</span>
|
||||
</a>
|
||||
<div class="dropdown-menu pull-right">
|
||||
<button class="dropdown-item small" data-toggle="modal" data-target="#myModal" onclick="$('#invoiceId').val('@invoice.InvoiceId')">Make Invalid</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td>@invoice.Status</td>
|
||||
}
|
||||
<td>@invoice.Status</td>
|
||||
<td style="text-align:right">@invoice.AmountCurrency</td>
|
||||
<td style="text-align:right">
|
||||
@if (invoice.ShowCheckout)
|
||||
@ -123,9 +107,32 @@
|
||||
<span>
|
||||
<a asp-action="Checkout" asp-route-invoiceId="@invoice.InvoiceId">Checkout</a>
|
||||
<a href="javascript:btcpay.showInvoice('@invoice.InvoiceId')">[^]</a>
|
||||
-
|
||||
@if (!invoice.CanMarkStatus)
|
||||
{
|
||||
<span>-</span>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
@if (invoice.CanMarkStatus)
|
||||
{
|
||||
<a class="dropdown-toggle dropdown-toggle-split" href="#" style="cursor: pointer;" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Change status <span class="sr-only">Toggle Dropdown</span>
|
||||
</a>
|
||||
<div class="dropdown-menu pull-right">
|
||||
@if (invoice.CanMarkInvalid)
|
||||
{
|
||||
<form method="get" asp-action="ChangeInvoiceState" asp-route-invoiceId="@invoice.InvoiceId" asp-route-newState="invalid">
|
||||
<button class="dropdown-item small">Mark as invalid <span class="fa fa-times"></span></button>
|
||||
</form>
|
||||
}
|
||||
@if (invoice.CanMarkComplete)
|
||||
{
|
||||
<form method="get" asp-action="ChangeInvoiceState" asp-route-invoiceId="@invoice.InvoiceId" asp-route-newState="complete">
|
||||
<button class="dropdown-item small">Mark as complete <span class="fa fa-check-circle"></span></button>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<a asp-action="Invoice" asp-route-invoiceId="@invoice.InvoiceId">Details</a>
|
||||
</td>
|
||||
</tr>
|
||||
@ -153,28 +160,3 @@
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Modal -->
|
||||
<div id="myModal" class="modal fade" role="dialog">
|
||||
<form method="post" action="/invoices/invalidatepaid">
|
||||
<input id="invoiceId" name="invoiceId" type="hidden" />
|
||||
<div class="modal-dialog">
|
||||
|
||||
<!-- Modal content-->
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Set Invoice status to Invalid</h4>
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to invalidate this transaction? This action is NOT undoable!</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-danger">Yes, make invoice Invalid</button>
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
1
BTCPayServer/Views/Invoice/_ViewImports.cshtml
Normal file
1
BTCPayServer/Views/Invoice/_ViewImports.cshtml
Normal file
@ -0,0 +1 @@
|
||||
@using BTCPayServer.Services.Invoices
|
60
BTCPayServer/Views/Server/LightningChargeServices.cshtml
Normal file
60
BTCPayServer/Views/Server/LightningChargeServices.cshtml
Normal file
@ -0,0 +1,60 @@
|
||||
@model ChargeServiceViewModel
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(ServerNavPages.Services);
|
||||
}
|
||||
|
||||
|
||||
<h4>Lightning charge service</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]" />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<p>
|
||||
<span>Lightning charge is a simple API for invoicing on lightning network, you can use it with several plugins:</span>
|
||||
<ul>
|
||||
<li><a href="https://github.com/ElementsProject/woocommerce-gateway-lightning" target="_blank">WooCommerce Lightning Gateway</a>: A comprehensive e-commerce application that integrates with stock-management and order-tracking systems</li>
|
||||
<li><a href="https://github.com/ElementsProject/nanopos" target="_blank">Nanopos</a>: A simple point-of-sale system for fixed-price goods</li>
|
||||
<li><a href="https://github.com/ElementsProject/filebazaar" target="_blank">FileBazaar</a>: A system for selling files such as documents, images, and videos</li>
|
||||
<li><a href="https://github.com/ElementsProject/wordpress-lightning-publisher" target="_blank">Lightning Publisher for WordPress</a>: A patronage model for unlocking WordPress blog entries</li>
|
||||
<li><a href="https://github.com/ElementsProject/paypercall" target="_blank">Paypercall</a>: A programmer’s toolkit for Lightning that enables micropayments for individual API calls</li>
|
||||
<li><a href="https://github.com/ElementsProject/ifpaytt" target="_blank">Ifpaytt</a>: An extension of paypercall that allows web developers using IFTTT to request payments for service usage</li>
|
||||
<li><a href="https://github.com/ElementsProject/lightning-jukebox" target="_blank">Lightning Jukebox</a>: A fun demo that reimagines a classic technology for the Lightning Network</li>
|
||||
<li><a href="https://github.com/ElementsProject/nanotip" target="_blank">Nanotip</a>: The simple tip jar, rebuilt to issue Lightning Network invoices</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<h4>Credentials</h4>
|
||||
@if (Model.Uri != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="Uri"></label>
|
||||
<input asp-for="Uri" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if (Model.APIToken != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="APIToken"></label>
|
||||
<input asp-for="APIToken" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if (Model.AuthenticatedUri != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="AuthenticatedUri"></label>
|
||||
<input asp-for="AuthenticatedUri" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
@ -1,41 +0,0 @@
|
||||
@model LndRestServicesViewModel
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(ServerNavPages.Services);
|
||||
}
|
||||
|
||||
|
||||
<h4>LND REST</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]" />
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<p>BTCPay exposes LND Rest services for outside consumption. See connection information below</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="BaseApiUrl">Base API Url</label>
|
||||
<input asp-for="BaseApiUrl" readonly class="form-control" />
|
||||
</div>
|
||||
@if (Model.Macaroon != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="Macaroon"></label>
|
||||
<input asp-for="Macaroon" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if (Model.CertificateThumbprint != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="CertificateThumbprint"></label>
|
||||
<input asp-for="CertificateThumbprint" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
@ -4,7 +4,7 @@
|
||||
}
|
||||
|
||||
|
||||
<h4>LND GRPC</h4>
|
||||
<h4>LND @Model.ConnectionType</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]" />
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
@ -17,14 +17,14 @@
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<p>
|
||||
<span>BTCPay exposes gRPC services for outside consumption, you will find connection information here.<br /></span>
|
||||
<span>BTCPay exposes LND's @Model.ConnectionType service for outside consumption, you will find connection information here.<br /></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<h5>QR Code connection</h5>
|
||||
<p>
|
||||
<span>You can use this QR Code to connect your Zap wallet to your LND instance.<br /></span>
|
||||
<span>You can use this QR Code to connect external software to your LND instance.<br /></span>
|
||||
<span>This QR Code is only valid for 10 minutes</span>
|
||||
</p>
|
||||
</div>
|
||||
@ -61,22 +61,60 @@
|
||||
<p>Alternatively, you can see the settings by clicking <a href="#details" data-toggle="collapse">here</a></p>
|
||||
</div>
|
||||
<div id="details" class="collapse">
|
||||
<div class="form-group">
|
||||
<label asp-for="Host"></label>
|
||||
<input asp-for="Host" readonly class="form-control" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="SSL"></label>
|
||||
<input asp-for="SSL" disabled type="checkbox" class="form-check-inline" />
|
||||
</div>
|
||||
@if(Model.Macaroon != null)
|
||||
@if (Model.Uri == null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="Host"></label>
|
||||
<input asp-for="Host" readonly class="form-control" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="SSL"></label>
|
||||
<input asp-for="SSL" disabled type="checkbox" class="form-check-inline" />
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="Uri"></label>
|
||||
<input asp-for="Uri" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if (Model.Macaroon != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="Macaroon"></label>
|
||||
<input asp-for="Macaroon" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if(Model.CertificateThumbprint != null)
|
||||
@if (Model.AdminMacaroon != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="AdminMacaroon"></label>
|
||||
<input asp-for="AdminMacaroon" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if (Model.InvoiceMacaroon != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="InvoiceMacaroon"></label>
|
||||
<input asp-for="InvoiceMacaroon" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if (Model.ReadonlyMacaroon != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="ReadonlyMacaroon"></label>
|
||||
<input asp-for="ReadonlyMacaroon" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if (Model.GRPCSSLCipherSuites != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="GRPCSSLCipherSuites"></label>
|
||||
<input asp-for="GRPCSSLCipherSuites" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if (Model.CertificateThumbprint != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="CertificateThumbprint"></label>
|
@ -15,8 +15,9 @@
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-8">
|
||||
<h4>Crypto services</h4>
|
||||
<div class="form-group">
|
||||
<span>You can get access here to LND (gRPC, Rest) or SSH services exposed by your server</span>
|
||||
<span>You can get access here to LND (gRPC, Rest) services exposed by your server</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
@ -33,26 +34,9 @@
|
||||
{
|
||||
<tr>
|
||||
<td>@lnd.Crypto</td>
|
||||
<td>LND @lnd.Type.ToString()</td>
|
||||
<td>@lnd.Type</td>
|
||||
<td style="text-align:right">
|
||||
@if (lnd.Type == BTCPayServer.Configuration.External.LndTypes.gRPC)
|
||||
{
|
||||
<a asp-action="LNDGRPCServices" asp-route-cryptoCode="@lnd.Crypto" asp-route-index="@lnd.Index">See information</a>
|
||||
}
|
||||
else if (lnd.Type == BTCPayServer.Configuration.External.LndTypes.Rest)
|
||||
{
|
||||
<a asp-action="LndRestServices" asp-route-cryptoCode="@lnd.Crypto" asp-route-index="@lnd.Index">See information</a>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (Model.HasSSH)
|
||||
{
|
||||
<tr>
|
||||
<td>None</td>
|
||||
<td>SSH</td>
|
||||
<td style="text-align:right">
|
||||
<a asp-action="SSHService">See information</a>
|
||||
<a asp-action="@lnd.Action" asp-route-cryptoCode="@lnd.Crypto" asp-route-index="@lnd.Index">See information</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@ -62,6 +46,39 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.ExternalServices.Count != 0)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<h4>Other services</h4>
|
||||
<div class="form-group">
|
||||
<span>Other external services</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var s in Model.ExternalServices)
|
||||
{
|
||||
<tr>
|
||||
<td>@s.Name</td>
|
||||
<td style="text-align:right">
|
||||
<a href="@s.Link" target="_blank">See information</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
||||
|
79
BTCPayServer/Views/Server/SparkServices.cshtml
Normal file
79
BTCPayServer/Views/Server/SparkServices.cshtml
Normal file
@ -0,0 +1,79 @@
|
||||
@model SparkServicesViewModel
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(ServerNavPages.Services);
|
||||
}
|
||||
|
||||
|
||||
<h4>Spark service</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]" />
|
||||
|
||||
@if (Model.ShowQR)
|
||||
{
|
||||
<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>
|
||||
<div>
|
||||
<span><b>CONFIDENTIAL:</b> This QR Code is confidential, close this window as soon as you don't need it anymore.<br /></span>
|
||||
<span>A malicious actor with access to this QR Code could steal the funds on your lightning wallet.</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<h5>Browser connection</h5>
|
||||
<p>
|
||||
<span>You can go to spark from your browser by <a href="@Model.SparkLink" target="_blank">clicking here</a><br /></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<h5>QR Code connection</h5>
|
||||
<p>
|
||||
<span>You can use QR Code to connect to your clightning from your mobile.<br /></span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@if (!Model.ShowQR)
|
||||
{
|
||||
<div class="form-group">
|
||||
<form method="get">
|
||||
<input type="hidden" asp-for="ShowQR" value="true" />
|
||||
<button type="submit" class="btn btn-primary">Show Confidential QR Code</button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="form-group">
|
||||
<div id="qrCode"></div>
|
||||
<div id="qrCodeData" data-url="@Html.Raw(Model.SparkLink)"></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
|
||||
@if (Model.ShowQR)
|
||||
{
|
||||
<script type="text/javascript" src="~/js/qrcode.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
new QRCode(document.getElementById("qrCode"),
|
||||
{
|
||||
text: "@Html.Raw(Model.SparkLink)",
|
||||
width: 150,
|
||||
height: 150
|
||||
});
|
||||
</script>
|
||||
}
|
||||
}
|
@ -19,8 +19,8 @@
|
||||
<title>BTCPay Server</title>
|
||||
|
||||
@* CSS *@
|
||||
<link href="@this.Context.Request.GetAbsoluteUri(themeManager.BootstrapUri)" rel="stylesheet" />
|
||||
<link href="@this.Context.Request.GetAbsoluteUri(themeManager.CreativeStartUri)" rel="stylesheet" />
|
||||
<link href="@this.Context.Request.GetRelativePathOrAbsolute(themeManager.BootstrapUri)" rel="stylesheet" />
|
||||
<link href="@this.Context.Request.GetRelativePathOrAbsolute(themeManager.CreativeStartUri)" rel="stylesheet" />
|
||||
|
||||
<bundle name="wwwroot/bundles/main-bundle.min.css" />
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
<span>The DerivationScheme represents the destination of the funds received by your invoice. It is generated by your wallet software. Please, verify that you are generating the right addresses by clicking on 'Check ExtPubKey'</span>
|
||||
</div>
|
||||
<input id="CryptoCurrency" asp-for="CryptoCode" type="hidden" />
|
||||
<input id="KeyPath" asp-for="KeyPath" type="hidden" />
|
||||
<div class="form-group">
|
||||
<label asp-for="DerivationScheme"></label>
|
||||
<input asp-for="DerivationScheme" class="form-control" />
|
||||
@ -40,7 +41,7 @@
|
||||
<ul>
|
||||
@for (int i = 0; i < 4; i++)
|
||||
{
|
||||
<li><a class="ledger-info-recommended" data-ledgeraccount="@i" href="#">Account @i (<span>@Model.RootKeyPath.Derive(i, true)</span>)</a></li>
|
||||
<li><a class="ledger-info-recommended" data-ledgerkeypath="@Model.RootKeyPath.Derive(i, true)" href="#">Account @i (<span>@Model.RootKeyPath.Derive(i, true)</span>)</a></li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
@ -95,6 +96,7 @@
|
||||
<span>Please check that your @Model.CryptoCode wallet is generating the same addresses as below.</span>
|
||||
</div>
|
||||
<input type="hidden" asp-for="Confirmation" />
|
||||
<input id="KeyPath" asp-for="KeyPath" type="hidden" />
|
||||
<input type="hidden" asp-for="DerivationScheme" />
|
||||
<input type="hidden" asp-for="Enabled" />
|
||||
<div class="form-group">
|
||||
|
@ -7,6 +7,24 @@
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<partial name="_StatusMessage" for="StatusMessage" />
|
||||
|
||||
<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>Before you proceed, please understand that Lightning Network is still in the experimental stage. Do not put the money you can't afford to lose. There is a high risk of you losing the money.</span>
|
||||
</p>
|
||||
<p>
|
||||
Take time to familiarize yourself with the risk. There's no backup for LND or c-lightning keys in BTCPay. Your keys are in a hot-wallet. This means :
|
||||
</p>
|
||||
<ul>
|
||||
<li>Most of BTCPay Server deployments run on a pruned node, which, while working, is not officially supported by lightning network implementations.</li>
|
||||
<li>If you erase your BTCPay Server virtual machine - you lose all the funds.</li>
|
||||
<li>If your server gets hacked - a hacker can take all of your funds by accessing your keys.</li>
|
||||
<li>If there is a bug in a lightning network implementation - you might lose all the funds.</li>
|
||||
<li>You approve being #reckless and being the sole responsible party for your loss.</li>
|
||||
<li>You approve to keep on your lightning node only what you can afford to lose.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<form method="post">
|
||||
@ -54,7 +72,7 @@
|
||||
<label asp-for="ConnectionString"></label>
|
||||
<input id="lightningurl" asp-for="ConnectionString" class="form-control" />
|
||||
<span asp-validation-for="ConnectionString" class="text-danger"></span>
|
||||
@if(Model.InternalLightningNode != null)
|
||||
@if (Model.InternalLightningNode != null)
|
||||
{
|
||||
<p class="form-text text-muted">
|
||||
You can use the internal lightning node by <a href="#" onclick="$('#lightningurl').val('@Model.InternalLightningNode'); return false;">clicking here</a>
|
||||
|
@ -28,10 +28,6 @@
|
||||
<p id="hw-loading"><span class="fa fa-question-circle" style="color:orange"></span> <span>Detecting hardware wallet...</span></p>
|
||||
<p id="hw-error" style="display:none;"><span class="fa fa-times-circle" style="color:red;"></span> <span class="hw-label">An error happened</span></p>
|
||||
<p id="hw-success" style="display:none;"><span class="fa fa-check-circle" style="color:green;"></span> <span class="hw-label">Detecting hardware wallet...</span></p>
|
||||
|
||||
<p id="check-loading" style="display:none;"><span class="fa fa-question-circle" style="color:orange"></span> <span class="check-label">Detecting hardware wallet...</span></p>
|
||||
<p id="check-error" style="display:none;"><span class="fa fa-times-circle" style="color:red;"></span> <span class="check-label">An error happened</span></p>
|
||||
<p id="check-success" style="display:none;"><span class="fa fa-check-circle" style="color:green;"></span> <span class="check-label">Detecting hardware wallet...</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -249,9 +249,15 @@ Cart.prototype.formatCurrency = function(amount, currency, symbol) {
|
||||
|
||||
if (srvModel.currencyInfo.prefixed) {
|
||||
prefix = srvModel.currencyInfo.currencySymbol;
|
||||
if (srvModel.currencyInfo.symbolSpace) {
|
||||
prefix = prefix + ' ';
|
||||
}
|
||||
}
|
||||
else {
|
||||
postfix = srvModel.currencyInfo.currencySymbol;
|
||||
if (srvModel.currencyInfo.symbolSpace) {
|
||||
postfix = ' ' + postfix;
|
||||
}
|
||||
}
|
||||
thousandsSep = srvModel.currencyInfo.thousandSeparator;
|
||||
decimalSep = srvModel.currencyInfo.decimalSeparator;
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 93 KiB |
54
BTCPayServer/wwwroot/imlegacy/groestlcoin-lightning.svg
Normal file
54
BTCPayServer/wwwroot/imlegacy/groestlcoin-lightning.svg
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.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" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="1000px" height="1000px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
|
||||
<g id="Groestlcoin_LN">
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M286.425,653.96"/>
|
||||
<g>
|
||||
<polygon fill="#FFFFFF" points="603.786,724.791 601.652,728.634 601.652,728.634 "/>
|
||||
<path fill="#FFFFFF" d="M502.935,79.215c-46.6,0-91.46,7.565-133.443,21.495L243.38,13.165l-14.07,17.69l96.15,87.27
|
||||
C179.64,185.465,78.2,333.075,78.2,503.95c0,234.2,190.535,424.733,424.733,424.733c50.125,0,98.24-8.755,142.927-24.77
|
||||
l-32.592-22.73c-35.02,10.262-72.038,15.79-110.335,15.79c-216.714,0-393.023-176.31-393.023-393.023
|
||||
c0-162.935,99.665-303.02,241.229-362.515c3.025-1.27,3.025-1.27,0,0l244.506,221.93L460.67,634.56l148.255,132.08l-5.14-41.903
|
||||
l-2.135,3.845l2.135-3.845l-13.28-108.262l134.37-269.043l-322.04-223.565c31.97-8.425,65.521-12.935,100.101-12.935
|
||||
c216.716,0,393.024,176.31,393.024,393.025c0,160.175-96.325,298.26-234.085,359.415l25.775,23.005
|
||||
c141.925-68.83,240.02-214.38,240.02-382.42C927.67,269.75,737.135,79.215,502.935,79.215z"/>
|
||||
</g>
|
||||
<path fill="#FFFFFF" d="M687.65,886.375l-25.775-23.005c-3.925,1.74-3.925,1.74,0,0L409.78,638.36l135.08-271.985L396.12,232.45
|
||||
l-0.555,1.19L418.38,391.2l0,0L288.575,654.75l324.688,226.435l32.592,22.73l126.01,87.88l14.02-17.74L687.65,886.375z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M286.425,653.96"/>
|
||||
<g>
|
||||
<polygon fill="#FFFFFF" points="603.786,724.791 601.652,728.634 601.652,728.634 "/>
|
||||
<path fill="#FFFFFF" d="M502.935,79.215c-46.6,0-91.46,7.565-133.443,21.495L243.38,13.165l-14.07,17.69l96.15,87.27
|
||||
C179.64,185.465,78.2,333.075,78.2,503.95c0,234.2,190.535,424.733,424.733,424.733c50.125,0,98.24-8.755,142.927-24.77
|
||||
l-32.592-22.73c-35.02,10.262-72.038,15.79-110.335,15.79c-216.714,0-393.023-176.31-393.023-393.023
|
||||
c0-162.935,99.665-303.02,241.229-362.515c3.025-1.27,3.025-1.27,0,0l244.506,221.93L460.67,634.56l148.255,132.08l-5.14-41.903
|
||||
l-2.135,3.845l2.135-3.845l-13.28-108.262l134.37-269.043l-322.04-223.565c31.97-8.425,65.521-12.935,100.101-12.935
|
||||
c216.716,0,393.024,176.31,393.024,393.025c0,160.175-96.325,298.26-234.085,359.415l25.775,23.005
|
||||
c141.925-68.83,240.02-214.38,240.02-382.42C927.67,269.75,737.135,79.215,502.935,79.215z"/>
|
||||
</g>
|
||||
<path fill="#FFFFFF" d="M687.65,886.375l-25.775-23.005c-3.925,1.74-3.925,1.74,0,0L409.78,638.36l135.08-271.985L396.12,232.45
|
||||
l-0.555,1.19L418.38,391.2l0,0L288.575,654.75l324.688,226.435l32.592,22.73l126.01,87.88l14.02-17.74L687.65,886.375z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M286.425,653.96"/>
|
||||
<g>
|
||||
<circle fill="#FFFFFF" cx="510" cy="500" r="408.335"/>
|
||||
<polygon fill="#FFFFFF" points="603.786,724.791 601.652,728.634 601.652,728.634 "/>
|
||||
<path fill="#009CCD" d="M502.935,79.215c-46.6,0-91.46,7.565-133.443,21.495L243.38,13.165l-14.07,17.69l96.15,87.27
|
||||
C179.64,185.465,78.2,333.075,78.2,503.95c0,234.2,190.535,424.733,424.733,424.733c50.125,0,98.24-8.755,142.927-24.77
|
||||
l-32.592-22.73c-35.02,10.262-72.038,15.79-110.335,15.79c-216.714,0-393.023-176.31-393.023-393.023
|
||||
c0-162.935,99.665-303.02,241.229-362.515c3.025-1.27,3.025-1.27,0,0l244.506,221.93L460.67,634.56l148.255,132.08l-5.14-41.903
|
||||
l-2.135,3.845l2.135-3.845l-13.28-108.262l134.37-269.043l-322.04-223.565c31.97-8.425,65.521-12.935,100.101-12.935
|
||||
c216.716,0,393.024,176.31,393.024,393.025c0,160.175-96.325,298.26-234.085,359.415l25.775,23.005
|
||||
c141.925-68.83,240.02-214.38,240.02-382.42C927.67,269.75,737.135,79.215,502.935,79.215z"/>
|
||||
</g>
|
||||
<path fill="#006694" d="M687.65,886.375l-25.775-23.005c-3.925,1.74-3.925,1.74,0,0L409.78,638.36l135.08-271.985L396.12,232.45
|
||||
l-0.555,1.19L418.38,391.2l0,0L288.575,654.75l324.688,226.435l32.592,22.73l126.01,87.88l14.02-17.74L687.65,886.375z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.0 KiB |
@ -31,15 +31,17 @@
|
||||
showFeedback("no-ledger-info");
|
||||
}
|
||||
}
|
||||
|
||||
$("#DerivationScheme").change(function () {
|
||||
$("#KeyPath").val("");
|
||||
});
|
||||
$(".ledger-info-recommended").on("click", function (elem) {
|
||||
elem.preventDefault();
|
||||
|
||||
showFeedback("ledger-validate");
|
||||
|
||||
var account = elem.currentTarget.getAttribute("data-ledgeraccount");
|
||||
var keypath = elem.currentTarget.getAttribute("data-ledgerkeypath");
|
||||
var cryptoCode = GetSelectedCryptoCode();
|
||||
bridge.sendCommand("getxpub", "cryptoCode=" + cryptoCode + "&account=" + account)
|
||||
bridge.sendCommand("getxpub", "cryptoCode=" + cryptoCode + "&keypath=" + keypath)
|
||||
.then(function (result) {
|
||||
if (cryptoCode !== GetSelectedCryptoCode())
|
||||
return;
|
||||
@ -48,33 +50,12 @@
|
||||
|
||||
$("#DerivationScheme").val(result.extPubKey);
|
||||
$("#DerivationSchemeFormat").val("BTCPay");
|
||||
$("#KeyPath").val(keypath);
|
||||
})
|
||||
.catch(function (reason) { Write('check', 'error', reason); });
|
||||
return false;
|
||||
});
|
||||
|
||||
var updateInfo = function () {
|
||||
if (!ledgerDetected)
|
||||
return false;
|
||||
var cryptoCode = GetSelectedCryptoCode();
|
||||
bridge.sendCommand("getxpub", "cryptoCode=" + cryptoCode)
|
||||
.catch(function (reason) { Write('check', 'error', reason); })
|
||||
.then(function (result) {
|
||||
if (!result)
|
||||
return;
|
||||
if (cryptoCode !== GetSelectedCryptoCode())
|
||||
return;
|
||||
if (result.error) {
|
||||
Write('check', 'error', result.error);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
Write('check', 'success', 'This store is configured to use your ledger');
|
||||
showFeedback("ledger-info");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
bridge.isSupported()
|
||||
.then(function (supported) {
|
||||
if (!supported) {
|
||||
@ -95,7 +76,7 @@
|
||||
} else {
|
||||
Write('hw', 'success', 'Ledger detected');
|
||||
ledgerDetected = true;
|
||||
updateInfo();
|
||||
showFeedback("ledger-info");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -41,52 +41,36 @@
|
||||
var updateInfo = function () {
|
||||
if (!ledgerDetected)
|
||||
return false;
|
||||
$(".crypto-info").css("display", "none");
|
||||
bridge.sendCommand("getinfo", "cryptoCode=" + cryptoCode)
|
||||
.catch(function (reason) { Write('check', 'error', reason); })
|
||||
$(".crypto-info").css("display", "block");
|
||||
var args = "";
|
||||
args += "cryptoCode=" + cryptoCode;
|
||||
args += "&destination=" + destination;
|
||||
args += "&amount=" + amount;
|
||||
args += "&feeRate=" + fee;
|
||||
args += "&substractFees=" + substractFee;
|
||||
|
||||
WriteAlert("warning", 'Please validate the transaction on your ledger');
|
||||
|
||||
var confirmButton = $("#confirm-button");
|
||||
confirmButton.prop("disabled", true);
|
||||
confirmButton.addClass("disabled");
|
||||
|
||||
bridge.sendCommand('sendtoaddress', args, 60 * 10 /* timeout */)
|
||||
.catch(function (reason) {
|
||||
WriteAlert("danger", reason);
|
||||
confirmButton.prop("disabled", false);
|
||||
confirmButton.removeClass("disabled");
|
||||
})
|
||||
.then(function (result) {
|
||||
if (!result)
|
||||
return;
|
||||
confirmButton.prop("disabled", false);
|
||||
confirmButton.removeClass("disabled");
|
||||
if (result.error) {
|
||||
Write('check', 'error', result.error);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
Write('check', 'success', 'This store is configured to use your ledger');
|
||||
$(".crypto-info").css("display", "block");
|
||||
|
||||
|
||||
var args = "";
|
||||
args += "cryptoCode=" + cryptoCode;
|
||||
args += "&destination=" + destination;
|
||||
args += "&amount=" + amount;
|
||||
args += "&feeRate=" + fee;
|
||||
args += "&substractFees=" + substractFee;
|
||||
|
||||
WriteAlert("warning", 'Please validate the transaction on your ledger');
|
||||
|
||||
var confirmButton = $("#confirm-button");
|
||||
confirmButton.prop("disabled", true);
|
||||
confirmButton.addClass("disabled");
|
||||
|
||||
bridge.sendCommand('sendtoaddress', args, 60 * 10 /* timeout */)
|
||||
.catch(function (reason) {
|
||||
WriteAlert("danger", reason);
|
||||
confirmButton.prop("disabled", false);
|
||||
confirmButton.removeClass("disabled");
|
||||
})
|
||||
.then(function (result) {
|
||||
if (!result)
|
||||
return;
|
||||
confirmButton.prop("disabled", false);
|
||||
confirmButton.removeClass("disabled");
|
||||
if (result.error) {
|
||||
WriteAlert("danger", result.error);
|
||||
} else {
|
||||
WriteAlert("success", 'Transaction broadcasted (' + result.transactionId + ')');
|
||||
window.location.replace(successCallback + "?txid=" + result.transactionId);
|
||||
}
|
||||
});
|
||||
WriteAlert("danger", result.error);
|
||||
} else {
|
||||
WriteAlert("success", 'Transaction broadcasted (' + result.transactionId + ')');
|
||||
window.location.replace(successCallback + "?txid=" + result.transactionId);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -22,18 +22,18 @@
|
||||
"Amount": "Cantidad",
|
||||
"Address": "Dirección",
|
||||
"Copied": "Copiado",
|
||||
"ConversionTab_BodyTop": "Puedes pagar {{btcDue}} {{cryptoCode}} usando Altcoins que este comercio no soporta directamente.",
|
||||
"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}} .",
|
||||
"ConversionTab_CalculateAmount_Error": "Reintentar",
|
||||
"ConversionTab_LoadCurrencies_Error": "Reintentar",
|
||||
"ConversionTab_Lightning": "No hay proveedores de conversión disponibles para los pagos de Lightning Network.",
|
||||
"ConversionTab_CurrencyList_Select_Option": "Por favor selecciona el tipo de moneda que de desea cambiar",
|
||||
"ConversionTab_CurrencyList_Select_Option": "Selecciona la moneda a convertir",
|
||||
"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. \nPuedes 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": "",
|
||||
"InvoiceExpired_Body_3": "Si recibimos el pago despues, procesaremos tu orden o te contactaremos para un reembolso...",
|
||||
"Invoice ID": "ID de la factura",
|
||||
"Order ID": "ID del pedido",
|
||||
"Return to StoreName": "Regresar a {{storeName}}",
|
||||
|
47
BTCPayServer/wwwroot/locales/hi.json
Normal file
47
BTCPayServer/wwwroot/locales/hi.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATED FROM TRANSIFEX, IF YOU WISH TO HELP TRANSLATION COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"code": "hi",
|
||||
"currentLanguage": "हिंदी",
|
||||
"lang": "भाषा",
|
||||
"Awaiting Payment...": "भुगतान के लिए प्रतीक्षा कर रहे हैं…",
|
||||
"Pay with": "इसके साथ भुगतान करें",
|
||||
"Contact and Refund Email": "संपर्क और धनवापसी ईमेल",
|
||||
"Contact_Body": "कृपया नीचे एक ईमेल पता प्रदान करें। यदि आपके भुगतान में कोई समस्या होती है तो हम इस पते पर आपसे संपर्क करेंगे।",
|
||||
"Your email": "आपका ईमेल",
|
||||
"Continue": "जारी रखें",
|
||||
"Please enter a valid email address": "कृपया एक वैध ईमेल पता दर्ज करें",
|
||||
"Order Amount": "ऑर्डर की राशि",
|
||||
"Network Cost": "नेटवर्क लागत",
|
||||
"Already Paid": "भुगतान पहले ही किया जा चुका है",
|
||||
"Due": "देय",
|
||||
"Scan": "स्कैन करें",
|
||||
"Copy": "कॉपी करें",
|
||||
"Conversion": "रूपांतरण",
|
||||
"Open in wallet": "वॉलेट खोलें",
|
||||
"CompletePay_Body": "अपना भुगतान पूरा करने के लिए, कृपया नीचे दिए गए पते पर {{btcDue}} {{cryptoCode}} भेजें.",
|
||||
"Amount": "धनराशि",
|
||||
"Address": "पता",
|
||||
"Copied": "कॉपी किया गया",
|
||||
"ConversionTab_BodyTop": "आप मर्चेंट द्वारा सीधे समर्थित भुगतान विकल्पों के मुकाबले {{btcDue}} {{cryptoCode}} का भुगतान altcoins का उपयोग करके भी कर सकते हैं।.",
|
||||
"ConversionTab_BodyDesc": "यह सेवा किसी तृतीय पक्ष द्वारा प्रदान की जाती है। कृपया ध्यान रखें कि प्रदाता आपके धन को कैसे आगे बढ़ाएंगे इस पर हमारा कोई नियंत्रण नहीं है। केवल {{cryptoCode}} ब्लॉकचेन पर धन प्राप्त होने के बाद ही चालान को चिह्नित किया जाएगा।.",
|
||||
"ConversionTab_CalculateAmount_Error": "Retry",
|
||||
"ConversionTab_LoadCurrencies_Error": "Retry",
|
||||
"ConversionTab_Lightning": "लाइटनिंग नेटवर्क भुगतान के लिए कोई रूपांतरण प्रदाता उपलब्ध नहीं है।",
|
||||
"ConversionTab_CurrencyList_Select_Option": "Please select a currency to convert from",
|
||||
"Invoice expiring soon...": "चालान जल्द ही समाप्त हो जाएगा...",
|
||||
"Invoice expired": "चालान की समय सीमा समाप्त हो गयी",
|
||||
"What happened?": "क्या हुआ?",
|
||||
"InvoiceExpired_Body_1": "इस चालान की समयसीमा समाप्त हो गयी। कोई भी चालान केवल {{maxTimeMinutes}} मिनटों के लिए वैध रहता है। \nयदि आप अपना भुगतान दोबारा जमा करना चाहते हैं तो आप {{storeName}} पर वापस जा सकते हैं।",
|
||||
"InvoiceExpired_Body_2": "यदि आपने भुगतान भेजने का प्रयास किया है, तो इसे अभी तक नेटवर्क द्वारा स्वीकार नहीं किया गया है। हमें अभी तक आपकी राशि प्राप्त नहीं हुई है।",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"Invoice ID": "चालान आईडी",
|
||||
"Order ID": "आर्डर आईडी",
|
||||
"Return to StoreName": "{{storeName}} पर वापस जाएं",
|
||||
"This invoice has been paid": "इस चालान का भुगतान कर दिया गया है",
|
||||
"This invoice has been archived": "यह चालान संग्रहीत कर दिया गया है",
|
||||
"Archived_Body": "ऑर्डर जानकारी या सहायता के लिए स्टोर से संपर्क करें",
|
||||
"BOLT 11 Invoice": "BOLT 11 चालान",
|
||||
"Node Info": "नोड जानकारी",
|
||||
"txCount": "लेनदेन",
|
||||
"txCount_plural": "लेनदेनों"
|
||||
}
|
@ -5,15 +5,15 @@
|
||||
"lang": "Język",
|
||||
"Awaiting Payment...": "Oczekiwanie na płatność...",
|
||||
"Pay with": "Płać z",
|
||||
"Contact and Refund Email": "Email do kontaktu reklamacji",
|
||||
"Contact and Refund Email": "Email do kontaktu i reklamacji",
|
||||
"Contact_Body": "Proszę podać adres email poniżej. Jeżeli będzie problem z płatnością użyjemy go do kontaktu",
|
||||
"Your email": "Twój email",
|
||||
"Continue": "Kontynuacja",
|
||||
"Continue": "Dalej",
|
||||
"Please enter a valid email address": "Proszę podać prawidłowy adres email",
|
||||
"Order Amount": "Kwota zamówienia",
|
||||
"Network Cost": "Koszt sieci",
|
||||
"Already Paid": "Już zapłacone",
|
||||
"Due": "Z powodu",
|
||||
"Due": "Razem",
|
||||
"Scan": "Skan",
|
||||
"Copy": "Kopia",
|
||||
"Conversion": "Konwersja",
|
||||
|
@ -32,8 +32,8 @@
|
||||
"Invoice expired": "Platnosť faktúry uplynula",
|
||||
"What happened?": "Čo sa stalo?",
|
||||
"InvoiceExpired_Body_1": "Platnosť tejto faktúry vypršala. Faktúra platí iba {{maxTimeMinutes}} minút. \nMôžete sa vrátiť na {{storeName}}, ak chcete platbu znova odoslať.",
|
||||
"InvoiceExpired_Body_2": "Ak ste sa pokúsili odoslať platbu, sieť ju ešte neprijala. Zatia+l sme nedostali Vaše finančné prostriedky.",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"InvoiceExpired_Body_2": "Ak ste sa pokúsili odoslať platbu, sieť ju ešte neprijala. Zatiaľ sme nedostali Vaše finančné prostriedky.",
|
||||
"InvoiceExpired_Body_3": "Ak ju obdržíme neskôr, Vašu objednávku buď spracujeme alebo Vás budeme kontaktovať, aby sme sa dohodli na vrátení...",
|
||||
"Invoice ID": "ID faktúry",
|
||||
"Order ID": "ID objednávky",
|
||||
"Return to StoreName": "Vrátiť sa na {{storeName}}",
|
||||
|
@ -33,7 +33,7 @@
|
||||
"What happened?": "Šta se desilo?",
|
||||
"InvoiceExpired_Body_1": "Ovaj račun je istekao. Račun važi samo {{maxTimeMinutes}} minuta. \nMožete se vratiti na {{storeName}} ukoliko želite da ponovo inicirate plaćanje.",
|
||||
"InvoiceExpired_Body_2": "Ako ste pokušali da pošaljete uplatu, još uvek nije prihvaćena na mreži. Nismo još zaprimili Vašu uplatu.",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"InvoiceExpired_Body_3": "Ukoliko vašu uplatu primimo kasnije, procesuiraćemo vašu narudžbinu ili ćemo vas kontaktirati za povraćaj novca...",
|
||||
"Invoice ID": "Broj računa",
|
||||
"Order ID": "Broj narudžbine",
|
||||
"Return to StoreName": "Vrati se na {{storeName}}",
|
||||
|
47
BTCPayServer/wwwroot/locales/tr.json
Normal file
47
BTCPayServer/wwwroot/locales/tr.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATED FROM TRANSIFEX, IF YOU WISH TO HELP TRANSLATION COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"code": "tr",
|
||||
"currentLanguage": "Türkçe",
|
||||
"lang": "Dil",
|
||||
"Awaiting Payment...": "Ödeme Bekleniyor...",
|
||||
"Pay with": "Aracılığıyla Öde",
|
||||
"Contact and Refund Email": "İletişim ve Geri Ödeme Email",
|
||||
"Contact_Body": "Lütfen aşağıda bir email belirtin. Ödemede sorun olması halinde bu adresten sizinle iletişime geçeceğiz.",
|
||||
"Your email": "Email Adresiniz",
|
||||
"Continue": "Devam Et",
|
||||
"Please enter a valid email address": "Lütfen geçerli bir email adresi giriniz",
|
||||
"Order Amount": "Ödeme Tutarı",
|
||||
"Network Cost": "Ağ Ücreti",
|
||||
"Already Paid": "Zaten Ödendi",
|
||||
"Due": "Bitiş Tarihi",
|
||||
"Scan": "Tara",
|
||||
"Copy": "Kopyala",
|
||||
"Conversion": "Dönüştürme",
|
||||
"Open in wallet": "Cüzdanda Aç",
|
||||
"CompletePay_Body": "Ödemenizi tamamlamak için, lütfen {{btcDue}} {{cryptoCode}} bilgilerini aşağıdaki adrese gönderin.",
|
||||
"Amount": "Tutar",
|
||||
"Address": "Adres",
|
||||
"Copied": "Kopyalandı",
|
||||
"ConversionTab_BodyTop": "{{btcDue}} {{cryptoCode}} ödemenizi satıcının desteklemediği altcoinler ile de yapabilirsiniz.",
|
||||
"ConversionTab_BodyDesc": "Bu servis 3. kişiler tarafından sağlanmaktadır. Ödemelerinizin iletildiği sağlayıcılar üzerinde bizim herhangi bir yetkimiz bulunmamaktadır. Faturanın ödendi olarak işaretlenmesi için ödemenin {{cryptoCode}} Blockchain ağından gelmesi gerekmektedir.",
|
||||
"ConversionTab_CalculateAmount_Error": "Tekar dene",
|
||||
"ConversionTab_LoadCurrencies_Error": "Tekrar dene",
|
||||
"ConversionTab_Lightning": "Lightning Network ödemesi için dönüşüm sağlayıcı mevcut değil.",
|
||||
"ConversionTab_CurrencyList_Select_Option": "Lütfen çevrim için bir para birimi seçin",
|
||||
"Invoice expiring soon...": "Fatura son kullanım tarihi yakın...",
|
||||
"Invoice expired": "Fatura son kullanım tarihi geçti",
|
||||
"What happened?": "Ne oldu?",
|
||||
"InvoiceExpired_Body_1": "Bu Fatura'nın son kullanım tarihi doldu. Bir Fatura sadece {{maxTimeMinutes}} dakika geçerlidir.\nÖdemenizi tekrar göndermek istiyorsanız {{storeName}} mağazasına dönebilirsiniz.",
|
||||
"InvoiceExpired_Body_2": "Ödemenizi göndermeyi denediyseniz, ağ tarafından henüz onaylanmadı. Ödemeniz bize ulaşmadı.",
|
||||
"InvoiceExpired_Body_3": "Eğer daha sonra bize ulaşırsa, ya siparişinizi onaylarız ya da iade için sizinle iletişime geçeriz.",
|
||||
"Invoice ID": "Fatura ID",
|
||||
"Order ID": "Sipariş ID",
|
||||
"Return to StoreName": "{{storeName}} dön",
|
||||
"This invoice has been paid": "Bu Fatura ödendi",
|
||||
"This invoice has been archived": "Bu Fatura arşivlendi",
|
||||
"Archived_Body": "Bilgi ve yardım için lütfen mağaza ile iletişime geçin",
|
||||
"BOLT 11 Invoice": "BOLT 11 Faturası",
|
||||
"Node Info": "Düğüm Bilgisi",
|
||||
"txCount": "{{count}} işlem",
|
||||
"txCount_plural": "{{count}} işlem"
|
||||
}
|
69
BTCPayServer/wwwroot/products/js/products.jquery.js
Normal file
69
BTCPayServer/wwwroot/products/js/products.jquery.js
Normal file
@ -0,0 +1,69 @@
|
||||
$(document).ready(function(){
|
||||
var products = new Products(),
|
||||
delay = null;
|
||||
|
||||
$('.js-product-template').on('input', function(){
|
||||
products.loadFromTemplate();
|
||||
|
||||
clearTimeout(delay);
|
||||
|
||||
// Delay rebuilding DOM for performance reasons
|
||||
delay = setTimeout(function(){
|
||||
products.showAll();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
$('.js-products').on('click', '.js-product-remove', function(event){
|
||||
event.preventDefault();
|
||||
|
||||
var id = $(this).closest('.card').parent().index();
|
||||
|
||||
products.removeItem(id);
|
||||
});
|
||||
|
||||
$('.js-products').on('click', '.js-product-edit', function(event){
|
||||
event.preventDefault();
|
||||
|
||||
var id = $(this).closest('.card').parent().index();
|
||||
|
||||
products.itemContent(id);
|
||||
});
|
||||
|
||||
$('.js-product-save').click(function(event){
|
||||
event.preventDefault();
|
||||
|
||||
var index = $('.js-product-index').val(),
|
||||
description = $('.js-product-description').val(),
|
||||
image = $('.js-product-image').val(),
|
||||
custom = $('.js-product-custom').val();
|
||||
obj = {
|
||||
id: products.escape($('.js-product-id').val()),
|
||||
price: products.escape($('.js-product-price').val()),
|
||||
title: products.escape($('.js-product-title').val()),
|
||||
};
|
||||
|
||||
// Only continue if price and title is provided
|
||||
if (obj.price && obj.title) {
|
||||
if (description) {
|
||||
obj.description = products.escape(description);
|
||||
}
|
||||
if (image) {
|
||||
obj.image = products.escape(image);
|
||||
}
|
||||
if (custom == 'true') {
|
||||
obj.custom = products.escape(custom);
|
||||
}
|
||||
|
||||
// Create an id from the title for a new product
|
||||
if (!Boolean(index)) {
|
||||
obj.id = products.escape(obj.title.toLowerCase() + ':');
|
||||
}
|
||||
|
||||
products.saveItem(obj, index);
|
||||
}
|
||||
});
|
||||
|
||||
$('.js-product-add').click(function(){
|
||||
products.itemContent();
|
||||
});
|
||||
});
|
186
BTCPayServer/wwwroot/products/js/products.js
Normal file
186
BTCPayServer/wwwroot/products/js/products.js
Normal file
@ -0,0 +1,186 @@
|
||||
function Products() {
|
||||
this.products = [];
|
||||
|
||||
// Get products from template
|
||||
this.loadFromTemplate();
|
||||
|
||||
// Show products in the DOM
|
||||
this.showAll();
|
||||
}
|
||||
|
||||
Products.prototype.loadFromTemplate = function() {
|
||||
var template = $('.js-product-template').val().trim(),
|
||||
lines = template.split("\n\n");
|
||||
|
||||
this.products = [];
|
||||
|
||||
// Split products from the template
|
||||
for (var kl in lines) {
|
||||
var line = lines[kl],
|
||||
product = line.split("\n"),
|
||||
id, price, title, description, image = null,
|
||||
custom;
|
||||
|
||||
for (var kp in product) {
|
||||
var productProperty = product[kp].trim();
|
||||
|
||||
if (kp == 0) {
|
||||
id = productProperty;
|
||||
}
|
||||
|
||||
if (productProperty.indexOf('price:') !== -1) {
|
||||
price = parseFloat(productProperty.replace('price:', '').trim());
|
||||
}
|
||||
if (productProperty.indexOf('title:') !== -1) {
|
||||
title = productProperty.replace('title:', '').trim();
|
||||
}
|
||||
if (productProperty.indexOf('description:') !== -1) {
|
||||
description = productProperty.replace('description:', '').trim();
|
||||
}
|
||||
if (productProperty.indexOf('image:') !== -1) {
|
||||
image = productProperty.replace('image:', '').trim();
|
||||
}
|
||||
if (productProperty.indexOf('custom:') !== -1) {
|
||||
custom = productProperty.replace('custom:', '').trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (price != null || title != null) {
|
||||
// Add product to the list
|
||||
this.products.push({
|
||||
'id': id,
|
||||
'title': title,
|
||||
'price': price,
|
||||
'image': image || null,
|
||||
'description': description || null,
|
||||
'custom': Boolean(custom)
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Products.prototype.saveTemplate = function() {
|
||||
var template = '';
|
||||
|
||||
// Construct template from the product list
|
||||
for (var key in this.products) {
|
||||
var product = this.products[key],
|
||||
id = product.id,
|
||||
title = product.title,
|
||||
price = product.price,
|
||||
image = product.image
|
||||
description = product.description,
|
||||
custom = product.custom;
|
||||
|
||||
template += id + '\n' +
|
||||
' price: ' + price + '\n' +
|
||||
' title: ' + title + '\n';
|
||||
|
||||
if (description) {
|
||||
template += ' description: ' + description + '\n';
|
||||
}
|
||||
if (image) {
|
||||
template += ' image: ' + image + '\n';
|
||||
}
|
||||
if (custom) {
|
||||
template += ' custom: true\n';
|
||||
}
|
||||
template += '\n';
|
||||
}
|
||||
|
||||
$('.js-product-template').val(template);
|
||||
}
|
||||
|
||||
Products.prototype.showAll = function() {
|
||||
var list = [];
|
||||
|
||||
for (var key in this.products) {
|
||||
var product = this.products[key],
|
||||
image = product.image;
|
||||
|
||||
list.push(this.template($('#template-product-item'), {
|
||||
'title': this.escape(product.title),
|
||||
'image': image ? '<img class="card-img-top" src="' + this.escape(image) + '" alt="Card image cap">' : ''
|
||||
}));
|
||||
}
|
||||
|
||||
$('.js-products').html(list);
|
||||
}
|
||||
|
||||
// Load the template
|
||||
Products.prototype.template = function($template, obj) {
|
||||
var template = $template.text();
|
||||
|
||||
for (var key in obj) {
|
||||
var re = new RegExp('{' + key + '}', 'mg');
|
||||
template = template.replace(re, obj[key]);
|
||||
}
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
Products.prototype.saveItem = function(obj, index) {
|
||||
// Edit product
|
||||
if (index) {
|
||||
this.products[index] = obj;
|
||||
} else { // Add new product
|
||||
this.products.push(obj);
|
||||
}
|
||||
|
||||
this.saveTemplate();
|
||||
this.showAll();
|
||||
this.modalEmpty();
|
||||
}
|
||||
|
||||
Products.prototype.removeItem = function(index) {
|
||||
if (this.products.length == 1) {
|
||||
this.products = [];
|
||||
$('.js-products').html('No products.');
|
||||
} else {
|
||||
this.products.splice(index, 1);
|
||||
$('.js-products').find('.card').parent().eq(index).remove();
|
||||
}
|
||||
|
||||
this.saveTemplate();
|
||||
}
|
||||
|
||||
Products.prototype.itemContent = function(index) {
|
||||
var product = null,
|
||||
custom = false;
|
||||
|
||||
// Existing product
|
||||
if (!isNaN(index)) {
|
||||
product = this.products[index];
|
||||
custom = product.custom;
|
||||
}
|
||||
|
||||
var template = this.template($('#template-product-content'), {
|
||||
'id': product != null ? this.escape(product.id) : '',
|
||||
'index': isNaN(index) ? '' : this.escape(index),
|
||||
'price': product != null ? this.escape(product.price) : '',
|
||||
'title': product != null ? this.escape(product.title) : '',
|
||||
'description': product != null ? this.escape(product.description) : '',
|
||||
'image': product != null ? this.escape(product.image) : '',
|
||||
'custom': '<option value="true"' + (custom ? ' selected' : '') + '>Yes</option><option value="false"' + (!custom ? ' selected' : '') + '>No</option>'
|
||||
});
|
||||
|
||||
$('#product-modal').find('.modal-body').html(template);
|
||||
}
|
||||
|
||||
Products.prototype.modalEmpty = function() {
|
||||
var $modal = $('#product-modal');
|
||||
|
||||
$modal.modal('hide');
|
||||
$modal.find('.modal-body').empty();
|
||||
}
|
||||
|
||||
Products.prototype.escape = function(input) {
|
||||
return ('' + input) /* Forces the conversion to string. */
|
||||
.replace(/&/g, '&') /* This MUST be the 1st replacement. */
|
||||
.replace(/'/g, ''') /* The 4 other predefined entities, required. */
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
;
|
||||
}
|
Reference in New Issue
Block a user