Compare commits
46 Commits
Author | SHA1 | Date | |
---|---|---|---|
0eccc6085b | |||
a89da1f705 | |||
5b297e539a | |||
1d932c3753 | |||
5a77fc74ba | |||
7b47b96252 | |||
a4bec83ecc | |||
8509a0de18 | |||
8e30b7430d | |||
9235d32a45 | |||
dd503570ac | |||
613281a1e7 | |||
bab7bf6633 | |||
1831692761 | |||
e144d2479b | |||
c25831316e | |||
60b72aabe8 | |||
c8fcb0ab18 | |||
9911d18390 | |||
e24630ac1e | |||
4c1fd3edae | |||
f65492dd66 | |||
5d978c7670 | |||
11788cece9 | |||
1aaa55dc62 | |||
ce57a2b8fb | |||
0604cc5bd0 | |||
3d2c0bcc6c | |||
0f222979a6 | |||
a1eb6a14f5 | |||
186ce01022 | |||
0096ec1d12 | |||
2929d7bf51 | |||
d90fb5764d | |||
4dccd0c733 | |||
300d912331 | |||
9d21c89151 | |||
24a8c4015c | |||
5eb40d6b7f | |||
36f486e91b | |||
5b684ac26e | |||
85062725bd | |||
401d9c8565 | |||
6f276ac1bc | |||
4350785cef | |||
9a2a85ac3d |
@ -5,6 +5,7 @@ jobs:
|
||||
docker_layer_caching: true
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
test:
|
||||
machine: true
|
||||
steps:
|
||||
@ -23,8 +24,82 @@ jobs:
|
||||
dotnet test --filter Fast=Fast
|
||||
docker-compose up -d dev
|
||||
dotnet test --filter Integration=Integration
|
||||
|
||||
# publish jobs require $DOCKERHUB_REPO, $DOCKERHUB_USER, $DOCKERHUB_PASS defined
|
||||
publish_docker_linuxamd64:
|
||||
machine:
|
||||
docker_layer_caching: true
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
command: |
|
||||
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
|
||||
#
|
||||
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
|
||||
|
||||
publish_docker_linuxarm:
|
||||
machine:
|
||||
docker_layer_caching: true
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
command: |
|
||||
sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
|
||||
#
|
||||
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
|
||||
|
||||
publish_docker_multiarch:
|
||||
machine:
|
||||
enabled: true
|
||||
image: circleci/classic:201808-01
|
||||
steps:
|
||||
- run:
|
||||
command: |
|
||||
# Turn on Experimental features
|
||||
sudo mkdir $HOME/.docker
|
||||
sudo sh -c 'echo "{ \"experimental\": \"enabled\" }" >> $HOME/.docker/config.json'
|
||||
#
|
||||
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
|
||||
#
|
||||
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
|
||||
sudo docker manifest create --amend $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-amd64 $DOCKERHUB_REPO:$LATEST_TAG-arm32v7
|
||||
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-amd64 --os linux --arch amd64
|
||||
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 --os linux --arch arm --variant v7
|
||||
sudo docker manifest push $DOCKERHUB_REPO:$LATEST_TAG -p
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build_and_test:
|
||||
jobs:
|
||||
- test
|
||||
|
||||
publish:
|
||||
jobs:
|
||||
- publish_docker_linuxamd64:
|
||||
filters:
|
||||
# ignore any commit on any branch by default
|
||||
branches:
|
||||
ignore: /.*/
|
||||
# only act on version tags
|
||||
tags:
|
||||
only: /v[1-9]+(\.[0-9]+)*/
|
||||
- publish_docker_linuxarm:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /v[1-9]+(\.[0-9]+)*/
|
||||
- publish_docker_multiarch:
|
||||
requires:
|
||||
- publish_docker_linuxamd64
|
||||
- publish_docker_linuxarm
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /v[1-9]+(\.[0-9]+)*/
|
||||
|
@ -43,6 +43,7 @@ using System.Security.Cryptography.X509Certificates;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
using System.Security.Claims;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Models.ServerViewModels;
|
||||
using BTCPayServer.Security;
|
||||
using NBXplorer.Models;
|
||||
@ -595,7 +596,7 @@ namespace BTCPayServer.Tests
|
||||
var fetcher = new RateFetcher(factory);
|
||||
|
||||
Assert.True(RateRules.TryParse("X_X=kraken(X_BTC) * kraken(BTC_X)", out var rule));
|
||||
foreach(var pair in new[] { "DOGE_USD", "DOGE_CAD", "DASH_CAD", "DASH_USD", "DASH_EUR" })
|
||||
foreach (var pair in new[] { "DOGE_USD", "DOGE_CAD", "DASH_CAD", "DASH_USD", "DASH_EUR" })
|
||||
{
|
||||
var result = fetcher.FetchRate(CurrencyPair.Parse(pair), rule).GetAwaiter().GetResult();
|
||||
Assert.NotNull(result.BidAsk);
|
||||
@ -811,7 +812,7 @@ namespace BTCPayServer.Tests
|
||||
output.ScriptPubKey = invoiceAddress.ScriptPubKey;
|
||||
|
||||
using (var cts = new CancellationTokenSource(10000))
|
||||
using (var listener = tester.ExplorerClient.CreateNotificationSession())
|
||||
using (var listener = tester.ExplorerClient.CreateWebsocketNotificationSession())
|
||||
{
|
||||
listener.ListenAllDerivationSchemes();
|
||||
var replaced = tester.ExplorerNode.SignRawTransaction(tx);
|
||||
@ -1322,6 +1323,19 @@ namespace BTCPayServer.Tests
|
||||
|
||||
result = parser.Parse(tpub);
|
||||
Assert.Equal($"{tpub}-[p2sh]", result.ToString());
|
||||
|
||||
parser = new DerivationSchemeParser(Network.RegTest);
|
||||
var parsed = parser.Parse("xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]");
|
||||
Assert.Equal("tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[p2sh]", parsed.ToString());
|
||||
|
||||
// Let's make sure we can't generate segwit with dogecoin
|
||||
parser = new DerivationSchemeParser(NBitcoin.Altcoins.Dogecoin.Instance.Regtest);
|
||||
parsed = parser.Parse("xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]");
|
||||
Assert.Equal("tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[legacy]", parsed.ToString());
|
||||
|
||||
parser = new DerivationSchemeParser(NBitcoin.Altcoins.Dogecoin.Instance.Regtest);
|
||||
parsed = parser.Parse("tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[p2sh]");
|
||||
Assert.Equal("tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[legacy]", parsed.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -1447,12 +1461,19 @@ namespace BTCPayServer.Tests
|
||||
var vmpos = Assert.IsType<UpdatePointOfSaleViewModel>(Assert.IsType<ViewResult>(apps.UpdatePointOfSale(appId).Result).Model);
|
||||
vmpos.Title = "hello";
|
||||
vmpos.Currency = "CAD";
|
||||
vmpos.Template =
|
||||
"apple:\n" +
|
||||
" price: 5.0\n" +
|
||||
" title: good apple\n" +
|
||||
"orange:\n" +
|
||||
" price: 10.0\n";
|
||||
vmpos.ButtonText = "{0} Purchase";
|
||||
vmpos.CustomButtonText = "Nicolas Sexy Hair";
|
||||
vmpos.CustomTipText = "Wanna tip?";
|
||||
vmpos.Template = @"
|
||||
apple:
|
||||
price: 5.0
|
||||
title: good apple
|
||||
orange:
|
||||
price: 10.0
|
||||
donation:
|
||||
price: 1.02
|
||||
custom: true
|
||||
";
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(appId, vmpos).Result);
|
||||
vmpos = Assert.IsType<UpdatePointOfSaleViewModel>(Assert.IsType<ViewResult>(apps.UpdatePointOfSale(appId).Result).Model);
|
||||
Assert.Equal("hello", vmpos.Title);
|
||||
@ -1460,19 +1481,225 @@ namespace BTCPayServer.Tests
|
||||
var publicApps = user.GetController<AppsPublicController>();
|
||||
var vmview = Assert.IsType<ViewPointOfSaleViewModel>(Assert.IsType<ViewResult>(publicApps.ViewPointOfSale(appId).Result).Model);
|
||||
Assert.Equal("hello", vmview.Title);
|
||||
Assert.Equal(2, vmview.Items.Length);
|
||||
Assert.Equal(3, vmview.Items.Length);
|
||||
Assert.Equal("good apple", vmview.Items[0].Title);
|
||||
Assert.Equal("orange", vmview.Items[1].Title);
|
||||
Assert.Equal(10.0m, vmview.Items[1].Price.Value);
|
||||
Assert.Equal("$5.00", vmview.Items[0].Price.Formatted);
|
||||
Assert.IsType<RedirectResult>(publicApps.ViewPointOfSale(appId, 0, null, null, null, null, "orange").Result);
|
||||
var invoice = user.BitPay.GetInvoices().First();
|
||||
Assert.Equal(10.00m, invoice.Price);
|
||||
Assert.Equal("CAD", invoice.Currency);
|
||||
Assert.Equal("orange", invoice.ItemDesc);
|
||||
Assert.Equal("{0} Purchase", vmview.ButtonText);
|
||||
Assert.Equal("Nicolas Sexy Hair", vmview.CustomButtonText);
|
||||
Assert.Equal("Wanna tip?", vmview.CustomTipText);
|
||||
Assert.IsType<RedirectToActionResult>(publicApps.ViewPointOfSale(appId, 0, null, null, null, null, "orange").Result);
|
||||
|
||||
//
|
||||
var invoices = user.BitPay.GetInvoices();
|
||||
var orangeInvoice = invoices.First();
|
||||
Assert.Equal(10.00m, orangeInvoice.Price);
|
||||
Assert.Equal("CAD", orangeInvoice.Currency);
|
||||
Assert.Equal("orange", orangeInvoice.ItemDesc);
|
||||
|
||||
// testing custom amount
|
||||
var action = Assert.IsType<RedirectToActionResult>(publicApps.ViewPointOfSale(appId, 5, null, null, null, null, "donation").Result);
|
||||
Assert.Equal(nameof(InvoiceController.Checkout), action.ActionName);
|
||||
invoices = user.BitPay.GetInvoices();
|
||||
var donationInvoice = invoices.Single(i => i.Price == 5m);
|
||||
Assert.NotNull(donationInvoice);
|
||||
Assert.Equal("CAD", donationInvoice.Currency);
|
||||
Assert.Equal("donation", donationInvoice.ItemDesc);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void PosDataParser_ParsesCorrectly()
|
||||
{
|
||||
var testCases =
|
||||
new List<(string input, Dictionary<string, string> expectedOutput)>()
|
||||
{
|
||||
{ (null, new Dictionary<string, string>())},
|
||||
{("", new Dictionary<string, string>())},
|
||||
{("{}", new Dictionary<string, string>())},
|
||||
{("non-json-content", new Dictionary<string, string>(){ {string.Empty, "non-json-content"}})},
|
||||
{("[1,2,3]", new Dictionary<string, string>(){ {string.Empty, "[1,2,3]"}})},
|
||||
{("{ \"key\": \"value\"}", new Dictionary<string, string>(){ {"key", "value"}})},
|
||||
{("{ \"key\": true}", new Dictionary<string, string>(){ {"key", "True"}})},
|
||||
{("{ \"key\": \"value\", \"key2\": [\"value\", \"value2\"]}",
|
||||
new Dictionary<string, string>(){ {"key", "value"}, {"key2", "value,value2"}})},
|
||||
{("{ invalidjson file here}", new Dictionary<string, string>(){ {String.Empty, "{ invalidjson file here}"}})}
|
||||
};
|
||||
|
||||
testCases.ForEach(tuple =>
|
||||
{
|
||||
Assert.Equal(tuple.expectedOutput, InvoiceController.PosDataParser.ParsePosData(tuple.input));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task PosDataParser_ParsesCorrectly_Slower()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
|
||||
var controller = tester.PayTester.GetController<InvoiceController>(null);
|
||||
|
||||
var testCases =
|
||||
new List<(string input, Dictionary<string, string> expectedOutput)>()
|
||||
{
|
||||
{ (null, new Dictionary<string, string>())},
|
||||
{("", new Dictionary<string, string>())},
|
||||
{("{}", new Dictionary<string, string>())},
|
||||
{("non-json-content", new Dictionary<string, string>(){ {string.Empty, "non-json-content"}})},
|
||||
{("[1,2,3]", new Dictionary<string, string>(){ {string.Empty, "[1,2,3]"}})},
|
||||
{("{ \"key\": \"value\"}", new Dictionary<string, string>(){ {"key", "value"}})},
|
||||
{("{ \"key\": true}", new Dictionary<string, string>(){ {"key", "True"}})},
|
||||
{("{ \"key\": \"value\", \"key2\": [\"value\", \"value2\"]}",
|
||||
new Dictionary<string, string>(){ {"key", "value"}, {"key2", "value,value2"}})},
|
||||
{("{ invalidjson file here}", new Dictionary<string, string>(){ {String.Empty, "{ invalidjson file here}"}})}
|
||||
};
|
||||
|
||||
var tasks = new List<Task>();
|
||||
foreach (var valueTuple in testCases)
|
||||
{
|
||||
tasks.Add(user.BitPay.CreateInvoiceAsync(new Invoice(1, "BTC")
|
||||
{
|
||||
PosData = valueTuple.input
|
||||
}).ContinueWith(async task =>
|
||||
{
|
||||
var result = await controller.Invoice(task.Result.Id);
|
||||
var viewModel =
|
||||
Assert.IsType<InvoiceDetailsModel>(
|
||||
Assert.IsType<ViewResult>(result).Model);
|
||||
Assert.Equal(valueTuple.expectedOutput, viewModel.PosData);
|
||||
}));
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public void CanExportInvoicesJson()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
|
||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Price = 500,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some \", description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
// ensure 0 invoices exported because there are no payments yet
|
||||
var jsonResult = user.GetController<InvoiceController>().Export("json").GetAwaiter().GetResult();
|
||||
var result = Assert.IsType<ContentResult>(jsonResult);
|
||||
Assert.Equal("application/json", result.ContentType);
|
||||
Assert.Equal("[]", result.Content);
|
||||
|
||||
var cashCow = tester.ExplorerNode;
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
|
||||
var firstPayment = invoice.CryptoInfo[0].TotalDue - Money.Satoshis(10);
|
||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||
|
||||
Eventually(() =>
|
||||
{
|
||||
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("\"ConversionRate\": 5000.0", paidresult.Content);
|
||||
Assert.Contains("\"PaymentDue\": \"0.10020000 BTC\"", paidresult.Content);
|
||||
Assert.Contains($"\"InvoiceId\": \"{invoice.Id}\",", paidresult.Content);
|
||||
});
|
||||
|
||||
/*
|
||||
[
|
||||
{
|
||||
"ReceivedDate": "2018-11-30T10:27:13Z",
|
||||
"StoreId": "FKaSZrXLJ2tcLfCyeiYYfmZp1UM5nZ1LDecQqbwBRuHi",
|
||||
"OrderId": "orderId",
|
||||
"InvoiceId": "4XUkgPMaTBzwJGV9P84kPC",
|
||||
"CreatedDate": "2018-11-30T10:27:06Z",
|
||||
"ExpirationDate": "2018-11-30T10:42:06Z",
|
||||
"MonitoringDate": "2018-11-30T11:42:06Z",
|
||||
"PaymentId": "6e5755c3357b20fd66f5fc478778d81371eab341e7112ab66ed6122c0ec0d9e5-1",
|
||||
"CryptoCode": "BTC",
|
||||
"Destination": "mhhSEQuoM993o6vwnBeufJ4TaWov2ZUsPQ",
|
||||
"PaymentType": "OnChain",
|
||||
"PaymentDue": "0.10020000 BTC",
|
||||
"PaymentPaid": "0.10009990 BTC",
|
||||
"PaymentOverpaid": "0.00000000 BTC",
|
||||
"ConversionRate": 5000.0,
|
||||
"FiatPrice": 500.0,
|
||||
"FiatCurrency": "USD",
|
||||
"ItemCode": null,
|
||||
"ItemDesc": "Some \", description",
|
||||
"Status": "new"
|
||||
}
|
||||
]
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public void CanExportInvoicesCsv()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
|
||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Price = 500,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some \", description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
var cashCow = tester.ExplorerNode;
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
|
||||
var firstPayment = invoice.CryptoInfo[0].TotalDue - Money.Satoshis(10);
|
||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||
|
||||
Eventually(() =>
|
||||
{
|
||||
var exportResultPaid = user.GetController<InvoiceController>().Export("csv").GetAwaiter().GetResult();
|
||||
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);
|
||||
});
|
||||
|
||||
/*
|
||||
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"
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public void CanCreateAndDeleteApps()
|
||||
@ -1784,12 +2011,12 @@ namespace BTCPayServer.Tests
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
|
||||
|
||||
var serverController = user.GetController<ServerController>();
|
||||
var vm = Assert.IsType<LogsViewModel>(Assert.IsType<ViewResult>(await serverController.LogsView()).Model);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void CheckRatesProvider()
|
||||
|
@ -1 +1,2 @@
|
||||
docker exec -ti btcpayservertests_bitcoind_1 bitcoin-cli -datadir="/data" $args
|
||||
$bitcoind_container_id=$(docker ps -q --filter label=com.docker.compose.project=btcpayservertests --filter label=com.docker.compose.service=bitcoind)
|
||||
docker exec -ti $bitcoind_container_id bitcoin-cli -datadir="/data" $args
|
||||
|
@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker exec -ti btcpayservertests_bitcoind_1 bitcoin-cli -datadir="/data" "$@"
|
||||
bitcoind_container_id="$(docker ps -q --filter label=com.docker.compose.project=btcpayservertests --filter label=com.docker.compose.service=bitcoind)"
|
||||
docker exec -ti "$bitcoind_container_id" bitcoin-cli -datadir="/data" "$@"
|
||||
|
@ -69,7 +69,7 @@ services:
|
||||
|
||||
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:1.1.0.18
|
||||
image: nicolasdorier/nbxplorer:2.0.0.1
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
|
@ -1 +1,2 @@
|
||||
docker exec -ti btcpayservertests_customer_lightningd_1 lightning-cli $args
|
||||
$customer_lightning_container_id=$(docker ps -q --filter label=com.docker.compose.project=btcpayservertests --filter label=com.docker.compose.service=customer_lightningd)
|
||||
docker exec -ti $customer_lightning_container_id lightning-cli $args
|
||||
|
@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker exec -ti btcpayservertests_customer_lightningd_1 lightning-cli "$@"
|
||||
customer_lightning_container_id="$(docker ps -q --filter label=com.docker.compose.project=btcpayservertests --filter label=com.docker.compose.service=customer_lightningd)"
|
||||
docker exec -ti $customer_lightning_container_id lightning-cli "$@"
|
||||
|
@ -1 +1,2 @@
|
||||
docker exec -ti btcpayservertests_litecoind_1 litecoin-cli -datadir="/data" $args
|
||||
$litecoind_container_id=$(docker ps -q --filter label=com.docker.compose.project=btcpayservertests --filter label=com.docker.compose.service=litecoind)
|
||||
docker exec -ti $litecoind_container_id litecoin-cli -datadir="/data" $args
|
||||
|
@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker exec -ti btcpayservertests_litecoind_1 litecoin-cli -datadir="/data" "$@"
|
||||
litecoind_container_id="$(docker ps -q --filter label=com.docker.compose.project=btcpayservertests --filter label=com.docker.compose.service=litecoind)"
|
||||
docker exec -ti "$litecoind_container_id" litecoin-cli -datadir="/data" "$@"
|
||||
|
@ -1 +1,2 @@
|
||||
docker exec -ti btcpayservertests_merchant_lightningd_1 lightning-cli $args
|
||||
$merchant_lightning_container_id=$(docker ps -q --filter label=com.docker.compose.project=btcpayservertests --filter label=com.docker.compose.service=merchant_lightningd)
|
||||
docker exec -ti $merchant_lightning_container_id lightning-cli $args
|
||||
|
@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker exec -ti btcpayservertests_merchant_lightningd_1 lightning-cli "$@"
|
||||
merchant_lightning_container_id="$(docker ps -q --filter label=com.docker.compose.project=btcpayservertests --filter label=com.docker.compose.service=merchant_lightningd)"
|
||||
docker exec -ti $merchant_lightning_container_id lightning-cli "$@"
|
||||
|
@ -24,7 +24,7 @@ namespace BTCPayServer
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"MONA_X = MONA_BTC * BTC_X",
|
||||
"MONA_BTC = zaif(MONA_BTC)"
|
||||
"MONA_BTC = bittrex(MONA_BTC)"
|
||||
},
|
||||
CryptoImagePath = "imlegacy/monacoin.png",
|
||||
LightningImagePath = "imlegacy/mona-lightning.svg",
|
||||
|
@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<Version>1.0.3.10</Version>
|
||||
<Version>1.0.3.20</Version>
|
||||
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
@ -49,7 +49,7 @@
|
||||
<PackageReference Include="NBitcoin" Version="4.1.1.71" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.30" />
|
||||
<PackageReference Include="DBreeze" Version="1.92.0" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="1.0.3.11" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="2.0.0" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.2" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.3" />
|
||||
<PackageReference Include="NicolasDorier.RateLimits" Version="1.0.0.3" />
|
||||
|
@ -50,12 +50,23 @@ namespace BTCPayServer.Controllers
|
||||
" description: The Tibetan Himalayas, the land is majestic and beautiful—a spiritual place where, despite the perilous environment, many journey seeking enlightenment. Pay us what you want!\n" +
|
||||
" image: https://cdn.pixabay.com/photo/2016/09/16/11/24/darts-1673812__480.jpg\n" +
|
||||
" custom: true";
|
||||
EnableShoppingCart = false;
|
||||
ShowCustomAmount = true;
|
||||
}
|
||||
public string Title { get; set; }
|
||||
public string Currency { get; set; }
|
||||
public string Template { get; set; }
|
||||
public bool EnableShoppingCart { get; set; }
|
||||
public bool ShowCustomAmount { get; set; }
|
||||
|
||||
public const string BUTTON_TEXT_DEF = "Buy for {0}";
|
||||
public string ButtonText { get; set; } = BUTTON_TEXT_DEF;
|
||||
public const string CUSTOM_BUTTON_TEXT_DEF = "Pay";
|
||||
public string CustomButtonText { get; set; } = CUSTOM_BUTTON_TEXT_DEF;
|
||||
public const string CUSTOM_TIP_TEXT_DEF = "Do you want to leave a tip?";
|
||||
public string CustomTipText { get; set; } = CUSTOM_TIP_TEXT_DEF;
|
||||
|
||||
public string CustomCSSLink { get; set; }
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@ -69,9 +80,14 @@ namespace BTCPayServer.Controllers
|
||||
var vm = new UpdatePointOfSaleViewModel()
|
||||
{
|
||||
Title = settings.Title,
|
||||
EnableShoppingCart = settings.EnableShoppingCart,
|
||||
ShowCustomAmount = settings.ShowCustomAmount,
|
||||
Currency = settings.Currency,
|
||||
Template = settings.Template
|
||||
Template = settings.Template,
|
||||
ButtonText = settings.ButtonText ?? PointOfSaleSettings.BUTTON_TEXT_DEF,
|
||||
CustomButtonText = settings.CustomButtonText ?? PointOfSaleSettings.CUSTOM_BUTTON_TEXT_DEF,
|
||||
CustomTipText = settings.CustomTipText ?? PointOfSaleSettings.CUSTOM_TIP_TEXT_DEF,
|
||||
CustomCSSLink = settings.CustomCSSLink
|
||||
};
|
||||
if (HttpContext?.Request != null)
|
||||
{
|
||||
@ -134,9 +150,14 @@ namespace BTCPayServer.Controllers
|
||||
app.SetSettings(new PointOfSaleSettings()
|
||||
{
|
||||
Title = vm.Title,
|
||||
EnableShoppingCart = vm.EnableShoppingCart,
|
||||
ShowCustomAmount = vm.ShowCustomAmount,
|
||||
Currency = vm.Currency.ToUpperInvariant(),
|
||||
Template = vm.Template
|
||||
Template = vm.Template,
|
||||
ButtonText = vm.ButtonText,
|
||||
CustomButtonText = vm.CustomButtonText,
|
||||
CustomTipText = vm.CustomTipText,
|
||||
CustomCSSLink = vm.CustomCSSLink
|
||||
});
|
||||
await UpdateAppSettings(app);
|
||||
StatusMessage = "App updated";
|
||||
|
@ -45,9 +45,14 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
Title = settings.Title,
|
||||
Step = step.ToString(CultureInfo.InvariantCulture),
|
||||
EnableShoppingCart = settings.EnableShoppingCart,
|
||||
ShowCustomAmount = settings.ShowCustomAmount,
|
||||
CurrencySymbol = currency.Symbol,
|
||||
Items = _AppsHelper.Parse(settings.Template, settings.Currency)
|
||||
Items = _AppsHelper.Parse(settings.Template, settings.Currency),
|
||||
ButtonText = settings.ButtonText,
|
||||
CustomButtonText = settings.CustomButtonText,
|
||||
CustomTipText = settings.CustomTipText,
|
||||
CustomCSSLink = settings.CustomCSSLink
|
||||
});
|
||||
}
|
||||
|
||||
@ -71,7 +76,7 @@ namespace BTCPayServer.Controllers
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
var settings = app.GetSettings<PointOfSaleSettings>();
|
||||
if (string.IsNullOrEmpty(choiceKey) && !settings.ShowCustomAmount)
|
||||
if (string.IsNullOrEmpty(choiceKey) && !settings.ShowCustomAmount && !settings.EnableShoppingCart)
|
||||
{
|
||||
return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId });
|
||||
}
|
||||
@ -85,10 +90,12 @@ namespace BTCPayServer.Controllers
|
||||
return NotFound();
|
||||
title = choice.Title;
|
||||
price = choice.Price.Value;
|
||||
if (amount > price)
|
||||
price = amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!settings.ShowCustomAmount)
|
||||
if (!settings.ShowCustomAmount && !settings.EnableShoppingCart)
|
||||
return NotFound();
|
||||
price = amount;
|
||||
title = settings.Title;
|
||||
@ -107,7 +114,7 @@ namespace BTCPayServer.Controllers
|
||||
RedirectURL = redirectUrl,
|
||||
FullNotifications = true
|
||||
}, store, HttpContext.Request.GetAbsoluteRoot());
|
||||
return Redirect(invoice.Data.Url);
|
||||
return RedirectToAction(nameof(InvoiceController.Checkout), "Invoice", new { invoiceId = invoice.Data.Id });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -14,13 +15,14 @@ using BTCPayServer.Payments.Changelly;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Invoices.Export;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using NBXplorer;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -41,7 +43,6 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
var dto = invoice.EntityToDTO(_NetworkProvider);
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
|
||||
InvoiceDetailsModel model = new InvoiceDetailsModel()
|
||||
{
|
||||
StoreName = store.StoreName,
|
||||
@ -58,13 +59,14 @@ namespace BTCPayServer.Controllers
|
||||
MonitoringDate = invoice.MonitoringExpiration,
|
||||
OrderId = invoice.OrderId,
|
||||
BuyerInformation = invoice.BuyerInformation,
|
||||
Fiat = _CurrencyNameTable.DisplayFormatCurrency((decimal)dto.Price, dto.Currency),
|
||||
Fiat = _CurrencyNameTable.DisplayFormatCurrency(dto.Price, dto.Currency),
|
||||
NotificationEmail = invoice.NotificationEmail,
|
||||
NotificationUrl = invoice.NotificationURL,
|
||||
RedirectUrl = invoice.RedirectURL,
|
||||
ProductInformation = invoice.ProductInformation,
|
||||
StatusException = invoice.ExceptionStatus,
|
||||
Events = invoice.Events
|
||||
Events = invoice.Events,
|
||||
PosData = PosDataParser.ParsePosData(dto.PosData)
|
||||
};
|
||||
|
||||
foreach (var data in invoice.GetPaymentMethods(null))
|
||||
@ -74,9 +76,9 @@ namespace BTCPayServer.Controllers
|
||||
var paymentMethodId = data.GetId();
|
||||
var cryptoPayment = new InvoiceDetailsModel.CryptoPayment();
|
||||
cryptoPayment.PaymentMethod = ToString(paymentMethodId);
|
||||
cryptoPayment.Due = accounting.Due.ToString() + $" {paymentMethodId.CryptoCode}";
|
||||
cryptoPayment.Paid = accounting.CryptoPaid.ToString() + $" {paymentMethodId.CryptoCode}";
|
||||
cryptoPayment.Overpaid = (accounting.DueUncapped > Money.Zero ? Money.Zero : -accounting.DueUncapped).ToString() + $" {paymentMethodId.CryptoCode}";
|
||||
cryptoPayment.Due = $"{accounting.Due} {paymentMethodId.CryptoCode}";
|
||||
cryptoPayment.Paid = $"{accounting.CryptoPaid} {paymentMethodId.CryptoCode}";
|
||||
cryptoPayment.Overpaid = $"{accounting.OverpaidHelper} {paymentMethodId.CryptoCode}";
|
||||
|
||||
var onchainMethod = data.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod;
|
||||
if (onchainMethod != null)
|
||||
@ -469,6 +471,28 @@ namespace BTCPayServer.Controllers
|
||||
return list;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> Export(string format, string searchTerm = null)
|
||||
{
|
||||
var model = new InvoiceExport();
|
||||
|
||||
var invoices = await ListInvoicesProcess(searchTerm, 0, int.MaxValue);
|
||||
var res = model.Process(invoices, format);
|
||||
|
||||
var cd = new ContentDisposition
|
||||
{
|
||||
FileName = $"btcpay-export-{DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture)}.{format}",
|
||||
Inline = true
|
||||
};
|
||||
Response.Headers.Add("Content-Disposition", cd.ToString());
|
||||
Response.Headers.Add("X-Content-Type-Options", "nosniff");
|
||||
return Content(res, "application/" + format);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpGet]
|
||||
[Route("invoices/create")]
|
||||
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
|
||||
@ -586,5 +610,43 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
return _UserManager.GetUserId(User);
|
||||
}
|
||||
|
||||
public class PosDataParser
|
||||
{
|
||||
public static Dictionary<string, string> ParsePosData(string posData)
|
||||
{
|
||||
var result = new Dictionary<string,string>();
|
||||
if (string.IsNullOrEmpty(posData))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
var jObject =JObject.Parse(posData);
|
||||
foreach (var item in jObject)
|
||||
{
|
||||
|
||||
switch (item.Value.Type)
|
||||
{
|
||||
case JTokenType.Array:
|
||||
result.Add(item.Key, string.Join(',', item.Value.AsEnumerable()));
|
||||
break;
|
||||
default:
|
||||
result.Add(item.Key, item.Value.ToString());
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
result.Add(string.Empty, posData);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -163,6 +163,7 @@ namespace BTCPayServer.Controllers
|
||||
// - The user is clicking on continue without changing anything
|
||||
(!vm.Confirmation && willBeExcluded == wasExcluded);
|
||||
|
||||
showAddress = showAddress && strategy != null;
|
||||
if (!showAddress)
|
||||
{
|
||||
try
|
||||
|
@ -789,7 +789,8 @@ namespace BTCPayServer.Controllers
|
||||
StatusMessage = "Server initiated pairing code: " + pairingCode;
|
||||
return RedirectToAction(nameof(ListTokens), new
|
||||
{
|
||||
storeId = store.Id
|
||||
storeId = store.Id,
|
||||
pairingCode = pairingCode
|
||||
});
|
||||
}
|
||||
else
|
||||
|
@ -63,10 +63,18 @@ namespace BTCPayServer
|
||||
electrumMapping.Add(p2wpkh, Array.Empty<string>());
|
||||
|
||||
var parts = str.Split('-');
|
||||
bool hasLabel = false;
|
||||
for (int i = 0; i < parts.Length; i++)
|
||||
{
|
||||
if (IsLabel(parts[i]))
|
||||
{
|
||||
if (!hasLabel)
|
||||
{
|
||||
hintedLabels.Clear();
|
||||
if (!Network.Consensus.SupportSegwit)
|
||||
hintedLabels.Add("legacy");
|
||||
}
|
||||
hasLabel = true;
|
||||
hintedLabels.Add(parts[i].Substring(1, parts[i].Length - 2).ToLowerInvariant());
|
||||
continue;
|
||||
}
|
||||
|
@ -17,11 +17,30 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
[MaxLength(5000)]
|
||||
public string Template { get; set; }
|
||||
|
||||
[Display(Name = "Enable shopping cart")]
|
||||
public bool EnableShoppingCart { get; set; }
|
||||
[Display(Name = "User can input custom amount")]
|
||||
public bool ShowCustomAmount { get; set; }
|
||||
public string Example1 { get; internal set; }
|
||||
public string Example2 { get; internal set; }
|
||||
public string ExampleCallback { get; internal set; }
|
||||
public string InvoiceUrl { get; internal set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(30)]
|
||||
[Display(Name = "Text to display on each buttons for items with a specific price")]
|
||||
public string ButtonText { get; set; }
|
||||
[Required]
|
||||
[MaxLength(30)]
|
||||
[Display(Name = "Text to display on buttons next to the input allowing the user to enter a custom amount")]
|
||||
public string CustomButtonText { get; set; }
|
||||
[Required]
|
||||
[MaxLength(30)]
|
||||
[Display(Name = "Do you want to leave a tip?")]
|
||||
public string CustomTipText { get; set; }
|
||||
|
||||
[MaxLength(500)]
|
||||
[Display(Name = "Custom bootstrap CSS file")]
|
||||
public string CustomCSSLink { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -22,10 +22,17 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
public bool Custom { get; set; }
|
||||
}
|
||||
|
||||
public bool EnableShoppingCart { get; set; }
|
||||
public bool ShowCustomAmount { get; set; }
|
||||
public string Step { get; set; }
|
||||
public string Title { get; set; }
|
||||
public Item[] Items { get; set; }
|
||||
public string CurrencySymbol { get; set; }
|
||||
|
||||
public string ButtonText { get; set; }
|
||||
public string CustomButtonText { get; set; }
|
||||
public string CustomTipText { get; set; }
|
||||
|
||||
public string CustomCSSLink { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -143,5 +143,6 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
public DateTimeOffset MonitoringDate { get; internal set; }
|
||||
public List<Data.InvoiceEventData> Events { get; internal set; }
|
||||
public string NotificationEmail { get; internal set; }
|
||||
public Dictionary<string, string> PosData { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
}
|
||||
|
||||
CompositeDisposable leases = new CompositeDisposable();
|
||||
ConcurrentDictionary<string, NotificationSession> _SessionsByCryptoCode = new ConcurrentDictionary<string, NotificationSession>();
|
||||
ConcurrentDictionary<string, WebsocketNotificationSession> _SessionsByCryptoCode = new ConcurrentDictionary<string, WebsocketNotificationSession>();
|
||||
private Timer _ListenPoller;
|
||||
|
||||
TimeSpan _PollInterval;
|
||||
@ -114,7 +114,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
return;
|
||||
if (_Cts.IsCancellationRequested)
|
||||
return;
|
||||
var session = await client.CreateNotificationSessionAsync(_Cts.Token).ConfigureAwait(false);
|
||||
var session = await client.CreateWebsocketNotificationSessionAsync(_Cts.Token).ConfigureAwait(false);
|
||||
if (!_SessionsByCryptoCode.TryAdd(network.CryptoCode, session))
|
||||
{
|
||||
await session.DisposeAsync();
|
||||
@ -187,7 +187,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
if (cleanup)
|
||||
{
|
||||
Logs.PayServer.LogInformation($"Disconnected from WebSocket of NBXplorer ({network.CryptoCode})");
|
||||
_SessionsByCryptoCode.TryRemove(network.CryptoCode, out NotificationSession unused);
|
||||
_SessionsByCryptoCode.TryRemove(network.CryptoCode, out WebsocketNotificationSession unused);
|
||||
if (_SessionsByCryptoCode.Count == 0 && _Cts.IsCancellationRequested)
|
||||
{
|
||||
_RunningTask.TrySetResult(true);
|
||||
|
146
BTCPayServer/Services/Invoices/Export/CsvSerializer.cs
Normal file
146
BTCPayServer/Services/Invoices/Export/CsvSerializer.cs
Normal file
@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
// Ref: https://www.codeproject.com/Articles/566656/CSV-Serializer-for-NET
|
||||
namespace BTCPayServer.Services.Invoices.Export
|
||||
{
|
||||
/// <summary>
|
||||
/// Serialize and Deserialize Lists of any object type to CSV.
|
||||
/// </summary>
|
||||
public class CsvSerializer<T> where T : class, new()
|
||||
{
|
||||
private List<PropertyInfo> _properties;
|
||||
|
||||
public bool IgnoreEmptyLines { get; set; } = true;
|
||||
public bool IgnoreReferenceTypesExceptString { get; set; } = true;
|
||||
public string NewlineReplacement { get; set; } = ((char)0x254).ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
public char Separator { get; set; } = ',';
|
||||
|
||||
public string RowNumberColumnTitle { get; set; } = "RowNumber";
|
||||
public bool UseLineNumbers { get; set; } = false;
|
||||
public bool UseEofLiteral { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Csv Serializer
|
||||
/// Initialize by selected properties from the type to be de/serialized
|
||||
/// </summary>
|
||||
public CsvSerializer()
|
||||
{
|
||||
var type = typeof(T);
|
||||
|
||||
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance
|
||||
| BindingFlags.GetProperty | BindingFlags.SetProperty);
|
||||
|
||||
|
||||
var q = properties.AsQueryable();
|
||||
|
||||
if (IgnoreReferenceTypesExceptString)
|
||||
{
|
||||
q = q.Where(a => a.PropertyType.IsValueType || a.PropertyType.Name == "String");
|
||||
}
|
||||
|
||||
var r = from a in q
|
||||
where a.GetCustomAttribute<CsvIgnoreAttribute>() == null
|
||||
select a;
|
||||
|
||||
_properties = r.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize
|
||||
/// </summary>
|
||||
/// <param name="stream">stream</param>
|
||||
/// <param name="data">data</param>
|
||||
public string Serialize(IList<T> data)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var values = new List<string>();
|
||||
|
||||
sb.AppendLine(GetHeader());
|
||||
|
||||
var row = 1;
|
||||
foreach (var item in data)
|
||||
{
|
||||
values.Clear();
|
||||
|
||||
if (UseLineNumbers)
|
||||
{
|
||||
values.Add(row++.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
foreach (var p in _properties)
|
||||
{
|
||||
var raw = p.GetValue(item);
|
||||
var value = raw == null ? "" :
|
||||
raw.ToString()
|
||||
.Replace("\"", "``", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace(Environment.NewLine, NewlineReplacement, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
value = String.Format(CultureInfo.InvariantCulture, "\"{0}\"", value);
|
||||
|
||||
values.Add(value);
|
||||
}
|
||||
sb.AppendLine(String.Join(Separator.ToString(CultureInfo.InvariantCulture), values.ToArray()));
|
||||
}
|
||||
|
||||
if (UseEofLiteral)
|
||||
{
|
||||
values.Clear();
|
||||
|
||||
if (UseLineNumbers)
|
||||
{
|
||||
values.Add(row++.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
values.Add("EOF");
|
||||
|
||||
sb.AppendLine(string.Join(Separator.ToString(CultureInfo.InvariantCulture), values.ToArray()));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Header
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private string GetHeader()
|
||||
{
|
||||
var header = _properties.Select(a => a.Name);
|
||||
|
||||
if (UseLineNumbers)
|
||||
{
|
||||
header = new string[] { RowNumberColumnTitle }.Union(header);
|
||||
}
|
||||
|
||||
return string.Join(Separator.ToString(CultureInfo.InvariantCulture), header.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public class CsvIgnoreAttribute : Attribute { }
|
||||
|
||||
public class InvalidCsvFormatException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Invalid Csv Format Exception
|
||||
/// </summary>
|
||||
/// <param name="message">message</param>
|
||||
public InvalidCsvFormatException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public InvalidCsvFormatException(string message, Exception ex)
|
||||
: base(message, ex)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
121
BTCPayServer/Services/Invoices/Export/InvoiceExport.cs
Normal file
121
BTCPayServer/Services/Invoices/Export/InvoiceExport.cs
Normal file
@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Invoices.Export
|
||||
{
|
||||
public class InvoiceExport
|
||||
{
|
||||
public string Process(InvoiceEntity[] invoices, string fileFormat)
|
||||
{
|
||||
var csvInvoices = new List<ExportInvoiceHolder>();
|
||||
foreach (var i in invoices)
|
||||
{
|
||||
csvInvoices.AddRange(convertFromDb(i));
|
||||
}
|
||||
|
||||
if (String.Equals(fileFormat, "json", StringComparison.OrdinalIgnoreCase))
|
||||
return processJson(csvInvoices);
|
||||
else if (String.Equals(fileFormat, "csv", StringComparison.OrdinalIgnoreCase))
|
||||
return processCsv(csvInvoices);
|
||||
else
|
||||
throw new Exception("Export format not supported");
|
||||
}
|
||||
|
||||
private string processJson(List<ExportInvoiceHolder> invoices)
|
||||
{
|
||||
var serializerSett = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
|
||||
var json = JsonConvert.SerializeObject(invoices, Formatting.Indented, serializerSett);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
private string processCsv(List<ExportInvoiceHolder> invoices)
|
||||
{
|
||||
var serializer = new CsvSerializer<ExportInvoiceHolder>();
|
||||
var csv = serializer.Serialize(invoices);
|
||||
|
||||
return csv;
|
||||
}
|
||||
|
||||
private IEnumerable<ExportInvoiceHolder> convertFromDb(InvoiceEntity invoice)
|
||||
{
|
||||
var exportList = new List<ExportInvoiceHolder>();
|
||||
// in this first version we are only exporting invoices that were paid
|
||||
foreach (var payment in invoice.GetPayments())
|
||||
{
|
||||
// not accounted payments are payments which got double spent like RBfed
|
||||
if (!payment.Accounted)
|
||||
continue;
|
||||
|
||||
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 target = new ExportInvoiceHolder
|
||||
{
|
||||
ReceivedDate = payment.ReceivedTime.UtcDateTime,
|
||||
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}",
|
||||
|
||||
OrderId = invoice.OrderId,
|
||||
StoreId = invoice.StoreId,
|
||||
InvoiceId = invoice.Id,
|
||||
CreatedDate = invoice.InvoiceTime.UtcDateTime,
|
||||
ExpirationDate = invoice.ExpirationTime.UtcDateTime,
|
||||
MonitoringDate = invoice.MonitoringExpiration.UtcDateTime,
|
||||
Status = invoice.Status,
|
||||
ItemCode = invoice.ProductInformation?.ItemCode,
|
||||
ItemDesc = invoice.ProductInformation?.ItemDesc,
|
||||
FiatPrice = invoice.ProductInformation?.Price ?? 0,
|
||||
FiatCurrency = invoice.ProductInformation?.Currency,
|
||||
};
|
||||
|
||||
exportList.Add(target);
|
||||
}
|
||||
|
||||
exportList = exportList.OrderBy(a => a.ReceivedDate).ToList();
|
||||
|
||||
return exportList;
|
||||
}
|
||||
}
|
||||
|
||||
public class ExportInvoiceHolder
|
||||
{
|
||||
public DateTime ReceivedDate { get; set; }
|
||||
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 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 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; }
|
||||
}
|
||||
}
|
@ -533,20 +533,21 @@ namespace BTCPayServer.Services.Invoices
|
||||
|
||||
public class PaymentMethodAccounting
|
||||
{
|
||||
/// <summary>
|
||||
/// Total amount of this invoice
|
||||
/// </summary>
|
||||
/// <summary>Total amount of this invoice</summary>
|
||||
public Money TotalDue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Amount of crypto remaining to pay this invoice
|
||||
/// </summary>
|
||||
/// <summary>Amount of crypto remaining to pay this invoice</summary>
|
||||
public Money Due { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Same as Due, can be negative
|
||||
/// </summary>
|
||||
/// <summary>Same as Due, can be negative</summary>
|
||||
public Money DueUncapped { get; set; }
|
||||
|
||||
/// <summary>If DueUncapped is negative, that means user overpaid invoice</summary>
|
||||
public Money OverpaidHelper
|
||||
{
|
||||
get { return DueUncapped > Money.Zero ? Money.Zero : -DueUncapped; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Total amount of the invoice paid after conversion to this crypto currency
|
||||
/// </summary>
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
@ -9,6 +10,8 @@ namespace BTCPayServer.Services
|
||||
public class ThemeSettings
|
||||
{
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
[MaxLength(500)]
|
||||
[Display(Name = "Custom bootstrap CSS file")]
|
||||
public string BootstrapCssUri { get; set; }
|
||||
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
|
@ -122,7 +122,7 @@ namespace BTCPayServer.Services.Wallets
|
||||
UTXOChanges result = null;
|
||||
try
|
||||
{
|
||||
result = await _Client.GetUTXOsAsync(strategy, null, false, cancellation).ConfigureAwait(false);
|
||||
result = await _Client.GetUTXOsAsync(strategy, cancellation).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -153,7 +153,7 @@ namespace BTCPayServer.Services.Wallets
|
||||
|
||||
public Task<GetTransactionsResponse> FetchTransactions(DerivationStrategyBase derivationStrategyBase)
|
||||
{
|
||||
return _Client.GetTransactionsAsync(derivationStrategyBase, null, false);
|
||||
return _Client.GetTransactionsAsync(derivationStrategyBase);
|
||||
}
|
||||
|
||||
public Task<BroadcastResult[]> BroadcastTransactionsAsync(List<Transaction> transactions)
|
||||
|
@ -29,10 +29,35 @@
|
||||
<input asp-for="Currency" class="form-control" />
|
||||
<span asp-validation-for="Currency" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="EnableShoppingCart"></label>
|
||||
<input asp-for="EnableShoppingCart" type="checkbox" class="form-check" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="ShowCustomAmount"></label>
|
||||
<input asp-for="ShowCustomAmount" type="checkbox" class="form-check" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="ButtonText" class="control-label"></label>*
|
||||
<input asp-for="ButtonText" class="form-control" />
|
||||
<span asp-validation-for="ButtonText" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="CustomButtonText" class="control-label"></label>*
|
||||
<input asp-for="CustomButtonText" class="form-control" />
|
||||
<span asp-validation-for="CustomButtonText" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="CustomTipText" class="control-label"></label>*
|
||||
<input asp-for="CustomTipText" class="form-control" />
|
||||
<span asp-validation-for="CustomTipText" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="CustomCSSLink" class="control-label"></label>
|
||||
<a href="https://docs.btcpayserver.org/development/theme#bootstrap-themes" target="_blank"><span class="fa fa-question-circle-o" title="More information..."></span></a>
|
||||
<input asp-for="CustomCSSLink" class="form-control" />
|
||||
<span asp-validation-for="CustomCSSLink" class="text-danger"></span>
|
||||
</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>
|
||||
@ -41,12 +66,12 @@
|
||||
<div class="form-group">
|
||||
<h5>Host button externally</h5>
|
||||
<p>You can host point of sale buttons in an external website with the following code.</p>
|
||||
@if(Model.Example1 != null)
|
||||
@if (Model.Example1 != null)
|
||||
{
|
||||
<span>For anything with a custom amount</span>
|
||||
<pre><code class="html">@Model.Example1</code></pre>
|
||||
}
|
||||
@if(Model.Example2 != null)
|
||||
@if (Model.Example2 != null)
|
||||
{
|
||||
<span>For a specific item of your template</span>
|
||||
<pre><code class="html">@Model.Example2</code></pre>
|
||||
@ -63,7 +88,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" class="btn btn-primary" />
|
||||
<input type="submit" class="btn btn-primary" value="Save Settings" />
|
||||
</div>
|
||||
</form>
|
||||
<a asp-action="ListApps">Back to the app list</a>
|
||||
|
@ -1,4 +1,5 @@
|
||||
@inject BTCPayServer.HostedServices.CssThemeManager themeManager
|
||||
@addTagHelper *, Meziantou.AspNetCore.BundleTagHelpers
|
||||
@inject BTCPayServer.HostedServices.CssThemeManager themeManager
|
||||
|
||||
@model BTCPayServer.Models.AppViewModels.ViewPointOfSaleViewModel
|
||||
@{
|
||||
@ -14,11 +15,63 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<link href="@this.Context.Request.GetAbsoluteUri(themeManager.BootstrapUri)" rel="stylesheet" />
|
||||
@if (Model.CustomCSSLink != null)
|
||||
{
|
||||
<link href="@Model.CustomCSSLink" rel="stylesheet" />
|
||||
}
|
||||
<link href="~/vendor/font-awesome/css/font-awesome.min.css" rel="stylesheet" />
|
||||
|
||||
@if (Model.EnableShoppingCart)
|
||||
{
|
||||
<script type="text/javascript">
|
||||
var srvModel = @Html.Raw(Json.Serialize(Model));
|
||||
</script>
|
||||
<bundle name="wwwroot/bundles/cart-bundle.min.js" />
|
||||
}
|
||||
</head>
|
||||
<body class="h-100">
|
||||
@if (Model.EnableShoppingCart)
|
||||
{
|
||||
<div id="cartModal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Shopping cart</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table id="js-cart-list" class="table mt-2 mb-3">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th colspan="2">Product</th>
|
||||
<th class="text-right" width="80">Quantity</th>
|
||||
<th class="text-right" width="25%">Price</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<form method="post" asp-antiforgery="false" data-buy>
|
||||
<input id="js-cart-amount" class="form-control" type="hidden" name="amount">
|
||||
<button id="js-cart-pay" class="btn btn-primary" type="submit"><b>@Model.CustomButtonText</b></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="container d-flex h-100">
|
||||
<div class="justify-content-center align-self-center text-center mx-auto px-2 py-3 w-100" style="margin: auto;">
|
||||
<h1 class="mb-4">@Model.Title</h1>
|
||||
@if (Model.EnableShoppingCart)
|
||||
{
|
||||
<a id="js-cart" class="btn btn-warning text-white text-right" href="#" data-toggle="modal" data-target="#cartModal"><i class="fa fa-shopping-basket"></i> <span class="badge badge-light badge-pill"><span id="js-cart-items">0</span></span></a>
|
||||
}
|
||||
<div class="row">
|
||||
@for (int i = 0; i < Model.Items.Length; i++)
|
||||
{
|
||||
@ -27,7 +80,7 @@
|
||||
var image = item.Image;
|
||||
var description = item.Description;
|
||||
<div class="@className my-3 px-2">
|
||||
<div class="card">
|
||||
<div class="card" data-id="@i">
|
||||
@if (!String.IsNullOrWhiteSpace(image))
|
||||
{
|
||||
<img class="card-img-top" src="@image" alt="Card image cap">
|
||||
@ -38,9 +91,10 @@
|
||||
{
|
||||
<p class="card-text">@description</p>
|
||||
}
|
||||
@if (item.Custom)
|
||||
@if (item.Custom && !Model.EnableShoppingCart)
|
||||
{
|
||||
<form method="post" asp-antiforgery="false" data-buy>
|
||||
<input type="hidden" name="choicekey" value="@item.Id" />
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">@Model.CurrencySymbol</span>
|
||||
@ -48,7 +102,7 @@
|
||||
<input class="form-control" type="number" min="@item.Price.Value" step="@Model.Step" name="amount"
|
||||
value="@item.Price.Value" placeholder="Amount">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-primary" type="submit">Pay</button>
|
||||
<button class="btn btn-primary" type="submit">@Model.CustomButtonText</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@ -56,7 +110,8 @@
|
||||
else
|
||||
{
|
||||
<form method="post" asp-antiforgery="false">
|
||||
<button type="submit" name="choiceKey" class="btn btn-primary" value="@item.Id">Buy for @item.Price.Formatted</button>
|
||||
<button type="submit" name="choiceKey" class="js-add-cart btn btn-primary" value="@item.Id">
|
||||
@String.Format(Model.ButtonText, @item.Price.Formatted)</button>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
@ -78,7 +133,7 @@
|
||||
<span class="input-group-text">@Model.CurrencySymbol</span>
|
||||
</div>
|
||||
<input class="form-control" type="number" min="0" step="@Model.Step" name="amount" placeholder="Amount">
|
||||
<div class="input-group-append"><button class="btn btn-primary" type="submit">Pay</button></div>
|
||||
<div class="input-group-append"><button class="btn btn-primary" type="submit">@Model.CustomButtonText</button></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -88,7 +143,5 @@
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<script src="~/vendor/jquery/jquery.js"></script>
|
||||
<script src="~/vendor/bootstrap4/js/bootstrap.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -296,8 +296,8 @@
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<a v-on:click="openDialog($event)" :href="url" class="changelly-component-button">
|
||||
<img src="https://changelly.com/pay_button.png" alt="Changelly" v-show="url"/>
|
||||
<a v-on:click="openDialog($event)" :href="url" class="btn btn-primary retry-button changelly-component-button" v-show="url">
|
||||
Pay with Changelly
|
||||
</a>
|
||||
<button class="retry-button" v-if="calculateError" v-on:click="retry('calculateAmount')">
|
||||
{{$t("ConversionTab_CalculateAmount_Error")}}
|
||||
@ -329,7 +329,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="success-message">{{$t("This invoice has been paid")}}</div>
|
||||
<a class="action-button" :href="srvModel.merchantRefLink" v-show="srvModel.merchantRefLink && !isModal">
|
||||
<a class="action-button" :href="srvModel.merchantRefLink" v-show="!isModal">
|
||||
<span>{{$t("Return to StoreName", srvModel)}}</span>
|
||||
</a>
|
||||
<button class="action-button close-action" v-show="isModal">
|
||||
@ -376,7 +376,7 @@
|
||||
{{srvModel.orderId}}
|
||||
</div>
|
||||
</div>
|
||||
<a class="action-button" :href="srvModel.merchantRefLink" v-show="srvModel.merchantRefLink && !isModal">
|
||||
<a class="action-button" :href="srvModel.merchantRefLink" v-show="!isModal">
|
||||
<span>{{$t("Return to StoreName", srvModel)}}</span>
|
||||
</a>
|
||||
<button class="action-button close-action" v-show="isModal">
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<META NAME="robots" CONTENT="noindex,nofollow">
|
||||
<title>@Model.HtmlTitle</title>
|
||||
|
||||
<bundle name="wwwroot/bundles/checkout-bundle.min.css" />
|
||||
@ -176,7 +177,7 @@
|
||||
lndModel: null,
|
||||
scanDisplayQr: "",
|
||||
expiringSoon: false,
|
||||
isModal: '@(Model.IsModal ? "true" : "false")'
|
||||
isModal: srvModel.isModal
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -2,6 +2,14 @@
|
||||
@{
|
||||
ViewData["Title"] = "Create an invoice";
|
||||
}
|
||||
<script>
|
||||
$(function() {
|
||||
$("#create-invoice-form").on("submit",
|
||||
function() {
|
||||
$(this).find("input[type='submit']").prop('disabled', true);
|
||||
});
|
||||
})
|
||||
</script>
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
@ -12,7 +20,7 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<form asp-action="CreateInvoice" method="post">
|
||||
<form asp-action="CreateInvoice" method="post" id="create-invoice-form">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Amount" class="control-label"></label>*
|
||||
|
@ -2,6 +2,9 @@
|
||||
@{
|
||||
ViewData["Title"] = "Invoice " + Model.Id;
|
||||
}
|
||||
@section HeaderContent{
|
||||
<META NAME="robots" CONTENT="noindex,nofollow">
|
||||
}
|
||||
|
||||
<style type="text/css">
|
||||
.linethrough {
|
||||
@ -39,110 +42,134 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h3>Information</h3>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<tr>
|
||||
<th>Store</th>
|
||||
<td><a href="@Model.StoreLink">@Model.StoreName</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<td>@Model.Id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Created date</th>
|
||||
<td>@Model.CreatedDate.ToBrowserDate()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Expiration date</th>
|
||||
<td>@Model.ExpirationDate.ToBrowserDate()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Monitoring date</th>
|
||||
<td>@Model.MonitoringDate.ToBrowserDate()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Transaction speed</th>
|
||||
<td>@Model.TransactionSpeed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<td>@Model.Status</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status Exception</th>
|
||||
<td>@Model.StatusException</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Refund email</th>
|
||||
<td><a href="mailto:@Model.RefundEmail">@Model.RefundEmail</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Order Id</th>
|
||||
<td>@Model.OrderId</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Total fiat due</th>
|
||||
<td>@Model.Fiat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Notification Email</th>
|
||||
<td>@Model.NotificationEmail</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Notification Url</th>
|
||||
<td>@Model.NotificationUrl</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Redirect Url</th>
|
||||
<td><a href="@Model.RedirectUrl">@Model.RedirectUrl</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<h3>Buyer information</h3>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<td>@Model.BuyerInformation.BuyerName</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<td><a href="mailto:@Model.BuyerInformation.BuyerEmail">@Model.BuyerInformation.BuyerEmail</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Phone</th>
|
||||
<td>@Model.BuyerInformation.BuyerPhone</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Address 1</th>
|
||||
<td>@Model.BuyerInformation.BuyerAddress1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Address 2</th>
|
||||
<td>@Model.BuyerInformation.BuyerAddress2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>City</th>
|
||||
<td>@Model.BuyerInformation.BuyerCity</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>State</th>
|
||||
<td>@Model.BuyerInformation.BuyerState</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Country</th>
|
||||
<td>@Model.BuyerInformation.BuyerCountry</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Zip</th>
|
||||
<td>@Model.BuyerInformation.BuyerZip</td>
|
||||
</tr>
|
||||
</table>
|
||||
@if (Model.PosData.Count == 0)
|
||||
{
|
||||
<h3>Product information</h3>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<tr>
|
||||
<th>Item code</th>
|
||||
<td>@Model.ProductInformation.ItemCode</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Item Description</th>
|
||||
<td>@Model.ProductInformation.ItemDesc</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Price</th>
|
||||
<td>@Model.ProductInformation.Price @Model.ProductInformation.Currency</td>
|
||||
</tr>
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.PosData.Count != 0)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h3>Information</h3>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<tr>
|
||||
<th>Store</th>
|
||||
<td><a href="@Model.StoreLink">@Model.StoreName</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<td>@Model.Id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Created date</th>
|
||||
<td>@Model.CreatedDate.ToBrowserDate()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Expiration date</th>
|
||||
<td>@Model.ExpirationDate.ToBrowserDate()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Monitoring date</th>
|
||||
<td>@Model.MonitoringDate.ToBrowserDate()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Transaction speed</th>
|
||||
<td>@Model.TransactionSpeed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<td>@Model.Status</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status Exception</th>
|
||||
<td>@Model.StatusException</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Refund email</th>
|
||||
<td><a href="mailto:@Model.RefundEmail">@Model.RefundEmail</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Order Id</th>
|
||||
<td>@Model.OrderId</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Total fiat due</th>
|
||||
<td>@Model.Fiat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Notification Email</th>
|
||||
<td>@Model.NotificationEmail</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Notification Url</th>
|
||||
<td>@Model.NotificationUrl</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Redirect Url</th>
|
||||
<td><a href="@Model.RedirectUrl">@Model.RedirectUrl</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<h3>Buyer information</h3>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<td>@Model.BuyerInformation.BuyerName</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<td><a href="mailto:@Model.BuyerInformation.BuyerEmail">@Model.BuyerInformation.BuyerEmail</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Phone</th>
|
||||
<td>@Model.BuyerInformation.BuyerPhone</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Address 1</th>
|
||||
<td>@Model.BuyerInformation.BuyerAddress1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Address 2</th>
|
||||
<td>@Model.BuyerInformation.BuyerAddress2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>City</th>
|
||||
<td>@Model.BuyerInformation.BuyerCity</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>State</th>
|
||||
<td>@Model.BuyerInformation.BuyerState</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Country</th>
|
||||
<td>@Model.BuyerInformation.BuyerCountry</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Zip</th>
|
||||
<td>@Model.BuyerInformation.BuyerZip</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Product information</h3>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<tr>
|
||||
@ -159,8 +186,31 @@
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h3>Point of Sale Data</h3>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
@foreach (var posDataItem in Model.PosData)
|
||||
{
|
||||
<tr>
|
||||
@if (!string.IsNullOrEmpty(posDataItem.Key))
|
||||
{
|
||||
|
||||
<th>@posDataItem.Key</th>
|
||||
<td>@posDataItem.Value</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
<td colspan="2">@posDataItem.Value</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Paid summary</h3>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
@ -255,29 +305,6 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Addresses</h3>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th class="firstCol">Payment method</th>
|
||||
<th>Address</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var address in Model.Addresses)
|
||||
{
|
||||
var current = address.Current ? "font-weight-bold" : "";
|
||||
<tr>
|
||||
<td>@address.PaymentMethod</td>
|
||||
<td class="smMaxWidth text-truncate @current">@address.Destination</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
@ -41,11 +41,19 @@
|
||||
</div>
|
||||
|
||||
<div class="row no-gutter" style="margin-bottom: 5px;">
|
||||
<div class="col-lg-4">
|
||||
<div class="col-lg-6">
|
||||
<a asp-action="CreateInvoice" class="btn btn-primary" role="button"><span class="fa fa-plus"></span> Create a new invoice</a>
|
||||
|
||||
<a class="btn btn-primary dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Export
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
|
||||
<a asp-action="Export" asp-route-format="csv" asp-route-searchTerm="@Model.SearchTerm" class="dropdown-item" target="_blank">CSV</a>
|
||||
<a asp-action="Export" asp-route-format="json" asp-route-searchTerm="@Model.SearchTerm" class="dropdown-item" target="_blank">JSON</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-8">
|
||||
<div class="col-lg-6">
|
||||
<div class="form-group">
|
||||
<form asp-action="SearchInvoice" method="post" style="float:right;">
|
||||
<div class="input-group">
|
||||
|
@ -16,6 +16,7 @@
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<label asp-for="BootstrapCssUri"></label>
|
||||
<a href="https://docs.btcpayserver.org/development/theme#bootstrap-themes" target="_blank"><span class="fa fa-question-circle-o" title="More information..."></span></a>
|
||||
<input asp-for="BootstrapCssUri" class="form-control" />
|
||||
<span asp-validation-for="BootstrapCssUri" class="text-danger"></span>
|
||||
<p class="form-text text-muted">
|
||||
|
@ -28,6 +28,7 @@
|
||||
<bundle name="wwwroot/bundles/main-bundle.min.js" />
|
||||
|
||||
@RenderSection("HeadScripts", required: false)
|
||||
@RenderSection("HeaderContent", false)
|
||||
</head>
|
||||
|
||||
<body id="page-top">
|
||||
|
@ -52,5 +52,14 @@
|
||||
"wwwroot/vendor/vex/js/vex.combined.min.js",
|
||||
"wwwroot/checkout/**/*.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "wwwroot/bundles/cart-bundle.min.js",
|
||||
"inputFiles": [
|
||||
"wwwroot/vendor/jquery/jquery.js",
|
||||
"wwwroot/vendor/bootstrap4/js/bootstrap.js",
|
||||
"wwwroot/cart/js/cart.js",
|
||||
"wwwroot/cart/js/cart.jquery.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
57
BTCPayServer/wwwroot/cart/js/cart.jquery.js
Normal file
57
BTCPayServer/wwwroot/cart/js/cart.jquery.js
Normal file
@ -0,0 +1,57 @@
|
||||
$.fn.addAnimate = function(completeCallback) {
|
||||
var documentHeight = $(document).height(),
|
||||
itemPos = $(this).offset(),
|
||||
itemY = itemPos.top,
|
||||
cartPos = $('#js-cart').find('.badge').position();
|
||||
tempItem = '<span id="js-cart-temp-item" class="badge badge-primary text-white badge-pill " style="' +
|
||||
'position: absolute;' +
|
||||
'top: ' + itemPos.top + 'px;' +
|
||||
'left: ' + (itemPos.left + 50) + 'px;">'+
|
||||
'<i class="fa fa-shopping-basket"></i></span>';
|
||||
|
||||
// Make animation speed look constant regardless of how far the object is from the cart
|
||||
var animationSpeed = (Math.log(itemY) * (documentHeight / Math.log2(documentHeight - itemY))) / 2;
|
||||
|
||||
// Add the cart item badge and animate it
|
||||
$('body').after(tempItem);
|
||||
$('#js-cart-temp-item').animate({
|
||||
easing: 'swing',
|
||||
top: cartPos.top,
|
||||
left: cartPos.left
|
||||
}, animationSpeed, function() {
|
||||
$(this).remove();
|
||||
completeCallback && completeCallback();
|
||||
});
|
||||
};
|
||||
|
||||
$(document).ready(function(){
|
||||
var cart = new Cart();
|
||||
|
||||
$('.js-add-cart').click(function(event){
|
||||
event.preventDefault();
|
||||
|
||||
var $btn = $(event.target),
|
||||
id = $btn.closest('.card').data('id'),
|
||||
item = srvModel.items[id];
|
||||
|
||||
// Animate adding and then add then save
|
||||
$(this).addAnimate(function(){
|
||||
cart.addItem({
|
||||
id: id,
|
||||
title: item.title,
|
||||
price: item.price,
|
||||
image: typeof item.image != 'underfined' ? item.image : null
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Destroy the cart when the "pay button is clicked"
|
||||
$('#js-cart-pay').click(function(){
|
||||
cart.destroy();
|
||||
});
|
||||
|
||||
// Repopulate cart items in the modal when it opens
|
||||
$('#cartModal').on('show.bs.modal', function () {
|
||||
cart.listItems();
|
||||
});
|
||||
});
|
282
BTCPayServer/wwwroot/cart/js/cart.js
Normal file
282
BTCPayServer/wwwroot/cart/js/cart.js
Normal file
@ -0,0 +1,282 @@
|
||||
function Cart() {
|
||||
this.items = 0;
|
||||
this.totalAmount = 0;
|
||||
this.content = [];
|
||||
this.tip = 0;
|
||||
|
||||
this.loadLocalStorage();
|
||||
this.itemsCount();
|
||||
this.listItems();
|
||||
this.updateAmount();
|
||||
}
|
||||
|
||||
Cart.prototype.addItem = function(item) {
|
||||
// Increment the existing item count
|
||||
var result = this.content.filter(function(obj){
|
||||
if (obj.id === item.id){
|
||||
obj.count++;
|
||||
}
|
||||
|
||||
return obj.id === item.id
|
||||
});
|
||||
|
||||
// Add new item because it doesn't exist yet
|
||||
if (!result.length) {
|
||||
this.content.push({id: item.id, title: item.title, price: item.price, count: 1, image: item.image})
|
||||
}
|
||||
|
||||
this.items++;
|
||||
this.saveLocalStorage();
|
||||
this.itemsCount();
|
||||
this.updateTotal();
|
||||
this.updateAmount();
|
||||
}
|
||||
|
||||
Cart.prototype.decrementItem = function(id) {
|
||||
var self = this;
|
||||
|
||||
// Decrement the existing item count
|
||||
this.content.filter(function(obj, index, arr){
|
||||
if (obj.id === id)
|
||||
{
|
||||
obj.count--;
|
||||
|
||||
// It's the last item with the same ID, remove it
|
||||
if (obj.count === 0) {
|
||||
self.removeItem(id, index, arr);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.items--;
|
||||
this.saveLocalStorage();
|
||||
this.itemsCount();
|
||||
this.updateTotal();
|
||||
this.updateAmount();
|
||||
|
||||
if (this.items === 0) {
|
||||
this.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
Cart.prototype.removeItemAll = function(id) {
|
||||
var self = this;
|
||||
|
||||
this.content.filter(function(obj, index, arr){
|
||||
if (obj.id === id)
|
||||
{
|
||||
self.removeItem(id, index, arr);
|
||||
|
||||
for (var i = 0; i < obj.count; i++) {
|
||||
self.items--;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.saveLocalStorage();
|
||||
this.itemsCount();
|
||||
this.updateTotal();
|
||||
this.updateAmount();
|
||||
|
||||
if (this.items === 0) {
|
||||
this.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
Cart.prototype.removeItem = function(id, index, arr) {
|
||||
// Remove from the array
|
||||
arr.splice(index, 1);
|
||||
// Remove from the DOM
|
||||
$('#js-cart-list').find('tr').eq(index+1).remove();
|
||||
}
|
||||
|
||||
Cart.prototype.setTip = function(tip) {
|
||||
return this.tip = tip;
|
||||
}
|
||||
|
||||
Cart.prototype.itemsCount = function() {
|
||||
$('#js-cart-items').text(this.items);
|
||||
}
|
||||
|
||||
Cart.prototype.getTotal = function(plain) {
|
||||
this.totalAmount = 0;
|
||||
|
||||
// Always calculate the total amount based on the cart content
|
||||
for (var key in this.content) {
|
||||
if (this.content.hasOwnProperty(key) && typeof this.content[key] != 'undefined') {
|
||||
var price = this.toCents(this.content[key].price.value);
|
||||
this.totalAmount += (this.content[key].count * price);
|
||||
}
|
||||
}
|
||||
|
||||
this.totalAmount += this.toCents(this.tip);
|
||||
|
||||
return this.fromCents(this.totalAmount);
|
||||
}
|
||||
|
||||
Cart.prototype.updateTotal = function() {
|
||||
$('#js-cart-total').text(this.formatCurrency(this.getTotal()));
|
||||
}
|
||||
|
||||
Cart.prototype.updateAmount = function() {
|
||||
$('#js-cart-amount').val(this.getTotal());
|
||||
}
|
||||
|
||||
Cart.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, '>')
|
||||
;
|
||||
}
|
||||
|
||||
Cart.prototype.listItems = function() {
|
||||
var $table = $('#js-cart-list').find('tbody'),
|
||||
self = this,
|
||||
list = []
|
||||
tableTemplate = '';
|
||||
|
||||
if (this.content.length > 0) {
|
||||
// Prepare the list of items in the cart
|
||||
for (var key in this.content) {
|
||||
var item = this.content[key],
|
||||
id = this.escape(item.id),
|
||||
title = this.escape(item.title),
|
||||
image = this.escape(item.image),
|
||||
count = this.escape(item.count),
|
||||
price = this.escape(item.price.formatted),
|
||||
currencySymbol = this.escape(srvModel.currencySymbol),
|
||||
step = this.escape(srvModel.step),
|
||||
customTipText = this.escape(srvModel.customTipText);
|
||||
|
||||
tableTemplate = '<tr data-id="' + id + '">' +
|
||||
(image !== null ? '<td class="align-middle pr-0" width="60"><img src="' + image + '" width="100%"></td>' : '') +
|
||||
'<td class="align-middle pr-0"><b>' + title + '</b></td>' +
|
||||
'<td class="align-middle pr-0" align="right"><div class="input-group">' +
|
||||
' <input class="js-cart-item-count form-control form-control-sm pull-left" type="number" min="0" step="1" name="count" placeholder="Qty" value="' + count + '" data-prev="' + count + '">' +
|
||||
' <div class="input-group-append"><a class="js-cart-item-remove btn btn-danger btn-sm" href="#"><i class="fa fa-remove"></i></a></div>' +
|
||||
'</div></td>' +
|
||||
'<td class="align-middle" align="right">' + price + '</td>' +
|
||||
'</tr>';
|
||||
list.push($(tableTemplate));
|
||||
}
|
||||
|
||||
tableTemplate = '<tr><td colspan="4"><div class="row"><div class="col-sm-8 py-2">' + customTipText + '</div><div class="col-sm-4">' +
|
||||
'<div class="input-group">' +
|
||||
'<div class="input-group-prepend">' +
|
||||
'<span class="input-group-text">' + currencySymbol + '</span>' +
|
||||
'</div>' +
|
||||
'<input class="js-cart-tip form-control" type="number" min="0" step="' + step + '" name="tip" placeholder="Amount">' +
|
||||
'</div>' +
|
||||
'</div></div></td></tr>';
|
||||
list.push($(tableTemplate));
|
||||
|
||||
tableTemplate = '<tr class="bg-light h4"><td colspan="2">Total</td><td colspan="2" align="right"><span id="js-cart-total">' + this.formatCurrency(this.getTotal()) + '</span></td></tr>';
|
||||
list.push($(tableTemplate));
|
||||
|
||||
// Add the list to DOM
|
||||
$table.html(list);
|
||||
|
||||
// Update the cart when number of items is changed
|
||||
$('.js-cart-item-count').off().on('input', function(event){
|
||||
var _this = this,
|
||||
id = $(this).closest('tr').data('id'),
|
||||
count = parseInt($(this).val()),
|
||||
prevCount = parseInt($(this).data('prev')),
|
||||
increased = count > prevCount;
|
||||
|
||||
// User hasn't inputed any number so stop here
|
||||
if (isNaN(count)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$(this).data('prev', count);
|
||||
|
||||
var item = self.content.filter(function(obj){
|
||||
return obj.id === id
|
||||
});
|
||||
|
||||
// Must be in the loop because user may change the count manually by more than 1
|
||||
for (var i = 0; i < Math.abs(count - prevCount); i++) {
|
||||
if (increased) {
|
||||
self.addItem({
|
||||
id: id,
|
||||
title: item.title,
|
||||
price: item.price,
|
||||
image: typeof item.image != 'underfined' ? item.image : null
|
||||
});
|
||||
} else {
|
||||
self.decrementItem(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Remove item from the cart
|
||||
$('.js-cart-item-remove').off().on('click', function(event){
|
||||
event.preventDefault();
|
||||
|
||||
var id = $(this).closest('tr').data('id');
|
||||
|
||||
self.removeItemAll(id);
|
||||
});
|
||||
|
||||
// Change total when tip is changed
|
||||
$('.js-cart-tip').off().on('input', function(event){
|
||||
self.setTip($(this).val());
|
||||
self.updateTotal();
|
||||
self.updateAmount();
|
||||
});
|
||||
} else { // No item in the cart
|
||||
self.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
Cart.prototype.emptyList = function() {
|
||||
var $table = $('#js-cart-list').find('tbody');
|
||||
|
||||
$table.html('<tr><td colspan="4">The cart is empty.</td></tr>');
|
||||
}
|
||||
|
||||
/* Get the currency symbol from an existing amount and use it with the new amount*/
|
||||
Cart.prototype.formatCurrency = function(amount, example) {
|
||||
var regex = /([0-9.]+)/gm;
|
||||
|
||||
// Get the first item's formated price
|
||||
if (typeof example == 'undefined' && typeof srvModel != 'undefined') {
|
||||
example = srvModel.items[0].price.formatted;
|
||||
}
|
||||
|
||||
return example.replace(regex, amount.toFixed(2));
|
||||
}
|
||||
|
||||
Cart.prototype.toCents = function(num) {
|
||||
return num * 100;
|
||||
}
|
||||
|
||||
Cart.prototype.fromCents = function(num) {
|
||||
return num / 100;
|
||||
}
|
||||
|
||||
Cart.prototype.saveLocalStorage = function() {
|
||||
localStorage.setItem('cart', JSON.stringify(this.content));
|
||||
}
|
||||
|
||||
Cart.prototype.loadLocalStorage = function() {
|
||||
this.content = $.parseJSON(localStorage.getItem('cart')) || [];
|
||||
|
||||
// Get number of cart items
|
||||
for (var key in this.content) {
|
||||
if (this.content.hasOwnProperty(key) && typeof this.content[key] != 'undefined' && this.content[key] != null) {
|
||||
this.items += this.content[key].count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cart.prototype.destroy = function() {
|
||||
localStorage.removeItem('cart');
|
||||
this.content = [];
|
||||
this.items = 0;
|
||||
this.totalAmount = 0;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATE FROM TRANSIFEX, IF YOU WISH TO ADD A NEW LANGUAGE COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"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": "cs-CZ",
|
||||
"currentLanguage": "Česky",
|
||||
"lang": "Jazyk",
|
||||
@ -31,7 +31,7 @@
|
||||
"Invoice expiring soon...": "Faktura brzy vyprší...",
|
||||
"Invoice expired": "Faktura vypršela",
|
||||
"What happened?": "Co se stalo?",
|
||||
"InvoiceExpired_Body_1": "Tato faktura již vypršela. Faktura je platná pouze {{maxTimeMinutes}} minut. \nMůžete se vrátit do {{storeName}}, pokud chcete svojí objednávku založit znovu.",
|
||||
"InvoiceExpired_Body_1": "Tato faktura již vypršela. Faktura je platná pouze {{maxTimeMinutes}} minut. \nMůžete se vrátit do {{storeName}}, pokud chcete svojí objednávku založit znovu.",
|
||||
"InvoiceExpired_Body_2": "Pokud jste se pokoušeli poslat platbu, nebyla zatím zaznamenána v Bitcoinové síti. Zatím jsme neobdrželi vaše prostředky.",
|
||||
"InvoiceExpired_Body_3": "Pokud nebude transakce přijata Bitcoinovou sítí, vaše prostředky bude opět použitelné ve vaší peněžence. V závislosti na vaší peněžence toto může trvat 48-72 hodin.",
|
||||
"Invoice ID": "ID Faktury",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATE FROM TRANSIFEX, IF YOU WISH TO ADD A NEW LANGUAGE COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"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": "de-DE",
|
||||
"currentLanguage": "Deutsch",
|
||||
"lang": "Sprache",
|
||||
|
@ -31,7 +31,7 @@
|
||||
"Invoice expiring soon...": "Invoice expiring soon...",
|
||||
"Invoice expired": "Invoice expired",
|
||||
"What happened?": "What happened?",
|
||||
"InvoiceExpired_Body_1": "This invoice has expired. An invoice is only valid for {{maxTimeMinutes}} minutes. \\nYou can return to {{storeName}} if you would like to submit your payment again.",
|
||||
"InvoiceExpired_Body_1": "This invoice has expired. An invoice is only valid for {{maxTimeMinutes}} minutes. \nYou can return to {{storeName}} if you would like to submit your payment again.",
|
||||
"InvoiceExpired_Body_2": "If you tried to send a payment, it has not yet been accepted by the network. We have not yet received your funds.",
|
||||
"InvoiceExpired_Body_3": "If the transaction is not accepted by the network, the funds will be spendable again in your wallet. Depending on your wallet, this may take 48-72 hours.",
|
||||
"Invoice ID": "Invoice ID",
|
||||
@ -44,4 +44,4 @@
|
||||
"Node Info": "Node Info",
|
||||
"txCount": "{{count}} transaction",
|
||||
"txCount_plural": "{{count}} transactions"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATE FROM TRANSIFEX, IF YOU WISH TO ADD A NEW LANGUAGE COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"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": "es-ES",
|
||||
"currentLanguage": "Español",
|
||||
"lang": "Lenguaje",
|
||||
@ -31,7 +31,7 @@
|
||||
"Invoice expiring soon...": "La factura expira pronto...",
|
||||
"Invoice expired": "La factura expiró",
|
||||
"What happened?": "¿Qué sucedió?",
|
||||
"InvoiceExpired_Body_1": "Esta factura ha expirado. Una factura solo es válida por {{maxTimeMinutes}} minutos. Puedes regresar a {{storeName}} si deseas volver a enviar tu pago.",
|
||||
"InvoiceExpired_Body_1": "Esta factura ha expirado. Una factura solo es válida por {{maxTimeMinutes}} minutos. \nPuedes regresar a {{storeName}} si deseas volver a enviar tu pago.",
|
||||
"InvoiceExpired_Body_2": "Si intentaste enviar un pago, aún no ha sido aceptado por la red de Bitcoin. Todavía no hemos recibido tus fondos.",
|
||||
"InvoiceExpired_Body_3": "Si la transacción no es aceptada por la red de Bitcoin, los fondos se podrán gastar nuevamente en tu billetera. Dependiendo de tu billetera, esto puede tomar 48-72 horas.",
|
||||
"Invoice ID": "ID de la factura",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATE FROM TRANSIFEX, IF YOU WISH TO ADD A NEW LANGUAGE COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"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": "fr-FR",
|
||||
"currentLanguage": "Français",
|
||||
"lang": "Langue",
|
||||
@ -31,7 +31,7 @@
|
||||
"Invoice expiring soon...": "La facture va bientôt expirer...",
|
||||
"Invoice expired": "Facture expirée",
|
||||
"What happened?": "Que s'est-il passé ?",
|
||||
"InvoiceExpired_Body_1": "La facture a expiré. Une facture est valide seulement {{maxTimeMinutes}} minutes. \\nSi vous voulez soumettre à nouveau votre paiement, vous pouvez revenir sur {{storeName}} .",
|
||||
"InvoiceExpired_Body_1": "La facture a expiré. Une facture est valide seulement {{maxTimeMinutes}} minutes. \nSi vous voulez soumettre à nouveau votre paiement, vous pouvez revenir sur {{storeName}} .",
|
||||
"InvoiceExpired_Body_2": "Si vous avez envoyé un paiement, ce dernier n'a pas encore été inscrit dans la blockchain. Nous n'avons pas reçu vos fonds.",
|
||||
"InvoiceExpired_Body_3": "Si votre transaction n'est pas inscrite dans la blockchain, vos fonds reviendront dans votre portefeuille. Selon votre portefeuille, cela peut prendre entre 48 et 72 heures.",
|
||||
"Invoice ID": "Numéro de facture",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATE FROM TRANSIFEX, IF YOU WISH TO ADD A NEW LANGUAGE COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"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": "hr-HR",
|
||||
"currentLanguage": "Croatian",
|
||||
"lang": "Croatian",
|
||||
@ -31,7 +31,7 @@
|
||||
"Invoice expiring soon...": "Račun uskoro ističe...",
|
||||
"Invoice expired": "Račun je istekao",
|
||||
"What happened?": "Što se dogodilo",
|
||||
"InvoiceExpired_Body_1": "Račun je istekao i nije više valjan. Račun vrijedi samo {{maxTimeMinutes}} minuta. \nMožete se vratiti na {{storeName}}, gdje možete ponovo inicirati plaćanje.",
|
||||
"InvoiceExpired_Body_1": "Račun je istekao i nije više valjan. Račun vrijedi samo {{maxTimeMinutes}} minuta. \nMožete se vratiti na {{storeName}}, gdje možete ponovo inicirati plaćanje.",
|
||||
"InvoiceExpired_Body_2": "Ako ste pokušali poslati uplatu, ista nije registrirana na Blockchainu. Nismo još zaprimili Vašu uplatu.",
|
||||
"InvoiceExpired_Body_3": "Ako poslana sredstva na budu potvrđena na Blockchainu, sredstva će biti ponovo dostupna u Vašem novčaniku.",
|
||||
"Invoice ID": "Broj računa",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATE FROM TRANSIFEX, IF YOU WISH TO ADD A NEW LANGUAGE COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"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": "is-IS",
|
||||
"currentLanguage": "Íslenska",
|
||||
"lang": "Tungumál",
|
||||
@ -31,7 +31,7 @@
|
||||
"Invoice expiring soon...": "Reikningurinn rennur út fljótlega...",
|
||||
"Invoice expired": "Reikningurinn er útrunnin",
|
||||
"What happened?": "Hvað gerðist?",
|
||||
"InvoiceExpired_Body_1": "Þessi reikningur er útrunnin. Reikningurinn er aðeins gildur í {{maxTimeMinutes}} mínútur. \nÞú getur farið aftur á {{storeName}} ef þú vilt reyna aftur.",
|
||||
"InvoiceExpired_Body_1": "Þessi reikningur er útrunnin. Reikningurinn er aðeins gildur í {{maxTimeMinutes}} mínútur. \nÞú getur farið aftur á {{storeName}} ef þú vilt reyna aftur.",
|
||||
"InvoiceExpired_Body_2": "Ef þú reyndir að senda greiðslu, þá hefur hún ekki verið samþykkt.",
|
||||
"InvoiceExpired_Body_3": "Ef viðskiptin eru ekki samþykkt af netinu verða fjármunirnir aðgengilegar aftur í veskinu þínu. Það fer eftir veskinu þínu og getur tekið 48-72 klukkustundir.",
|
||||
"Invoice ID": "Innheimtu ID",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATE FROM TRANSIFEX, IF YOU WISH TO ADD A NEW LANGUAGE COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"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": "it-IT",
|
||||
"currentLanguage": "Italiano",
|
||||
"lang": "Lingua",
|
||||
@ -31,7 +31,7 @@
|
||||
"Invoice expiring soon...": "Fattura in scadenza a breve...",
|
||||
"Invoice expired": "Fattura scaduta",
|
||||
"What happened?": "Cosa è successo?",
|
||||
"InvoiceExpired_Body_1": "Questa fattura è scaduta. Una fattura è valida solo per {{maxTime minuti}} minuti. \nPuoi tornare a {{store name}} se desideri inviare nuovamente il pagamento.",
|
||||
"InvoiceExpired_Body_1": "Questa fattura è scaduta. Una fattura è valida solo per {{maxTime minuti}} minuti. \nPuoi tornare a {{store name}} se desideri inviare nuovamente il pagamento.",
|
||||
"InvoiceExpired_Body_2": "Se hai provato a inviare un pagamento, non è ancora stato accettato dalla rete. Non abbiamo ancora ricevuto i tuoi fondi.",
|
||||
"InvoiceExpired_Body_3": "Se la transazione non viene accettata dalla rete, i fondi saranno nuovamente spendibili nel tuo portafoglio. A seconda del portafoglio, potrebbero essere necessarie 48-72 ore.",
|
||||
"Invoice ID": "Numero della Fattura",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATE FROM TRANSIFEX, IF YOU WISH TO ADD A NEW LANGUAGE COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"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": "ja-JP",
|
||||
"currentLanguage": "日本語",
|
||||
"lang": "Language",
|
||||
@ -31,7 +31,7 @@
|
||||
"Invoice expiring soon...": "お支払いの期限が迫っています...",
|
||||
"Invoice expired": "お支払いの期限が切れました",
|
||||
"What happened?": "え!?ナニコレ!?",
|
||||
"InvoiceExpired_Body_1": "当件のお支払いの有効期限が過ぎてしまいました。最大 {{maxTimeMinutes}} 分以内に支払うことが義務付けられています。 \nまだお支払いのご希望の場合 {{storeName}} に一旦戻っていただき、もう一度お支払いの手続きを最初からやり直してみてください。",
|
||||
"InvoiceExpired_Body_1": "当件のお支払いの有効期限が過ぎてしまいました。最大 {{maxTimeMinutes}} 分以内に支払うことが義務付けられています。 \nまだお支払いのご希望の場合 {{storeName}} に一旦戻っていただき、もう一度お支払いの手続きを最初からやり直してみてください。",
|
||||
"InvoiceExpired_Body_2": "送金手続きを完了したつもりでも、ネットワークにて取り込まれて処理されるまでは決済となりません。現時点ではまだ着金しておりません。",
|
||||
"InvoiceExpired_Body_3": "ネットワークにて取り込まれなかった送金はいずれ送金元のウォレットに戻りますが、ウォレットソフトによっては2〜3日かかる場合もございますのでご了承ください。",
|
||||
"Invoice ID": "お支払い ID",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATE FROM TRANSIFEX, IF YOU WISH TO ADD A NEW LANGUAGE COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"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": "kk-KZ",
|
||||
"currentLanguage": "Қазақша",
|
||||
"lang": "Тіл",
|
||||
@ -31,7 +31,7 @@
|
||||
"Invoice expiring soon...": "Шот-фактура жақын арада аяқталады…",
|
||||
"Invoice expired": "Шот-фактураның мерзімі аяқталды",
|
||||
"What happened?": "Не жағдай болды?",
|
||||
"InvoiceExpired_Body_1": "Бұл шот-фактураның мерзімі аяқталды. Шот-фактура {{maxTimeMinutes}} минуттарға жарамды. \nТөлеміңізді қайтадан жібергіңіз келсе {{storeName}} ға оралуға болады.",
|
||||
"InvoiceExpired_Body_1": "Бұл шот-фактураның мерзімі аяқталды. Шот-фактура {{maxTimeMinutes}} минуттарға жарамды. \nТөлеміңізді қайтадан жібергіңіз келсе {{storeName}} ға оралуға болады.",
|
||||
"InvoiceExpired_Body_2": "Егер сіз төлемді жіберуге тырыссқан болсаңыз, ол әлі желімен қабылданған жоқ. Біз сіздің қаражатыңызды әлі алған жоқпыз.",
|
||||
"InvoiceExpired_Body_3": "Егер транзакция желі арқылы қабылданбаса, қаражат әмияныңызға қайтадан қайтарылады. Бұл сіздің әмияныңызға байланысты 48-72 сағат алуы мүмкін.",
|
||||
"Invoice ID": "Шот анықтамасы",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATE FROM TRANSIFEX, IF YOU WISH TO ADD A NEW LANGUAGE COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"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": "nl-NL",
|
||||
"currentLanguage": "Dutch",
|
||||
"lang": "Taal",
|
||||
@ -31,7 +31,7 @@
|
||||
"Invoice expiring soon...": "De factuur verloopt binnenkort...",
|
||||
"Invoice expired": "Vervallen factuur",
|
||||
"What happened?": "Wat gebeurde er?",
|
||||
"InvoiceExpired_Body_1": "De factuur is vervallen. Een factuur is alleen geldig voor {{maxTimeMinutes}} minuten. \nJe kan terug komen naar {{storeName}} als je de betaling opnieuw wilt proberen",
|
||||
"InvoiceExpired_Body_1": "De factuur is vervallen. Een factuur is alleen geldig voor {{maxTimeMinutes}} minuten. \nJe kan terug komen naar {{storeName}} als je de betaling opnieuw wilt proberen",
|
||||
"InvoiceExpired_Body_2": "Als je een betaling uitvoerde, dan werd dit nog niet bevestigd door het netwerk. We hebben je fondsen nog niet ontvangen.",
|
||||
"InvoiceExpired_Body_3": "Als de transactie niet geaccepteerd werd door het netwerk, zullen je fondsen terug in wallet verschijnen. Afhankelijk van je wallet, kan dit 48 to 72 uren duren.",
|
||||
"Invoice ID": "Factuurnummer",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATE FROM TRANSIFEX, IF YOU WISH TO ADD A NEW LANGUAGE COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"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": "np-NP",
|
||||
"currentLanguage": "नेपाली",
|
||||
"lang": "भाषा",
|
||||
@ -31,7 +31,7 @@
|
||||
"Invoice expiring soon...": "चलानीको म्याद सकियो",
|
||||
"Invoice expired": "इनभ्वाइस समाप्त भयो",
|
||||
"What happened?": "के भयो",
|
||||
"InvoiceExpired_Body_1": "यो चलानी समाप्त भएको छ। इनभ्वाइस {{maxTimeMinutes}} मिनेटको लागि मात्र वैध छ। \nतपाइँ आफ्नो भुक्तानी पुन: पेश गर्न चाहानुहुन्छ यदि तपाइँ {{ storeName }} भुक्तानी पठाउन प्रयास गर्नुभयो भने।",
|
||||
"InvoiceExpired_Body_1": "यो चलानी समाप्त भएको छ। इनभ्वाइस {{maxTimeMinutes}} मिनेटको लागि मात्र वैध छ। \nतपाइँ आफ्नो भुक्तानी पुन: पेश गर्न चाहानुहुन्छ यदि तपाइँ {{ storeName }} भुक्तानी पठाउन प्रयास गर्नुभयो भने।",
|
||||
"InvoiceExpired_Body_2": "भने यो अझै नेटवर्कद्वारा स्वीकार गरिएको छैन। हामीले अझै सम्म तपाईंको रकम प्राप्त गरेका छैनौ।",
|
||||
"InvoiceExpired_Body_3": "यदि लेनदेन नेटवर्क द्वारा स्वीकार गरेन भने, रकम तपाईंको वालेटमा फेरि खर्चयोग्य हुनेछ। तपाईंको वालेटको आधारमा, यो 48-72 घण्टा लाग्न सक्छ",
|
||||
"Invoice ID": "चलानी आईडी",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATE FROM TRANSIFEX, IF YOU WISH TO ADD A NEW LANGUAGE COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"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": "pt-BR",
|
||||
"currentLanguage": "Portuguese (Brazil)",
|
||||
"lang": "Idioma",
|
||||
@ -31,7 +31,7 @@
|
||||
"Invoice expiring soon...": "A fatura está expirando...",
|
||||
"Invoice expired": "Fatura expirada",
|
||||
"What happened?": "O que aconteceu?",
|
||||
"InvoiceExpired_Body_1": "Essa fatura expirou. Uma fatura é válida por apenas {{maxTimeMinutes}} minutos. \nVocê pode voltar para {{storeName}} se quiser enviar o seu pagamento novamente.",
|
||||
"InvoiceExpired_Body_1": "Essa fatura expirou. Uma fatura é válida por apenas {{maxTimeMinutes}} minutos. \nVocê pode voltar para {{storeName}} se quiser enviar o seu pagamento novamente.",
|
||||
"InvoiceExpired_Body_2": "Se você tentou enviar um pagamento, ele ainda não foi aceito pela rede Bitcoin. Nós ainda não recebemos o valor enviado.",
|
||||
"InvoiceExpired_Body_3": "Se a transação não for aceita pela rede Bitcoin, o valor voltará para sua carteira. Dependendo da sua carteira, isso pode demorar de 48 a 72 horas.",
|
||||
"Invoice ID": "Nº da Fatura",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATE FROM TRANSIFEX, IF YOU WISH TO ADD A NEW LANGUAGE COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"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": "pt-PT",
|
||||
"currentLanguage": "Portuguese",
|
||||
"lang": "Idioma",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATE FROM TRANSIFEX, IF YOU WISH TO ADD A NEW LANGUAGE COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"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": "ru-RU",
|
||||
"currentLanguage": "русский",
|
||||
"lang": "язык",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATE FROM TRANSIFEX, IF YOU WISH TO ADD A NEW LANGUAGE COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"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": "sk-SK",
|
||||
"currentLanguage": "Slovenčina",
|
||||
"lang": "Jazyk",
|
||||
@ -31,7 +31,7 @@
|
||||
"Invoice expiring soon...": "Faktúra čoskoro vyprší...",
|
||||
"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. Môž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_3": "Ak transakcia nie je sieťou prijatá, finančné prostriedky sa budú dať znova použiť vo Vašej peňaženke. V závislosti od peňaženky to môže trvať 48-72 hodín.",
|
||||
"Invoice ID": "ID faktúry",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATE FROM TRANSIFEX, IF YOU WISH TO ADD A NEW LANGUAGE COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"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": "uk-UA",
|
||||
"currentLanguage": "Українська",
|
||||
"lang": "Мова",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATE FROM TRANSIFEX, IF YOU WISH TO ADD A NEW LANGUAGE COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"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": "vi-VN",
|
||||
"currentLanguage": "Tiếng Việt",
|
||||
"lang": "Ngôn ngữ",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATE FROM TRANSIFEX, IF YOU WISH TO ADD A NEW LANGUAGE COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"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": "zh-SP",
|
||||
"currentLanguage": "中文(简体)",
|
||||
"lang": "语言",
|
||||
@ -31,7 +31,7 @@
|
||||
"Invoice expiring soon...": "订单即将过期...",
|
||||
"Invoice expired": "订单已过期 ",
|
||||
"What happened?": "发生了什么?",
|
||||
"InvoiceExpired_Body_1": "您的订单已过期。一个订单只在{{maxTimeMinutes}}分钟内有效。 \n如果您想要再次支付,可返回{{storeName}}。",
|
||||
"InvoiceExpired_Body_1": "您的订单已过期。一个订单只在{{maxTimeMinutes}}分钟内有效。 \n如果您想要再次支付,可返回{{storeName}}。",
|
||||
"InvoiceExpired_Body_2": "如果您已支付,但付款没有被网络确认接收。这说明我方目前还未收到您的支付款。",
|
||||
"InvoiceExpired_Body_3": "如果您的支付最终被网络拒绝,支付款将被返回您的钱包。根据您使用的钱包不同,可能需要48-72小时。",
|
||||
"Invoice ID": "订单号",
|
||||
|
@ -1,7 +1,6 @@
|
||||
FROM microsoft/dotnet:2.1.500-sdk-alpine3.7 AS builder
|
||||
WORKDIR /source
|
||||
COPY BTCPayServer/BTCPayServer.csproj BTCPayServer.csproj
|
||||
# Cache some dependencies
|
||||
RUN dotnet restore
|
||||
COPY BTCPayServer/. .
|
||||
RUN dotnet publish --output /app/ --configuration Release
|
21
Dockerfile.linuxarm32v7
Normal file
21
Dockerfile.linuxarm32v7
Normal file
@ -0,0 +1,21 @@
|
||||
# This is a manifest image, will pull the image with the same arch as the builder machine
|
||||
FROM microsoft/dotnet:2.1.500-sdk AS builder
|
||||
WORKDIR /source
|
||||
COPY BTCPayServer/BTCPayServer.csproj BTCPayServer.csproj
|
||||
RUN dotnet restore
|
||||
COPY BTCPayServer/. .
|
||||
RUN dotnet publish --output /app/ --configuration Release
|
||||
|
||||
# Force the builder machine to take make an arm runtime image. This is fine as long as the builder does not run any program
|
||||
FROM microsoft/dotnet:2.1.6-aspnetcore-runtime-stretch-slim-arm32v7
|
||||
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
ENV LANG en_US.UTF-8
|
||||
|
||||
WORKDIR /datadir
|
||||
WORKDIR /app
|
||||
ENV BTCPAY_DATADIR=/datadir
|
||||
VOLUME /datadir
|
||||
|
||||
COPY --from=builder "/app" .
|
||||
ENTRYPOINT ["dotnet", "BTCPayServer.dll"]
|
@ -7,6 +7,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BTCPayServer", "BTCPayServe
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BTCPayServer.Tests", "BTCPayServer.Tests\BTCPayServer.Tests.csproj", "{B373F439-6E75-4A94-985D-10A0C7C500D0}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Misc", "Misc", "{29290EC7-00E6-4C4B-96D9-4D7E9611DF28}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.circleci\config.yml = .circleci\config.yml
|
||||
Dockerfile.linuxamd64 = Dockerfile.linuxamd64
|
||||
Dockerfile.linuxarm32v7 = Dockerfile.linuxarm32v7
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
Reference in New Issue
Block a user