mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Merge remote-tracking branch 'btcpayserver/master' into feature/coinswitch
This commit is contained in:
@@ -7,24 +7,16 @@ jobs:
|
|||||||
- checkout
|
- checkout
|
||||||
|
|
||||||
test:
|
test:
|
||||||
machine: true
|
machine:
|
||||||
|
docker_layer_caching: true
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run:
|
- run:
|
||||||
command: |
|
command: |
|
||||||
echo "117.18.232.200 api.nuget.org" | sudo tee -a /etc/hosts
|
|
||||||
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
|
cd BTCPayServer.Tests
|
||||||
dotnet test --filter Fast=Fast
|
docker-compose down --v
|
||||||
docker-compose up -d dev
|
docker-compose build
|
||||||
dotnet test --filter Integration=Integration
|
docker-compose run tests
|
||||||
|
|
||||||
# publish jobs require $DOCKERHUB_REPO, $DOCKERHUB_USER, $DOCKERHUB_PASS defined
|
# publish jobs require $DOCKERHUB_REPO, $DOCKERHUB_USER, $DOCKERHUB_PASS defined
|
||||||
publish_docker_linuxamd64:
|
publish_docker_linuxamd64:
|
||||||
@@ -36,7 +28,7 @@ jobs:
|
|||||||
command: |
|
command: |
|
||||||
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
|
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
|
||||||
#
|
#
|
||||||
sudo docker build --add-host "api.nuget.org:117.18.232.200" --pull -t $DOCKERHUB_REPO:$LATEST_TAG-amd64 -f Dockerfile.linuxamd64 .
|
sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-amd64 -f Dockerfile.linuxamd64 .
|
||||||
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
|
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
|
||||||
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-amd64
|
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-amd64
|
||||||
|
|
||||||
@@ -50,7 +42,7 @@ jobs:
|
|||||||
sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||||
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
|
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
|
||||||
#
|
#
|
||||||
sudo docker build --add-host "api.nuget.org:117.18.232.200" --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 -f Dockerfile.linuxarm32v7 .
|
sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 -f Dockerfile.linuxarm32v7 .
|
||||||
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
|
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
|
||||||
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm32v7
|
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm32v7
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
FROM microsoft/dotnet:2.1.403-sdk-alpine3.7
|
FROM microsoft/dotnet:2.1.500-sdk-alpine3.7 AS builder
|
||||||
WORKDIR /app
|
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT false
|
||||||
# caches restore result by copying csproj file separately
|
RUN apk add --no-cache icu-libs
|
||||||
COPY BTCPayServer.Tests/BTCPayServer.Tests.csproj BTCPayServer.Tests/BTCPayServer.Tests.csproj
|
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
|
COPY BTCPayServer/BTCPayServer.csproj BTCPayServer/BTCPayServer.csproj
|
||||||
|
COPY BTCPayServer.Tests/BTCPayServer.Tests.csproj BTCPayServer.Tests/BTCPayServer.Tests.csproj
|
||||||
WORKDIR /app/BTCPayServer.Tests
|
RUN dotnet restore BTCPayServer.Tests/BTCPayServer.Tests.csproj
|
||||||
RUN dotnet restore
|
COPY . .
|
||||||
# copies the rest of your code
|
RUN dotnet build
|
||||||
COPY . ../.
|
WORKDIR /source/BTCPayServer.Tests
|
||||||
|
ENTRYPOINT ["./docker-entrypoint.sh"]
|
||||||
ENTRYPOINT ["dotnet", "test"]
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using BTCPayServer.Tests.Logging;
|
using BTCPayServer.Tests.Logging;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using NBitcoin.DataEncoders;
|
using NBitcoin.DataEncoders;
|
||||||
@@ -340,13 +340,17 @@ namespace BTCPayServer.Tests
|
|||||||
{
|
{
|
||||||
foreach (var test in new[]
|
foreach (var test in new[]
|
||||||
{
|
{
|
||||||
(0.0005m, "$0.0005 (USD)"),
|
(0.0005m, "$0.0005 (USD)", "USD"),
|
||||||
(0.001m, "$0.001 (USD)"),
|
(0.001m, "$0.001 (USD)", "USD"),
|
||||||
(0.01m, "$0.01 (USD)"),
|
(0.01m, "$0.01 (USD)", "USD"),
|
||||||
(0.1m, "$0.10 (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);
|
Assert.Equal(test.Item2, actual);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -884,7 +888,7 @@ namespace BTCPayServer.Tests
|
|||||||
var result = client.SendAsync(message).GetAwaiter().GetResult();
|
var result = client.SendAsync(message).GetAwaiter().GetResult();
|
||||||
result.EnsureSuccessStatusCode();
|
result.EnsureSuccessStatusCode();
|
||||||
/////////////////////
|
/////////////////////
|
||||||
|
|
||||||
// Have error 403 with bad signature
|
// Have error 403 with bad signature
|
||||||
client = new HttpClient();
|
client = new HttpClient();
|
||||||
HttpRequestMessage mess = new HttpRequestMessage(HttpMethod.Get, tester.PayTester.ServerUri.AbsoluteUri + "tokens");
|
HttpRequestMessage mess = new HttpRequestMessage(HttpMethod.Get, tester.PayTester.ServerUri.AbsoluteUri + "tokens");
|
||||||
@@ -1467,6 +1471,44 @@ donation:
|
|||||||
Assert.NotNull(donationInvoice);
|
Assert.NotNull(donationInvoice);
|
||||||
Assert.Equal("CAD", donationInvoice.Currency);
|
Assert.Equal("CAD", donationInvoice.Currency);
|
||||||
Assert.Equal("donation", donationInvoice.ItemDesc);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1580,10 +1622,9 @@ donation:
|
|||||||
var jsonResultPaid = user.GetController<InvoiceController>().Export("json").GetAwaiter().GetResult();
|
var jsonResultPaid = user.GetController<InvoiceController>().Export("json").GetAwaiter().GetResult();
|
||||||
var paidresult = Assert.IsType<ContentResult>(jsonResultPaid);
|
var paidresult = Assert.IsType<ContentResult>(jsonResultPaid);
|
||||||
Assert.Equal("application/json", paidresult.ContentType);
|
Assert.Equal("application/json", paidresult.ContentType);
|
||||||
Assert.Contains("\"ItemDesc\": \"Some \\\", description\"", paidresult.Content);
|
Assert.Contains("\"InvoiceItemDesc\": \"Some \\\", description\"", paidresult.Content);
|
||||||
Assert.Contains("\"FiatPrice\": 500.0", paidresult.Content);
|
Assert.Contains("\"InvoicePrice\": 500.0", paidresult.Content);
|
||||||
Assert.Contains("\"ConversionRate\": 5000.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);
|
Assert.Contains($"\"InvoiceId\": \"{invoice.Id}\",", paidresult.Content);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1648,14 +1689,9 @@ donation:
|
|||||||
var paidresult = Assert.IsType<ContentResult>(exportResultPaid);
|
var paidresult = Assert.IsType<ContentResult>(exportResultPaid);
|
||||||
Assert.Equal("application/csv", paidresult.ContentType);
|
Assert.Equal("application/csv", paidresult.ContentType);
|
||||||
Assert.Contains($",\"orderId\",\"{invoice.Id}\",", paidresult.Content);
|
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($",\"OnChain\",\"0.1000999\",\"BTC\",\"5000.0\",\"500.0\"", paidresult.Content);
|
||||||
Assert.Contains($",\"USD\",\"\",\"Some ``, description\",\"new\"", 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"
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,8 +36,6 @@ namespace BTCPayServer.Tests
|
|||||||
Task.WaitAll(langs.Select(async l =>
|
Task.WaitAll(langs.Select(async l =>
|
||||||
{
|
{
|
||||||
bool isSourceLang = l == "en";
|
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}/");
|
var j = await client.GetTransifexAsync($"https://www.transifex.com/api/2/project/btcpayserver/resource/enjson/translation/{l}/");
|
||||||
if(!isSourceLang)
|
if(!isSourceLang)
|
||||||
{
|
{
|
||||||
@@ -56,8 +54,12 @@ namespace BTCPayServer.Tests
|
|||||||
var langFile = Path.Combine(langsDir, langCode + ".json");
|
var langFile = Path.Combine(langsDir, langCode + ".json");
|
||||||
var jobj = JObject.Parse(content);
|
var jobj = JObject.Parse(content);
|
||||||
jobj["code"] = langCode;
|
jobj["code"] = langCode;
|
||||||
|
|
||||||
if ((string)jobj["currentLanguage"] == "English" && !isSourceLang)
|
if ((string)jobj["currentLanguage"] == "English" && !isSourceLang)
|
||||||
return; // Not translated
|
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/"));
|
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)
|
if (isSourceLang)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ services:
|
|||||||
TESTS_MYSQL: User ID=root;Host=mysql;Port=3306;Database=btcpayserver
|
TESTS_MYSQL: User ID=root;Host=mysql;Port=3306;Database=btcpayserver
|
||||||
TESTS_PORT: 80
|
TESTS_PORT: 80
|
||||||
TESTS_HOSTNAME: tests
|
TESTS_HOSTNAME: tests
|
||||||
TEST_MERCHANTLIGHTNINGD: "type=clightning;server=/etc/merchant_lightningd_datadir/lightning-rpc"
|
TEST_MERCHANTLIGHTNINGD: "type=clightning;server=unix://etc/merchant_lightningd_datadir/lightning-rpc"
|
||||||
TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=/etc/customer_lightningd_datadir/lightning-rpc"
|
TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=unix://etc/customer_lightningd_datadir/lightning-rpc"
|
||||||
TEST_MERCHANTCHARGE: "type=charge;server=https://lightning-charged:9112/;api-token=foiewnccewuify;allowinsecure=true"
|
TEST_MERCHANTCHARGE: "type=charge;server=http://lightning-charged:9112/;api-token=foiewnccewuify"
|
||||||
TEST_MERCHANTLND: "type=lnd-rest;server=https://lnd:lnd@127.0.0.1:53280/;allowinsecure=true"
|
TEST_MERCHANTLND: "https://lnd:lnd@merchant_lnd:8080/"
|
||||||
TESTS_INCONTAINER: "true"
|
TESTS_INCONTAINER: "true"
|
||||||
expose:
|
expose:
|
||||||
- "80"
|
- "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
|
# The dev container is not actually used, it is just handy to run `docker-compose up dev` to start all services
|
||||||
dev:
|
dev:
|
||||||
image: nicolasdorier/docker-bitcoin:0.17.0
|
image: btcpayserver/bitcoin:0.17.0
|
||||||
environment:
|
environment:
|
||||||
BITCOIN_NETWORK: regtest
|
BITCOIN_NETWORK: regtest
|
||||||
BITCOIN_EXTRA_ARGS: |
|
BITCOIN_EXTRA_ARGS: |
|
||||||
@@ -53,7 +53,7 @@ services:
|
|||||||
- merchant_lnd
|
- merchant_lnd
|
||||||
|
|
||||||
devlnd:
|
devlnd:
|
||||||
image: nicolasdorier/docker-bitcoin:0.17.0
|
image: btcpayserver/bitcoin:0.17.0
|
||||||
environment:
|
environment:
|
||||||
BITCOIN_NETWORK: regtest
|
BITCOIN_NETWORK: regtest
|
||||||
BITCOIN_EXTRA_ARGS: |
|
BITCOIN_EXTRA_ARGS: |
|
||||||
@@ -94,7 +94,7 @@ services:
|
|||||||
- litecoind
|
- litecoind
|
||||||
|
|
||||||
bitcoind:
|
bitcoind:
|
||||||
image: nicolasdorier/docker-bitcoin:0.17.0
|
image: btcpayserver/bitcoin:0.17.0
|
||||||
environment:
|
environment:
|
||||||
BITCOIN_NETWORK: regtest
|
BITCOIN_NETWORK: regtest
|
||||||
BITCOIN_EXTRA_ARGS: |
|
BITCOIN_EXTRA_ARGS: |
|
||||||
@@ -118,7 +118,7 @@ services:
|
|||||||
- "bitcoin_datadir:/data"
|
- "bitcoin_datadir:/data"
|
||||||
|
|
||||||
customer_lightningd:
|
customer_lightningd:
|
||||||
image: nicolasdorier/clightning:v0.6.2-3-dev
|
image: btcpayserver/lightning:v0.6.2-dev
|
||||||
stop_signal: SIGKILL
|
stop_signal: SIGKILL
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
@@ -164,7 +164,7 @@ services:
|
|||||||
- merchant_lightningd
|
- merchant_lightningd
|
||||||
|
|
||||||
merchant_lightningd:
|
merchant_lightningd:
|
||||||
image: nicolasdorier/clightning:v0.6.2-3-dev
|
image: btcpayserver/lightning:v0.6.2-dev
|
||||||
stop_signal: SIGKILL
|
stop_signal: SIGKILL
|
||||||
environment:
|
environment:
|
||||||
EXPOSE_TCP: "true"
|
EXPOSE_TCP: "true"
|
||||||
@@ -188,7 +188,7 @@ services:
|
|||||||
- bitcoind
|
- bitcoind
|
||||||
|
|
||||||
litecoind:
|
litecoind:
|
||||||
image: nicolasdorier/docker-litecoin:0.15.1
|
image: nicolasdorier/docker-litecoin:0.16.3
|
||||||
environment:
|
environment:
|
||||||
BITCOIN_EXTRA_ARGS: |
|
BITCOIN_EXTRA_ARGS: |
|
||||||
rpcuser=ceiwHEbqWI83
|
rpcuser=ceiwHEbqWI83
|
||||||
|
|||||||
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
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||||
<Version>1.0.3.28</Version>
|
<Version>1.0.3.31</Version>
|
||||||
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@@ -136,6 +136,9 @@
|
|||||||
<Content Update="Views\Home\BitpayTranslator.cshtml">
|
<Content Update="Views\Home\BitpayTranslator.cshtml">
|
||||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Update="Views\Server\SparkServices.cshtml">
|
||||||
|
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||||
|
</Content>
|
||||||
<Content Update="Views\Server\SSHService.cshtml">
|
<Content Update="Views\Server\SSHService.cshtml">
|
||||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||||
</Content>
|
</Content>
|
||||||
|
|||||||
@@ -137,6 +137,17 @@ namespace BTCPayServer.Configuration
|
|||||||
|
|
||||||
externalLnd<ExternalLndGrpc>($"{net.CryptoCode}.external.lnd.grpc", "lnd-grpc");
|
externalLnd<ExternalLndGrpc>($"{net.CryptoCode}.external.lnd.grpc", "lnd-grpc");
|
||||||
externalLnd<ExternalLndRest>($"{net.CryptoCode}.external.lnd.rest", "lnd-rest");
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logs.Configuration.LogInformation("Supported chains: " + String.Join(',', supportedChains.ToArray()));
|
Logs.Configuration.LogInformation("Supported chains: " + String.Join(',', supportedChains.ToArray()));
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ namespace BTCPayServer.Configuration
|
|||||||
app.Option($"--{crypto}explorercookiefile", $"Path to the cookie file (default: {network.NBXplorerNetwork.DefaultSettings.DefaultCookieFile})", CommandOptionType.SingleValue);
|
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}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}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", $"The connection string to spark server (default: empty)", CommandOptionType.SingleValue);
|
||||||
}
|
}
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,28 +8,23 @@ namespace BTCPayServer.Configuration.External
|
|||||||
{
|
{
|
||||||
public abstract class ExternalLnd : ExternalService
|
public abstract class ExternalLnd : ExternalService
|
||||||
{
|
{
|
||||||
public ExternalLnd(LightningConnectionString connectionString, LndTypes type)
|
public ExternalLnd(LightningConnectionString connectionString, string type)
|
||||||
{
|
{
|
||||||
ConnectionString = connectionString;
|
ConnectionString = connectionString;
|
||||||
Type = type;
|
Type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LndTypes Type { get; set; }
|
public string Type { get; set; }
|
||||||
public LightningConnectionString ConnectionString { get; set; }
|
public LightningConnectionString ConnectionString { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum LndTypes
|
|
||||||
{
|
|
||||||
gRPC, Rest
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ExternalLndGrpc : ExternalLnd
|
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 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
46
BTCPayServer/Configuration/SparkConnectionString.cs
Normal file
46
BTCPayServer/Configuration/SparkConnectionString.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
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":
|
||||||
|
if (resultTemp.CookeFile != null)
|
||||||
|
return false;
|
||||||
|
resultTemp.CookeFile = kv[1];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = resultTemp;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,25 +40,26 @@ namespace BTCPayServer.Controllers
|
|||||||
if (app == null)
|
if (app == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
var settings = app.GetSettings<PointOfSaleSettings>();
|
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()
|
return View(new ViewPointOfSaleViewModel()
|
||||||
{
|
{
|
||||||
Title = settings.Title,
|
Title = settings.Title,
|
||||||
Step = step.ToString(CultureInfo.InvariantCulture),
|
Step = step.ToString(CultureInfo.InvariantCulture),
|
||||||
EnableShoppingCart = settings.EnableShoppingCart,
|
EnableShoppingCart = settings.EnableShoppingCart,
|
||||||
ShowCustomAmount = settings.ShowCustomAmount,
|
ShowCustomAmount = settings.ShowCustomAmount,
|
||||||
CurrencyCode = currency.Code,
|
CurrencyCode = settings.Currency,
|
||||||
CurrencySymbol = currency.Symbol,
|
CurrencySymbol = numberFormatInfo.CurrencySymbol,
|
||||||
CurrencyInfo = new ViewPointOfSaleViewModel.CurrencyInfoData()
|
CurrencyInfo = new ViewPointOfSaleViewModel.CurrencyInfoData()
|
||||||
{
|
{
|
||||||
CurrencySymbol = string.IsNullOrEmpty(currency.Symbol) ? currency.Code : currency.Symbol,
|
CurrencySymbol = string.IsNullOrEmpty(numberFormatInfo.CurrencySymbol) ? settings.Currency : numberFormatInfo.CurrencySymbol,
|
||||||
Divisibility = currency.Divisibility,
|
Divisibility = numberFormatInfo.CurrencyDecimalDigits,
|
||||||
DecimalSeparator = numberFormatInfo.CurrencyDecimalSeparator,
|
DecimalSeparator = numberFormatInfo.CurrencyDecimalSeparator,
|
||||||
ThousandSeparator = numberFormatInfo.NumberGroupSeparator,
|
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),
|
Items = _AppsHelper.Parse(settings.Template, settings.Currency),
|
||||||
ButtonText = settings.ButtonText,
|
ButtonText = settings.ButtonText,
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
var m = new InvoiceDetailsModel.Payment();
|
var m = new InvoiceDetailsModel.Payment();
|
||||||
m.Crypto = payment.GetPaymentMethodId().CryptoCode;
|
m.Crypto = payment.GetPaymentMethodId().CryptoCode;
|
||||||
m.DepositAddress = onChainPaymentData.Output.ScriptPubKey.GetDestinationAddress(paymentNetwork.NBitcoinNetwork);
|
m.DepositAddress = onChainPaymentData.GetDestination(paymentNetwork);
|
||||||
|
|
||||||
int confirmationCount = 0;
|
int confirmationCount = 0;
|
||||||
if ((onChainPaymentData.ConfirmationCount < paymentNetwork.MaxTrackedConfirmation && payment.Accounted)
|
if ((onChainPaymentData.ConfirmationCount < paymentNetwork.MaxTrackedConfirmation && payment.Accounted)
|
||||||
@@ -493,7 +493,7 @@ namespace BTCPayServer.Controllers
|
|||||||
[BitpayAPIConstraint(false)]
|
[BitpayAPIConstraint(false)]
|
||||||
public async Task<IActionResult> Export(string format, string searchTerm = null)
|
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 invoices = await ListInvoicesProcess(searchTerm, 0, int.MaxValue);
|
||||||
var res = model.Process(invoices, format);
|
var res = model.Process(invoices, format);
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ namespace BTCPayServer.Controllers
|
|||||||
RateFetcher rateProviderFactory,
|
RateFetcher rateProviderFactory,
|
||||||
SettingsRepository settingsRepository,
|
SettingsRepository settingsRepository,
|
||||||
NBXplorerDashboard dashBoard,
|
NBXplorerDashboard dashBoard,
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
LightningConfigurationProvider lnConfigProvider,
|
LightningConfigurationProvider lnConfigProvider,
|
||||||
Services.Stores.StoreRepository storeRepository)
|
Services.Stores.StoreRepository storeRepository)
|
||||||
{
|
{
|
||||||
@@ -53,6 +54,7 @@ namespace BTCPayServer.Controllers
|
|||||||
_UserManager = userManager;
|
_UserManager = userManager;
|
||||||
_SettingsRepository = settingsRepository;
|
_SettingsRepository = settingsRepository;
|
||||||
_dashBoard = dashBoard;
|
_dashBoard = dashBoard;
|
||||||
|
HttpClientFactory = httpClientFactory;
|
||||||
_RateProviderFactory = rateProviderFactory;
|
_RateProviderFactory = rateProviderFactory;
|
||||||
_StoreRepository = storeRepository;
|
_StoreRepository = storeRepository;
|
||||||
_LnConfigProvider = lnConfigProvider;
|
_LnConfigProvider = lnConfigProvider;
|
||||||
@@ -395,6 +397,7 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
public IHttpClientFactory HttpClientFactory { get; }
|
||||||
|
|
||||||
[Route("server/emails")]
|
[Route("server/emails")]
|
||||||
public async Task<IActionResult> Emails()
|
public async Task<IActionResult> Emails()
|
||||||
@@ -431,6 +434,18 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
Crypto = cryptoCode,
|
Crypto = cryptoCode,
|
||||||
Type = grpcService.Type,
|
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++,
|
Index = i++,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -454,6 +469,40 @@ namespace BTCPayServer.Controllers
|
|||||||
return View(result);
|
return View(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[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).Skip(index).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}")]
|
[Route("server/services/lnd/{cryptoCode}/{index}")]
|
||||||
public IActionResult LndServices(string cryptoCode, int index, uint? nonce)
|
public IActionResult LndServices(string cryptoCode, int index, uint? nonce)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ namespace BTCPayServer.Models.AppViewModels
|
|||||||
public string CurrencySymbol { get; set; }
|
public string CurrencySymbol { get; set; }
|
||||||
public string ThousandSeparator { get; set; }
|
public string ThousandSeparator { get; set; }
|
||||||
public string DecimalSeparator { 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; }
|
public CurrencyInfoData CurrencyInfo { get; set; }
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ namespace BTCPayServer.Models.ServerViewModels
|
|||||||
public class LNDServiceViewModel
|
public class LNDServiceViewModel
|
||||||
{
|
{
|
||||||
public string Crypto { get; set; }
|
public string Crypto { get; set; }
|
||||||
public LndTypes Type { get; set; }
|
public string Type { get; set; }
|
||||||
public int Index { get; set; }
|
public int Index { get; set; }
|
||||||
|
public string Action { get; internal set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ExternalService
|
public class 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -78,5 +78,15 @@ namespace BTCPayServer.Payments.Bitcoin
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BitcoinAddress GetDestination(BTCPayNetwork network)
|
||||||
|
{
|
||||||
|
return Output.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork);
|
||||||
|
}
|
||||||
|
|
||||||
|
string CryptoPaymentData.GetDestination(BTCPayNetwork network)
|
||||||
|
{
|
||||||
|
return GetDestination(network).ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||||
public LightMoney Amount { get; set; }
|
public LightMoney Amount { get; set; }
|
||||||
public string BOLT11 { get; set; }
|
public string BOLT11 { get; set; }
|
||||||
|
|
||||||
|
public string GetDestination(BTCPayNetwork network)
|
||||||
|
{
|
||||||
|
return GetPaymentId();
|
||||||
|
}
|
||||||
|
|
||||||
public string GetPaymentId()
|
public string GetPaymentId()
|
||||||
{
|
{
|
||||||
return BOLT11;
|
return BOLT11;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
"BTCPAY_BTCLIGHTNING": "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify",
|
"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_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",
|
"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_BTCEXPLORERURL": "http://127.0.0.1:32838/",
|
"BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/",
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
"BTCPAY_CHAINS": "btc,ltc",
|
"BTCPAY_CHAINS": "btc,ltc",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Payments.Bitcoin;
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
@@ -10,6 +11,12 @@ namespace BTCPayServer.Services.Invoices.Export
|
|||||||
{
|
{
|
||||||
public class InvoiceExport
|
public class InvoiceExport
|
||||||
{
|
{
|
||||||
|
public BTCPayNetworkProvider Networks { get; }
|
||||||
|
|
||||||
|
public InvoiceExport(BTCPayNetworkProvider networks)
|
||||||
|
{
|
||||||
|
Networks = networks;
|
||||||
|
}
|
||||||
public string Process(InvoiceEntity[] invoices, string fileFormat)
|
public string Process(InvoiceEntity[] invoices, string fileFormat)
|
||||||
{
|
{
|
||||||
var csvInvoices = new List<ExportInvoiceHolder>();
|
var csvInvoices = new List<ExportInvoiceHolder>();
|
||||||
@@ -55,9 +62,7 @@ namespace BTCPayServer.Services.Invoices.Export
|
|||||||
var cryptoCode = payment.GetPaymentMethodId().CryptoCode;
|
var cryptoCode = payment.GetPaymentMethodId().CryptoCode;
|
||||||
var pdata = payment.GetCryptoPaymentData();
|
var pdata = payment.GetCryptoPaymentData();
|
||||||
|
|
||||||
var pmethod = invoice.GetPaymentMethod(payment.GetPaymentMethodId(), null);
|
var pmethod = invoice.GetPaymentMethod(payment.GetPaymentMethodId(), Networks);
|
||||||
var accounting = pmethod.Calculate();
|
|
||||||
var details = pmethod.GetPaymentMethodDetails();
|
|
||||||
|
|
||||||
var target = new ExportInvoiceHolder
|
var target = new ExportInvoiceHolder
|
||||||
{
|
{
|
||||||
@@ -65,25 +70,24 @@ namespace BTCPayServer.Services.Invoices.Export
|
|||||||
PaymentId = pdata.GetPaymentId(),
|
PaymentId = pdata.GetPaymentId(),
|
||||||
CryptoCode = cryptoCode,
|
CryptoCode = cryptoCode,
|
||||||
ConversionRate = pmethod.Rate,
|
ConversionRate = pmethod.Rate,
|
||||||
PaymentType = details.GetPaymentType() == Payments.PaymentTypes.BTCLike ? "OnChain" : "OffChain",
|
PaymentType = payment.GetPaymentMethodId().PaymentType == Payments.PaymentTypes.BTCLike ? "OnChain" : "OffChain",
|
||||||
Destination = details.GetPaymentDestination(),
|
Destination = payment.GetCryptoPaymentData().GetDestination(Networks.GetNetwork(cryptoCode)),
|
||||||
PaymentDue = $"{accounting.MinimumTotalDue} {cryptoCode}",
|
Paid = pdata.GetValue().ToString(CultureInfo.InvariantCulture),
|
||||||
PaymentPaid = $"{accounting.CryptoPaid} {cryptoCode}",
|
|
||||||
PaymentOverpaid = $"{accounting.OverpaidHelper} {cryptoCode}",
|
|
||||||
|
|
||||||
OrderId = invoice.OrderId,
|
OrderId = invoice.OrderId,
|
||||||
StoreId = invoice.StoreId,
|
StoreId = invoice.StoreId,
|
||||||
InvoiceId = invoice.Id,
|
InvoiceId = invoice.Id,
|
||||||
CreatedDate = invoice.InvoiceTime.UtcDateTime,
|
InvoiceCreatedDate = invoice.InvoiceTime.UtcDateTime,
|
||||||
ExpirationDate = invoice.ExpirationTime.UtcDateTime,
|
InvoiceExpirationDate = invoice.ExpirationTime.UtcDateTime,
|
||||||
MonitoringDate = invoice.MonitoringExpiration.UtcDateTime,
|
InvoiceMonitoringDate = invoice.MonitoringExpiration.UtcDateTime,
|
||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
Status = invoice.StatusString,
|
InvoiceFullStatus = invoice.GetInvoiceState().ToString(),
|
||||||
|
InvoiceStatus = invoice.StatusString,
|
||||||
|
InvoiceExceptionStatus = invoice.ExceptionStatusString,
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
ItemCode = invoice.ProductInformation?.ItemCode,
|
InvoiceItemCode = invoice.ProductInformation.ItemCode,
|
||||||
ItemDesc = invoice.ProductInformation?.ItemDesc,
|
InvoiceItemDesc = invoice.ProductInformation.ItemDesc,
|
||||||
FiatPrice = invoice.ProductInformation?.Price ?? 0,
|
InvoicePrice = invoice.ProductInformation.Price,
|
||||||
FiatCurrency = invoice.ProductInformation?.Currency,
|
InvoiceCurrency = invoice.ProductInformation.Currency,
|
||||||
};
|
};
|
||||||
|
|
||||||
exportList.Add(target);
|
exportList.Add(target);
|
||||||
@@ -101,23 +105,23 @@ namespace BTCPayServer.Services.Invoices.Export
|
|||||||
public string StoreId { get; set; }
|
public string StoreId { get; set; }
|
||||||
public string OrderId { get; set; }
|
public string OrderId { get; set; }
|
||||||
public string InvoiceId { get; set; }
|
public string InvoiceId { get; set; }
|
||||||
public DateTime CreatedDate { get; set; }
|
public DateTime InvoiceCreatedDate { get; set; }
|
||||||
public DateTime ExpirationDate { get; set; }
|
public DateTime InvoiceExpirationDate { get; set; }
|
||||||
public DateTime MonitoringDate { get; set; }
|
public DateTime InvoiceMonitoringDate { get; set; }
|
||||||
|
|
||||||
public string PaymentId { get; set; }
|
public string PaymentId { get; set; }
|
||||||
public string CryptoCode { get; set; }
|
|
||||||
public string Destination { get; set; }
|
public string Destination { get; set; }
|
||||||
public string PaymentType { get; set; }
|
public string PaymentType { get; set; }
|
||||||
public string PaymentDue { get; set; }
|
public string Paid { get; set; }
|
||||||
public string PaymentPaid { get; set; }
|
public string CryptoCode { get; set; }
|
||||||
public string PaymentOverpaid { get; set; }
|
|
||||||
public decimal ConversionRate { get; set; }
|
public decimal ConversionRate { get; set; }
|
||||||
|
|
||||||
public decimal FiatPrice { get; set; }
|
public decimal InvoicePrice { get; set; }
|
||||||
public string FiatCurrency { get; set; }
|
public string InvoiceCurrency { get; set; }
|
||||||
public string ItemCode { get; set; }
|
public string InvoiceItemCode { get; set; }
|
||||||
public string ItemDesc { get; set; }
|
public string InvoiceItemDesc { get; set; }
|
||||||
public string Status { get; set; }
|
public string InvoiceFullStatus { get; set; }
|
||||||
|
public string InvoiceStatus { get; set; }
|
||||||
|
public string InvoiceExceptionStatus { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -982,5 +982,6 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy, BTCPayNetwork network);
|
bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy, BTCPayNetwork network);
|
||||||
|
|
||||||
PaymentTypes GetPaymentType();
|
PaymentTypes GetPaymentType();
|
||||||
|
string GetDestination(BTCPayNetwork network);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,33 +3,39 @@
|
|||||||
ViewData["Title"] = "Reset password";
|
ViewData["Title"] = "Reset password";
|
||||||
}
|
}
|
||||||
|
|
||||||
<h2>@ViewData["Title"]</h2>
|
<section>
|
||||||
<h4>Reset your password.</h4>
|
<div class="container">
|
||||||
<hr />
|
<div class="row">
|
||||||
<div class="row">
|
<div class="col-lg-12 text-center">
|
||||||
<div class="col-md-4">
|
<partial name="_StatusMessage" for="@TempData["StatusMessage"]" />
|
||||||
<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>
|
||||||
<div class="form-group">
|
</div>
|
||||||
<label asp-for="Password"></label>
|
<div class="row">
|
||||||
<input asp-for="Password" class="form-control" />
|
<div class="col-md-4">
|
||||||
<span asp-validation-for="Password" class="text-danger"></span>
|
<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>
|
||||||
<div class="form-group">
|
</div>
|
||||||
<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 {
|
@section Scripts {
|
||||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<h2>@ViewData["Title"]</h2>
|
<h2>@ViewData["Title"]</h2>
|
||||||
<p>
|
<section>
|
||||||
Your password has been reset. Please <a asp-action="Login">click here to log in</a>.
|
<div class="container">
|
||||||
</p>
|
<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";
|
ViewData["Title"] = "Update Point of Sale";
|
||||||
}
|
}
|
||||||
<section>
|
<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="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12 text-center">
|
<div class="col-lg-12 text-center">
|
||||||
@@ -58,9 +78,17 @@
|
|||||||
<input asp-for="CustomCSSLink" class="form-control" />
|
<input asp-for="CustomCSSLink" class="form-control" />
|
||||||
<span asp-validation-for="CustomCSSLink" class="text-danger"></span>
|
<span asp-validation-for="CustomCSSLink" class="text-danger"></span>
|
||||||
</div>
|
</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">
|
<div class="form-group">
|
||||||
<label asp-for="Template" class="control-label"></label>*
|
<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>
|
<span asp-validation-for="Template" class="text-danger"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -101,5 +129,56 @@
|
|||||||
<link rel="stylesheet" href="~/vendor/highlightjs/default.min.css">
|
<link rel="stylesheet" href="~/vendor/highlightjs/default.min.css">
|
||||||
<script src="~/vendor/highlightjs/highlight.min.js"></script>
|
<script src="~/vendor/highlightjs/highlight.min.js"></script>
|
||||||
<script>hljs.initHighlightingOnLoad();</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">
|
<div class="header-content-inner text-white">
|
||||||
<h1>Welcome to BTCPay Server</h1>
|
<h1>Welcome to BTCPay Server</h1>
|
||||||
<hr />
|
<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>
|
<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://github.com/btcpayserver/btcpayserver-doc">Getting started</a>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -88,10 +88,10 @@
|
|||||||
}
|
}
|
||||||
@if (Model.RestrictedMacaroon != null)
|
@if (Model.RestrictedMacaroon != null)
|
||||||
{
|
{
|
||||||
<div class="form-group">
|
@*<div class="form-group">
|
||||||
<label asp-for="RestrictedMacaroon"></label>
|
<label asp-for="RestrictedMacaroon"></label>
|
||||||
<input asp-for="RestrictedMacaroon" readonly class="form-control" />
|
<input asp-for="RestrictedMacaroon" readonly class="form-control" />
|
||||||
</div>
|
</div>*@
|
||||||
}
|
}
|
||||||
@if (Model.CertificateThumbprint != null)
|
@if (Model.CertificateThumbprint != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -34,16 +34,9 @@
|
|||||||
{
|
{
|
||||||
<tr>
|
<tr>
|
||||||
<td>@lnd.Crypto</td>
|
<td>@lnd.Crypto</td>
|
||||||
<td>LND @lnd.Type.ToString()</td>
|
<td>@lnd.Type</td>
|
||||||
<td style="text-align:right">
|
<td style="text-align:right">
|
||||||
@if (lnd.Type == BTCPayServer.Configuration.External.LndTypes.gRPC)
|
<a asp-action="@lnd.Action" asp-route-cryptoCode="@lnd.Crypto" asp-route-index="@lnd.Index">See information</a>
|
||||||
{
|
|
||||||
<a asp-action="LNDServices" 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="LNDServices" asp-route-cryptoCode="@lnd.Crypto" asp-route-index="@lnd.Index">See information</a>
|
|
||||||
}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
|
|||||||
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>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -249,9 +249,15 @@ Cart.prototype.formatCurrency = function(amount, currency, symbol) {
|
|||||||
|
|
||||||
if (srvModel.currencyInfo.prefixed) {
|
if (srvModel.currencyInfo.prefixed) {
|
||||||
prefix = srvModel.currencyInfo.currencySymbol;
|
prefix = srvModel.currencyInfo.currencySymbol;
|
||||||
|
if (srvModel.currencyInfo.symbolSpace) {
|
||||||
|
prefix = prefix + ' ';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
postfix = srvModel.currencyInfo.currencySymbol;
|
postfix = srvModel.currencyInfo.currencySymbol;
|
||||||
|
if (srvModel.currencyInfo.symbolSpace) {
|
||||||
|
postfix = ' ' + postfix;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
thousandsSep = srvModel.currencyInfo.thousandSeparator;
|
thousandsSep = srvModel.currencyInfo.thousandSeparator;
|
||||||
decimalSep = srvModel.currencyInfo.decimalSeparator;
|
decimalSep = srvModel.currencyInfo.decimalSeparator;
|
||||||
|
|||||||
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",
|
"lang": "Język",
|
||||||
"Awaiting Payment...": "Oczekiwanie na płatność...",
|
"Awaiting Payment...": "Oczekiwanie na płatność...",
|
||||||
"Pay with": "Płać z",
|
"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",
|
"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",
|
"Your email": "Twój email",
|
||||||
"Continue": "Kontynuacja",
|
"Continue": "Dalej",
|
||||||
"Please enter a valid email address": "Proszę podać prawidłowy adres email",
|
"Please enter a valid email address": "Proszę podać prawidłowy adres email",
|
||||||
"Order Amount": "Kwota zamówienia",
|
"Order Amount": "Kwota zamówienia",
|
||||||
"Network Cost": "Koszt sieci",
|
"Network Cost": "Koszt sieci",
|
||||||
"Already Paid": "Już zapłacone",
|
"Already Paid": "Już zapłacone",
|
||||||
"Due": "Z powodu",
|
"Due": "Razem",
|
||||||
"Scan": "Skan",
|
"Scan": "Skan",
|
||||||
"Copy": "Kopia",
|
"Copy": "Kopia",
|
||||||
"Conversion": "Konwersja",
|
"Conversion": "Konwersja",
|
||||||
|
|||||||
@@ -32,8 +32,8 @@
|
|||||||
"Invoice expired": "Platnosť faktúry uplynula",
|
"Invoice expired": "Platnosť faktúry uplynula",
|
||||||
"What happened?": "Čo sa stalo?",
|
"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_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_2": "Ak ste sa pokúsili odoslať platbu, sieť ju ešte neprijala. Zatiaľ sme nedostali Vaše finančné prostriedky.",
|
||||||
"InvoiceExpired_Body_3": "",
|
"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",
|
"Invoice ID": "ID faktúry",
|
||||||
"Order ID": "ID objednávky",
|
"Order ID": "ID objednávky",
|
||||||
"Return to StoreName": "Vrátiť sa na {{storeName}}",
|
"Return to StoreName": "Vrátiť sa na {{storeName}}",
|
||||||
|
|||||||
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