Compare commits
89 Commits
Author | SHA1 | Date | |
---|---|---|---|
41e88c07fe | |||
67c5027b16 | |||
a341d4f800 | |||
f0ff47af8d | |||
991826b686 | |||
22d59a1ed7 | |||
475ea68696 | |||
864e84706a | |||
9c93e76eeb | |||
94be2b46d5 | |||
4b4d0d2d19 | |||
0d06cf63b7 | |||
7b24c02d51 | |||
e89e8226e4 | |||
a533a96598 | |||
27321c0919 | |||
058472d325 | |||
b5c9a03052 | |||
07dad3affa | |||
8afc103ae7 | |||
591d7b4b80 | |||
2162afc78e | |||
25e226d219 | |||
8472bfe90d | |||
93645b2fbe | |||
d53c987f2e | |||
682693a9f0 | |||
e836faf792 | |||
6e27233be8 | |||
9209984a2f | |||
1477630c78 | |||
ab670080c7 | |||
8198f98376 | |||
65b4697229 | |||
e75a1a8b70 | |||
5a958da84d | |||
cad602ad14 | |||
1f14bd6188 | |||
156f52b76f | |||
d674b8ac71 | |||
861150971f | |||
a653421514 | |||
8f234a02cb | |||
92ecf99427 | |||
705dbf12d7 | |||
fe11b11c13 | |||
f2a43ad1f3 | |||
cbbe5cfb25 | |||
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 |
@ -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,10 +43,12 @@ 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;
|
||||
using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel;
|
||||
using NBitpayClient.Extensions;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -349,57 +351,6 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public void CanPayUsingBIP70()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
Assert.True(user.BitPay.TestAccess(Facade.Merchant));
|
||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Buyer = new Buyer() { email = "test@fwf.com" },
|
||||
Price = 5000.0m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
//RedirectURL = redirect + "redirect",
|
||||
//NotificationURL = CallbackUri + "/notification",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
Assert.False(invoice.Refundable);
|
||||
|
||||
var url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP72);
|
||||
var request = url.GetPaymentRequest();
|
||||
var payment = request.CreatePayment();
|
||||
|
||||
Transaction tx = new Transaction();
|
||||
tx.Outputs.AddRange(request.Details.Outputs.Select(o => new TxOut(o.Amount, o.Script)));
|
||||
var cashCow = tester.ExplorerNode;
|
||||
tx = cashCow.FundRawTransaction(tx).Transaction;
|
||||
tx = cashCow.SignRawTransaction(tx);
|
||||
|
||||
payment.Transactions.Add(tx);
|
||||
|
||||
payment.RefundTo.Add(new PaymentOutput(Money.Coins(1.0m), new Key().ScriptPubKey));
|
||||
var ack = payment.SubmitPayment();
|
||||
Assert.NotNull(ack);
|
||||
|
||||
Eventually(() =>
|
||||
{
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("paid", localInvoice.Status);
|
||||
Assert.True(localInvoice.Refundable);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanSetLightningServer()
|
||||
@ -595,7 +546,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 +762,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);
|
||||
@ -933,6 +884,17 @@ namespace BTCPayServer.Tests
|
||||
var result = client.SendAsync(message).GetAwaiter().GetResult();
|
||||
result.EnsureSuccessStatusCode();
|
||||
/////////////////////
|
||||
|
||||
// Have error 403 with bad signature
|
||||
client = new HttpClient();
|
||||
HttpRequestMessage mess = new HttpRequestMessage(HttpMethod.Get, tester.PayTester.ServerUri.AbsoluteUri + "tokens");
|
||||
mess.Content = new StringContent(string.Empty, Encoding.UTF8, "application/json");
|
||||
mess.Headers.Add("x-signature", "3045022100caa123193afc22ef93d9c6b358debce6897c09dd9869fe6fe029c9cb43623fac022000b90c65c50ba8bbbc6ebee8878abe5659e17b9f2e1b27d95eda4423da5608fe");
|
||||
mess.Headers.Add("x-identity", "04b4d82095947262dd70f94c0a0e005ec3916e3f5f2181c176b8b22a52db22a8c436c4703f43a9e8884104854a11e1eb30df8fdf116e283807a1f1b8fe4c182b99");
|
||||
mess.Method = HttpMethod.Get;
|
||||
result = client.SendAsync(mess).GetAwaiter().GetResult();
|
||||
Assert.Equal(System.Net.HttpStatusCode.Unauthorized, result.StatusCode);
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
@ -1326,6 +1288,15 @@ namespace BTCPayServer.Tests
|
||||
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]
|
||||
@ -1451,12 +1422,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);
|
||||
@ -1464,19 +1442,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()
|
||||
@ -1578,7 +1762,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(IsMapped(invoice, ctx));
|
||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||
|
||||
var invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult();
|
||||
var invoiceEntity = repo.GetInvoice(invoice.Id, true).GetAwaiter().GetResult();
|
||||
Assert.Single(invoiceEntity.HistoricalAddresses);
|
||||
Assert.Null(invoiceEntity.HistoricalAddresses[0].UnAssigned);
|
||||
|
||||
@ -1596,7 +1780,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(IsMapped(invoice, ctx));
|
||||
Assert.True(IsMapped(localInvoice, ctx));
|
||||
|
||||
invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult();
|
||||
invoiceEntity = repo.GetInvoice(invoice.Id, true).GetAwaiter().GetResult();
|
||||
var historical1 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.GetAddress() == invoice.BitcoinAddress);
|
||||
Assert.NotNull(historical1.UnAssigned);
|
||||
var historical2 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.GetAddress() == localInvoice.BitcoinAddress);
|
||||
@ -1788,12 +1972,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()
|
||||
|
@ -28,18 +28,21 @@ namespace BTCPayServer.Tests
|
||||
// 2. Run "dotnet user-secrets set TransifexAPIToken <youapitoken>"
|
||||
var client = new TransifexClient(GetTransifexAPIToken());
|
||||
var json = await client.GetTransifexAsync("https://api.transifex.com/organizations/btcpayserver/projects/btcpayserver/resources/enjson/");
|
||||
var langs = ((JObject)json["stats"]).Properties().Select(n => n.Name).ToArray();
|
||||
var langs = new[] { "en" }.Concat(((JObject)json["stats"]).Properties().Select(n => n.Name)).ToArray();
|
||||
|
||||
var langsDir = Path.Combine(Services.LanguageService.TryGetSolutionDirectoryInfo().FullName, "BTCPayServer", "wwwroot", "locales");
|
||||
|
||||
JObject sourceLang = null;
|
||||
Task.WaitAll(langs.Select(async l =>
|
||||
{
|
||||
if (l == "no")
|
||||
return;
|
||||
bool isSourceLang = l == "en";
|
||||
var j = await client.GetTransifexAsync($"https://www.transifex.com/api/2/project/btcpayserver/resource/enjson/translation/{l}/");
|
||||
if(!isSourceLang)
|
||||
{
|
||||
while (sourceLang == null)
|
||||
await Task.Delay(10);
|
||||
}
|
||||
var content = j["content"].Value<string>();
|
||||
if (l == "en_US")
|
||||
l = "en";
|
||||
if (l == "ne_NP")
|
||||
l = "np_NP";
|
||||
if (l == "zh_CN")
|
||||
@ -51,9 +54,24 @@ namespace BTCPayServer.Tests
|
||||
var langFile = Path.Combine(langsDir, langCode + ".json");
|
||||
var jobj = JObject.Parse(content);
|
||||
jobj["code"] = langCode;
|
||||
if ((string)jobj["currentLanguage"] == "English")
|
||||
return;
|
||||
|
||||
if ((string)jobj["currentLanguage"] == "English" && !isSourceLang)
|
||||
return; // Not translated
|
||||
if ((string)jobj["currentLanguage"] == "disable")
|
||||
return; // Not translated
|
||||
|
||||
jobj.AddFirst(new JProperty("NOTICE_WARN", "THIS CODE HAS BEEN AUTOMATICALLY GENERATED FROM TRANSIFEX, IF YOU WISH TO HELP TRANSLATION COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/"));
|
||||
if (isSourceLang)
|
||||
{
|
||||
sourceLang = jobj;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(jobj["InvoiceExpired_Body_3"].Value<string>() == sourceLang["InvoiceExpired_Body_3"].Value<string>())
|
||||
{
|
||||
jobj["InvoiceExpired_Body_3"] = string.Empty;
|
||||
}
|
||||
}
|
||||
content = jobj.ToString(Newtonsoft.Json.Formatting.Indented);
|
||||
File.WriteAllText(Path.Combine(langsDir, langFile), content);
|
||||
}).ToArray());
|
||||
|
@ -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.2
|
||||
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 "$@"
|
||||
|
@ -242,6 +242,8 @@ namespace BTCPayServer.Authentication
|
||||
using (var ctx = _Factory.CreateContext())
|
||||
{
|
||||
var token = await ctx.PairedSINData.FindAsync(tokenId);
|
||||
if (token == null)
|
||||
return null;
|
||||
return CreateTokenEntity(token);
|
||||
}
|
||||
}
|
||||
|
@ -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.12</Version>
|
||||
<Version>1.0.3.31</Version>
|
||||
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
@ -33,7 +33,7 @@
|
||||
<EmbeddedResource Include="Currencies.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.0.2" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.0.3" />
|
||||
<PackageReference Include="BuildBundlerMinifier" Version="2.7.385" />
|
||||
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.5.3" />
|
||||
<PackageReference Include="Hangfire" Version="1.6.20" />
|
||||
@ -46,10 +46,10 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NBitcoin" Version="4.1.1.71" />
|
||||
<PackageReference Include="NBitcoin" Version="4.1.1.73" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.30" />
|
||||
<PackageReference Include="DBreeze" Version="1.92.0" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="1.0.3.11" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="2.0.0.1" />
|
||||
<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" />
|
||||
@ -133,7 +133,10 @@
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\LndRestServices.cshtml">
|
||||
<Content Update="Views\Home\BitpayTranslator.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\SparkServices.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\SSHService.cshtml">
|
||||
@ -151,7 +154,7 @@
|
||||
<Content Update="Views\Public\PayButtonHandle.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\LndGrpcServices.cshtml">
|
||||
<Content Update="Views\Server\LndServices.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\Maintenance.cshtml">
|
||||
|
@ -137,10 +137,33 @@ namespace BTCPayServer.Configuration
|
||||
|
||||
externalLnd<ExternalLndGrpc>($"{net.CryptoCode}.external.lnd.grpc", "lnd-grpc");
|
||||
externalLnd<ExternalLndRest>($"{net.CryptoCode}.external.lnd.rest", "lnd-rest");
|
||||
|
||||
var spark = conf.GetOrDefault<string>($"{net.CryptoCode}.external.spark", string.Empty);
|
||||
if(spark.Length != 0)
|
||||
{
|
||||
if (!SparkConnectionString.TryParse(spark, out var connectionString))
|
||||
{
|
||||
throw new ConfigException($"Invalid setting {net.CryptoCode}.external.spark, " + Environment.NewLine +
|
||||
$"Valid example: 'server=https://btcpay.example.com/spark/btc/;cookiefile=/etc/clightning_bitcoin_spark/.cookie'");
|
||||
}
|
||||
ExternalServicesByCryptoCode.Add(net.CryptoCode, new ExternalSpark(connectionString));
|
||||
}
|
||||
}
|
||||
|
||||
Logs.Configuration.LogInformation("Supported chains: " + String.Join(',', supportedChains.ToArray()));
|
||||
|
||||
var services = conf.GetOrDefault<string>("externalservices", null);
|
||||
if(services != null)
|
||||
{
|
||||
foreach(var service in services.Split(new[] { ';', ',' })
|
||||
.Select(p => p.Split(':'))
|
||||
.Where(p => p.Length == 2)
|
||||
.Select(p => (Name: p[0], Link: p[1])))
|
||||
{
|
||||
ExternalServices.AddOrReplace(service.Name, service.Link);
|
||||
}
|
||||
}
|
||||
|
||||
PostgresConnectionString = conf.GetOrDefault<string>("postgres", null);
|
||||
MySQLConnectionString = conf.GetOrDefault<string>("mysql", null);
|
||||
BundleJsCss = conf.GetOrDefault<bool>("bundlejscss", true);
|
||||
@ -248,6 +271,8 @@ namespace BTCPayServer.Configuration
|
||||
|
||||
public string RootPath { get; set; }
|
||||
public Dictionary<string, LightningConnectionString> InternalLightningByCryptoCode { get; set; } = new Dictionary<string, LightningConnectionString>();
|
||||
public Dictionary<string, string> ExternalServices { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
public ExternalServices ExternalServicesByCryptoCode { get; set; } = new ExternalServices();
|
||||
|
||||
public BTCPayNetworkProvider NetworkProvider { get; set; }
|
||||
|
@ -33,6 +33,7 @@ namespace BTCPayServer.Configuration
|
||||
app.Option("--postgres", $"Connection string to a PostgreSQL database (default: SQLite)", CommandOptionType.SingleValue);
|
||||
app.Option("--mysql", $"Connection string to a MySQL database (default: SQLite)", CommandOptionType.SingleValue);
|
||||
app.Option("--externalurl", $"The expected external URL of this service, to use if BTCPay is behind a reverse proxy (default: empty, use the incoming HTTP request to figure out)", CommandOptionType.SingleValue);
|
||||
app.Option("--externalservices", $"Links added to external services inside Server Settings / Services under the format service1:path2;service2:path2.(default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--bundlejscss", $"Bundle JavaScript and CSS files for better performance (default: true)", CommandOptionType.SingleValue);
|
||||
app.Option("--rootpath", "The root path in the URL to access BTCPay (default: /)", CommandOptionType.SingleValue);
|
||||
app.Option("--sshconnection", "SSH server to manage BTCPay under the form user@server:port (default: root@externalhost or empty)", CommandOptionType.SingleValue);
|
||||
@ -49,6 +50,7 @@ namespace BTCPayServer.Configuration
|
||||
app.Option($"--{crypto}explorercookiefile", $"Path to the cookie file (default: {network.NBXplorerNetwork.DefaultSettings.DefaultCookieFile})", CommandOptionType.SingleValue);
|
||||
app.Option($"--{crypto}lightning", $"Easy configuration of lightning for the server administrator: Must be a UNIX socket of c-lightning (lightning-rpc) or URL to a charge server (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option($"--{crypto}externallndgrpc", $"The LND gRPC configuration BTCPay will expose to easily connect to the internal lnd wallet from Zap wallet (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option($"--{crypto}externalspark", $"The connection string to spark server (default: empty)", CommandOptionType.SingleValue);
|
||||
}
|
||||
return app;
|
||||
}
|
||||
|
@ -8,28 +8,23 @@ namespace BTCPayServer.Configuration.External
|
||||
{
|
||||
public abstract class ExternalLnd : ExternalService
|
||||
{
|
||||
public ExternalLnd(LightningConnectionString connectionString, LndTypes type)
|
||||
public ExternalLnd(LightningConnectionString connectionString, string type)
|
||||
{
|
||||
ConnectionString = connectionString;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public LndTypes Type { get; set; }
|
||||
public string Type { get; set; }
|
||||
public LightningConnectionString ConnectionString { get; set; }
|
||||
}
|
||||
|
||||
public enum LndTypes
|
||||
{
|
||||
gRPC, Rest
|
||||
}
|
||||
|
||||
public class ExternalLndGrpc : ExternalLnd
|
||||
{
|
||||
public ExternalLndGrpc(LightningConnectionString connectionString) : base(connectionString, LndTypes.gRPC) { }
|
||||
public ExternalLndGrpc(LightningConnectionString connectionString) : base(connectionString, "lnd-grpc") { }
|
||||
}
|
||||
|
||||
public class ExternalLndRest : ExternalLnd
|
||||
{
|
||||
public ExternalLndRest(LightningConnectionString connectionString) : base(connectionString, LndTypes.Rest) { }
|
||||
public ExternalLndRest(LightningConnectionString connectionString) : base(connectionString, "lnd-rest") { }
|
||||
}
|
||||
}
|
||||
|
19
BTCPayServer/Configuration/External/ExternalSpark.cs
vendored
Normal file
19
BTCPayServer/Configuration/External/ExternalSpark.cs
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Configuration.External
|
||||
{
|
||||
public class ExternalSpark : ExternalService
|
||||
{
|
||||
public SparkConnectionString ConnectionString { get; }
|
||||
|
||||
public ExternalSpark(SparkConnectionString connectionString)
|
||||
{
|
||||
if (connectionString == null)
|
||||
throw new ArgumentNullException(nameof(connectionString));
|
||||
ConnectionString = connectionString;
|
||||
}
|
||||
}
|
||||
}
|
46
BTCPayServer/Configuration/SparkConnectionString.cs
Normal file
46
BTCPayServer/Configuration/SparkConnectionString.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Configuration
|
||||
{
|
||||
public class SparkConnectionString
|
||||
{
|
||||
public Uri Server { get; private set; }
|
||||
public string CookeFile { get; private set; }
|
||||
|
||||
public static bool TryParse(string str, out SparkConnectionString result)
|
||||
{
|
||||
if (str == null)
|
||||
throw new ArgumentNullException(nameof(str));
|
||||
|
||||
result = null;
|
||||
var resultTemp = new SparkConnectionString();
|
||||
foreach(var kv in str.Split(';')
|
||||
.Select(part => part.Split('='))
|
||||
.Where(kv => kv.Length == 2))
|
||||
{
|
||||
switch (kv[0].ToLowerInvariant())
|
||||
{
|
||||
case "server":
|
||||
if (resultTemp.Server != null)
|
||||
return false;
|
||||
if (!Uri.IsWellFormedUriString(kv[1], UriKind.Absolute))
|
||||
return false;
|
||||
resultTemp.Server = new Uri(kv[1], UriKind.Absolute);
|
||||
break;
|
||||
case "cookiefile":
|
||||
if (resultTemp.CookeFile != null)
|
||||
return false;
|
||||
resultTemp.CookeFile = kv[1];
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
result = resultTemp;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -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";
|
||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Apps;
|
||||
@ -32,6 +33,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpGet]
|
||||
[Route("/apps/{appId}/pos")]
|
||||
[XFrameOptionsAttribute(null)]
|
||||
public async Task<IActionResult> ViewPointOfSale(string appId)
|
||||
{
|
||||
var app = await _AppsHelper.GetApp(appId, AppType.PointOfSale);
|
||||
@ -41,13 +43,28 @@ namespace BTCPayServer.Controllers
|
||||
var currency = _AppsHelper.GetCurrencyData(settings.Currency, false);
|
||||
double step = currency == null ? 1 : Math.Pow(10, -(currency.Divisibility));
|
||||
|
||||
var numberFormatInfo = _AppsHelper.Currencies.GetNumberFormatInfo(currency.Code) ?? _AppsHelper.Currencies.GetNumberFormatInfo("USD");
|
||||
return View(new ViewPointOfSaleViewModel()
|
||||
{
|
||||
Title = settings.Title,
|
||||
Step = step.ToString(CultureInfo.InvariantCulture),
|
||||
EnableShoppingCart = settings.EnableShoppingCart,
|
||||
ShowCustomAmount = settings.ShowCustomAmount,
|
||||
CurrencyCode = currency.Code,
|
||||
CurrencySymbol = currency.Symbol,
|
||||
Items = _AppsHelper.Parse(settings.Template, settings.Currency)
|
||||
CurrencyInfo = new ViewPointOfSaleViewModel.CurrencyInfoData()
|
||||
{
|
||||
CurrencySymbol = string.IsNullOrEmpty(currency.Symbol) ? currency.Code : currency.Symbol,
|
||||
Divisibility = currency.Divisibility,
|
||||
DecimalSeparator = numberFormatInfo.CurrencyDecimalSeparator,
|
||||
ThousandSeparator = numberFormatInfo.NumberGroupSeparator,
|
||||
Prefixed = new[] { 0, 2 }.Contains(numberFormatInfo.CurrencyPositivePattern)
|
||||
},
|
||||
Items = _AppsHelper.Parse(settings.Template, settings.Currency),
|
||||
ButtonText = settings.ButtonText,
|
||||
CustomButtonText = settings.CustomButtonText,
|
||||
CustomTipText = settings.CustomTipText,
|
||||
CustomCSSLink = settings.CustomCSSLink
|
||||
});
|
||||
}
|
||||
|
||||
@ -71,7 +88,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 +102,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 +126,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 });
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,7 +135,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
ApplicationDbContextFactory _ContextFactory;
|
||||
CurrencyNameTable _Currencies;
|
||||
|
||||
public CurrencyNameTable Currencies => _Currencies;
|
||||
public AppsHelper(ApplicationDbContextFactory contextFactory, CurrencyNameTable currencies)
|
||||
{
|
||||
_ContextFactory = contextFactory;
|
||||
|
@ -6,16 +6,85 @@ using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using BTCPayServer.Models;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitcoin.Payment;
|
||||
using System.Net.Http;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public class HomeController : Controller
|
||||
{
|
||||
public IHttpClientFactory HttpClientFactory { get; }
|
||||
|
||||
public HomeController(IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
HttpClientFactory = httpClientFactory;
|
||||
}
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View("Home");
|
||||
}
|
||||
|
||||
[Route("translate")]
|
||||
public IActionResult BitpayTranslator()
|
||||
{
|
||||
return View(new BitpayTranslatorViewModel());
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("translate")]
|
||||
public async Task<IActionResult> BitpayTranslator(BitpayTranslatorViewModel vm)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return View(vm);
|
||||
vm.BitpayLink = vm.BitpayLink ?? string.Empty;
|
||||
vm.BitpayLink = vm.BitpayLink.Trim();
|
||||
if (!vm.BitpayLink.StartsWith("bitcoin:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var invoiceId = vm.BitpayLink.Substring(vm.BitpayLink.LastIndexOf("=", StringComparison.OrdinalIgnoreCase) + 1);
|
||||
vm.BitpayLink = $"bitcoin:?r=https://bitpay.com/i/{invoiceId}";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
BitcoinUrlBuilder urlBuilder = new BitcoinUrlBuilder(vm.BitpayLink);
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (!urlBuilder.PaymentRequestUrl.DnsSafeHost.EndsWith("bitpay.com", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new Exception("This tool only work with bitpay");
|
||||
}
|
||||
|
||||
var client = HttpClientFactory.CreateClient();
|
||||
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, urlBuilder.PaymentRequestUrl);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/payment-request"));
|
||||
var result = await client.SendAsync(request);
|
||||
// {"network":"main","currency":"BTC","requiredFeeRate":29.834,"outputs":[{"amount":255900,"address":"1PgPo5d4swD6pKfCgoXtoW61zqTfX9H7tj"}],"time":"2018-12-03T14:39:47.162Z","expires":"2018-12-03T14:54:47.162Z","memo":"Payment request for BitPay invoice HHfG8cprRMzZG6MErCqbjv for merchant VULTR Holdings LLC","paymentUrl":"https://bitpay.com/i/HHfG8cprRMzZG6MErCqbjv","paymentId":"HHfG8cprRMzZG6MErCqbjv"}
|
||||
var str = await result.Content.ReadAsStringAsync();
|
||||
try
|
||||
{
|
||||
var jobj = JObject.Parse(str);
|
||||
vm.Address = ((JArray)jobj["outputs"])[0]["address"].Value<string>();
|
||||
var amount = Money.Satoshis(((JArray)jobj["outputs"])[0]["amount"].Value<long>());
|
||||
vm.Amount = amount.ToString();
|
||||
vm.BitcoinUri = $"bitcoin:{vm.Address}?amount={amount.ToString()}";
|
||||
}
|
||||
catch (JsonReaderException)
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.BitpayLink), $"Invalid or expired bitpay invoice");
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.BitpayLink), $"Error while requesting {ex.Message}");
|
||||
return View(vm);
|
||||
}
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
public IActionResult About()
|
||||
{
|
||||
ViewData["Message"] = "Your application description page.";
|
||||
|
@ -40,16 +40,18 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpGet]
|
||||
[Route("invoices/{id}")]
|
||||
[AllowAnonymous]
|
||||
public async Task<DataWrapper<InvoiceResponse>> GetInvoice(string id, string token)
|
||||
public async Task<DataWrapper<InvoiceResponse>> GetInvoice(string id)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, id);
|
||||
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
InvoiceId = id,
|
||||
StoreId = new[] { HttpContext.GetStoreData().Id }
|
||||
})).FirstOrDefault();
|
||||
if (invoice == null)
|
||||
throw new BitpayHttpException(404, "Object not found");
|
||||
var resp = invoice.EntityToDTO(_NetworkProvider);
|
||||
return new DataWrapper<InvoiceResponse>(resp);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("invoices")]
|
||||
public async Task<DataWrapper<InvoiceResponse[]>> GetInvoices(
|
||||
|
@ -1,117 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Payments;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Payment;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class InvoiceController
|
||||
{
|
||||
[HttpGet]
|
||||
[Route("i/{invoiceId}/{cryptoCode?}")]
|
||||
[AcceptMediaTypeConstraint("application/bitcoin-paymentrequest")]
|
||||
public async Task<IActionResult> GetInvoiceRequest(string invoiceId, string cryptoCode = null)
|
||||
{
|
||||
if (cryptoCode == null)
|
||||
cryptoCode = "BTC";
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
var network = _NetworkProvider.GetNetwork(cryptoCode);
|
||||
var paymentMethodId = new PaymentMethodId(cryptoCode, Payments.PaymentTypes.BTCLike);
|
||||
if (invoice == null || invoice.IsExpired() || network == null || !invoice.Support(paymentMethodId))
|
||||
return NotFound();
|
||||
|
||||
var dto = invoice.EntityToDTO(_NetworkProvider);
|
||||
var paymentMethod = dto.CryptoInfo.First(c => c.GetpaymentMethodId() == paymentMethodId);
|
||||
PaymentRequest request = new PaymentRequest
|
||||
{
|
||||
DetailsVersion = 1
|
||||
};
|
||||
request.Details.Expires = invoice.ExpirationTime;
|
||||
request.Details.Memo = invoice.ProductInformation.ItemDesc;
|
||||
request.Details.Network = network.NBitcoinNetwork;
|
||||
request.Details.Outputs.Add(new PaymentOutput() { Amount = paymentMethod.Due, Script = BitcoinAddress.Create(paymentMethod.Address, network.NBitcoinNetwork).ScriptPubKey });
|
||||
request.Details.MerchantData = Encoding.UTF8.GetBytes(invoice.Id);
|
||||
request.Details.Time = DateTimeOffset.UtcNow;
|
||||
request.Details.PaymentUrl = new Uri(invoice.ServerUrl.WithTrailingSlash() + ($"i/{invoice.Id}"), UriKind.Absolute);
|
||||
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
if (store == null)
|
||||
throw new BitpayHttpException(401, "Unknown store");
|
||||
|
||||
if (store.StoreCertificate != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
request.Sign(store.StoreCertificate, PKIType.X509SHA256);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logs.PayServer.LogWarning(ex, "Error while signing payment request");
|
||||
}
|
||||
}
|
||||
|
||||
return new PaymentRequestActionResult(request);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("i/{invoiceId}", Order = 99)]
|
||||
[Route("i/{invoiceId}/{cryptoCode}", Order = 99)]
|
||||
[MediaTypeConstraint("application/bitcoin-payment")]
|
||||
public async Task<IActionResult> PostPayment(string invoiceId, string cryptoCode = null)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if (cryptoCode == null)
|
||||
cryptoCode = "BTC";
|
||||
var network = _NetworkProvider.GetNetwork(cryptoCode);
|
||||
if (network == null || invoice == null || invoice.IsExpired() || !invoice.Support(new PaymentMethodId(cryptoCode, Payments.PaymentTypes.BTCLike)))
|
||||
return NotFound();
|
||||
|
||||
var wallet = _WalletProvider.GetWallet(network);
|
||||
if (wallet == null)
|
||||
return NotFound();
|
||||
var payment = PaymentMessage.Load(Request.Body, network.NBitcoinNetwork);
|
||||
var unused = wallet.BroadcastTransactionsAsync(payment.Transactions);
|
||||
await _InvoiceRepository.AddRefundsAsync(invoiceId, payment.RefundTo.Select(p => new TxOut(p.Amount, p.Script)).ToArray(), network.NBitcoinNetwork);
|
||||
return new PaymentAckActionResult(payment.CreateACK(invoiceId + " is currently processing, thanks for your purchase..."));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class PaymentRequestActionResult : IActionResult
|
||||
{
|
||||
PaymentRequest req;
|
||||
public PaymentRequestActionResult(PaymentRequest req)
|
||||
{
|
||||
this.req = req;
|
||||
}
|
||||
public Task ExecuteResultAsync(ActionContext context)
|
||||
{
|
||||
context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary";
|
||||
context.HttpContext.Response.ContentType = "application/bitcoin-paymentrequest";
|
||||
req.WriteTo(context.HttpContext.Response.Body);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
public class PaymentAckActionResult : IActionResult
|
||||
{
|
||||
PaymentACK req;
|
||||
public PaymentAckActionResult(PaymentACK req)
|
||||
{
|
||||
this.req = req;
|
||||
}
|
||||
public Task ExecuteResultAsync(ActionContext context)
|
||||
{
|
||||
context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary";
|
||||
context.HttpContext.Response.ContentType = "application/bitcoin-paymentack";
|
||||
req.WriteTo(context.HttpContext.Response.Body);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,25 +2,28 @@
|
||||
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;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments;
|
||||
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
|
||||
{
|
||||
@ -28,11 +31,13 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
[HttpGet]
|
||||
[Route("invoices/{invoiceId}")]
|
||||
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
|
||||
public async Task<IActionResult> Invoice(string invoiceId)
|
||||
{
|
||||
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
InvoiceId = invoiceId,
|
||||
UserId = GetUserId(),
|
||||
IncludeAddresses = true,
|
||||
IncludeEvents = true
|
||||
})).FirstOrDefault();
|
||||
@ -41,13 +46,12 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
var dto = invoice.EntityToDTO(_NetworkProvider);
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
|
||||
InvoiceDetailsModel model = new InvoiceDetailsModel()
|
||||
{
|
||||
StoreName = store.StoreName,
|
||||
StoreLink = Url.Action(nameof(StoresController.UpdateStore), "Stores", new { storeId = store.Id }),
|
||||
Id = invoice.Id,
|
||||
Status = invoice.Status,
|
||||
State = invoice.GetInvoiceState().ToString(),
|
||||
TransactionSpeed = invoice.SpeedPolicy == SpeedPolicy.HighSpeed ? "high" :
|
||||
invoice.SpeedPolicy == SpeedPolicy.MediumSpeed ? "medium" :
|
||||
invoice.SpeedPolicy == SpeedPolicy.LowMediumSpeed ? "low-medium" :
|
||||
@ -58,13 +62,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 +79,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)
|
||||
@ -207,7 +212,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
private async Task<PaymentModel> GetInvoiceModel(string invoiceId, string paymentMethodIdStr)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
||||
if (invoice == null)
|
||||
return null;
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
@ -296,7 +301,9 @@ namespace BTCPayServer.Controllers
|
||||
throw new NotSupportedException(),
|
||||
TxCount = accounting.TxRequired,
|
||||
BtcPaid = accounting.Paid.ToString(),
|
||||
Status = invoice.Status,
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Status = invoice.StatusString,
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
NetworkFee = paymentMethodDetails.GetTxFee(),
|
||||
IsMultiCurrency = invoice.GetPayments().Select(p => p.GetPaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1,
|
||||
ChangellyEnabled = changelly != null,
|
||||
@ -367,8 +374,8 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (!HttpContext.WebSockets.IsWebSocketRequest)
|
||||
return NotFound();
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if (invoice == null || invoice.Status == "complete" || invoice.Status == "invalid" || invoice.Status == "expired")
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
||||
if (invoice == null || invoice.Status == InvoiceStatus.Complete || invoice.Status == InvoiceStatus.Invalid || invoice.Status == InvoiceStatus.Expired)
|
||||
return NotFound();
|
||||
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
||||
CompositeDisposable leases = new CompositeDisposable();
|
||||
@ -435,15 +442,18 @@ namespace BTCPayServer.Controllers
|
||||
var list = await ListInvoicesProcess(searchTerm, skip, count);
|
||||
foreach (var invoice in list)
|
||||
{
|
||||
var state = invoice.GetInvoiceState();
|
||||
model.Invoices.Add(new InvoiceModel()
|
||||
{
|
||||
Status = invoice.Status + (invoice.ExceptionStatus == null ? string.Empty : $" ({invoice.ExceptionStatus})"),
|
||||
ShowCheckout = invoice.Status == "new",
|
||||
Status = state.ToString(),
|
||||
ShowCheckout = invoice.Status == InvoiceStatus.New,
|
||||
Date = invoice.InvoiceTime,
|
||||
InvoiceId = invoice.Id,
|
||||
OrderId = invoice.OrderId ?? string.Empty,
|
||||
RedirectUrl = invoice.RedirectURL ?? string.Empty,
|
||||
AmountCurrency = $"{invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture)} {invoice.ProductInformation.Currency}"
|
||||
AmountCurrency = $"{invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture)} {invoice.ProductInformation.Currency}",
|
||||
CanMarkInvalid = state.CanMarkInvalid(),
|
||||
CanMarkComplete = state.CanMarkComplete()
|
||||
});
|
||||
}
|
||||
return View(model);
|
||||
@ -469,6 +479,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)]
|
||||
@ -561,17 +593,60 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("invoices/invalidatepaid")]
|
||||
[HttpGet]
|
||||
[Route("invoices/{invoiceId}/changestate/{newState}")]
|
||||
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> InvalidatePaidInvoice(string invoiceId)
|
||||
public IActionResult ChangeInvoiceState(string invoiceId, string newState)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if (newState == "invalid")
|
||||
{
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Action = "Make invoice invalid",
|
||||
Title = "Change invoice state",
|
||||
Description = $"You will transition the state of this invoice to \"invalid\", do you want to continue?",
|
||||
});
|
||||
}
|
||||
else if (newState == "complete")
|
||||
{
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Action = "Make invoice complete",
|
||||
Title = "Change invoice state",
|
||||
Description = $"You will transition the state of this invoice to \"complete\", do you want to continue?",
|
||||
ButtonClass = "btn-primary"
|
||||
});
|
||||
}
|
||||
else
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("invoices/{invoiceId}/changestate/{newState}")]
|
||||
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> ChangeInvoiceStateConfirm(string invoiceId, string newState)
|
||||
{
|
||||
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
InvoiceId = invoiceId,
|
||||
UserId = GetUserId()
|
||||
})).FirstOrDefault();
|
||||
if (invoice == null)
|
||||
return NotFound();
|
||||
await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoiceId);
|
||||
_EventAggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1008, "invoice_markedInvalid"));
|
||||
if (newState == "invalid")
|
||||
{
|
||||
await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoiceId);
|
||||
_EventAggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1008, "invoice_markedInvalid"));
|
||||
StatusMessage = "Invoice marked invalid";
|
||||
}
|
||||
else if(newState == "complete")
|
||||
{
|
||||
await _InvoiceRepository.UpdatePaidInvoiceToComplete(invoiceId);
|
||||
_EventAggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 2008, "invoice_markedComplete"));
|
||||
StatusMessage = "Invoice marked complete";
|
||||
}
|
||||
return RedirectToAction(nameof(ListInvoices));
|
||||
}
|
||||
|
||||
@ -586,5 +661,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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ namespace BTCPayServer.Controllers
|
||||
if (!Uri.IsWellFormedUriString(entity.RedirectURL, UriKind.Absolute))
|
||||
entity.RedirectURL = null;
|
||||
|
||||
entity.Status = "new";
|
||||
entity.Status = InvoiceStatus.New;
|
||||
entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);
|
||||
|
||||
HashSet<CurrencyPair> currencyPairsToFetch = new HashSet<CurrencyPair>();
|
||||
|
@ -46,6 +46,7 @@ namespace BTCPayServer.Controllers
|
||||
RateFetcher rateProviderFactory,
|
||||
SettingsRepository settingsRepository,
|
||||
NBXplorerDashboard dashBoard,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
LightningConfigurationProvider lnConfigProvider,
|
||||
Services.Stores.StoreRepository storeRepository)
|
||||
{
|
||||
@ -53,6 +54,7 @@ namespace BTCPayServer.Controllers
|
||||
_UserManager = userManager;
|
||||
_SettingsRepository = settingsRepository;
|
||||
_dashBoard = dashBoard;
|
||||
HttpClientFactory = httpClientFactory;
|
||||
_RateProviderFactory = rateProviderFactory;
|
||||
_StoreRepository = storeRepository;
|
||||
_LnConfigProvider = lnConfigProvider;
|
||||
@ -395,6 +397,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public IHttpClientFactory HttpClientFactory { get; }
|
||||
|
||||
[Route("server/emails")]
|
||||
public async Task<IActionResult> Emails()
|
||||
@ -431,16 +434,77 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
Crypto = cryptoCode,
|
||||
Type = grpcService.Type,
|
||||
Action = nameof(LndServices),
|
||||
Index = i++,
|
||||
});
|
||||
}
|
||||
i = 0;
|
||||
foreach (var sparkService in _Options.ExternalServicesByCryptoCode.GetServices<ExternalSpark>(cryptoCode))
|
||||
{
|
||||
result.LNDServices.Add(new ServicesViewModel.LNDServiceViewModel()
|
||||
{
|
||||
Crypto = cryptoCode,
|
||||
Type = "Spark server",
|
||||
Action = nameof(SparkServices),
|
||||
Index = i++,
|
||||
});
|
||||
}
|
||||
}
|
||||
result.HasSSH = _Options.SSHSettings != null;
|
||||
foreach(var externalService in _Options.ExternalServices)
|
||||
{
|
||||
result.ExternalServices.Add(new ServicesViewModel.ExternalService()
|
||||
{
|
||||
Name = externalService.Key,
|
||||
Link = this.Request.GetRelativePath(externalService.Value)
|
||||
});
|
||||
}
|
||||
if(_Options.SSHSettings != null)
|
||||
{
|
||||
result.ExternalServices.Add(new ServicesViewModel.ExternalService()
|
||||
{
|
||||
Name = "SSH",
|
||||
Link = this.Url.Action(nameof(SSHService))
|
||||
});
|
||||
}
|
||||
return View(result);
|
||||
}
|
||||
|
||||
[Route("server/services/lnd-grpc/{cryptoCode}/{index}")]
|
||||
public IActionResult LndGrpcServices(string cryptoCode, int index, uint? nonce)
|
||||
[Route("server/services/spark/{cryptoCode}/{index}")]
|
||||
public async Task<IActionResult> SparkServices(string cryptoCode, int index, bool showQR = false)
|
||||
{
|
||||
if (!_dashBoard.IsFullySynched(cryptoCode, out var unusud))
|
||||
{
|
||||
StatusMessage = $"Error: {cryptoCode} is not fully synched";
|
||||
return RedirectToAction(nameof(Services));
|
||||
}
|
||||
var spark = _Options.ExternalServicesByCryptoCode.GetServices<ExternalSpark>(cryptoCode).Skip(index).Select(c => c.ConnectionString).FirstOrDefault();
|
||||
if(spark == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
SparkServicesViewModel vm = new SparkServicesViewModel();
|
||||
vm.ShowQR = showQR;
|
||||
try
|
||||
{
|
||||
var cookie = (spark.CookeFile == "fake"
|
||||
? "fake:fake:fake" // If we are testing, it should not crash
|
||||
: await System.IO.File.ReadAllTextAsync(spark.CookeFile)).Split(':');
|
||||
if (cookie.Length >= 3)
|
||||
{
|
||||
vm.SparkLink = $"{spark.Server.AbsoluteUri}?access-key={cookie[2]}";
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
StatusMessage = $"Error: {ex.Message}";
|
||||
return RedirectToAction(nameof(Services));
|
||||
}
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[Route("server/services/lnd/{cryptoCode}/{index}")]
|
||||
public IActionResult LndServices(string cryptoCode, int index, uint? nonce)
|
||||
{
|
||||
if (!_dashBoard.IsFullySynched(cryptoCode, out var unusud))
|
||||
{
|
||||
@ -451,9 +515,18 @@ namespace BTCPayServer.Controllers
|
||||
if (external == null)
|
||||
return NotFound();
|
||||
var model = new LndGrpcServicesViewModel();
|
||||
if (external.ConnectionType == LightningConnectionType.LndGRPC)
|
||||
{
|
||||
model.Host = $"{external.BaseUri.DnsSafeHost}:{external.BaseUri.Port}";
|
||||
model.SSL = external.BaseUri.Scheme == "https";
|
||||
model.ConnectionType = "GRPC";
|
||||
}
|
||||
else if(external.ConnectionType == LightningConnectionType.LndREST)
|
||||
{
|
||||
model.Uri = external.BaseUri.AbsoluteUri;
|
||||
model.ConnectionType = "REST";
|
||||
}
|
||||
|
||||
model.Host = $"{external.BaseUri.DnsSafeHost}:{external.BaseUri.Port}";
|
||||
model.SSL = external.BaseUri.Scheme == "https";
|
||||
if (external.CertificateThumbprint != null)
|
||||
{
|
||||
model.CertificateThumbprint = Encoders.Hex.EncodeData(external.CertificateThumbprint);
|
||||
@ -462,10 +535,14 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
model.Macaroon = Encoders.Hex.EncodeData(external.Macaroon);
|
||||
}
|
||||
if (external.RestrictedMacaroon != null)
|
||||
{
|
||||
model.RestrictedMacaroon = Encoders.Hex.EncodeData(external.RestrictedMacaroon);
|
||||
}
|
||||
|
||||
if (nonce != null)
|
||||
{
|
||||
var configKey = GetConfigKey("lnd-grpc", cryptoCode, index, nonce.Value);
|
||||
var configKey = GetConfigKey("lnd", cryptoCode, index, nonce.Value);
|
||||
var lnConfig = _LnConfigProvider.GetConfig(configKey);
|
||||
if (lnConfig != null)
|
||||
{
|
||||
@ -492,29 +569,44 @@ namespace BTCPayServer.Controllers
|
||||
return Json(conf);
|
||||
}
|
||||
|
||||
[Route("server/services/lnd-grpc/{cryptoCode}/{index}")]
|
||||
[Route("server/services/lnd/{cryptoCode}/{index}")]
|
||||
[HttpPost]
|
||||
public IActionResult LndGrpcServicesPost(string cryptoCode, int index)
|
||||
public IActionResult LndServicesPost(string cryptoCode, int index)
|
||||
{
|
||||
var external = GetExternalLndConnectionString(cryptoCode, index);
|
||||
if (external == null)
|
||||
return NotFound();
|
||||
LightningConfigurations confs = new LightningConfigurations();
|
||||
LightningConfiguration conf = new LightningConfiguration();
|
||||
conf.Type = "grpc";
|
||||
conf.ChainType = _Options.NetworkType.ToString();
|
||||
conf.CryptoCode = cryptoCode;
|
||||
conf.Host = external.BaseUri.DnsSafeHost;
|
||||
conf.Port = external.BaseUri.Port;
|
||||
conf.SSL = external.BaseUri.Scheme == "https";
|
||||
conf.Macaroon = external.Macaroon == null ? null : Encoders.Hex.EncodeData(external.Macaroon);
|
||||
conf.CertificateThumbprint = external.CertificateThumbprint == null ? null : Encoders.Hex.EncodeData(external.CertificateThumbprint);
|
||||
confs.Configurations.Add(conf);
|
||||
|
||||
if (external.ConnectionType == LightningConnectionType.LndGRPC)
|
||||
{
|
||||
LightningConfiguration conf = new LightningConfiguration();
|
||||
conf.Type = "grpc";
|
||||
conf.ChainType = _Options.NetworkType.ToString();
|
||||
conf.CryptoCode = cryptoCode;
|
||||
conf.Host = external.BaseUri.DnsSafeHost;
|
||||
conf.Port = external.BaseUri.Port;
|
||||
conf.SSL = external.BaseUri.Scheme == "https";
|
||||
conf.Macaroon = external.Macaroon == null ? null : Encoders.Hex.EncodeData(external.Macaroon);
|
||||
conf.RestrictedMacaroon = external.RestrictedMacaroon == null ? null : Encoders.Hex.EncodeData(external.RestrictedMacaroon);
|
||||
conf.CertificateThumbprint = external.CertificateThumbprint == null ? null : Encoders.Hex.EncodeData(external.CertificateThumbprint);
|
||||
confs.Configurations.Add(conf);
|
||||
}
|
||||
else if (external.ConnectionType == LightningConnectionType.LndREST)
|
||||
{
|
||||
var restconf = new LNDRestConfiguration();
|
||||
restconf.Type = "lnd-rest";
|
||||
restconf.ChainType = _Options.NetworkType.ToString();
|
||||
restconf.CryptoCode = cryptoCode;
|
||||
restconf.Uri = external.BaseUri.AbsoluteUri;
|
||||
restconf.Macaroon = external.Macaroon == null ? null : Encoders.Hex.EncodeData(external.Macaroon);
|
||||
restconf.RestrictedMacaroon = external.RestrictedMacaroon == null ? null : Encoders.Hex.EncodeData(external.RestrictedMacaroon);
|
||||
restconf.CertificateThumbprint = external.CertificateThumbprint == null ? null : Encoders.Hex.EncodeData(external.CertificateThumbprint);
|
||||
confs.Configurations.Add(restconf);
|
||||
}
|
||||
var nonce = RandomUtils.GetUInt32();
|
||||
var configKey = GetConfigKey("lnd-grpc", cryptoCode, index, nonce);
|
||||
var configKey = GetConfigKey("lnd", cryptoCode, index, nonce);
|
||||
_LnConfigProvider.KeepConfig(configKey, confs);
|
||||
return RedirectToAction(nameof(LndGrpcServices), new { cryptoCode = cryptoCode, nonce = nonce });
|
||||
return RedirectToAction(nameof(LndServices), new { cryptoCode = cryptoCode, nonce = nonce });
|
||||
}
|
||||
|
||||
private LightningConnectionString GetExternalLndConnectionString(string cryptoCode, int index)
|
||||
@ -536,29 +628,19 @@ namespace BTCPayServer.Controllers
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
[Route("server/services/lnd-rest/{cryptoCode}/{index}")]
|
||||
public IActionResult LndRestServices(string cryptoCode, int index, uint? nonce)
|
||||
{
|
||||
if (!_dashBoard.IsFullySynched(cryptoCode, out var unusud))
|
||||
if (connectionString.RestrictedMacaroonFilePath != null)
|
||||
{
|
||||
StatusMessage = $"Error: {cryptoCode} is not fully synched";
|
||||
return RedirectToAction(nameof(Services));
|
||||
try
|
||||
{
|
||||
connectionString.RestrictedMacaroon = System.IO.File.ReadAllBytes(connectionString.RestrictedMacaroonFilePath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logs.Configuration.LogWarning($"{cryptoCode}: The restrictedmacaroon file path of the external LND grpc config was not found ({connectionString.RestrictedMacaroonFilePath})");
|
||||
}
|
||||
connectionString.RestrictedMacaroonFilePath = null;
|
||||
}
|
||||
var external = GetExternalLndConnectionString(cryptoCode, index);
|
||||
if (external == null)
|
||||
return NotFound();
|
||||
var model = new LndRestServicesViewModel();
|
||||
|
||||
model.BaseApiUrl = external.BaseUri.ToString();
|
||||
if (external.CertificateThumbprint != null)
|
||||
model.CertificateThumbprint = Encoders.Hex.EncodeData(external.CertificateThumbprint);
|
||||
if (external.Macaroon != null)
|
||||
model.Macaroon = Encoders.Hex.EncodeData(external.Macaroon);
|
||||
|
||||
return View(model);
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
[Route("server/services/ssh")]
|
||||
|
@ -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
|
||||
|
@ -81,5 +81,10 @@ namespace BTCPayServer.Data
|
||||
get; set;
|
||||
}
|
||||
public List<PendingInvoiceData> PendingInvoices { get; set; }
|
||||
|
||||
public Services.Invoices.InvoiceState GetInvoiceState()
|
||||
{
|
||||
return new Services.Invoices.InvoiceState(Status, ExceptionStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,11 @@ namespace BTCPayServer
|
||||
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;
|
||||
|
@ -11,23 +11,14 @@ namespace BTCPayServer.Events
|
||||
public InvoiceDataChangedEvent(InvoiceEntity invoice)
|
||||
{
|
||||
InvoiceId = invoice.Id;
|
||||
Status = invoice.Status;
|
||||
ExceptionStatus = invoice.ExceptionStatus;
|
||||
State = invoice.GetInvoiceState();
|
||||
}
|
||||
public string InvoiceId { get; set; }
|
||||
public string Status { get; internal set; }
|
||||
public string ExceptionStatus { get; internal set; }
|
||||
public string InvoiceId { get; }
|
||||
public InvoiceState State { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (string.IsNullOrEmpty(ExceptionStatus) || ExceptionStatus == "false")
|
||||
{
|
||||
return $"Invoice status is {Status}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"Invoice status is {Status} (Exception status: {ExceptionStatus})";
|
||||
}
|
||||
return $"Invoice status is {State}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -168,6 +168,15 @@ namespace BTCPayServer
|
||||
request.Path.ToUriComponent());
|
||||
}
|
||||
|
||||
public static string GetRelativePath(this HttpRequest request, string path)
|
||||
{
|
||||
if (path.Length > 0 && path[0] != '/')
|
||||
path = $"/{path}";
|
||||
return string.Concat(
|
||||
request.PathBase.ToUriComponent(),
|
||||
path);
|
||||
}
|
||||
|
||||
public static string GetAbsoluteUri(this HttpRequest request, string redirectUrl)
|
||||
{
|
||||
bool isRelative =
|
||||
|
@ -330,7 +330,7 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
leases.Add(_EventAggregator.Subscribe<InvoiceEvent>(async e =>
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, e.Invoice.Id);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(e.Invoice.Id);
|
||||
if (invoice == null)
|
||||
return;
|
||||
List<Task> tasks = new List<Task>();
|
||||
@ -345,6 +345,7 @@ namespace BTCPayServer.HostedServices
|
||||
e.Name == "invoice_paidInFull" ||
|
||||
e.Name == "invoice_failedToConfirm" ||
|
||||
e.Name == "invoice_markedInvalid" ||
|
||||
e.Name == "invoice_markedComplete" ||
|
||||
e.Name == "invoice_failedToConfirm" ||
|
||||
e.Name == "invoice_completed" ||
|
||||
e.Name == "invoice_expiredPaidPartial"
|
||||
|
@ -61,14 +61,14 @@ namespace BTCPayServer.HostedServices
|
||||
private async Task UpdateInvoice(UpdateInvoiceContext context)
|
||||
{
|
||||
var invoice = context.Invoice;
|
||||
if (invoice.Status == "new" && invoice.ExpirationTime < DateTimeOffset.UtcNow)
|
||||
if (invoice.Status == InvoiceStatus.New && invoice.ExpirationTime < DateTimeOffset.UtcNow)
|
||||
{
|
||||
context.MarkDirty();
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1004, "invoice_expired"));
|
||||
invoice.Status = "expired";
|
||||
if(invoice.ExceptionStatus == "paidPartial")
|
||||
invoice.Status = InvoiceStatus.Expired;
|
||||
if(invoice.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 2000, "invoice_expiredPaidPartial"));
|
||||
}
|
||||
|
||||
@ -78,57 +78,57 @@ namespace BTCPayServer.HostedServices
|
||||
if (paymentMethod == null)
|
||||
return;
|
||||
var network = _NetworkProvider.GetNetwork(paymentMethod.GetId().CryptoCode);
|
||||
if (invoice.Status == "new" || invoice.Status == "expired")
|
||||
if (invoice.Status == InvoiceStatus.New || invoice.Status == InvoiceStatus.Expired)
|
||||
{
|
||||
if (accounting.Paid >= accounting.MinimumTotalDue)
|
||||
{
|
||||
if (invoice.Status == "new")
|
||||
if (invoice.Status == InvoiceStatus.New)
|
||||
{
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1003, "invoice_paidInFull"));
|
||||
invoice.Status = "paid";
|
||||
invoice.ExceptionStatus = accounting.Paid > accounting.TotalDue ? "paidOver" : null;
|
||||
invoice.Status = InvoiceStatus.Paid;
|
||||
invoice.ExceptionStatus = accounting.Paid > accounting.TotalDue ? InvoiceExceptionStatus.PaidOver : InvoiceExceptionStatus.None;
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
context.MarkDirty();
|
||||
}
|
||||
else if (invoice.Status == "expired" && invoice.ExceptionStatus != "paidLate")
|
||||
else if (invoice.Status == InvoiceStatus.Expired && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidLate)
|
||||
{
|
||||
invoice.ExceptionStatus = "paidLate";
|
||||
invoice.ExceptionStatus = InvoiceExceptionStatus.PaidLate;
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1009, "invoice_paidAfterExpiration"));
|
||||
context.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
if (accounting.Paid < accounting.MinimumTotalDue && invoice.GetPayments().Count != 0 && invoice.ExceptionStatus != "paidPartial")
|
||||
if (accounting.Paid < accounting.MinimumTotalDue && invoice.GetPayments().Count != 0 && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidPartial)
|
||||
{
|
||||
invoice.ExceptionStatus = "paidPartial";
|
||||
invoice.ExceptionStatus = InvoiceExceptionStatus.PaidPartial;
|
||||
context.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
// Just make sure RBF did not cancelled a payment
|
||||
if (invoice.Status == "paid")
|
||||
if (invoice.Status == InvoiceStatus.Paid)
|
||||
{
|
||||
if (accounting.MinimumTotalDue <= accounting.Paid && accounting.Paid <= accounting.TotalDue && invoice.ExceptionStatus == "paidOver")
|
||||
if (accounting.MinimumTotalDue <= accounting.Paid && accounting.Paid <= accounting.TotalDue && invoice.ExceptionStatus == InvoiceExceptionStatus.PaidOver)
|
||||
{
|
||||
invoice.ExceptionStatus = null;
|
||||
invoice.ExceptionStatus = InvoiceExceptionStatus.None;
|
||||
context.MarkDirty();
|
||||
}
|
||||
|
||||
if (accounting.Paid > accounting.TotalDue && invoice.ExceptionStatus != "paidOver")
|
||||
if (accounting.Paid > accounting.TotalDue && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidOver)
|
||||
{
|
||||
invoice.ExceptionStatus = "paidOver";
|
||||
invoice.ExceptionStatus = InvoiceExceptionStatus.PaidOver;
|
||||
context.MarkDirty();
|
||||
}
|
||||
|
||||
if (accounting.Paid < accounting.MinimumTotalDue)
|
||||
{
|
||||
invoice.Status = "new";
|
||||
invoice.ExceptionStatus = accounting.Paid == Money.Zero ? null : "paidPartial";
|
||||
invoice.Status = InvoiceStatus.New;
|
||||
invoice.ExceptionStatus = accounting.Paid == Money.Zero ? InvoiceExceptionStatus.None : InvoiceExceptionStatus.PaidPartial;
|
||||
context.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
if (invoice.Status == "paid")
|
||||
if (invoice.Status == InvoiceStatus.Paid)
|
||||
{
|
||||
var confirmedAccounting = paymentMethod.Calculate(p => p.GetCryptoPaymentData().PaymentConfirmed(p, invoice.SpeedPolicy, network));
|
||||
|
||||
@ -140,25 +140,25 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1013, "invoice_failedToConfirm"));
|
||||
invoice.Status = "invalid";
|
||||
invoice.Status = InvoiceStatus.Invalid;
|
||||
context.MarkDirty();
|
||||
}
|
||||
else if (confirmedAccounting.Paid >= accounting.MinimumTotalDue)
|
||||
{
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1005, "invoice_confirmed"));
|
||||
invoice.Status = "confirmed";
|
||||
invoice.Status = InvoiceStatus.Confirmed;
|
||||
context.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
if (invoice.Status == "confirmed")
|
||||
if (invoice.Status == InvoiceStatus.Confirmed)
|
||||
{
|
||||
var completedAccounting = paymentMethod.Calculate(p => p.GetCryptoPaymentData().PaymentCompleted(p, network));
|
||||
if (completedAccounting.Paid >= accounting.MinimumTotalDue)
|
||||
{
|
||||
context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1006, "invoice_completed"));
|
||||
invoice.Status = "complete";
|
||||
invoice.Status = InvoiceStatus.Complete;
|
||||
context.MarkDirty();
|
||||
}
|
||||
}
|
||||
@ -208,7 +208,7 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
private async Task Wait(string invoiceId)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
||||
try
|
||||
{
|
||||
var delay = invoice.ExpirationTime - DateTimeOffset.UtcNow;
|
||||
@ -283,14 +283,14 @@ namespace BTCPayServer.HostedServices
|
||||
loopCount++;
|
||||
try
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, true);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId, true);
|
||||
if (invoice == null)
|
||||
break;
|
||||
var updateContext = new UpdateInvoiceContext(invoice);
|
||||
await UpdateInvoice(updateContext);
|
||||
if (updateContext.Dirty)
|
||||
{
|
||||
await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.Status, invoice.ExceptionStatus);
|
||||
await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.GetInvoiceState());
|
||||
updateContext.Events.Insert(0, new InvoiceDataChangedEvent(invoice));
|
||||
}
|
||||
|
||||
@ -299,8 +299,8 @@ namespace BTCPayServer.HostedServices
|
||||
_EventAggregator.Publish(evt, evt.GetType());
|
||||
}
|
||||
|
||||
if (invoice.Status == "complete" ||
|
||||
((invoice.Status == "invalid" || invoice.Status == "expired") && invoice.MonitoringExpiration < DateTimeOffset.UtcNow))
|
||||
if (invoice.Status == InvoiceStatus.Complete ||
|
||||
((invoice.Status == InvoiceStatus.Invalid || invoice.Status == InvoiceStatus.Expired) && invoice.MonitoringExpiration < DateTimeOffset.UtcNow))
|
||||
{
|
||||
if (await _InvoiceRepository.RemovePendingInvoice(invoice.Id))
|
||||
_EventAggregator.Publish(new InvoiceStopWatchedEvent(invoice.Id));
|
||||
|
@ -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,29 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
public bool Custom { get; set; }
|
||||
}
|
||||
|
||||
public class CurrencyInfoData
|
||||
{
|
||||
public bool Prefixed { get; set; }
|
||||
public string CurrencySymbol { get; set; }
|
||||
public string ThousandSeparator { get; set; }
|
||||
public string DecimalSeparator { get; set; }
|
||||
public int Divisibility { get; internal set; }
|
||||
}
|
||||
|
||||
public CurrencyInfoData CurrencyInfo { 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 CurrencyCode { get; set; }
|
||||
public string CurrencySymbol { get; set; }
|
||||
public string AppId { get; set; }
|
||||
public string ButtonText { get; set; }
|
||||
public string CustomButtonText { get; set; }
|
||||
public string CustomTipText { get; set; }
|
||||
|
||||
public string CustomCSSLink { get; set; }
|
||||
}
|
||||
}
|
||||
|
17
BTCPayServer/Models/BitpayTranslatorViewModel.cs
Normal file
17
BTCPayServer/Models/BitpayTranslatorViewModel.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Models
|
||||
{
|
||||
public class BitpayTranslatorViewModel
|
||||
{
|
||||
[Display(Name = "Bitpay's invoice URL or obsolete invoice url")]
|
||||
public string BitpayLink { get; set; }
|
||||
public string Address { get; set; }
|
||||
public string Amount { get; set; }
|
||||
public string BitcoinUri { get; set; }
|
||||
}
|
||||
}
|
@ -81,11 +81,11 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
public string BOLT11 { get; set; }
|
||||
}
|
||||
|
||||
public string Status
|
||||
public string State
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string StatusException { get; set; }
|
||||
public InvoiceExceptionStatus StatusException { get; set; }
|
||||
public DateTimeOffset CreatedDate
|
||||
{
|
||||
get; set;
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,9 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public bool CanMarkComplete { get; set; }
|
||||
public bool CanMarkInvalid { get; set; }
|
||||
public bool CanMarkStatus => CanMarkComplete || CanMarkInvalid;
|
||||
public bool ShowCheckout { get; set; }
|
||||
public string ExceptionStatus { get; set; }
|
||||
public string AmountCurrency
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -10,8 +11,12 @@ namespace BTCPayServer.Models.ServerViewModels
|
||||
public string Host { get; set; }
|
||||
public bool SSL { get; set; }
|
||||
public string Macaroon { get; set; }
|
||||
public string RestrictedMacaroon { get; set; }
|
||||
public string CertificateThumbprint { get; set; }
|
||||
public string QRCode { get; set; }
|
||||
public string QRCodeLink { get; set; }
|
||||
[Display(Name = "REST Uri")]
|
||||
public string Uri { get; set; }
|
||||
public string ConnectionType { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
@ -11,11 +11,18 @@ namespace BTCPayServer.Models.ServerViewModels
|
||||
public class LNDServiceViewModel
|
||||
{
|
||||
public string Crypto { get; set; }
|
||||
public LndTypes Type { get; set; }
|
||||
public string Type { get; set; }
|
||||
public int Index { get; set; }
|
||||
public string Action { get; internal set; }
|
||||
}
|
||||
|
||||
public class ExternalService
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Link { get; set; }
|
||||
}
|
||||
|
||||
public List<LNDServiceViewModel> LNDServices { get; set; } = new List<LNDServiceViewModel>();
|
||||
public bool HasSSH { get; set; }
|
||||
public List<ExternalService> ExternalServices { get; set; } = new List<ExternalService>();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Models.ServerViewModels
|
||||
{
|
||||
public class SparkServicesViewModel
|
||||
{
|
||||
public string SparkLink { get; set; }
|
||||
public bool ShowQR { get; set; }
|
||||
}
|
||||
}
|
@ -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);
|
||||
@ -205,7 +205,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
|
||||
async Task<InvoiceEntity> UpdatePaymentStates(BTCPayWallet wallet, string invoiceId)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, false);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId, false);
|
||||
if (invoice == null)
|
||||
return null;
|
||||
List<PaymentEntity> updatedPaymentEntities = new List<PaymentEntity>();
|
||||
@ -315,7 +315,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
var invoices = await _InvoiceRepository.GetPendingInvoices();
|
||||
foreach (var invoiceId in invoices)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, true);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId, true);
|
||||
if (invoice == null)
|
||||
continue;
|
||||
var alreadyAccounted = GetAllBitcoinPaymentData(invoice).Select(p => p.Outpoint).ToHashSet();
|
||||
|
@ -73,7 +73,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
{
|
||||
if (Listening(invoiceId))
|
||||
return;
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
||||
foreach (var paymentMethod in invoice.GetPaymentMethods(_NetworkProvider)
|
||||
.Where(c => c.GetId().PaymentType == PaymentTypes.LightningLike))
|
||||
{
|
||||
@ -194,7 +194,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
}, network.CryptoCode, accounted: true);
|
||||
if (payment != null)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, listenedInvoice.InvoiceId);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(listenedInvoice.InvoiceId);
|
||||
if (invoice != null)
|
||||
_Aggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1002, "invoice_receivedPayment"));
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"BTCPAY_NETWORK": "regtest",
|
||||
"BTCPAY_BUNDLEJSCSS": "true",
|
||||
"BTCPAY_BUNDLEJSCSS": "false",
|
||||
"BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32838/",
|
||||
"BTCPAY_BTCLIGHTNING": "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify",
|
||||
"BTCPAY_BTCEXTERNALLNDGRPC": "type=lnd-grpc;server=https://lnd:lnd@127.0.0.1:53280/;allowinsecure=true",
|
||||
@ -24,15 +24,17 @@
|
||||
"BTCPAY_NETWORK": "regtest",
|
||||
"BTCPAY_PORT": "14142",
|
||||
"BTCPAY_HttpsUseDefaultCertificate": "true",
|
||||
"BTCPAY_BUNDLEJSCSS": "true",
|
||||
"BTCPAY_BUNDLEJSCSS": "false",
|
||||
"BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32838/",
|
||||
"BTCPAY_BTCLIGHTNING": "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify",
|
||||
"BTCPAY_BTCEXTERNALLNDGRPC": "type=lnd-grpc;server=https://lnd:lnd@127.0.0.1:53280/;allowinsecure=true",
|
||||
"BTCPAY_BTCEXTERNALLNDREST": "type=lnd-rest;server=https://lnd:lnd@127.0.0.1:53280/lnd-rest/btc/;allowinsecure=true;macaroonfilepath=D:\\admin.macaroon",
|
||||
"BTCPAY_BTCEXTERNALLNDREST": "type=lnd-rest;server=https://lnd:lnd@127.0.0.1:53280/lnd-rest/btc/;allowinsecure=true",
|
||||
"BTCPAY_BTCEXTERNALSPARK": "server=https://127.0.0.1:53280/spark/btc/;cookiefile=fake",
|
||||
"BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/",
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"BTCPAY_CHAINS": "btc,ltc",
|
||||
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver"
|
||||
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver",
|
||||
"BTCPAY_EXTERNALSERVICES": "totoservice:totolink;"
|
||||
},
|
||||
"applicationUrl": "https://localhost:14142/"
|
||||
}
|
||||
|
@ -57,43 +57,37 @@ namespace BTCPayServer.Security
|
||||
List<Claim> claims = new List<Claim>();
|
||||
var bitpayAuth = Context.Request.HttpContext.GetBitpayAuth();
|
||||
string storeId = null;
|
||||
// Careful, those are not the opposite. failedAuth says if a the tentative failed.
|
||||
// successAuth, ensure that at least one succeed.
|
||||
var failedAuth = false;
|
||||
var successAuth = false;
|
||||
|
||||
bool? success = null;
|
||||
if (!string.IsNullOrEmpty(bitpayAuth.Signature) && !string.IsNullOrEmpty(bitpayAuth.Id))
|
||||
{
|
||||
var result = await CheckBitId(Context.Request.HttpContext, bitpayAuth.Signature, bitpayAuth.Id, claims);
|
||||
storeId = result.StoreId;
|
||||
failedAuth = !result.SuccessAuth;
|
||||
successAuth = result.SuccessAuth;
|
||||
success = result.SuccessAuth;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(bitpayAuth.Authorization))
|
||||
{
|
||||
storeId = await CheckLegacyAPIKey(Context.Request.HttpContext, bitpayAuth.Authorization);
|
||||
if (storeId == null)
|
||||
{
|
||||
Logs.PayServer.LogDebug("API key check failed");
|
||||
failedAuth = true;
|
||||
}
|
||||
successAuth = storeId != null;
|
||||
success = storeId != null;
|
||||
}
|
||||
|
||||
if (failedAuth)
|
||||
if (success.HasValue)
|
||||
{
|
||||
return AuthenticateResult.Fail("Invalid credentials");
|
||||
}
|
||||
|
||||
if (successAuth)
|
||||
{
|
||||
if (storeId != null)
|
||||
if (success.Value)
|
||||
{
|
||||
claims.Add(new Claim(Policies.CanCreateInvoice.Key, storeId));
|
||||
var store = await _StoreRepository.FindStore(storeId);
|
||||
store.AdditionalClaims.AddRange(claims);
|
||||
Context.Request.HttpContext.SetStoreData(store);
|
||||
if (storeId != null)
|
||||
{
|
||||
claims.Add(new Claim(Policies.CanCreateInvoice.Key, storeId));
|
||||
var store = await _StoreRepository.FindStore(storeId);
|
||||
store.AdditionalClaims.AddRange(claims);
|
||||
Context.Request.HttpContext.SetStoreData(store);
|
||||
}
|
||||
return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, Policies.BitpayAuthentication)), Policies.BitpayAuthentication));
|
||||
}
|
||||
else
|
||||
{
|
||||
return AuthenticateResult.Fail("Invalid credentials");
|
||||
}
|
||||
return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, Policies.BitpayAuthentication)), Policies.BitpayAuthentication));
|
||||
}
|
||||
}
|
||||
return AuthenticateResult.NoResult();
|
||||
@ -148,6 +142,10 @@ namespace BTCPayServer.Security
|
||||
storeId = bitToken.StoreId;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return (storeId, false);
|
||||
}
|
||||
}
|
||||
catch (FormatException) { }
|
||||
return (storeId, 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
123
BTCPayServer/Services/Invoices/Export/InvoiceExport.cs
Normal file
123
BTCPayServer/Services/Invoices/Export/InvoiceExport.cs
Normal file
@ -0,0 +1,123 @@
|
||||
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,
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Status = invoice.StatusString,
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
ItemCode = invoice.ProductInformation?.ItemCode,
|
||||
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; }
|
||||
}
|
||||
}
|
@ -226,15 +226,23 @@ namespace BTCPayServer.Services.Invoices
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
public string Status
|
||||
[JsonIgnore]
|
||||
public InvoiceStatus Status
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string ExceptionStatus
|
||||
[JsonProperty(PropertyName = "status")]
|
||||
[Obsolete("Use Status instead")]
|
||||
public string StatusString => InvoiceState.ToString(Status);
|
||||
[JsonIgnore]
|
||||
public InvoiceExceptionStatus ExceptionStatus
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
[JsonProperty(PropertyName = "exceptionStatus")]
|
||||
[Obsolete("Use ExceptionStatus instead")]
|
||||
public string ExceptionStatusString => InvoiceState.ToString(ExceptionStatus);
|
||||
|
||||
[Obsolete("Use GetPayments instead")]
|
||||
public List<PaymentEntity> Payments
|
||||
@ -341,7 +349,10 @@ namespace BTCPayServer.Services.Invoices
|
||||
CurrentTime = DateTimeOffset.UtcNow,
|
||||
InvoiceTime = InvoiceTime,
|
||||
ExpirationTime = ExpirationTime,
|
||||
Status = Status,
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Status = StatusString,
|
||||
ExceptionStatus = ExceptionStatus == InvoiceExceptionStatus.None ? new JValue(false) : new JValue(ExceptionStatusString),
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
Currency = ProductInformation.Currency,
|
||||
Flags = new Flags() { Refundable = Refundable },
|
||||
PaymentSubtotals = new Dictionary<string, long>(),
|
||||
@ -395,9 +406,6 @@ namespace BTCPayServer.Services.Invoices
|
||||
var cryptoSuffix = cryptoInfo.CryptoCode == "BTC" ? "" : "/" + cryptoInfo.CryptoCode;
|
||||
cryptoInfo.PaymentUrls = new NBitpayClient.InvoicePaymentUrls()
|
||||
{
|
||||
BIP72 = $"{scheme}:{cryptoInfo.Address}?amount={cryptoInfo.Due}&r={ServerUrl.WithTrailingSlash() + ($"i/{Id}{cryptoSuffix}")}",
|
||||
BIP72b = $"{scheme}:?r={ServerUrl.WithTrailingSlash() + ($"i/{Id}{cryptoSuffix}")}",
|
||||
BIP73 = ServerUrl.WithTrailingSlash() + ($"i/{Id}{cryptoSuffix}"),
|
||||
BIP21 = $"{scheme}:{cryptoInfo.Address}?amount={cryptoInfo.Due}",
|
||||
};
|
||||
}
|
||||
@ -450,7 +458,6 @@ namespace BTCPayServer.Services.Invoices
|
||||
|
||||
dto.Token = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16)); //No idea what it is useful for
|
||||
dto.Guid = Guid.NewGuid().ToString();
|
||||
dto.ExceptionStatus = ExceptionStatus == null ? new JValue(false) : new JValue(ExceptionStatus);
|
||||
return dto;
|
||||
}
|
||||
|
||||
@ -529,24 +536,127 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
public InvoiceState GetInvoiceState()
|
||||
{
|
||||
return new InvoiceState(Status, ExceptionStatus);
|
||||
}
|
||||
}
|
||||
|
||||
public enum InvoiceStatus
|
||||
{
|
||||
New,
|
||||
Paid,
|
||||
Expired,
|
||||
Invalid,
|
||||
Complete,
|
||||
Confirmed
|
||||
}
|
||||
public enum InvoiceExceptionStatus
|
||||
{
|
||||
None,
|
||||
PaidLate,
|
||||
PaidPartial,
|
||||
Marked,
|
||||
Invalid,
|
||||
PaidOver
|
||||
}
|
||||
public class InvoiceState
|
||||
{
|
||||
static Dictionary<string, InvoiceStatus> _StringToInvoiceStatus;
|
||||
static Dictionary<InvoiceStatus, string> _InvoiceStatusToString;
|
||||
|
||||
static Dictionary<string, InvoiceExceptionStatus> _StringToExceptionStatus;
|
||||
static Dictionary<InvoiceExceptionStatus, string> _ExceptionStatusToString;
|
||||
|
||||
static InvoiceState()
|
||||
{
|
||||
_StringToInvoiceStatus = new Dictionary<string, InvoiceStatus>();
|
||||
_StringToInvoiceStatus.Add("paid", InvoiceStatus.Paid);
|
||||
_StringToInvoiceStatus.Add("expired", InvoiceStatus.Expired);
|
||||
_StringToInvoiceStatus.Add("invalid", InvoiceStatus.Invalid);
|
||||
_StringToInvoiceStatus.Add("complete", InvoiceStatus.Complete);
|
||||
_StringToInvoiceStatus.Add("new", InvoiceStatus.New);
|
||||
_StringToInvoiceStatus.Add("confirmed", InvoiceStatus.Confirmed);
|
||||
_InvoiceStatusToString = _StringToInvoiceStatus.ToDictionary(kv => kv.Value, kv => kv.Key);
|
||||
|
||||
_StringToExceptionStatus = new Dictionary<string, InvoiceExceptionStatus>();
|
||||
_StringToExceptionStatus.Add(string.Empty, InvoiceExceptionStatus.None);
|
||||
_StringToExceptionStatus.Add("paidPartial", InvoiceExceptionStatus.PaidPartial);
|
||||
_StringToExceptionStatus.Add("paidLate", InvoiceExceptionStatus.PaidLate);
|
||||
_StringToExceptionStatus.Add("paidOver", InvoiceExceptionStatus.PaidOver);
|
||||
_StringToExceptionStatus.Add("marked", InvoiceExceptionStatus.Marked);
|
||||
_ExceptionStatusToString = _StringToExceptionStatus.ToDictionary(kv => kv.Value, kv => kv.Key);
|
||||
_StringToExceptionStatus.Add("false", InvoiceExceptionStatus.None);
|
||||
}
|
||||
public InvoiceState(string status, string exceptionStatus)
|
||||
{
|
||||
Status = _StringToInvoiceStatus[status];
|
||||
ExceptionStatus = _StringToExceptionStatus[exceptionStatus ?? string.Empty];
|
||||
}
|
||||
public InvoiceState(InvoiceStatus status, InvoiceExceptionStatus exceptionStatus)
|
||||
{
|
||||
Status = status;
|
||||
ExceptionStatus = exceptionStatus;
|
||||
}
|
||||
|
||||
public InvoiceStatus Status { get; }
|
||||
public InvoiceExceptionStatus ExceptionStatus { get; }
|
||||
|
||||
public static string ToString(InvoiceStatus status)
|
||||
{
|
||||
return _InvoiceStatusToString[status];
|
||||
}
|
||||
|
||||
public static string ToString(InvoiceExceptionStatus exceptionStatus)
|
||||
{
|
||||
return _ExceptionStatusToString[exceptionStatus];
|
||||
}
|
||||
|
||||
public bool CanMarkComplete()
|
||||
{
|
||||
return (Status == InvoiceStatus.Paid) ||
|
||||
#pragma warning disable CA1305 // Specify IFormatProvider
|
||||
((Status == InvoiceStatus.New || Status == InvoiceStatus.Expired) && ExceptionStatus == InvoiceExceptionStatus.PaidPartial) ||
|
||||
((Status == InvoiceStatus.New || Status == InvoiceStatus.Expired) && ExceptionStatus == InvoiceExceptionStatus.PaidLate) ||
|
||||
(Status != InvoiceStatus.Complete && ExceptionStatus == InvoiceExceptionStatus.Marked) ||
|
||||
(Status == InvoiceStatus.Invalid);
|
||||
#pragma warning restore CA1305 // Specify IFormatProvider
|
||||
}
|
||||
|
||||
public bool CanMarkInvalid()
|
||||
{
|
||||
return (Status == InvoiceStatus.Paid) ||
|
||||
(Status == InvoiceStatus.New) ||
|
||||
#pragma warning disable CA1305 // Specify IFormatProvider
|
||||
((Status == InvoiceStatus.New || Status == InvoiceStatus.Expired) && ExceptionStatus == InvoiceExceptionStatus.PaidPartial) ||
|
||||
((Status == InvoiceStatus.New || Status == InvoiceStatus.Expired) && ExceptionStatus == InvoiceExceptionStatus.PaidLate) ||
|
||||
(Status != InvoiceStatus.Invalid && ExceptionStatus == InvoiceExceptionStatus.Marked);
|
||||
#pragma warning restore CA1305 // Specify IFormatProvider;
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return ToString(Status) + (ExceptionStatus == InvoiceExceptionStatus.None ? string.Empty : $" ({ToString(ExceptionStatus)})");
|
||||
}
|
||||
}
|
||||
|
||||
public class PaymentMethodAccounting
|
||||
{
|
||||
/// <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>
|
||||
|
@ -126,7 +126,9 @@ namespace BTCPayServer.Services.Invoices
|
||||
Created = invoice.InvoiceTime,
|
||||
Blob = ToBytes(invoice, null),
|
||||
OrderId = invoice.OrderId,
|
||||
Status = invoice.Status,
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Status = invoice.StatusString,
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
ItemCode = invoice.ProductInformation.ItemCode,
|
||||
CustomerEmail = invoice.RefundMail
|
||||
});
|
||||
@ -316,15 +318,15 @@ namespace BTCPayServer.Services.Invoices
|
||||
});
|
||||
}
|
||||
|
||||
public async Task UpdateInvoiceStatus(string invoiceId, string status, string exceptionStatus)
|
||||
public async Task UpdateInvoiceStatus(string invoiceId, InvoiceState invoiceState)
|
||||
{
|
||||
using (var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||
if (invoiceData == null)
|
||||
return;
|
||||
invoiceData.Status = status;
|
||||
invoiceData.ExceptionStatus = exceptionStatus;
|
||||
invoiceData.Status = InvoiceState.ToString(invoiceState.Status);
|
||||
invoiceData.ExceptionStatus = InvoiceState.ToString(invoiceState.ExceptionStatus);
|
||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@ -334,13 +336,26 @@ namespace BTCPayServer.Services.Invoices
|
||||
using (var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||
if (invoiceData?.Status != "paid")
|
||||
if (invoiceData == null || !invoiceData.GetInvoiceState().CanMarkInvalid())
|
||||
return;
|
||||
invoiceData.Status = "invalid";
|
||||
invoiceData.ExceptionStatus = "marked";
|
||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
public async Task<InvoiceEntity> GetInvoice(string storeId, string id, bool inludeAddressData = false)
|
||||
public async Task UpdatePaidInvoiceToComplete(string invoiceId)
|
||||
{
|
||||
using (var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||
if (invoiceData == null || !invoiceData.GetInvoiceState().CanMarkComplete())
|
||||
return;
|
||||
invoiceData.Status = "complete";
|
||||
invoiceData.ExceptionStatus = "marked";
|
||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
public async Task<InvoiceEntity> GetInvoice(string id, bool inludeAddressData = false)
|
||||
{
|
||||
using (var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
@ -353,9 +368,6 @@ namespace BTCPayServer.Services.Invoices
|
||||
query = query.Include(o => o.HistoricalAddressInvoices).Include(o => o.AddressInvoices);
|
||||
query = query.Where(i => i.Id == id);
|
||||
|
||||
if (storeId != null)
|
||||
query = query.Where(i => i.StoreDataId == storeId);
|
||||
|
||||
var invoice = await query.FirstOrDefaultAsync().ConfigureAwait(false);
|
||||
if (invoice == null)
|
||||
return null;
|
||||
@ -375,8 +387,9 @@ namespace BTCPayServer.Services.Invoices
|
||||
return paymentEntity;
|
||||
}).ToList();
|
||||
#pragma warning restore CS0618
|
||||
entity.ExceptionStatus = invoice.ExceptionStatus;
|
||||
entity.Status = invoice.Status;
|
||||
var state = invoice.GetInvoiceState();
|
||||
entity.ExceptionStatus = state.ExceptionStatus;
|
||||
entity.Status = state.Status;
|
||||
entity.RefundMail = invoice.CustomerEmail;
|
||||
entity.Refundable = invoice.RefundAddresses.Count != 0;
|
||||
if (invoice.HistoricalAddressInvoices != null)
|
||||
|
@ -39,7 +39,7 @@ namespace BTCPayServer.Services
|
||||
|
||||
public class LightningConfigurations
|
||||
{
|
||||
public List<LightningConfiguration> Configurations { get; set; } = new List<LightningConfiguration>();
|
||||
public List<object> Configurations { get; set; } = new List<object>();
|
||||
}
|
||||
public class LightningConfiguration
|
||||
{
|
||||
@ -51,5 +51,16 @@ namespace BTCPayServer.Services
|
||||
public bool SSL { get; set; }
|
||||
public string CertificateThumbprint { get; set; }
|
||||
public string Macaroon { get; set; }
|
||||
public string RestrictedMacaroon { get; set; }
|
||||
}
|
||||
public class LNDRestConfiguration
|
||||
{
|
||||
public string ChainType { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string Uri { get; set; }
|
||||
public string Macaroon { get; set; }
|
||||
public string CertificateThumbprint { get; set; }
|
||||
public string RestrictedMacaroon { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,15 @@ namespace BTCPayServer.Services.Rates
|
||||
currencyInfo.CurrencySymbol = currency;
|
||||
return currencyInfo;
|
||||
}
|
||||
|
||||
public NumberFormatInfo GetNumberFormatInfo(string currency)
|
||||
{
|
||||
var curr = GetCurrencyProvider(currency);
|
||||
if (curr is CultureInfo cu)
|
||||
return cu.NumberFormat;
|
||||
if (curr is NumberFormatInfo ni)
|
||||
return ni;
|
||||
return null;
|
||||
}
|
||||
public IFormatProvider GetCurrencyProvider(string currency)
|
||||
{
|
||||
lock (_CurrencyProviders)
|
||||
|
@ -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>
|
||||
|
61
BTCPayServer/Views/Home/BitpayTranslator.cshtml
Normal file
61
BTCPayServer/Views/Home/BitpayTranslator.cshtml
Normal file
@ -0,0 +1,61 @@
|
||||
@model BitpayTranslatorViewModel
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
@if (Model.Address != null)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<div class="alert alert-success alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<p>You need to pay <b>@Model.Amount</b> to <b>@Model.Address</b></p>
|
||||
<div id="qrCode"></div>
|
||||
<div id="qrCodeData" data-url="@Html.Raw(Model.BitcoinUri)" style="margin-bottom:20px;"></div>
|
||||
<p>
|
||||
<a class="btn btn-primary" href="@Model.BitcoinUri">
|
||||
<span>Open in wallet</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<h2 class="section-heading">The Bitpay Translator</h2>
|
||||
<hr class="primary">
|
||||
<p>Bitpay is using deprecated standard in their invoices which multiple wallet do not support, use this transform their invoices to regular address/amount.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-4 text-center"> </div>
|
||||
<div class="col-lg-4 text-center">
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<label asp-for="BitpayLink" class="control-label"></label>*
|
||||
<input asp-for="BitpayLink" class="form-control" />
|
||||
<span asp-validation-for="BitpayLink" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-secondary" title="Continue">Translate to address</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-lg-4 text-center"> </div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
<script type="text/javascript" src="~/js/qrcode.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
new QRCode(document.getElementById("qrCode"),
|
||||
{
|
||||
text: "@Html.Raw(Model.BitcoinUri)",
|
||||
width: 150,
|
||||
height: 150
|
||||
});
|
||||
$("#qrCode > img").css({ "margin": "auto" });
|
||||
</script>
|
||||
}
|
@ -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,130 @@
|
||||
</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>State</th>
|
||||
<td>@Model.State</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>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 +182,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">
|
||||
@ -171,7 +217,7 @@
|
||||
<th class="text-right">Rate</th>
|
||||
<th class="text-right">Paid</th>
|
||||
<th class="text-right">Due</th>
|
||||
@if (Model.StatusException == "paidOver")
|
||||
@if (Model.StatusException == InvoiceExceptionStatus.PaidOver)
|
||||
{
|
||||
<th class="text-right">Overpaid</th>
|
||||
}
|
||||
@ -186,7 +232,7 @@
|
||||
<td class="text-right">@payment.Rate</td>
|
||||
<td class="text-right">@payment.Paid</td>
|
||||
<td class="text-right">@payment.Due</td>
|
||||
@if (Model.StatusException == "paidOver")
|
||||
@if (Model.StatusException == InvoiceExceptionStatus.PaidOver)
|
||||
{
|
||||
<td class="text-right">@payment.Overpaid</td>
|
||||
}
|
||||
@ -255,29 +301,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">
|
||||
@ -91,23 +99,7 @@
|
||||
}
|
||||
</td>
|
||||
<td>@invoice.InvoiceId</td>
|
||||
@if (invoice.Status == "paid")
|
||||
{
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
<a class="dropdown-toggle dropdown-toggle-split" style="cursor: pointer;" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
@invoice.Status <span class="sr-only">Toggle Dropdown</span>
|
||||
</a>
|
||||
<div class="dropdown-menu pull-right">
|
||||
<button class="dropdown-item small" data-toggle="modal" data-target="#myModal" onclick="$('#invoiceId').val('@invoice.InvoiceId')">Make Invalid</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td>@invoice.Status</td>
|
||||
}
|
||||
<td>@invoice.Status</td>
|
||||
<td style="text-align:right">@invoice.AmountCurrency</td>
|
||||
<td style="text-align:right">
|
||||
@if (invoice.ShowCheckout)
|
||||
@ -115,9 +107,32 @@
|
||||
<span>
|
||||
<a asp-action="Checkout" asp-route-invoiceId="@invoice.InvoiceId">Checkout</a>
|
||||
<a href="javascript:btcpay.showInvoice('@invoice.InvoiceId')">[^]</a>
|
||||
-
|
||||
@if (!invoice.CanMarkStatus)
|
||||
{
|
||||
<span>-</span>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
@if (invoice.CanMarkStatus)
|
||||
{
|
||||
<a class="dropdown-toggle dropdown-toggle-split" href="#" style="cursor: pointer;" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Change status <span class="sr-only">Toggle Dropdown</span>
|
||||
</a>
|
||||
<div class="dropdown-menu pull-right">
|
||||
@if (invoice.CanMarkInvalid)
|
||||
{
|
||||
<form method="get" asp-action="ChangeInvoiceState" asp-route-invoiceId="@invoice.InvoiceId" asp-route-newState="invalid">
|
||||
<button class="dropdown-item small">Mark as invalid <span class="fa fa-times"></span></button>
|
||||
</form>
|
||||
}
|
||||
@if (invoice.CanMarkComplete)
|
||||
{
|
||||
<form method="get" asp-action="ChangeInvoiceState" asp-route-invoiceId="@invoice.InvoiceId" asp-route-newState="complete">
|
||||
<button class="dropdown-item small">Mark as complete <span class="fa fa-check-circle"></span></button>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<a asp-action="Invoice" asp-route-invoiceId="@invoice.InvoiceId">Details</a>
|
||||
</td>
|
||||
</tr>
|
||||
@ -145,28 +160,3 @@
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Modal -->
|
||||
<div id="myModal" class="modal fade" role="dialog">
|
||||
<form method="post" action="/invoices/invalidatepaid">
|
||||
<input id="invoiceId" name="invoiceId" type="hidden" />
|
||||
<div class="modal-dialog">
|
||||
|
||||
<!-- Modal content-->
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Set Invoice status to Invalid</h4>
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to invalidate this transaction? This action is NOT undoable!</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-danger">Yes, make invoice Invalid</button>
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
1
BTCPayServer/Views/Invoice/_ViewImports.cshtml
Normal file
1
BTCPayServer/Views/Invoice/_ViewImports.cshtml
Normal file
@ -0,0 +1 @@
|
||||
@using BTCPayServer.Services.Invoices
|
@ -1,41 +0,0 @@
|
||||
@model LndRestServicesViewModel
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(ServerNavPages.Services);
|
||||
}
|
||||
|
||||
|
||||
<h4>LND REST</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]" />
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<p>BTCPay exposes LND Rest services for outside consumption. See connection information below</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="BaseApiUrl">Base API Url</label>
|
||||
<input asp-for="BaseApiUrl" readonly class="form-control" />
|
||||
</div>
|
||||
@if (Model.Macaroon != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="Macaroon"></label>
|
||||
<input asp-for="Macaroon" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if (Model.CertificateThumbprint != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="CertificateThumbprint"></label>
|
||||
<input asp-for="CertificateThumbprint" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
@ -4,7 +4,7 @@
|
||||
}
|
||||
|
||||
|
||||
<h4>LND GRPC</h4>
|
||||
<h4>LND @Model.ConnectionType</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]" />
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
@ -17,14 +17,14 @@
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<p>
|
||||
<span>BTCPay exposes gRPC services for outside consumption, you will find connection information here.<br /></span>
|
||||
<span>BTCPay exposes LND's @Model.ConnectionType service for outside consumption, you will find connection information here.<br /></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<h5>QR Code connection</h5>
|
||||
<p>
|
||||
<span>You can use this QR Code to connect your Zap wallet to your LND instance.<br /></span>
|
||||
<span>You can use this QR Code to connect external software to your LND instance.<br /></span>
|
||||
<span>This QR Code is only valid for 10 minutes</span>
|
||||
</p>
|
||||
</div>
|
||||
@ -61,22 +61,39 @@
|
||||
<p>Alternatively, you can see the settings by clicking <a href="#details" data-toggle="collapse">here</a></p>
|
||||
</div>
|
||||
<div id="details" class="collapse">
|
||||
<div class="form-group">
|
||||
<label asp-for="Host"></label>
|
||||
<input asp-for="Host" readonly class="form-control" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="SSL"></label>
|
||||
<input asp-for="SSL" disabled type="checkbox" class="form-check-inline" />
|
||||
</div>
|
||||
@if(Model.Macaroon != null)
|
||||
@if (Model.Uri == null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="Host"></label>
|
||||
<input asp-for="Host" readonly class="form-control" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="SSL"></label>
|
||||
<input asp-for="SSL" disabled type="checkbox" class="form-check-inline" />
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="Uri"></label>
|
||||
<input asp-for="Uri" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if (Model.Macaroon != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="Macaroon"></label>
|
||||
<input asp-for="Macaroon" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if(Model.CertificateThumbprint != null)
|
||||
@if (Model.RestrictedMacaroon != null)
|
||||
{
|
||||
@*<div class="form-group">
|
||||
<label asp-for="RestrictedMacaroon"></label>
|
||||
<input asp-for="RestrictedMacaroon" readonly class="form-control" />
|
||||
</div>*@
|
||||
}
|
||||
@if (Model.CertificateThumbprint != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="CertificateThumbprint"></label>
|
@ -15,8 +15,9 @@
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-8">
|
||||
<h4>Crypto services</h4>
|
||||
<div class="form-group">
|
||||
<span>You can get access here to LND (gRPC, Rest) or SSH services exposed by your server</span>
|
||||
<span>You can get access here to LND (gRPC, Rest) services exposed by your server</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
@ -33,26 +34,9 @@
|
||||
{
|
||||
<tr>
|
||||
<td>@lnd.Crypto</td>
|
||||
<td>LND @lnd.Type.ToString()</td>
|
||||
<td>@lnd.Type</td>
|
||||
<td style="text-align:right">
|
||||
@if (lnd.Type == BTCPayServer.Configuration.External.LndTypes.gRPC)
|
||||
{
|
||||
<a asp-action="LNDGRPCServices" asp-route-cryptoCode="@lnd.Crypto" asp-route-index="@lnd.Index">See information</a>
|
||||
}
|
||||
else if (lnd.Type == BTCPayServer.Configuration.External.LndTypes.Rest)
|
||||
{
|
||||
<a asp-action="LndRestServices" asp-route-cryptoCode="@lnd.Crypto" asp-route-index="@lnd.Index">See information</a>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (Model.HasSSH)
|
||||
{
|
||||
<tr>
|
||||
<td>None</td>
|
||||
<td>SSH</td>
|
||||
<td style="text-align:right">
|
||||
<a asp-action="SSHService">See information</a>
|
||||
<a asp-action="@lnd.Action" asp-route-cryptoCode="@lnd.Crypto" asp-route-index="@lnd.Index">See information</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@ -62,6 +46,39 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.ExternalServices.Count != 0)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<h4>Other services</h4>
|
||||
<div class="form-group">
|
||||
<span>Other external services</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var s in Model.ExternalServices)
|
||||
{
|
||||
<tr>
|
||||
<td>@s.Name</td>
|
||||
<td style="text-align:right">
|
||||
<a href="@s.Link" target="_blank">See information</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
||||
|
79
BTCPayServer/Views/Server/SparkServices.cshtml
Normal file
79
BTCPayServer/Views/Server/SparkServices.cshtml
Normal file
@ -0,0 +1,79 @@
|
||||
@model SparkServicesViewModel
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(ServerNavPages.Services);
|
||||
}
|
||||
|
||||
|
||||
<h4>Spark service</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]" />
|
||||
|
||||
@if (Model.ShowQR)
|
||||
{
|
||||
<div class="alert alert-warning alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<div>
|
||||
<span><b>CONFIDENTIAL:</b> This QR Code is confidential, close this window as soon as you don't need it anymore.<br /></span>
|
||||
<span>A malicious actor with access to this QR Code could steal the funds on your lightning wallet.</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<h5>Browser connection</h5>
|
||||
<p>
|
||||
<span>You can go to spark from your browser by <a href="@Model.SparkLink" target="_blank">clicking here</a><br /></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<h5>QR Code connection</h5>
|
||||
<p>
|
||||
<span>You can use QR Code to connect to your clightning from your mobile.<br /></span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@if (!Model.ShowQR)
|
||||
{
|
||||
<div class="form-group">
|
||||
<form method="get">
|
||||
<input type="hidden" asp-for="ShowQR" value="true" />
|
||||
<button type="submit" class="btn btn-primary">Show Confidential QR Code</button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="form-group">
|
||||
<div id="qrCode"></div>
|
||||
<div id="qrCodeData" data-url="@Html.Raw(Model.SparkLink)"></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
|
||||
@if (Model.ShowQR)
|
||||
{
|
||||
<script type="text/javascript" src="~/js/qrcode.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
new QRCode(document.getElementById("qrCode"),
|
||||
{
|
||||
text: "@Html.Raw(Model.SparkLink)",
|
||||
width: 150,
|
||||
height: 150
|
||||
});
|
||||
</script>
|
||||
}
|
||||
}
|
@ -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">
|
||||
|
@ -15,7 +15,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<form method="post">
|
||||
@if(!Model.Confirmation)
|
||||
@if (!Model.Confirmation)
|
||||
{
|
||||
<div class="form-group">
|
||||
<h5>Derivation Scheme</h5>
|
||||
@ -26,13 +26,19 @@
|
||||
<label asp-for="DerivationScheme"></label>
|
||||
<input asp-for="DerivationScheme" class="form-control" />
|
||||
<span asp-validation-for="DerivationScheme" class="text-danger"></span>
|
||||
<div id="ledger-loading" class="form-text text-muted">
|
||||
Checking if a ledger wallet is connected...
|
||||
</div>
|
||||
<div id="ledger-validate" class="form-text text-muted" style="display: none;">
|
||||
Please validate access on your screen...
|
||||
</div>
|
||||
<p id="no-ledger-info" class="form-text text-muted" style="display: none;">
|
||||
No ledger wallet detected. If you own one, use chrome, open the app, and refresh this page.
|
||||
</p>
|
||||
<div id="ledger-info" class="form-text text-muted" style="display: none;">
|
||||
<span>A ledger wallet is detected, which account do you want to use?</span>
|
||||
<ul>
|
||||
@for(int i = 0; i < 4; i++)
|
||||
@for (int i = 0; i < 4; i++)
|
||||
{
|
||||
<li><a class="ledger-info-recommended" data-ledgeraccount="@i" href="#">Account @i (<span>@Model.RootKeyPath.Derive(i, true)</span>)</a></li>
|
||||
}
|
||||
@ -100,7 +106,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(var sample in Model.AddressSamples)
|
||||
@foreach (var sample in Model.AddressSamples)
|
||||
{
|
||||
<tr>
|
||||
<td>@sample.KeyPath</td>
|
||||
|
@ -7,6 +7,24 @@
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<partial name="_StatusMessage" for="StatusMessage" />
|
||||
|
||||
<div class="alert alert-warning alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<p>
|
||||
<span>Before you proceed, please understand that Lightning Network is still in the experimental stage. Do not put the money you can't afford to lose. There is a high risk of you losing the money.</span>
|
||||
</p>
|
||||
<p>
|
||||
Take time to familiarize yourself with the risk. There's no backup for LND or c-lightning keys in BTCPay. Your keys are in a hot-wallet. This means :
|
||||
</p>
|
||||
<ul>
|
||||
<li>Most of BTCPay Server deployments run on a pruned node, which, while working, is not officially supported by lightning network implementations.</li>
|
||||
<li>If you erase your BTCPay Server virtual machine - you lose all the funds.</li>
|
||||
<li>If your server gets hacked - a hacker can take all of your funds by accessing your keys.</li>
|
||||
<li>If there is a bug in a lightning network implementation - you might lose all the funds.</li>
|
||||
<li>You approve being #reckless and being the sole responsible party for your loss.</li>
|
||||
<li>You approve to keep on your lightning node only what you can afford to lose.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<form method="post">
|
||||
@ -54,7 +72,7 @@
|
||||
<label asp-for="ConnectionString"></label>
|
||||
<input id="lightningurl" asp-for="ConnectionString" class="form-control" />
|
||||
<span asp-validation-for="ConnectionString" class="text-danger"></span>
|
||||
@if(Model.InternalLightningNode != null)
|
||||
@if (Model.InternalLightningNode != null)
|
||||
{
|
||||
<p class="form-text text-muted">
|
||||
You can use the internal lightning node by <a href="#" onclick="$('#lightningurl').val('@Model.InternalLightningNode'); return false;">clicking here</a>
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
305
BTCPayServer/wwwroot/cart/js/cart.js
Normal file
305
BTCPayServer/wwwroot/cart/js/cart.js
Normal file
@ -0,0 +1,305 @@
|
||||
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(), srvModel.currencyCode, srvModel.currencySymbol));
|
||||
}
|
||||
|
||||
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),
|
||||
total = this.escape(this.formatCurrency(this.getTotal(), srvModel.currencyCode, srvModel.currencySymbol)),
|
||||
step = this.escape(srvModel.step),
|
||||
tip = this.escape(this.tip || ''),
|
||||
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-7 py-2">' + customTipText + '</div><div class="col-sm-5">' +
|
||||
'<div class="input-group">' +
|
||||
'<div class="input-group-prepend">' +
|
||||
'<span class="input-group-text"><i class="fa fa-money"></i></span>' +
|
||||
'</div>' +
|
||||
'<input class="js-cart-tip form-control" type="number" min="0" step="' + step + '" value="' + tip + '" name="tip" placeholder="Amount">' +
|
||||
'</div>' +
|
||||
'</div></div></td></tr>';
|
||||
list.push($(tableTemplate));
|
||||
|
||||
tableTemplate = '<tr class="bg-light h4"><td colspan="1">Total</td><td colspan="3" align="right"><span id="js-cart-total">' + total + '</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>');
|
||||
}
|
||||
|
||||
Cart.prototype.formatCurrency = function(amount, currency, symbol) {
|
||||
var amt = '',
|
||||
thousandsSep = '',
|
||||
decimalSep = ''
|
||||
prefix = '',
|
||||
postfix = '';
|
||||
|
||||
if (srvModel.currencyInfo.prefixed) {
|
||||
prefix = srvModel.currencyInfo.currencySymbol;
|
||||
}
|
||||
else {
|
||||
postfix = srvModel.currencyInfo.currencySymbol;
|
||||
}
|
||||
thousandsSep = srvModel.currencyInfo.thousandSeparator;
|
||||
decimalSep = srvModel.currencyInfo.decimalSeparator;
|
||||
amt = amount.toFixed(srvModel.currencyInfo.divisibility);
|
||||
|
||||
// Add currency sign and thousands separator
|
||||
var splittedAmount = amt.split('.');
|
||||
amt = (splittedAmount[0] + '.').replace(/(\d)(?=(\d{3})+\.)/g, '$1' + thousandsSep);
|
||||
amt = amt.substr(0, amt.length - 1);
|
||||
if(splittedAmount.length == 2)
|
||||
amt = amt + '.' + splittedAmount[1];
|
||||
if (srvModel.currencyInfo.divisibility !== 0) {
|
||||
amt[amt.length - srvModel.currencyInfo.divisibility - 1] = decimalSep;
|
||||
}
|
||||
amt = prefix + amt + postfix;
|
||||
|
||||
return amt;
|
||||
}
|
||||
|
||||
Cart.prototype.toCents = function(num) {
|
||||
return num * Math.pow(10, srvModel.currencyInfo.divisibility);
|
||||
}
|
||||
|
||||
Cart.prototype.fromCents = function(num) {
|
||||
return num / Math.pow(10, srvModel.currencyInfo.divisibility);
|
||||
}
|
||||
|
||||
Cart.prototype.getStorageKey = function () { return ('cart' + srvModel.appId + srvModel.currencyCode); }
|
||||
|
||||
Cart.prototype.saveLocalStorage = function() {
|
||||
localStorage.setItem(this.getStorageKey(), JSON.stringify(this.content));
|
||||
}
|
||||
|
||||
Cart.prototype.loadLocalStorage = function() {
|
||||
this.content = $.parseJSON(localStorage.getItem(this.getStorageKey())) || [];
|
||||
|
||||
// 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(this.getStorageKey());
|
||||
this.content = [];
|
||||
this.items = 0;
|
||||
this.totalAmount = 0;
|
||||
this.tip = 0;
|
||||
}
|
@ -20,22 +20,32 @@
|
||||
function WriteAlert(type, message) {
|
||||
|
||||
}
|
||||
|
||||
function showFeedback(id) {
|
||||
$("#ledger-loading").css("display", id === "ledger-loading" ? "block" : "none");
|
||||
$("#no-ledger-info").css("display", id === "no-ledger-info" ? "block" : "none");
|
||||
$("#ledger-validate").css("display", id === "ledger-validate" ? "block" : "none");
|
||||
$("#ledger-info").css("display", id === "ledger-info" ? "block" : "none");
|
||||
}
|
||||
function Write(prefix, type, message) {
|
||||
if (type === "error") {
|
||||
$("#no-ledger-info").css("display", "block");
|
||||
$("#ledger-in fo").css("display", "none");
|
||||
showFeedback("no-ledger-info");
|
||||
}
|
||||
}
|
||||
|
||||
$(".ledger-info-recommended").on("click", function (elem) {
|
||||
elem.preventDefault();
|
||||
|
||||
showFeedback("ledger-validate");
|
||||
|
||||
var account = elem.currentTarget.getAttribute("data-ledgeraccount");
|
||||
var cryptoCode = GetSelectedCryptoCode();
|
||||
bridge.sendCommand("getxpub", "cryptoCode=" + cryptoCode + "&account=" + account)
|
||||
.then(function (result) {
|
||||
if (cryptoCode !== GetSelectedCryptoCode())
|
||||
return;
|
||||
|
||||
showFeedback("ledger-info");
|
||||
|
||||
$("#DerivationScheme").val(result.extPubKey);
|
||||
$("#DerivationSchemeFormat").val("BTCPay");
|
||||
})
|
||||
@ -60,8 +70,7 @@
|
||||
}
|
||||
else {
|
||||
Write('check', 'success', 'This store is configured to use your ledger');
|
||||
$("#no-ledger-info").css("display", "none");
|
||||
$("#ledger-info").css("display", "block");
|
||||
showFeedback("ledger-info");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -33,7 +33,7 @@
|
||||
"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_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.",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"Invoice ID": "ID Faktury",
|
||||
"Order ID": "ID Objednávky",
|
||||
"Return to StoreName": "Vrátit se na {{storeName}}",
|
||||
|
@ -33,7 +33,7 @@
|
||||
"What happened?": "Was ist passiert?",
|
||||
"InvoiceExpired_Body_1": "Diese Rechnung ist abgelaufen. Eine Rechnung ist nur für {{maxTimeMinutes}} Minuten gültig. \nSie können zu {{storeName}} zurückkehren, wenn Sie Ihre Zahlung erneut senden möchten.",
|
||||
"InvoiceExpired_Body_2": "Wenn Sie versucht haben, eine Zahlung zu senden, wurde sie vom Netzwerk noch nicht akzeptiert. Wir haben Ihre Gelder noch nicht erhalten.",
|
||||
"InvoiceExpired_Body_3": "Wenn die Transaktion vom Netzwerk nicht akzeptiert wird, ist das Geld wieder in Ihrer Wallet verfügbar. Abhängig von Ihrer Wallet, kann dies 48-72 Stunden dauern.",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"Invoice ID": "Rechnungs ID",
|
||||
"Order ID": "Auftrag ID",
|
||||
"Return to StoreName": "Zurück zu {{storeName}}",
|
||||
|
@ -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": "en",
|
||||
"currentLanguage": "English",
|
||||
"lang": "Language",
|
||||
@ -33,7 +33,7 @@
|
||||
"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_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.",
|
||||
"InvoiceExpired_Body_3": "If we receive it at a later point, we will either process your order or contact you to make refund arrangements...",
|
||||
"Invoice ID": "Invoice ID",
|
||||
"Order ID": "Order ID",
|
||||
"Return to StoreName": "Return to {{storeName}}",
|
||||
@ -44,4 +44,4 @@
|
||||
"Node Info": "Node Info",
|
||||
"txCount": "{{count}} transaction",
|
||||
"txCount_plural": "{{count}} transactions"
|
||||
}
|
||||
}
|
@ -33,7 +33,7 @@
|
||||
"What happened?": "¿Qué sucedió?",
|
||||
"InvoiceExpired_Body_1": "Esta factura ha expirado. Una factura solo es válida por {{maxTimeMinutes}} minutos. \nPuedes regresar a {{storeName}} si deseas volver a enviar tu pago.",
|
||||
"InvoiceExpired_Body_2": "Si intentaste enviar un pago, aún no ha sido aceptado por la red de Bitcoin. Todavía no hemos recibido tus fondos.",
|
||||
"InvoiceExpired_Body_3": "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.",
|
||||
"InvoiceExpired_Body_3": "Si recibimos el pago despues, procesaremos tu orden o te contactaremos para un reembolso...",
|
||||
"Invoice ID": "ID de la factura",
|
||||
"Order ID": "ID del pedido",
|
||||
"Return to StoreName": "Regresar a {{storeName}}",
|
||||
|
@ -33,7 +33,7 @@
|
||||
"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_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.",
|
||||
"InvoiceExpired_Body_3": "Si nous recevons votre paiement plus tard, nous traiterons votre commande ou nous vous contacterons pour le remboursement.",
|
||||
"Invoice ID": "Numéro de facture",
|
||||
"Order ID": "Numéro de commande",
|
||||
"Return to StoreName": "Retourner sur {{storeName}}",
|
||||
|
47
BTCPayServer/wwwroot/locales/hi.json
Normal file
47
BTCPayServer/wwwroot/locales/hi.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATED FROM TRANSIFEX, IF YOU WISH TO HELP TRANSLATION COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"code": "hi",
|
||||
"currentLanguage": "हिंदी",
|
||||
"lang": "भाषा",
|
||||
"Awaiting Payment...": "भुगतान के लिए प्रतीक्षा कर रहे हैं…",
|
||||
"Pay with": "इसके साथ भुगतान करें",
|
||||
"Contact and Refund Email": "संपर्क और धनवापसी ईमेल",
|
||||
"Contact_Body": "कृपया नीचे एक ईमेल पता प्रदान करें। यदि आपके भुगतान में कोई समस्या होती है तो हम इस पते पर आपसे संपर्क करेंगे।",
|
||||
"Your email": "आपका ईमेल",
|
||||
"Continue": "जारी रखें",
|
||||
"Please enter a valid email address": "कृपया एक वैध ईमेल पता दर्ज करें",
|
||||
"Order Amount": "ऑर्डर की राशि",
|
||||
"Network Cost": "नेटवर्क लागत",
|
||||
"Already Paid": "भुगतान पहले ही किया जा चुका है",
|
||||
"Due": "देय",
|
||||
"Scan": "स्कैन करें",
|
||||
"Copy": "कॉपी करें",
|
||||
"Conversion": "रूपांतरण",
|
||||
"Open in wallet": "वॉलेट खोलें",
|
||||
"CompletePay_Body": "अपना भुगतान पूरा करने के लिए, कृपया नीचे दिए गए पते पर {{btcDue}} {{cryptoCode}} भेजें.",
|
||||
"Amount": "धनराशि",
|
||||
"Address": "पता",
|
||||
"Copied": "कॉपी किया गया",
|
||||
"ConversionTab_BodyTop": "आप मर्चेंट द्वारा सीधे समर्थित भुगतान विकल्पों के मुकाबले {{btcDue}} {{cryptoCode}} का भुगतान altcoins का उपयोग करके भी कर सकते हैं।.",
|
||||
"ConversionTab_BodyDesc": "यह सेवा किसी तृतीय पक्ष द्वारा प्रदान की जाती है। कृपया ध्यान रखें कि प्रदाता आपके धन को कैसे आगे बढ़ाएंगे इस पर हमारा कोई नियंत्रण नहीं है। केवल {{cryptoCode}} ब्लॉकचेन पर धन प्राप्त होने के बाद ही चालान को चिह्नित किया जाएगा।.",
|
||||
"ConversionTab_CalculateAmount_Error": "Retry",
|
||||
"ConversionTab_LoadCurrencies_Error": "Retry",
|
||||
"ConversionTab_Lightning": "लाइटनिंग नेटवर्क भुगतान के लिए कोई रूपांतरण प्रदाता उपलब्ध नहीं है।",
|
||||
"ConversionTab_CurrencyList_Select_Option": "Please select a currency to convert from",
|
||||
"Invoice expiring soon...": "चालान जल्द ही समाप्त हो जाएगा...",
|
||||
"Invoice expired": "चालान की समय सीमा समाप्त हो गयी",
|
||||
"What happened?": "क्या हुआ?",
|
||||
"InvoiceExpired_Body_1": "इस चालान की समयसीमा समाप्त हो गयी। कोई भी चालान केवल {{maxTimeMinutes}} मिनटों के लिए वैध रहता है। \nयदि आप अपना भुगतान दोबारा जमा करना चाहते हैं तो आप {{storeName}} पर वापस जा सकते हैं।",
|
||||
"InvoiceExpired_Body_2": "यदि आपने भुगतान भेजने का प्रयास किया है, तो इसे अभी तक नेटवर्क द्वारा स्वीकार नहीं किया गया है। हमें अभी तक आपकी राशि प्राप्त नहीं हुई है।",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"Invoice ID": "चालान आईडी",
|
||||
"Order ID": "आर्डर आईडी",
|
||||
"Return to StoreName": "{{storeName}} पर वापस जाएं",
|
||||
"This invoice has been paid": "इस चालान का भुगतान कर दिया गया है",
|
||||
"This invoice has been archived": "यह चालान संग्रहीत कर दिया गया है",
|
||||
"Archived_Body": "ऑर्डर जानकारी या सहायता के लिए स्टोर से संपर्क करें",
|
||||
"BOLT 11 Invoice": "BOLT 11 चालान",
|
||||
"Node Info": "नोड जानकारी",
|
||||
"txCount": "लेनदेन",
|
||||
"txCount_plural": "लेनदेनों"
|
||||
}
|
@ -33,7 +33,7 @@
|
||||
"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_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.",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"Invoice ID": "Broj računa",
|
||||
"Order ID": "Broj narudžbe",
|
||||
"Return to StoreName": "Vrati se na {{storeName}}",
|
||||
|
@ -33,7 +33,7 @@
|
||||
"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_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.",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"Invoice ID": "Innheimtu ID",
|
||||
"Order ID": "Pöntun ID",
|
||||
"Return to StoreName": "Fara aftur á {{storeName}}",
|
||||
|
@ -33,7 +33,7 @@
|
||||
"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_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.",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"Invoice ID": "Numero della Fattura",
|
||||
"Order ID": "Numero dell'Ordine",
|
||||
"Return to StoreName": "Ritorna a {{storeName}}",
|
||||
|
@ -33,7 +33,7 @@
|
||||
"What happened?": "え!?ナニコレ!?",
|
||||
"InvoiceExpired_Body_1": "当件のお支払いの有効期限が過ぎてしまいました。最大 {{maxTimeMinutes}} 分以内に支払うことが義務付けられています。 \nまだお支払いのご希望の場合 {{storeName}} に一旦戻っていただき、もう一度お支払いの手続きを最初からやり直してみてください。",
|
||||
"InvoiceExpired_Body_2": "送金手続きを完了したつもりでも、ネットワークにて取り込まれて処理されるまでは決済となりません。現時点ではまだ着金しておりません。",
|
||||
"InvoiceExpired_Body_3": "ネットワークにて取り込まれなかった送金はいずれ送金元のウォレットに戻りますが、ウォレットソフトによっては2〜3日かかる場合もございますのでご了承ください。",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"Invoice ID": "お支払い ID",
|
||||
"Order ID": "ご注文 ID",
|
||||
"Return to StoreName": "{{storeName}} に戻る",
|
||||
|
@ -33,7 +33,7 @@
|
||||
"What happened?": "Не жағдай болды?",
|
||||
"InvoiceExpired_Body_1": "Бұл шот-фактураның мерзімі аяқталды. Шот-фактура {{maxTimeMinutes}} минуттарға жарамды. \nТөлеміңізді қайтадан жібергіңіз келсе {{storeName}} ға оралуға болады.",
|
||||
"InvoiceExpired_Body_2": "Егер сіз төлемді жіберуге тырыссқан болсаңыз, ол әлі желімен қабылданған жоқ. Біз сіздің қаражатыңызды әлі алған жоқпыз.",
|
||||
"InvoiceExpired_Body_3": "Егер транзакция желі арқылы қабылданбаса, қаражат әмияныңызға қайтадан қайтарылады. Бұл сіздің әмияныңызға байланысты 48-72 сағат алуы мүмкін.",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"Invoice ID": "Шот анықтамасы",
|
||||
"Order ID": "Тапсырыс нөмірі",
|
||||
"Return to StoreName": "{{storeName}} оралу",
|
||||
|
@ -33,7 +33,7 @@
|
||||
"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_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.",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"Invoice ID": "Factuurnummer",
|
||||
"Order ID": "Bestllingsnummer",
|
||||
"Return to StoreName": "Terug naar {{storeName}}",
|
||||
|
@ -33,7 +33,7 @@
|
||||
"What happened?": "के भयो",
|
||||
"InvoiceExpired_Body_1": "यो चलानी समाप्त भएको छ। इनभ्वाइस {{maxTimeMinutes}} मिनेटको लागि मात्र वैध छ। \nतपाइँ आफ्नो भुक्तानी पुन: पेश गर्न चाहानुहुन्छ यदि तपाइँ {{ storeName }} भुक्तानी पठाउन प्रयास गर्नुभयो भने।",
|
||||
"InvoiceExpired_Body_2": "भने यो अझै नेटवर्कद्वारा स्वीकार गरिएको छैन। हामीले अझै सम्म तपाईंको रकम प्राप्त गरेका छैनौ।",
|
||||
"InvoiceExpired_Body_3": "यदि लेनदेन नेटवर्क द्वारा स्वीकार गरेन भने, रकम तपाईंको वालेटमा फेरि खर्चयोग्य हुनेछ। तपाईंको वालेटको आधारमा, यो 48-72 घण्टा लाग्न सक्छ",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"Invoice ID": "चलानी आईडी",
|
||||
"Order ID": "अर्डर आईडी",
|
||||
"Return to StoreName": "यसलाई फर्काउनुहोस् {{storeName}}",
|
||||
|
47
BTCPayServer/wwwroot/locales/pl.json
Normal file
47
BTCPayServer/wwwroot/locales/pl.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATED FROM TRANSIFEX, IF YOU WISH TO HELP TRANSLATION COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"code": "pl",
|
||||
"currentLanguage": "Polski",
|
||||
"lang": "Język",
|
||||
"Awaiting Payment...": "Oczekiwanie na płatność...",
|
||||
"Pay with": "Płać z",
|
||||
"Contact and Refund Email": "Email do kontaktu i reklamacji",
|
||||
"Contact_Body": "Proszę podać adres email poniżej. Jeżeli będzie problem z płatnością użyjemy go do kontaktu",
|
||||
"Your email": "Twój email",
|
||||
"Continue": "Dalej",
|
||||
"Please enter a valid email address": "Proszę podać prawidłowy adres email",
|
||||
"Order Amount": "Kwota zamówienia",
|
||||
"Network Cost": "Koszt sieci",
|
||||
"Already Paid": "Już zapłacone",
|
||||
"Due": "Razem",
|
||||
"Scan": "Skan",
|
||||
"Copy": "Kopia",
|
||||
"Conversion": "Konwersja",
|
||||
"Open in wallet": "Otwórz w porfelu",
|
||||
"CompletePay_Body": "Aby dokonać płatności proszę wysłać {{btcDue}} {{cryptoCode}} na adres podany niżej.",
|
||||
"Amount": "Kwota",
|
||||
"Address": "Adres",
|
||||
"Copied": "Skopiowano",
|
||||
"ConversionTab_BodyTop": "Możesz zapłacić {{btcDue}} {{cryptoCode}} używając altcoina innego niż ten, który akceptuje bezpośrednio sprzedawca.",
|
||||
"ConversionTab_BodyDesc": "Ten serwis prowadzony jest przez 3-cią stronę. Proszę pamiętać, że nie mamy kontroli nad tym jak dostawcy przekażą twoje fundusze. Płatność będzie uznana za opłaconą, tylko gdy otrzymane zostaną poprzez {{cryptoCode}}",
|
||||
"ConversionTab_CalculateAmount_Error": "Ponów",
|
||||
"ConversionTab_LoadCurrencies_Error": "Ponów",
|
||||
"ConversionTab_Lightning": "Brak dostawcy kowertującego dla płatności Lightning Network",
|
||||
"ConversionTab_CurrencyList_Select_Option": "Wybierz walutę która będzie konwertowana",
|
||||
"Invoice expiring soon...": "Płatność wkrótce wygaśnie...",
|
||||
"Invoice expired": "Płatność wygasła",
|
||||
"What happened?": "Co się stało?",
|
||||
"InvoiceExpired_Body_1": "Płatność wygasła. Płatność jest prawidłowa przez {{maxTimeMinutes}} minut. Jeśli chcesz wysłaść płatność ponownie wróć do {{storeName}}.",
|
||||
"InvoiceExpired_Body_2": "Jeżeli próbowałeś wysłać płatność to nie została ona zaakcepowana przez sieć. Nie otrzymaliśmy jeszcze twoich funduszy.",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"Invoice ID": "Płatność ID",
|
||||
"Order ID": "Zamówienie ID",
|
||||
"Return to StoreName": "Wróć do {{storeName}}",
|
||||
"This invoice has been paid": "Płatność wykonana",
|
||||
"This invoice has been archived": "Płatność została zarchiwizowana",
|
||||
"Archived_Body": "Proszę skontaktować się ze sprzedawcą w celu uzyskania informacji lub wsparcia",
|
||||
"BOLT 11 Invoice": "Płatność BOLT 11",
|
||||
"Node Info": "Informacja o węźle",
|
||||
"txCount": "{{count}} transakcja",
|
||||
"txCount_plural": "{{count}} transakcji"
|
||||
}
|
@ -33,7 +33,7 @@
|
||||
"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_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.",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"Invoice ID": "Nº da Fatura",
|
||||
"Order ID": "Nº do Pedido",
|
||||
"Return to StoreName": "Voltar para {{storeName}}",
|
||||
|
@ -33,7 +33,7 @@
|
||||
"What happened?": "O que aconteceu?",
|
||||
"InvoiceExpired_Body_1": "Esta fatura expirou. Uma fatura é válida durante {{maxTimeMinutes}} minutos. \nPode voltar para {{storeName}} se quiser enviar o seu pagamento novamente.",
|
||||
"InvoiceExpired_Body_2": "Se tentou enviar um pagamento, ele ainda não foi aceite pela rede Bitcoin. Nós ainda não recebemos o valor enviado.",
|
||||
"InvoiceExpired_Body_3": "Se a transação não for aceite pela rede Bitcoin, o valor voltará para sua carteira. Dependendo da sua carteira, isto pode demorar entre 48 e 72 horas.",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"Invoice ID": "Nº da Fatura",
|
||||
"Order ID": "Nº da Encomenda",
|
||||
"Return to StoreName": "Voltar para {{storeName}}",
|
||||
|
@ -33,7 +33,7 @@
|
||||
"What happened?": "Что случилось?",
|
||||
"InvoiceExpired_Body_1": "Срок действия этого счета истек. Счет действителен только {{maxTimeMinutes}} минут. \nвы можете посетить {{storeName}} опять, если захотите оплатить.",
|
||||
"InvoiceExpired_Body_2": "ваша оплата пока еще не принята системой. Мы еще не получили ваши фонды.",
|
||||
"InvoiceExpired_Body_3": "если транзакция не пройдёт Ваши фонды будут возвращены в ваш кошелек.в зависимости от Вашего кошелек это может занять от 48 до 72 часов.",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"Invoice ID": "Порядковый номер выставленного счета",
|
||||
"Order ID": "Порядковый номер заказа",
|
||||
"Return to StoreName": "возвратить в {{storeName}}",
|
||||
|
@ -32,8 +32,8 @@
|
||||
"Invoice expired": "Platnosť faktúry uplynula",
|
||||
"What happened?": "Čo sa stalo?",
|
||||
"InvoiceExpired_Body_1": "Platnosť tejto faktúry vypršala. Faktúra platí iba {{maxTimeMinutes}} minút. \nMôžete sa vrátiť na {{storeName}}, ak chcete platbu znova odoslať.",
|
||||
"InvoiceExpired_Body_2": "Ak ste sa pokúsili odoslať platbu, sieť ju ešte neprijala. Zatia+l sme nedostali Vaše finančné prostriedky.",
|
||||
"InvoiceExpired_Body_3": "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.",
|
||||
"InvoiceExpired_Body_2": "Ak ste sa pokúsili odoslať platbu, sieť ju ešte neprijala. Zatiaľ sme nedostali Vaše finančné prostriedky.",
|
||||
"InvoiceExpired_Body_3": "Ak ju obdržíme neskôr, Vašu objednávku buď spracujeme alebo Vás budeme kontaktovať, aby sme sa dohodli na vrátení...",
|
||||
"Invoice ID": "ID faktúry",
|
||||
"Order ID": "ID objednávky",
|
||||
"Return to StoreName": "Vrátiť sa na {{storeName}}",
|
||||
|
47
BTCPayServer/wwwroot/locales/sr.json
Normal file
47
BTCPayServer/wwwroot/locales/sr.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATED FROM TRANSIFEX, IF YOU WISH TO HELP TRANSLATION COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"code": "sr",
|
||||
"currentLanguage": "Srpski",
|
||||
"lang": "Jezik",
|
||||
"Awaiting Payment...": "Čeka se uplata...",
|
||||
"Pay with": "Plati sa",
|
||||
"Contact and Refund Email": "E-mail za kontakt i povraćaj sredstava",
|
||||
"Contact_Body": "Molimo upišite Vašu e-mail adresu. Kontaktiraćemo Vas ukoliko bude potrebe.",
|
||||
"Your email": "Vaš email",
|
||||
"Continue": "Nastavi",
|
||||
"Please enter a valid email address": "Molimo unesite ispravnu email adresu",
|
||||
"Order Amount": "Iznos narudžbine",
|
||||
"Network Cost": "Trošak mreže",
|
||||
"Already Paid": "Već plaćeno",
|
||||
"Due": "Za uplatu",
|
||||
"Scan": "Skeniraj",
|
||||
"Copy": "Kopiraj",
|
||||
"Conversion": "Konvertuj",
|
||||
"Open in wallet": "Otvori u novčaniku",
|
||||
"CompletePay_Body": "Da kompletirate vašu uplatu molimo pošaljite {{btcDue}} {{cryptoCode}} na adresu ispod.",
|
||||
"Amount": "Iznos",
|
||||
"Address": "Adresa",
|
||||
"Copied": "Kopirano",
|
||||
"ConversionTab_BodyTop": "Možete platiti {{btcDue}} {{cryptoCode}} pomoću altcoina koje prodavac ne podržava direktno.",
|
||||
"ConversionTab_BodyDesc": "Ovu usluga pruža treća strana. Vodite računa da nemamo kontroliu nad načinom kako će Vam davatelji usluge proslijediti sredstva. Račun je plaćen tek kada su sredstva primljena na {{cryptoCode}} blokčejnu.",
|
||||
"ConversionTab_CalculateAmount_Error": "Pokušaj ponovo",
|
||||
"ConversionTab_LoadCurrencies_Error": "Pokušaj ponovo",
|
||||
"ConversionTab_Lightning": "Ne postoji provajder koji će konvertovati Lightning Network uplate.",
|
||||
"ConversionTab_CurrencyList_Select_Option": "Molimo odaberite valutu iz koje se konvertuje",
|
||||
"Invoice expiring soon...": "Račun uskoro ističe...",
|
||||
"Invoice expired": "Račun je istekao",
|
||||
"What happened?": "Šta se desilo?",
|
||||
"InvoiceExpired_Body_1": "Ovaj račun je istekao. Račun važi samo {{maxTimeMinutes}} minuta. \nMožete se vratiti na {{storeName}} ukoliko želite da ponovo inicirate plaćanje.",
|
||||
"InvoiceExpired_Body_2": "Ako ste pokušali da pošaljete uplatu, još uvek nije prihvaćena na mreži. Nismo još zaprimili Vašu uplatu.",
|
||||
"InvoiceExpired_Body_3": "Ukoliko vašu uplatu primimo kasnije, procesuiraćemo vašu narudžbinu ili ćemo vas kontaktirati za povraćaj novca...",
|
||||
"Invoice ID": "Broj računa",
|
||||
"Order ID": "Broj narudžbine",
|
||||
"Return to StoreName": "Vrati se na {{storeName}}",
|
||||
"This invoice has been paid": "Račun je plaćen.",
|
||||
"This invoice has been archived": "Račun je arhiviran.",
|
||||
"Archived_Body": "Molimo kontaktirajte prodaca za bili kakvu informaciju ili pomoć u vezi narudžbine.",
|
||||
"BOLT 11 Invoice": "BOLT 11 Račun",
|
||||
"Node Info": "Informacije Node-a",
|
||||
"txCount": "{{count}} transakcija",
|
||||
"txCount_plural": "{{count}} transakcije"
|
||||
}
|
@ -33,7 +33,7 @@
|
||||
"What happened?": "Що сталось?",
|
||||
"InvoiceExpired_Body_1": "Строк дії цього рахунку закінчився. Рахунок дійсний лише {{maxTimeMinutes}} хвилин. \nВи можете повернутись до {{storeName}} якщо ви хочете ще раз надіслати ваш платіж.",
|
||||
"InvoiceExpired_Body_2": "Якщо ви пробували надіслати платіж, він ще не був прийнятий мережею. Ми ще не отримали ваші кошти.",
|
||||
"InvoiceExpired_Body_3": "Якщо транзакція не прийнята мережею, кошти будуть знову доступними у вашому гаманці. Залежно від вашого гаманця, це може зайняти приблизно 48-72 годин.",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"Invoice ID": "ID рахунку",
|
||||
"Order ID": "ID замовлення",
|
||||
"Return to StoreName": "Повернутись до {{storeName}}",
|
||||
|
@ -33,7 +33,7 @@
|
||||
"What happened?": "Đã xảy ra chuyện gì?",
|
||||
"InvoiceExpired_Body_1": "Hóa đơn này đã quá hạn. Một hóa đơn chỉ có giá trị trong {{maxTimeMinutes}} phút. \nNếu muốn đệ trình việc thanh toán một lần nữa, bạn có thể quay lại {{storeName}}.",
|
||||
"InvoiceExpired_Body_2": "Nếu bạn vừa định gửi thanh toán, thì hệ thống vẫn chưa chấp nhận thanh toán của bạn. Chúng tôi vẫn chưa nhận được tiền của bạn.",
|
||||
"InvoiceExpired_Body_3": "Nếu giao dịch không được hệ thống chấp nhận, các khoản tiền của bạn sẽ được phân bổ trở lại ví của bạn để bạn tiêu dùng. Tuỳ thuộc vào ví của bạn, việc này có thể mất 48-72 giờ.",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"Invoice ID": "Số Hóa đơn",
|
||||
"Order ID": "Số Đơn hàng",
|
||||
"Return to StoreName": "Quay lại {{storeName}}",
|
||||
|
@ -33,7 +33,7 @@
|
||||
"What happened?": "发生了什么?",
|
||||
"InvoiceExpired_Body_1": "您的订单已过期。一个订单只在{{maxTimeMinutes}}分钟内有效。 \n如果您想要再次支付,可返回{{storeName}}。",
|
||||
"InvoiceExpired_Body_2": "如果您已支付,但付款没有被网络确认接收。这说明我方目前还未收到您的支付款。",
|
||||
"InvoiceExpired_Body_3": "如果您的支付最终被网络拒绝,支付款将被返回您的钱包。根据您使用的钱包不同,可能需要48-72小时。",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"Invoice ID": "订单号",
|
||||
"Order ID": "订货号",
|
||||
"Return to StoreName": "返回{{storeName}}。",
|
||||
|
@ -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"]
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user