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
|
||||
|
||||
test:
|
||||
machine: true
|
||||
machine:
|
||||
docker_layer_caching: true
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
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
|
||||
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:
|
||||
@@ -36,7 +28,7 @@ jobs:
|
||||
command: |
|
||||
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 push $DOCKERHUB_REPO:$LATEST_TAG-amd64
|
||||
|
||||
@@ -50,7 +42,7 @@ jobs:
|
||||
sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
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 push $DOCKERHUB_REPO:$LATEST_TAG-arm32v7
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -340,13 +340,17 @@ 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);
|
||||
}
|
||||
}
|
||||
@@ -884,7 +888,7 @@ 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");
|
||||
@@ -1467,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1580,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);
|
||||
});
|
||||
|
||||
@@ -1648,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"
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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: |
|
||||
@@ -94,7 +94,7 @@ services:
|
||||
- litecoind
|
||||
|
||||
bitcoind:
|
||||
image: nicolasdorier/docker-bitcoin:0.17.0
|
||||
image: btcpayserver/bitcoin:0.17.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_EXTRA_ARGS: |
|
||||
@@ -118,7 +118,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:
|
||||
@@ -164,7 +164,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,7 +188,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
litecoind:
|
||||
image: nicolasdorier/docker-litecoin:0.15.1
|
||||
image: nicolasdorier/docker-litecoin:0.16.3
|
||||
environment:
|
||||
BITCOIN_EXTRA_ARGS: |
|
||||
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>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<Version>1.0.3.28</Version>
|
||||
<Version>1.0.3.31</Version>
|
||||
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
@@ -136,6 +136,9 @@
|
||||
<Content Update="Views\Home\BitpayTranslator.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\SparkServices.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\SSHService.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
|
||||
@@ -137,6 +137,17 @@ 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));
|
||||
}
|
||||
}
|
||||
|
||||
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}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", $"The connection string to spark server (default: empty)", CommandOptionType.SingleValue);
|
||||
}
|
||||
return app;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
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,
|
||||
|
||||
@@ -104,7 +104,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)
|
||||
@@ -493,7 +493,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);
|
||||
|
||||
@@ -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,6 +434,18 @@ 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++,
|
||||
});
|
||||
}
|
||||
@@ -454,6 +469,40 @@ namespace BTCPayServer.Controllers
|
||||
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}")]
|
||||
public IActionResult LndServices(string cryptoCode, int index, uint? nonce)
|
||||
{
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -11,8 +11,9 @@ 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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
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))]
|
||||
public LightMoney Amount { get; set; }
|
||||
public string BOLT11 { get; set; }
|
||||
|
||||
public string GetDestination(BTCPayNetwork network)
|
||||
{
|
||||
return GetPaymentId();
|
||||
}
|
||||
|
||||
public string GetPaymentId()
|
||||
{
|
||||
return BOLT11;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"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",
|
||||
"BTCPAY_BTCEXTERNALSPARK": "server=https://127.0.0.1:53280/spark/btc/;cookiefile=fake",
|
||||
"BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/",
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"BTCPAY_CHAINS": "btc,ltc",
|
||||
|
||||
@@ -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,25 +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,
|
||||
InvoiceCreatedDate = invoice.InvoiceTime.UtcDateTime,
|
||||
InvoiceExpirationDate = invoice.ExpirationTime.UtcDateTime,
|
||||
InvoiceMonitoringDate = invoice.MonitoringExpiration.UtcDateTime,
|
||||
#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
|
||||
ItemCode = invoice.ProductInformation?.ItemCode,
|
||||
ItemDesc = invoice.ProductInformation?.ItemDesc,
|
||||
FiatPrice = invoice.ProductInformation?.Price ?? 0,
|
||||
FiatCurrency = invoice.ProductInformation?.Currency,
|
||||
InvoiceItemCode = invoice.ProductInformation.ItemCode,
|
||||
InvoiceItemDesc = invoice.ProductInformation.ItemDesc,
|
||||
InvoicePrice = invoice.ProductInformation.Price,
|
||||
InvoiceCurrency = invoice.ProductInformation.Currency,
|
||||
};
|
||||
|
||||
exportList.Add(target);
|
||||
@@ -101,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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -982,5 +982,6 @@ namespace BTCPayServer.Services.Invoices
|
||||
bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy, BTCPayNetwork network);
|
||||
|
||||
PaymentTypes GetPaymentType();
|
||||
string GetDestination(BTCPayNetwork network);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -88,10 +88,10 @@
|
||||
}
|
||||
@if (Model.RestrictedMacaroon != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
@*<div class="form-group">
|
||||
<label asp-for="RestrictedMacaroon"></label>
|
||||
<input asp-for="RestrictedMacaroon" readonly class="form-control" />
|
||||
</div>
|
||||
</div>*@
|
||||
}
|
||||
@if (Model.CertificateThumbprint != null)
|
||||
{
|
||||
|
||||
@@ -34,16 +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="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>
|
||||
}
|
||||
<a asp-action="@lnd.Action" asp-route-cryptoCode="@lnd.Crypto" asp-route-index="@lnd.Index">See information</a>
|
||||
</td>
|
||||
</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) {
|
||||
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;
|
||||
|
||||
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}}",
|
||||
|
||||
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