Merge branch 'master' into MonetaryUnit

This commit is contained in:
Sotiris Blad 2020-08-19 15:33:02 +03:00 committed by GitHub
commit b5b32cacfc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
139 changed files with 11314 additions and 8683 deletions

View file

@ -124,7 +124,6 @@ workflows:
filters:
branches:
only: master
publish:
jobs:
- amd64:
@ -134,21 +133,22 @@ workflows:
ignore: /.*/
# only act on version tags v1.0.0.88 or v1.0.2-1
# OR feature tags like vlndseedbackup
# OR features on specific versions like v1.0.0.88-lndseedbackup-1
tags:
only: /(v[1-9]+(\.[0-9]+)*(-[0-9]+)?)|(v[a-z]+)/
only: /(v[1-9]+(\.[0-9]+)*(-[a-z0-9-]+)?)|(v[a-z0-9-]+)/
- arm32v7:
filters:
branches:
ignore: /.*/
tags:
only: /(v[1-9]+(\.[0-9]+)*(-[0-9]+)?)|(v[a-z]+)/
only: /(v[1-9]+(\.[0-9]+)*(-[a-z0-9-]+)?)|(v[a-z0-9-]+)/
- arm64v8:
filters:
branches:
ignore: /.*/
tags:
only: /(v[1-9]+(\.[0-9]+)*(-[0-9]+)?)|(v[a-z]+)/
only: /(v[1-9]+(\.[0-9]+)*(-[a-z0-9-]+)?)|(v[a-z0-9-]+)/
- multiarch:
requires:
- amd64
@ -158,4 +158,4 @@ workflows:
branches:
ignore: /.*/
tags:
only: /(v[1-9]+(\.[0-9]+)*(-[0-9]+)?)|(v[a-z]+)/
only: /(v[1-9]+(\.[0-9]+)*(-[a-z0-9-]+)?)|(v[a-z0-9-]+)/

38
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View file

@ -0,0 +1,38 @@
---
name: Bug report
about: File a bug report
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Your BTCPay Environment (please complete the following information):**
- BTCPay Server Version [available in the right bottom corner of footer]
- Deployment Method: [e.g. Docker, Manual, Third-Party-hoist]
- Browser [e.g. chrome, safari]
**Logs (if applicable)**
Basic logs can be found in Server Settings > Logs. More logs https://docs.btcpayserver.org/Troubleshooting/#2-looking-through-the-logs
**Setup Parameters**
If you're reporting a deployment issue run `. btcpay-setup.sh -i` and paste your the parameters by obscuring private information.
**Additional context**
Add any other context about the problem here.

View file

@ -1,37 +0,0 @@
---
name: Report a problem
about: File a technical problem or report a bug
---
**Describe the problem/bug**
A clear and concise description of what the bug is.
**Your environment**
* Version of BTCPay Server:
* Deployment method:
* Other relevant environment details:
**Logs (if applicable)**
Basic logs can be found in Server Settings > Logs.
**Setup Parameters**
If you're reporting a deployment issue run `. btcpay-setup.sh -i` and paste your the paremeters by obscuring private information.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Actual behavior**
Tell us what happens instead
**Screenshots/Links**
If applicable, add screenshots or links to help explain your problem.
**Additional context**
Add any other context about the problem here.

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Community Support Chat
url: https://chat.btcpayserver.org/
about: Ask general questions and get community support in real-time.

View file

@ -1,6 +1,9 @@
---
name: Feature request
about: Ideas and feature requests
about: Suggest a new feature or enhancement
title: ''
labels: ''
assignees: ''
---
@ -10,11 +13,11 @@ A clear and concise description of what the problem is. Ex. I'm always frustrate
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Sketch/Image/Wireframe/Mockup**
If applicable provide examples, wireframes, sketches or images to better explain your idea.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Provide examples**
If applicable provide examples, wireframes, sketches or images to better explain your idea.
**Additional context**
Add any other context or screenshots about the feature request here.

View file

@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NBitcoin" Version="5.0.43" />
<PackageReference Include="NBitcoin" Version="5.0.51" />
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>

View file

@ -6,11 +6,11 @@ namespace BTCPayServer
{
public static class LiquidExtensions
{
public static IEnumerable<string> GetAllElementsSubChains(this BTCPayNetworkProvider networkProvider)
public static IEnumerable<string> GetAllElementsSubChains(this BTCPayNetworkProvider networkProvider, BTCPayNetworkProvider unfilteredNetworkProvider)
{
var elementsBased = networkProvider.GetAll().OfType<ElementsBTCPayNetwork>();
var parentChains = elementsBased.Select(network => network.NetworkCryptoCode.ToUpperInvariant()).Distinct();
return networkProvider.GetAll().OfType<ElementsBTCPayNetwork>()
return unfilteredNetworkProvider.GetAll().OfType<ElementsBTCPayNetwork>()
.Where(network => parentChains.Contains(network.NetworkCryptoCode)).Select(network => network.CryptoCode.ToUpperInvariant());
}
}

View file

@ -9,4 +9,4 @@
<ItemGroup Condition="'$(Altcoins)' != 'true'">
<Compile Remove="Altcoins\**\*.cs"></Compile>
</ItemGroup>
</Project>
</Project>

View file

@ -6,7 +6,7 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.6.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="NBitcoin" Version="5.0.43" />
<PackageReference Include="NBitcoin" Version="5.0.51" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.6.3" />
</ItemGroup>

View file

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Services.Rates;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Rating
{
public class HitBTCRateProvider : IRateProvider
{
private readonly HttpClient _httpClient;
public HitBTCRateProvider(HttpClient httpClient)
{
_httpClient = httpClient ?? new HttpClient();
}
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://api.hitbtc.com/api/2/public/ticker", cancellationToken);
var jarray = await response.Content.ReadAsAsync<JArray>(cancellationToken);
return jarray
.Children<JObject>()
.Where(p => CurrencyPair.TryParse(p["symbol"].Value<string>(), out _))
.Select(p => new PairRate(CurrencyPair.Parse(p["symbol"].Value<string>()), CreateBidAsk(p)))
.ToArray();
}
private BidAsk CreateBidAsk(JObject p)
{
var bid = p["bid"].Value<decimal>();
var ask = p["ask"].Value<decimal>();
return new BidAsk(bid, ask);
}
}
}

View file

@ -90,10 +90,10 @@ namespace BTCPayServer.Services.Rates
AddExchangeSharpProviders<ExchangeBinanceAPI>("binance");
AddExchangeSharpProviders<ExchangeBittrexAPI>("bittrex");
AddExchangeSharpProviders<ExchangePoloniexAPI>("poloniex");
AddExchangeSharpProviders<ExchangeHitBTCAPI>("hitbtc");
AddExchangeSharpProviders<ExchangeNDAXAPI>("ndax");
// Handmade providers
Providers.Add("hitbtc", new HitBTCRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_HITBTC")));
Providers.Add("coingecko", new CoinGeckoRateProvider(_httpClientFactory));
Providers.Add("kraken", new KrakenExchangeRateProvider() { HttpClient = _httpClientFactory?.CreateClient("EXCHANGE_KRAKEN") });
Providers.Add("bylls", new ByllsRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BYLLS")));

View file

@ -3,9 +3,13 @@ using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Controllers;
using BTCPayServer.Models.PaymentRequestViewModels;
using BTCPayServer.PaymentRequest;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.PaymentRequests;
using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using NBitcoin;
using NBitpayClient;
using Xunit;
using Xunit.Abstractions;
@ -16,7 +20,7 @@ namespace BTCPayServer.Tests
{
public PaymentRequestTests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
Logs.Tester = new XUnitLog(helper) {Name = "Tests"};
Logs.LogProvider = new XUnitLogProvider(helper);
}
@ -46,8 +50,8 @@ namespace BTCPayServer.Tests
Description = "description"
};
var id = (Assert
.IsType<RedirectToActionResult>(await paymentRequestController.EditPaymentRequest(null, request)).RouteValues.Values.First().ToString());
.IsType<RedirectToActionResult>(await paymentRequestController.EditPaymentRequest(null, request))
.RouteValues.Values.First().ToString());
//permission guard for guests editing
@ -57,7 +61,9 @@ namespace BTCPayServer.Tests
request.Title = "update";
Assert.IsType<RedirectToActionResult>(await paymentRequestController.EditPaymentRequest(id, request));
Assert.Equal(request.Title, Assert.IsType<ViewPaymentRequestViewModel>(Assert.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Title);
Assert.Equal(request.Title,
Assert.IsType<ViewPaymentRequestViewModel>(Assert
.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Title);
Assert.False(string.IsNullOrEmpty(id));
@ -68,16 +74,24 @@ namespace BTCPayServer.Tests
Assert
.IsType<RedirectToActionResult>(await paymentRequestController.TogglePaymentRequestArchival(id));
Assert.True(Assert.IsType<ViewPaymentRequestViewModel>(Assert.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Archived);
Assert.True(Assert
.IsType<ViewPaymentRequestViewModel>(Assert
.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Archived);
Assert.Empty(Assert.IsType<ListPaymentRequestsViewModel>(Assert.IsType<ViewResult>(await paymentRequestController.GetPaymentRequests()).Model).Items);
Assert.Empty(Assert
.IsType<ListPaymentRequestsViewModel>(Assert
.IsType<ViewResult>(await paymentRequestController.GetPaymentRequests()).Model).Items);
//unarchive
Assert
.IsType<RedirectToActionResult>(await paymentRequestController.TogglePaymentRequestArchival(id));
Assert.False(Assert.IsType<ViewPaymentRequestViewModel>(Assert.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Archived);
Assert.False(Assert
.IsType<ViewPaymentRequestViewModel>(Assert
.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Archived);
Assert.Single(Assert.IsType<ListPaymentRequestsViewModel>(Assert.IsType<ViewResult>(await paymentRequestController.GetPaymentRequests()).Model).Items);
Assert.Single(Assert
.IsType<ListPaymentRequestsViewModel>(Assert
.IsType<ViewResult>(await paymentRequestController.GetPaymentRequests()).Model).Items);
}
}
@ -94,7 +108,8 @@ namespace BTCPayServer.Tests
var paymentRequestController = user.GetController<PaymentRequestController>();
Assert.IsType<NotFoundResult>(await paymentRequestController.PayPaymentRequest(Guid.NewGuid().ToString()));
Assert.IsType<NotFoundResult>(
await paymentRequestController.PayPaymentRequest(Guid.NewGuid().ToString()));
var request = new UpdatePaymentRequestViewModel()
@ -110,15 +125,18 @@ namespace BTCPayServer.Tests
.RouteValues.First();
var invoiceId = Assert
.IsType<OkObjectResult>(await paymentRequestController.PayPaymentRequest(response.Value.ToString(), false)).Value
.IsType<OkObjectResult>(
await paymentRequestController.PayPaymentRequest(response.Value.ToString(), false)).Value
.ToString();
var actionResult = Assert
.IsType<RedirectToActionResult>(await paymentRequestController.PayPaymentRequest(response.Value.ToString()));
.IsType<RedirectToActionResult>(
await paymentRequestController.PayPaymentRequest(response.Value.ToString()));
Assert.Equal("Checkout", actionResult.ActionName);
Assert.Equal("Invoice", actionResult.ControllerName);
Assert.Contains(actionResult.RouteValues, pair => pair.Key == "Id" && pair.Value.ToString() == invoiceId);
Assert.Contains(actionResult.RouteValues,
pair => pair.Key == "Id" && pair.Value.ToString() == invoiceId);
var invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
Assert.Equal(1, invoice.Price);
@ -138,8 +156,8 @@ namespace BTCPayServer.Tests
.RouteValues.First();
Assert
.IsType<BadRequestObjectResult>(await paymentRequestController.PayPaymentRequest(response.Value.ToString(), false));
.IsType<BadRequestObjectResult>(
await paymentRequestController.PayPaymentRequest(response.Value.ToString(), false));
}
}
@ -156,11 +174,9 @@ namespace BTCPayServer.Tests
var paymentRequestController = user.GetController<PaymentRequestController>();
Assert.IsType<NotFoundResult>(await
paymentRequestController.CancelUnpaidPendingInvoice(Guid.NewGuid().ToString(), false));
var request = new UpdatePaymentRequestViewModel()
{
Title = "original juice",
@ -176,15 +192,18 @@ namespace BTCPayServer.Tests
var paymentRequestId = response.Value.ToString();
var invoiceId = Assert
.IsType<OkObjectResult>(await paymentRequestController.PayPaymentRequest(paymentRequestId, false)).Value
.IsType<OkObjectResult>(await paymentRequestController.PayPaymentRequest(paymentRequestId, false))
.Value
.ToString();
var actionResult = Assert
.IsType<RedirectToActionResult>(await paymentRequestController.PayPaymentRequest(response.Value.ToString()));
.IsType<RedirectToActionResult>(
await paymentRequestController.PayPaymentRequest(response.Value.ToString()));
Assert.Equal("Checkout", actionResult.ActionName);
Assert.Equal("Invoice", actionResult.ControllerName);
Assert.Contains(actionResult.RouteValues, pair => pair.Key == "Id" && pair.Value.ToString() == invoiceId);
Assert.Contains(actionResult.RouteValues,
pair => pair.Key == "Id" && pair.Value.ToString() == invoiceId);
var invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
Assert.Equal(InvoiceState.ToString(InvoiceStatus.New), invoice.Status);
@ -194,11 +213,24 @@ namespace BTCPayServer.Tests
invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
Assert.Equal(InvoiceState.ToString(InvoiceStatus.Invalid), invoice.Status);
Assert.IsType<BadRequestObjectResult>(await
paymentRequestController.CancelUnpaidPendingInvoice(paymentRequestId, false));
invoiceId = Assert
.IsType<OkObjectResult>(await paymentRequestController.PayPaymentRequest(paymentRequestId, false))
.Value
.ToString();
invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
//a hack to generate invoices for the payment request is to manually create an invocie with an order id that matches:
user.BitPay.CreateInvoice(new Invoice(1, "USD")
{
OrderId = PaymentRequestRepository.GetOrderIdForPaymentRequest(paymentRequestId)
});
//shouldnt crash
await paymentRequestController.ViewPaymentRequest(paymentRequestId);
await paymentRequestController.CancelUnpaidPendingInvoice(paymentRequestId);
}
}
}

View file

@ -52,6 +52,9 @@ If you get this message:
Please, run the test `CanSetLightningServer`, this will establish a channel between the customer and the merchant, then, retry.
Alternatively you can run the `./docker-lightning-channel-setup.sh` script to establish the channel connection.
The `./docker-lightning-channel-teardown.sh` script closes any existing lightning channels.
## FAQ
`docker-compose up dev` failed or tests are not passing, what should I do?

View file

@ -157,7 +157,7 @@ namespace BTCPayServer.Tests
{
string connectionString = null;
if (connectionType == LightningConnectionType.Charge)
connectionString = "type=charge;server=" + Server.MerchantCharge.Client.Uri.AbsoluteUri;
connectionString = $"type=charge;server={Server.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true";
else if (connectionType == LightningConnectionType.CLightning)
connectionString = "type=clightning;server=" + ((CLightningClient)Server.MerchantLightningD).Address.AbsoluteUri;
else if (connectionType == LightningConnectionType.LndREST)

View file

@ -664,6 +664,7 @@ namespace BTCPayServer.Tests
Assert.Equal(parsedBip21.Address.ToString(), s.Driver.FindElement(By.Id($"Outputs_0__DestinationAddress")).GetAttribute("value"));
s.GoToWallet(new WalletId(storeId.storeId, "BTC"), WalletsNavPages.Settings);
var walletUrl = s.Driver.Url;
s.Driver.FindElement(By.Id("SettingsMenu")).ForceClick();
s.Driver.FindElement(By.CssSelector("button[value=view-seed]")).Click();
@ -671,7 +672,12 @@ namespace BTCPayServer.Tests
// Seed backup page
var recoveryPhrase = s.Driver.FindElements(By.Id("recovery-phrase")).First().GetAttribute("data-mnemonic");
Assert.Equal(mnemonic.ToString(), recoveryPhrase);
Assert.Contains("The recovery phrase will also be stored on a server as a hot wallet.", s.Driver.PageSource);
Assert.Contains("The recovery phrase will also be stored on the server as a hot wallet.", s.Driver.PageSource);
// No confirmation, just a link to return to the wallet
Assert.Empty(s.Driver.FindElements(By.Id("confirm")));
s.Driver.FindElement(By.Id("proceed")).Click();
Assert.Equal(walletUrl, s.Driver.Url);
}
}
void SetTransactionOutput(SeleniumTester s, int index, BitcoinAddress dest, decimal amount, bool subtract = false)

View file

@ -84,7 +84,7 @@ namespace BTCPayServer.Tests
var btc = NetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork;
CustomerLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_CUSTOMERLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30992/"), btc);
MerchantLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_MERCHANTLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30993/"), btc);
MerchantCharge = new ChargeTester(this, "TEST_MERCHANTCHARGE", "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify", "merchant_lightningd", btc);
MerchantCharge = new ChargeTester(this, "TEST_MERCHANTCHARGE", "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify;allowinsecure=true", "merchant_lightningd", btc);
MerchantLnd = new LndMockTester(this, "TEST_MERCHANTLND", "https://lnd:lnd@127.0.0.1:35531/", "merchant_lnd", btc);
PayTester.UseLightning = true;
PayTester.IntegratedLightning = MerchantCharge.Client.Uri;

View file

@ -260,7 +260,7 @@ namespace BTCPayServer.Tests
if (connectionType == LightningConnectionType.Charge)
{
if (isMerchant)
connectionString = "type=charge;server=" + parent.MerchantCharge.Client.Uri.AbsoluteUri;
connectionString = $"type=charge;server={parent.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true";
else
throw new NotSupportedException();
}

View file

@ -735,7 +735,7 @@ namespace BTCPayServer.Tests
var testResult = storeController.AddLightningNode(user.StoreId, new LightningNodeViewModel()
{
ConnectionString = "type=charge;server=" + tester.MerchantCharge.Client.Uri.AbsoluteUri,
ConnectionString = $"type=charge;server={tester.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true",
SkipPortTest = true // We can't test this as the IP can't be resolved by the test host :(
}, "test", "BTC").GetAwaiter().GetResult();
Assert.False(storeController.TempData.ContainsKey(WellKnownTempData.ErrorMessage));
@ -745,7 +745,7 @@ namespace BTCPayServer.Tests
Assert.IsType<RedirectToActionResult>(storeController.AddLightningNode(user.StoreId,
new LightningNodeViewModel()
{
ConnectionString = "type=charge;server=" + tester.MerchantCharge.Client.Uri.AbsoluteUri
ConnectionString = $"type=charge;server={tester.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true"
}, "save", "BTC").GetAwaiter().GetResult());
// Make sure old connection string format does not work

View file

@ -23,7 +23,7 @@ services:
TESTS_AzureBlobStorageConnectionString: ${TESTS_AzureBlobStorageConnectionString:-none}
TEST_MERCHANTLIGHTNINGD: "type=clightning;server=unix://etc/merchant_lightningd_datadir/lightning-rpc"
TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=unix://etc/customer_lightningd_datadir/lightning-rpc"
TEST_MERCHANTCHARGE: "type=charge;server=http://lightning-charged:9112/;api-token=foiewnccewuify"
TEST_MERCHANTCHARGE: "type=charge;server=http://lightning-charged:9112/;api-token=foiewnccewuify;allowinsecure=true"
TEST_MERCHANTLND: "https://lnd:lnd@merchant_lnd:8080/"
TESTS_INCONTAINER: "true"
TESTS_SSHCONNECTION: "root@sshd:22"
@ -81,7 +81,7 @@ services:
- customer_lnd
- merchant_lnd
nbxplorer:
image: nicolasdorier/nbxplorer:2.1.35
image: nicolasdorier/nbxplorer:2.1.40
restart: unless-stopped
ports:
- "32838:32838"
@ -140,7 +140,7 @@ services:
- "bitcoin_datadir:/data"
customer_lightningd:
image: btcpayserver/lightning:v0.8.2-dev
image: btcpayserver/lightning:v0.9.0-1-dev
stop_signal: SIGKILL
restart: unless-stopped
environment:
@ -187,7 +187,7 @@ services:
- merchant_lightningd
merchant_lightningd:
image: btcpayserver/lightning:v0.8.2-dev
image: btcpayserver/lightning:v0.9.0-1-dev
stop_signal: SIGKILL
environment:
EXPOSE_TCP: "true"

View file

@ -21,7 +21,7 @@ services:
TESTS_AzureBlobStorageConnectionString: ${TESTS_AzureBlobStorageConnectionString:-none}
TEST_MERCHANTLIGHTNINGD: "type=clightning;server=unix://etc/merchant_lightningd_datadir/lightning-rpc"
TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=unix://etc/customer_lightningd_datadir/lightning-rpc"
TEST_MERCHANTCHARGE: "type=charge;server=http://lightning-charged:9112/;api-token=foiewnccewuify"
TEST_MERCHANTCHARGE: "type=charge;server=http://lightning-charged:9112/;api-token=foiewnccewuify;allowinsecure=true"
TEST_MERCHANTLND: "https://lnd:lnd@merchant_lnd:8080/"
TESTS_INCONTAINER: "true"
TESTS_SSHCONNECTION: "root@sshd:22"
@ -78,7 +78,7 @@ services:
- customer_lnd
- merchant_lnd
nbxplorer:
image: nicolasdorier/nbxplorer:2.1.37
image: nicolasdorier/nbxplorer:2.1.40
restart: unless-stopped
ports:
- "32838:32838"
@ -127,7 +127,7 @@ services:
- "bitcoin_datadir:/data"
customer_lightningd:
image: btcpayserver/lightning:v0.8.2-dev
image: btcpayserver/lightning:v0.9.0-1-dev
stop_signal: SIGKILL
restart: unless-stopped
environment:
@ -174,7 +174,7 @@ services:
- merchant_lightningd
merchant_lightningd:
image: btcpayserver/lightning:v0.8.2-dev
image: btcpayserver/lightning:v0.9.0-1-dev
stop_signal: SIGKILL
environment:
EXPOSE_TCP: "true"

View file

@ -0,0 +1,69 @@
#!/bin/bash
# Commands
BCMD=./docker-bitcoin-cli.sh
GCMD=./docker-bitcoin-generate.sh
CCMD=./docker-customer-lightning-cli.sh
MCMD=./docker-merchant-lightning-cli.sh
function channel_count () {
local cmd=$1; local id=$2;
local count=$($cmd listchannels | jq -r ".channels | map(select(.destination == \"$id\")) | length | tonumber") 2>/dev/null
return $count
}
function create_channel () {
local cmd=$1; local id=$2;
local btcaddr=$($cmd newaddr | jq -r '.address')
$BCMD sendtoaddress $btcaddr 0.15 >/dev/null
$GCMD 10 >/dev/null
local fundres=$($cmd fundchannel $id 14500000 5000 | jq -r '.channel_id')
$GCMD 20 >/dev/null
sleep 2
channel_count $cmd $id
local count=$?
return $count
}
# General information
cinfo=$($CCMD getinfo | jq '.' 2>/dev/null)
minfo=$($MCMD getinfo | jq '.' 2>/dev/null)
cid=$(echo $cinfo | jq -r '.id')
mid=$(echo $minfo | jq -r '.id')
caddr=$(echo $cinfo | jq -r '.address[] | "\(.address):\(.port)"')
maddr=$(echo $minfo | jq -r '.address[] | "\(.address):\(.port)"')
printf "Customer ID: %s@%s\n\r" $cid $caddr
printf "Merchant ID: %s@%s\n\r" $mid $maddr
# Connections
printf "\n\rConnecting both parties …\n\r"
cconnid=$($CCMD connect "$mid@$maddr" | jq -r '.id' 2>/dev/null)
mconnid=$($MCMD connect "$cid@$caddr" | jq -r '.id' 2>/dev/null)
printf "Customer to merchant %s\n\r" $([[ $cconnid == $mid ]] && echo "succeeded" || echo "failed")
printf "Merchant to customer %s\n\r" $([[ $mconnid == $cid ]] && echo "succeeded" || echo "failed")
# Channels
printf "\n\rChecking channels …\n\r"
channel_count $CCMD $mid
cchanscount=$?
channel_count $MCMD $cid
mchanscount=$?
printf "Customer channel count to merchant: %d\n\r" $cchanscount
printf "Merchant channel count to customer: %d\n\r" $mchanscount
# Open channels if there are none, details: https://github.com/ElementsProject/lightning#opening-a-channel
if [[ $cchanscount -eq 0 ]]; then
create_channel $CCMD $mid
cchanres=$?
printf "Establishing channel from customer to merchant %s\n\r" $([[ $cchanres -gt 0 ]] && echo "succeeded" || echo "failed")
fi
if [[ $mchanscount -eq 0 ]]; then
create_channel $MCMD $cid
mchanres=$?
printf "Establishing channel from merchant to customer %s\n\r" $([[ $mchanres -gt 0 ]] && echo "succeeded" || echo "failed")
fi

View file

@ -0,0 +1,12 @@
#!/bin/bash
set -e
channels=$(./docker-merchant-lightning-cli.sh listchannels | jq -cr '.channels | map(.short_channel_id) | unique')
printf "Channels: %s\n\r" $channels
for chanid in $(echo "${channels}" | jq -cr '.[]')
do
printf "Closing channel ID: %s\n\r" $chanid
./docker-merchant-lightning-cli.sh close $chanid
./docker-bitcoin-generate.sh 20 > /dev/null
done

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" />
@ -46,7 +46,7 @@
<ItemGroup>
<PackageReference Include="BTCPayServer.Hwi" Version="1.1.3" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.2.0" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.2.4" />
<PackageReference Include="BuildBundlerMinifier" Version="3.2.435" />
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435" />
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />

View file

@ -1,4 +1,5 @@
@model BTCPayServer.Services.Notifications.NotificationSummaryViewModel
@inject LinkGenerator linkGenerator
@model BTCPayServer.Components.NotificationsDropdown.NotificationSummaryViewModel
@if (Model.UnseenCount > 0)
{
@ -31,3 +32,36 @@ else
</a>
</li>
}
<script type="text/javascript">
var supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
if (supportsWebSockets) {
var loc = window.location, ws_uri;
if (loc.protocol === "https:") {
ws_uri = "wss:";
} else {
ws_uri = "ws:";
}
ws_uri += "//" + loc.host;
ws_uri += "@linkGenerator.GetPathByAction("SubscribeUpdates", "Notifications")";
var newDataEndpoint = "@linkGenerator.GetPathByAction("GetNotificationDropdownUI", "Notifications")";
try {
socket = new WebSocket(ws_uri);
socket.onmessage = function (e) {
$.get(newDataEndpoint, function(data){
$("#notifications-nav-item").replaceWith($(data));
});
};
socket.onerror = function (e) {
console.error("Error while connecting to websocket for notifications (callback)", e);
};
}
catch (e) {
console.error("Error while connecting to websocket for notifications", e);
}
}
</script>

View file

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Services.Notifications;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Components.NotificationsDropdown
{
public class NotificationsDropdown : ViewComponent
{
private readonly NotificationManager _notificationManager;
public NotificationsDropdown(NotificationManager notificationManager)
{
_notificationManager = notificationManager;
}
public async Task<IViewComponentResult> InvokeAsync()
{
return View(await _notificationManager.GetSummaryNotifications(UserClaimsPrincipal));
}
}
}

View file

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Models.NotificationViewModels;
namespace BTCPayServer.Components.NotificationsDropdown
{
public class NotificationSummaryViewModel
{
public int UnseenCount { get; set; }
public List<NotificationViewModel> Last5 { get; set; }
}
}

View file

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Models;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Components
{
public class Pager : ViewComponent
{
public Pager()
{
}
public IViewComponentResult Invoke(BasePagingViewModel viewModel)
{
return View(viewModel);
}
}
}

View file

@ -92,7 +92,7 @@ namespace BTCPayServer.Configuration
var networkProvider = new BTCPayNetworkProvider(NetworkType);
var filtered = networkProvider.Filter(supportedChains.ToArray());
#if ALTCOINS
supportedChains.AddRange(filtered.GetAllElementsSubChains());
supportedChains.AddRange(filtered.GetAllElementsSubChains(networkProvider));
#endif
#if !ALTCOINS
var onlyBTC = supportedChains.Count == 1 && supportedChains.First() == "BTC";

View file

@ -135,7 +135,7 @@ namespace BTCPayServer.Controllers
else
{
var paymentMethods = invoice.GetBlob(_NetworkProvider).GetPaymentMethods();
var options = invoice.GetBlob(_NetworkProvider).GetPaymentMethods()
var options = paymentMethods
.Select(o => o.GetId())
.Select(o => o.CryptoCode)
.Where(o => _NetworkProvider.GetNetwork<BTCPayNetwork>(o) is BTCPayNetwork n && !n.ReadonlyWallet)
@ -143,14 +143,15 @@ namespace BTCPayServer.Controllers
.OrderBy(o => o)
.Select(o => new PaymentMethodId(o, PaymentTypes.BTCLike))
.ToList();
var defaultRefund = invoice.Payments.Select(p => p.GetBlob(_NetworkProvider))
.Select(p => p.GetPaymentMethodId().CryptoCode)
.FirstOrDefault();
var defaultRefund = invoice.Payments
.Select(p => p.GetBlob(_NetworkProvider))
.Select(p => p?.GetPaymentMethodId())
.FirstOrDefault(p => p != null && p.PaymentType == BitcoinPaymentType.Instance);
// TODO: What if no option?
var refund = new RefundModel();
refund.Title = "Select a payment method";
refund.AvailablePaymentMethods = new SelectList(options, nameof(PaymentMethodId.CryptoCode), nameof(PaymentMethodId.CryptoCode));
refund.SelectedPaymentMethod = defaultRefund ?? options.Select(o => o.CryptoCode).First();
refund.SelectedPaymentMethod = defaultRefund?.ToString() ?? options.Select(o => o.CryptoCode).First();
// Nothing to select, skip to next
if (refund.AvailablePaymentMethods.Count() == 1)

View file

@ -15,22 +15,6 @@ using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Controllers
{
public class NotificationsDropdown : ViewComponent
{
private readonly NotificationManager _notificationManager;
public NotificationsDropdown(NotificationManager notificationManager)
{
_notificationManager = notificationManager;
}
public async Task<IViewComponentResult> InvokeAsync(int noOfEmployee)
{
return View(await _notificationManager.GetSummaryNotifications(UserClaimsPrincipal));
}
}
[BitpayAPIConstraint(false)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Route("[controller]/[action]")]

View file

@ -292,18 +292,21 @@ namespace BTCPayServer.Controllers
return NotFound();
}
var invoice = result.Invoices.SingleOrDefault(requestInvoice =>
var invoices = result.Invoices.Where(requestInvoice =>
requestInvoice.Status.Equals(InvoiceState.ToString(InvoiceStatus.New),
StringComparison.InvariantCulture) && !requestInvoice.Payments.Any());
if (invoice == null)
if (!invoices.Any())
{
return BadRequest("No unpaid pending invoice to cancel");
}
await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoice.Id);
_EventAggregator.Publish(new InvoiceEvent(await _InvoiceRepository.GetInvoice(invoice.Id), 1008,
InvoiceEvent.MarkedInvalid));
foreach (var invoice in invoices)
{
await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoice.Id);
_EventAggregator.Publish(new InvoiceEvent(await _InvoiceRepository.GetInvoice(invoice.Id), 1008,
InvoiceEvent.MarkedInvalid));
}
if (redirect)
{

View file

@ -133,7 +133,7 @@ namespace BTCPayServer.Controllers
{
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Message = $"You posted a claim of {_currencyNameTable.DisplayFormatCurrency(vm.ClaimedAmount, ppBlob.Currency)} to {vm.Destination}, this will get fullfilled later.",
Message = $"Your claim request of {_currencyNameTable.DisplayFormatCurrency(vm.ClaimedAmount, ppBlob.Currency)} to {vm.Destination} has been submitted and is awaiting approval.",
Severity = StatusMessageModel.StatusSeverity.Success
});
}

View file

@ -285,14 +285,15 @@ namespace BTCPayServer.Controllers
return NotFound();
var admins = await _UserManager.GetUsersInRoleAsync(Roles.ServerAdmin);
if (!viewModel.IsAdmin && admins.Count == 1)
var roles = await _UserManager.GetRolesAsync(user);
var wasAdmin = IsAdmin(roles);
if (!viewModel.IsAdmin && admins.Count == 1 && wasAdmin)
{
TempData[WellKnownTempData.ErrorMessage] = "This is the only Admin, so their role can't be removed until another Admin is added.";
return View(viewModel); // return
}
var roles = await _UserManager.GetRolesAsync(user);
if (viewModel.IsAdmin != IsAdmin(roles))
if (viewModel.IsAdmin != wasAdmin)
{
if (viewModel.IsAdmin)
await _UserManager.AddToRoleAsync(user, Roles.ServerAdmin);
@ -571,15 +572,14 @@ namespace BTCPayServer.Controllers
[Route("server/services/{serviceName}/{cryptoCode?}")]
public async Task<IActionResult> Service(string serviceName, string cryptoCode, bool showQR = false, uint? nonce = null)
{
if (!string.IsNullOrEmpty(cryptoCode) && !_dashBoard.IsFullySynched(cryptoCode, out _))
var service = GetService(serviceName, cryptoCode);
if (service == null)
return NotFound();
if (!string.IsNullOrEmpty(cryptoCode) && !_dashBoard.IsFullySynched(cryptoCode, out _) && service.Type != ExternalServiceTypes.RPC)
{
TempData[WellKnownTempData.ErrorMessage] = $"{cryptoCode} is not fully synched";
return RedirectToAction(nameof(Services));
}
var service = GetService(serviceName, cryptoCode);
if (service == null)
return NotFound();
try
{

View file

@ -1160,6 +1160,7 @@ namespace BTCPayServer.Controllers
CryptoCode = walletId.CryptoCode,
Mnemonic = seed,
IsStored = true,
RequireConfirm = false,
ReturnUrl = Url.Action(nameof(WalletSettings), new { walletId })
};
return this.RedirectToRecoverySeedBackup(recoveryVm);

View file

@ -15,7 +15,7 @@ namespace BTCPayServer.Data
PaymentEntity paymentEntity = null;
if (network == null)
{
paymentEntity = NBitcoin.JsonConverters.Serializer.ToObject<PaymentEntity>(unziped, null);
return null;
}
else
{

View file

@ -15,8 +15,7 @@ namespace BTCPayServer.Data
public static PaymentMethodId GetDefaultPaymentId(this StoreData storeData, BTCPayNetworkProvider networks)
{
PaymentMethodId[] paymentMethodIds = storeData.GetEnabledPaymentIds(networks);
var defaultPaymentId = string.IsNullOrEmpty(storeData.DefaultCrypto) ? null : PaymentMethodId.Parse(storeData.DefaultCrypto);
PaymentMethodId.TryParse(storeData.DefaultCrypto, out var defaultPaymentId);
var chosen = paymentMethodIds.FirstOrDefault(f => f == defaultPaymentId) ??
paymentMethodIds.FirstOrDefault(f => f.CryptoCode == defaultPaymentId?.CryptoCode) ??
paymentMethodIds.FirstOrDefault();
@ -80,7 +79,10 @@ namespace BTCPayServer.Data
JObject strategies = JObject.Parse(storeData.DerivationStrategies);
foreach (var strat in strategies.Properties())
{
var paymentMethodId = PaymentMethodId.Parse(strat.Name);
if (!PaymentMethodId.TryParse(strat.Name, out var paymentMethodId))
{
continue;
}
var network = networks.GetNetwork<BTCPayNetworkBase>(paymentMethodId.CryptoCode);
if (network != null)
{

View file

@ -117,8 +117,9 @@ namespace BTCPayServer
public static IEnumerable<BitcoinLikePaymentData> GetAllBitcoinPaymentData(this InvoiceEntity invoice)
{
return invoice.GetPayments()
.Where(p => p.GetPaymentMethodId().PaymentType == PaymentTypes.BTCLike)
.Select(p => (BitcoinLikePaymentData)p.GetCryptoPaymentData());
.Where(p => p.GetPaymentMethodId()?.PaymentType == PaymentTypes.BTCLike)
.Select(p => (BitcoinLikePaymentData)p.GetCryptoPaymentData())
.Where(data => data != null);
}
public static async Task<Dictionary<uint256, TransactionResult>> GetTransactions(this BTCPayWallet client, uint256[] hashes, bool includeOffchain = false, CancellationToken cts = default(CancellationToken))
@ -445,6 +446,7 @@ namespace BTCPayServer
new KeyValuePair<string, string>("mnemonic", vm.Mnemonic),
new KeyValuePair<string, string>("passphrase", vm.Passphrase),
new KeyValuePair<string, string>("isStored", vm.IsStored ? "true" : "false"),
new KeyValuePair<string, string>("requireConfirm", vm.RequireConfirm ? "true" : "false"),
new KeyValuePair<string, string>("returnUrl", vm.ReturnUrl)
}
};

View file

@ -33,7 +33,7 @@ namespace BTCPayServer.HostedServices
protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken)
{
if (evt is InvoiceEvent invoiceEvent && invoiceEvent.Name == InvoiceEvent.ReceivedPayment &&
invoiceEvent.Payment.GetPaymentMethodId().PaymentType == BitcoinPaymentType.Instance &&
invoiceEvent.Payment.GetPaymentMethodId()?.PaymentType == BitcoinPaymentType.Instance &&
invoiceEvent.Payment.GetCryptoPaymentData() is BitcoinLikePaymentData bitcoinLikePaymentData)
{
var walletId = new WalletId(invoiceEvent.Invoice.StoreId, invoiceEvent.Payment.GetCryptoCode());

View file

@ -81,6 +81,12 @@ namespace BTCPayServer.Hosting
return builtInFactory(context);
};
})
.AddRazorOptions(o =>
{
// /Components/{View Component Name}/{View Name}.cshtml
o.ViewLocationFormats.Add("/{0}.cshtml");
o.PageViewLocationFormats.Add("/{0}.cshtml");
})
.AddNewtonsoftJson()
#if RAZOR_RUNTIME_COMPILE
.AddRazorRuntimeCompilation()

View file

@ -55,7 +55,7 @@ namespace BTCPayServer.Models.AppViewModels
}
public class Contribution
{
public PaymentMethodId PaymentMehtodId { get; set; }
public PaymentMethodId PaymentMethodId { get; set; }
public decimal Value { get; set; }
public decimal CurrencyValue { get; set; }
}

View file

@ -10,8 +10,9 @@ namespace BTCPayServer.Models.StoreViewModels
public string CryptoCode { get; set; }
public string Mnemonic { get; set; }
public string Passphrase { get; set; }
public bool IsStored { get; set; }
public string ReturnUrl { get; set; }
public bool IsStored { get; set; }
public bool RequireConfirm { get; set; } = true;
public string[] Words
{

View file

@ -159,7 +159,7 @@ namespace BTCPayServer.PaymentRequest
{
data.GetValue(),
invoiceEvent.Payment.GetCryptoCode(),
invoiceEvent.Payment.GetPaymentMethodId().PaymentType.ToString()
invoiceEvent.Payment.GetPaymentMethodId()?.PaymentType?.ToString()
});
}

View file

@ -80,7 +80,8 @@ namespace BTCPayServer.PaymentRequest
var paymentStats = _AppService.GetContributionsByPaymentMethodId(blob.Currency, invoices, true);
var amountDue = blob.Amount - paymentStats.TotalCurrency;
var pendingInvoice = invoices.SingleOrDefault(entity => entity.Status == InvoiceStatus.New);
var pendingInvoice = invoices.OrderByDescending(entity => entity.InvoiceTime)
.FirstOrDefault(entity => entity.Status == InvoiceStatus.New);
return new ViewPaymentRequestViewModel(pr)
{
@ -103,10 +104,16 @@ namespace BTCPayServer.PaymentRequest
Currency = entity.ProductInformation.Currency,
ExpiryDate = entity.ExpirationTime.DateTime,
Status = entity.GetInvoiceState().ToString(),
Payments = entity.GetPayments().Select(paymentEntity =>
Payments = entity
.GetPayments()
.Select(paymentEntity =>
{
var paymentData = paymentEntity.GetCryptoPaymentData();
var paymentMethodId = paymentEntity.GetPaymentMethodId();
if (paymentData is null || paymentMethodId is null)
{
return null;
}
string txId = paymentData.GetPaymentId();
string link = GetTransactionLink(paymentMethodId, txId);
@ -117,7 +124,9 @@ namespace BTCPayServer.PaymentRequest
Link = link,
Id = txId
};
}).ToList()
})
.Where(payment => payment != null)
.ToList()
}).ToList()
};
}

View file

@ -222,7 +222,7 @@ namespace BTCPayServer.Payments.Bitcoin
var paymentEntitiesByPrevOut = new Dictionary<OutPoint, PaymentEntity>();
foreach (var payment in invoice.GetPayments(wallet.Network))
{
if (payment.GetPaymentMethodId().PaymentType != PaymentTypes.BTCLike)
if (payment.GetPaymentMethodId()?.PaymentType != PaymentTypes.BTCLike)
continue;
var paymentData = (BitcoinLikePaymentData)payment.GetCryptoPaymentData();
if (!transactions.TryGetValue(paymentData.Outpoint.Hash, out TransactionResult tx))

View file

@ -72,6 +72,7 @@ namespace BTCPayServer.Payments
public static bool TryParse(string str, out PaymentMethodId paymentMethodId)
{
str ??= "";
paymentMethodId = null;
var parts = str.Split('_', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0 || parts.Length > 2)

View file

@ -22,7 +22,7 @@ namespace BTCPayServer.Payments
public override CryptoPaymentData DeserializePaymentData(BTCPayNetworkBase network, string str)
{
return ((BTCPayNetwork)network).ToObject<BitcoinLikePaymentData>(str);
return ((BTCPayNetwork)network)?.ToObject<BitcoinLikePaymentData>(str);
}
public override string SerializePaymentData(BTCPayNetworkBase network, CryptoPaymentData paymentData)

View file

@ -20,7 +20,7 @@ namespace BTCPayServer.Payments
public override CryptoPaymentData DeserializePaymentData(BTCPayNetworkBase network, string str)
{
return ((BTCPayNetwork)network).ToObject<LightningLikePaymentData>(str);
return ((BTCPayNetwork)network)?.ToObject<LightningLikePaymentData>(str);
}
public override string SerializePaymentData(BTCPayNetworkBase network, CryptoPaymentData paymentData)

View file

@ -39,7 +39,7 @@ namespace BTCPayServer.Services.Apps
{
data.GetValue(),
invoiceEvent.Payment.GetCryptoCode(),
invoiceEvent.Payment.GetPaymentMethodId().PaymentType.ToString()
invoiceEvent.Payment.GetPaymentMethodId()?.PaymentType?.ToString()
}, cancellationToken);
}
await InfoUpdated(appId);

View file

@ -182,7 +182,7 @@ namespace BTCPayServer.Services.Apps
});
// Old invoices may have invoices which were not tagged
invoices = invoices.Where(inv => inv.Version < InvoiceEntity.InternalTagSupport_Version ||
invoices = invoices.Where(inv => appData.TagAllInvoices || inv.Version < InvoiceEntity.InternalTagSupport_Version ||
inv.InternalTags.Contains(GetAppInternalTag(appData.Id))).ToArray();
return invoices;
}
@ -333,7 +333,7 @@ namespace BTCPayServer.Services.Apps
.SelectMany(p =>
{
var contribution = new Contribution();
contribution.PaymentMehtodId = new PaymentMethodId(p.ProductInformation.Currency, PaymentTypes.BTCLike);
contribution.PaymentMethodId = new PaymentMethodId(p.ProductInformation.Currency, PaymentTypes.BTCLike);
contribution.CurrencyValue = p.ProductInformation.Price;
contribution.Value = contribution.CurrencyValue;
@ -363,18 +363,18 @@ namespace BTCPayServer.Services.Apps
.Select(pay =>
{
var paymentMethodContribution = new Contribution();
paymentMethodContribution.PaymentMehtodId = pay.GetPaymentMethodId();
paymentMethodContribution.PaymentMethodId = pay.GetPaymentMethodId();
paymentMethodContribution.Value = pay.GetCryptoPaymentData().GetValue() - pay.NetworkFee;
var rate = p.GetPaymentMethod(paymentMethodContribution.PaymentMehtodId).Rate;
var rate = p.GetPaymentMethod(paymentMethodContribution.PaymentMethodId).Rate;
paymentMethodContribution.CurrencyValue = rate * paymentMethodContribution.Value;
return paymentMethodContribution;
})
.ToArray();
})
.GroupBy(p => p.PaymentMehtodId)
.GroupBy(p => p.PaymentMethodId)
.ToDictionary(p => p.Key, p => new Contribution()
{
PaymentMehtodId = p.Key,
PaymentMethodId = p.Key,
Value = p.Select(v => v.Value).Sum(),
CurrencyValue = p.Select(v => v.CurrencyValue).Sum()
});

View file

@ -47,10 +47,8 @@ namespace BTCPayServer.Services.Invoices.Export
using StringWriter writer = new StringWriter();
using var csvWriter = new CsvHelper.CsvWriter(writer, new CsvConfiguration(CultureInfo.InvariantCulture), true);
csvWriter.WriteHeader<ExportInvoiceHolder>();
foreach (var invoice in invoices)
{
csvWriter.WriteRecord(invoice);
}
csvWriter.NextRecord();
csvWriter.WriteRecords(invoices);
csvWriter.Flush();
return writer.ToString();
}

View file

@ -8,6 +8,7 @@ using BTCPayServer.JsonConverters;
using BTCPayServer.Models;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using Microsoft.AspNetCore.Http.Extensions;
using NBitcoin;
using NBitcoin.DataEncoders;
using NBitpayClient;
@ -266,11 +267,11 @@ namespace BTCPayServer.Services.Invoices
#pragma warning disable CS0618
public List<PaymentEntity> GetPayments()
{
return Payments?.ToList() ?? new List<PaymentEntity>();
return Payments?.Where(entity => entity.GetPaymentMethodId() != null).ToList() ?? new List<PaymentEntity>();
}
public List<PaymentEntity> GetPayments(string cryptoCode)
{
return Payments.Where(p => p.CryptoCode == cryptoCode).ToList();
return GetPayments().Where(p => p.CryptoCode == cryptoCode).ToList();
}
public List<PaymentEntity> GetPayments(BTCPayNetworkBase network)
{
@ -299,8 +300,8 @@ namespace BTCPayServer.Services.Invoices
private Uri FillPlaceholdersUri(string v)
{
var uriStr = (v ?? string.Empty).Replace("{OrderId}", OrderId ?? "", StringComparison.OrdinalIgnoreCase)
.Replace("{InvoiceId}", Id ?? "", StringComparison.OrdinalIgnoreCase);
var uriStr = (v ?? string.Empty).Replace("{OrderId}", System.Web.HttpUtility.UrlEncode(OrderId) ?? "", StringComparison.OrdinalIgnoreCase)
.Replace("{InvoiceId}", System.Web.HttpUtility.UrlEncode(Id) ?? "", StringComparison.OrdinalIgnoreCase);
if (Uri.TryCreate(uriStr, UriKind.Absolute, out var uri) && (uri.Scheme == "http" || uri.Scheme == "https"))
return uri;
return null;
@ -550,7 +551,10 @@ namespace BTCPayServer.Services.Invoices
foreach (var prop in PaymentMethod.Properties())
{
var r = serializer.ToObject<PaymentMethod>(prop.Value.ToString());
var paymentMethodId = PaymentMethodId.Parse(prop.Name);
if (!PaymentMethodId.TryParse(prop.Name, out var paymentMethodId))
{
continue;
}
r.CryptoCode = paymentMethodId.CryptoCode;
r.PaymentType = paymentMethodId.PaymentType.ToString();
r.ParentEntity = this;
@ -1005,7 +1009,18 @@ namespace BTCPayServer.Services.Invoices
}
else
{
paymentData = GetPaymentMethodId().PaymentType.DeserializePaymentData(Network, CryptoPaymentData);
var paymentMethodId = GetPaymentMethodId();
if (paymentMethodId is null)
{
return null;
}
paymentData = paymentMethodId.PaymentType.DeserializePaymentData(Network, CryptoPaymentData);
if (paymentData is null)
{
return null;
}
paymentData.Network = Network;
if (paymentData is BitcoinLikePaymentData bitcoin)
{
@ -1050,7 +1065,16 @@ namespace BTCPayServer.Services.Invoices
public PaymentMethodId GetPaymentMethodId()
{
#pragma warning disable CS0618 // Type or member is obsolete
return new PaymentMethodId(CryptoCode ?? "BTC", string.IsNullOrEmpty(CryptoPaymentDataType) ? PaymentTypes.BTCLike : PaymentTypes.Parse(CryptoPaymentDataType));
PaymentType paymentType;
if (string.IsNullOrEmpty(CryptoPaymentDataType))
{
paymentType = BitcoinPaymentType.Instance;;
}
else if(!PaymentTypes.TryParse(CryptoPaymentDataType, out paymentType))
{
return null;
}
return new PaymentMethodId(CryptoCode ?? "BTC", paymentType);
#pragma warning restore CS0618 // Type or member is obsolete
}

View file

@ -484,6 +484,8 @@ retry:
entity.Payments = invoice.Payments.Select(p =>
{
var paymentEntity = p.GetBlob(_Networks);
if (paymentEntity is null)
return null;
// PaymentEntity on version 0 does not have their own fee, because it was assumed that the payment method have fixed fee.
// We want to hide this legacy detail in InvoiceRepository, so we fetch the fee from the PaymentMethod and assign it to the PaymentEntity.
if (paymentEntity.Version == 0)
@ -497,6 +499,7 @@ retry:
return paymentEntity;
})
.Where(p => p != null)
.OrderBy(a => a.ReceivedTime).ToList();
#pragma warning restore CS0618
var state = invoice.GetInvoiceState();
@ -701,7 +704,7 @@ retry:
Accounted = accounted
};
context.Payments.Add(data);
await context.Payments.AddAsync(data);
try
{

View file

@ -20,7 +20,7 @@ namespace BTCPayServer.Services.Notifications.Blobs
public override string NotificationType => "payout";
protected override void FillViewModel(PayoutNotification notification, NotificationViewModel vm)
{
vm.Body = "A new payout is awaiting for payment";
vm.Body = "A new payout is awaiting for approval";
vm.ActionLink = _linkGenerator.GetPathByAction(nameof(WalletsController.Payouts),
"Wallets",
new { walletId = new WalletId(notification.StoreId, notification.PaymentMethod) }, _options.RootPath);

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using BTCPayServer.Components.NotificationsDropdown;
using BTCPayServer.Data;
using BTCPayServer.Models.NotificationViewModels;
using Microsoft.AspNetCore.Identity;
@ -124,10 +125,4 @@ namespace BTCPayServer.Services.Notifications
throw new InvalidOperationException($"No INotificationHandler found for {blobType.Name}");
}
}
public class NotificationSummaryViewModel
{
public int UnseenCount { get; set; }
public List<NotificationViewModel> Last5 { get; set; }
}
}

View file

@ -29,7 +29,7 @@
@if (env.OnionUrl != null)
{
<div class="text-center">
<a href="@env.OnionUrl" target="_onion" class="btn btn-sm btn-outline-onion d-inline-flex align-items-center p-2" data-clipboard="@env.OnionUrl">
<a href="@env.OnionUrl" target="_onion" class="btn btn-sm btn-outline-onion d-inline-flex align-items-center text-nowrap p-2" data-clipboard="@env.OnionUrl">
<img src="~/img/icons/onion-purple.svg" height="20" class="mr-2" asp-append-version="true" />
Copy Tor URL
</a>

View file

@ -33,7 +33,7 @@
@if (env.OnionUrl != null)
{
<div class="text-center">
<a href="@env.OnionUrl" target="_onion" class="btn btn-sm btn-outline-onion d-inline-flex align-items-center p-2" data-clipboard="@env.OnionUrl">
<a href="@env.OnionUrl" target="_onion" class="btn btn-sm btn-outline-onion d-inline-flex align-items-center text-nowrap p-2" data-clipboard="@env.OnionUrl">
<img src="~/img/icons/onion-purple.svg" height="20" class="mr-2" asp-append-version="true" />
Copy Tor URL
</a>

View file

@ -46,7 +46,7 @@
Do not photograph it. Do not store it digitally.
</p>
<p>
<strong>The recovery phrase will also be stored on a server as a hot wallet.</strong>
<strong>The recovery phrase will also be stored on the server as a hot wallet.</strong>
</p>
}
else
@ -65,11 +65,18 @@
<p class="mt-3 mb-0">Please make sure to also write down your passphrase.</p>
}
</div>
<form id="recovery-confirmation" action="@Model.ReturnUrl" class="d-flex align-items-start justify-content-center" style="margin-top:4rem;padding-bottom: 80px">
<label class="form-check-label lead order-2" for="confirm">I have written down my recovery phrase and stored it in a secure location</label>
<input type="checkbox" class="mt-2 mr-3 order-1" id="confirm">
<button type="submit" class="btn btn-primary btn-lg px-5 order-3" id="submit">Done</button>
<button type="submit" class="btn btn-primary btn-lg px-5 order-3" disabled>Done</button>
</form>
@if (Model.RequireConfirm)
{
<form id="recovery-confirmation" action="@Model.ReturnUrl" class="d-flex align-items-start justify-content-center" style="margin-top:4rem;padding-bottom: 80px">
<label class="form-check-label lead order-2" for="confirm">I have written down my recovery phrase and stored it in a secure location</label>
<input type="checkbox" class="mt-2 mr-3 order-1" id="confirm">
<button type="submit" class="btn btn-primary btn-lg px-5 order-3" id="submit">Done</button>
<button type="submit" class="btn btn-primary btn-lg px-5 order-3" disabled>Done</button>
</form>
}
else
{
<a href="@Model.ReturnUrl" class="btn btn-primary btn-lg mt-3 px-5 order-3" id="proceed">Done</a>
}
</div>
</div>

View file

@ -8,7 +8,8 @@
<!-- needed for adaptive design -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<link href="~/main/fonts/Roboto.css" rel="stylesheet" asp-append-version="true">
<link href="~/main/fonts/Montserrat.css" rel="stylesheet" asp-append-version="true">
<!--
ReDoc doesn't change outer page styles

View file

@ -5,16 +5,17 @@
@section HeadScripts {
<script src="~/modal/btcpay.js" asp-append-version="true"></script>
}
@Html.HiddenFor(a => a.Count)
<section>
<div class="container">
@if (TempData.HasStatusMessage())
{
<div class="row">
<div class="col-lg-12 text-center">
<partial name="_StatusMessage" />
</div>
<div class="row">
<div class="col-lg-12 text-center">
<partial name="_StatusMessage" />
</div>
</div>
}
<div class="row">
@ -49,7 +50,7 @@
<input type="hidden" asp-for="Count" />
<div class="input-group ">
<input asp-for="TimezoneOffset" type="hidden" />
<input asp-for="SearchTerm" class="form-control"/>
<input asp-for="SearchTerm" class="form-control" />
<div class="input-group-append">
<button type="submit" class="btn btn-primary" title="Search invoice">
<span class="fa fa-search"></span> Search
@ -91,7 +92,7 @@
<div>
<a asp-action="CreateInvoice" class="btn btn-primary mb-1" role="button" id="CreateNewInvoice"><span class="fa fa-plus"></span> Create a new invoice</a>
<span >
<span>
<button class="btn btn-primary dropdown-toggle mb-1" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Actions
</button>
@ -99,7 +100,7 @@
<button type="submit" asp-action="MassAction" class="dropdown-item" name="command" value="archive"><i class="fa fa-archive"></i> Archive</button>
</div>
</span>
<span>
<a class="btn btn-primary dropdown-toggle mb-1" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Export
@ -114,7 +115,7 @@
</span>
</div>
</div>
<br/>
<br />
@* Custom Range Modal *@
<div class="modal fade" id="customRangeModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document" style="max-width: 550px;">
@ -131,8 +132,8 @@
<div class="col-sm-9">
<div class="input-group">
<input id="dtpStartDate" class="form-control flatdtpicker" type="datetime-local"
data-fdtp='{ "enableTime": true, "enableSeconds": true, "dateFormat": "Y-m-d H:i:S", "time_24hr": true, "defaultHour": 0 }'
placeholder="Start Date" />
data-fdtp='{ "enableTime": true, "enableSeconds": true, "dateFormat": "Y-m-d H:i:S", "time_24hr": true, "defaultHour": 0 }'
placeholder="Start Date" />
<div class="input-group-append">
<button type="button" class="btn btn-primary input-group-clear" title="Clear">
<span class=" fa fa-times"></span>
@ -146,8 +147,8 @@
<div class="col-sm-9">
<div class="input-group">
<input id="dtpEndDate" class="form-control flatdtpicker" type="datetime-local"
data-fdtp='{ "enableTime": true, "enableSeconds": true, "dateFormat": "Y-m-d H:i:S", "time_24hr": true, "defaultHour": 0 }'
placeholder="End Date" />
data-fdtp='{ "enableTime": true, "enableSeconds": true, "dateFormat": "Y-m-d H:i:S", "time_24hr": true, "defaultHour": 0 }'
placeholder="End Date" />
<div class="input-group-append">
<button type="button" class="btn btn-primary input-group-clear" title="Clear">
<span class=" fa fa-times"></span>
@ -194,8 +195,7 @@
@* Custom Range Modal *@
<script type="text/javascript">
function selectAll(e)
{
function selectAll(e) {
var items = document.getElementsByClassName("selector");
for (var i = 0; i < items.length; i++) {
items[i].checked = e.checked;
@ -210,7 +210,7 @@
<th class="only-for-js">
@if (Model.Total > 0)
{
<input id="selectAllCheckbox" type="checkbox" onclick="selectAll(this);" />
<input id="selectAllCheckbox" type="checkbox" onclick="selectAll(this);" />
}
</th>
<th style="min-width: 90px;" class="col-md-auto">
@ -229,91 +229,91 @@
<tbody>
@foreach (var invoice in Model.Invoices)
{
<tr>
<td class="only-for-js">
<input name="selectedItems" type="checkbox" class="selector" value="@invoice.InvoiceId" />
</td>
<td>
<span class="switchTimeFormat" data-switch="@invoice.Date.ToTimeAgo()">
@invoice.Date.ToBrowserDate()
<tr>
<td class="only-for-js">
<input name="selectedItems" type="checkbox" class="selector" value="@invoice.InvoiceId" />
</td>
<td>
<span class="switchTimeFormat" data-switch="@invoice.Date.ToTimeAgo()">
@invoice.Date.ToBrowserDate()
</span>
</td>
<td style="max-width: 180px;">
@if (invoice.RedirectUrl != string.Empty)
{
<a href="@invoice.RedirectUrl" class="wraptext200">@invoice.OrderId</a>
}
else
{
<span>@invoice.OrderId</span>
}
</td>
<td>@invoice.InvoiceId</td>
<td>
@if(invoice.Details.Archived)
{
<span class="badge badge-warning">archived</span>
}
@if (invoice.CanMarkStatus)
{
<div id="pavpill_@invoice.InvoiceId">
<span class="dropdown-toggle dropdown-toggle-split pavpill pavpil-@invoice.Status.ToString().ToLower()"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@invoice.StatusString
</span>
</td>
<td style="max-width: 180px;">
@if (invoice.RedirectUrl != string.Empty)
{
<a href="@invoice.RedirectUrl" class="wraptext200">@invoice.OrderId</a>
}
else
{
<span>@invoice.OrderId</span>
}
</td>
<td>@invoice.InvoiceId</td>
<td>
@if(invoice.Details.Archived)
{
<span class="badge badge-warning" >archived</span>
}
@if (invoice.CanMarkStatus)
{
<div id="pavpill_@invoice.InvoiceId">
<span class="dropdown-toggle dropdown-toggle-split pavpill pavpil-@invoice.Status.ToString().ToLower()"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@invoice.StatusString
</span>
<div class="dropdown-menu pull-right">
@if (invoice.CanMarkInvalid)
<div class="dropdown-menu pull-right">
@if (invoice.CanMarkInvalid)
{
<button class="dropdown-item small cursorPointer" onclick="changeInvoiceState(this, '@invoice.InvoiceId', 'invalid')">
Mark as invalid <span class="fa fa-times"></span>
</button>
<button class="dropdown-item small cursorPointer" onclick="changeInvoiceState(this, '@invoice.InvoiceId', 'invalid')">
Mark as invalid <span class="fa fa-times"></span>
</button>
}
@if (invoice.CanMarkComplete)
@if (invoice.CanMarkComplete)
{
<button class="dropdown-item small cursorPointer" onclick="changeInvoiceState(this, '@invoice.InvoiceId', 'complete')">
Mark as complete <span class="fa fa-check-circle"></span>
</button>
<button class="dropdown-item small cursorPointer" onclick="changeInvoiceState(this, '@invoice.InvoiceId', 'complete')">
Mark as complete <span class="fa fa-check-circle"></span>
</button>
}
</div>
</div>
}
else
{
<span class="pavpill pavpil-@invoice.Status.ToString().ToLower()">@invoice.StatusString</span>
}
</td>
<td style="text-align:right">@invoice.AmountCurrency</td>
<td style="text-align:right">
@if (invoice.ShowCheckout)
{
<span>
<a asp-action="Checkout" class="invoice-checkout-link" id="invoice-checkout-@invoice.InvoiceId" asp-route-invoiceId="@invoice.InvoiceId">Checkout</a>
<a href="javascript:btcpay.showInvoice('@invoice.InvoiceId')">[^]</a>
@if (!invoice.CanMarkStatus)
{
<span>-</span>
}
</span>
}
&nbsp;
<a asp-action="Invoice" class="invoice-details-link" asp-route-invoiceId="@invoice.InvoiceId">Details</a>
<a href="javascript:void(0);" onclick="detailsToggle(this, '@invoice.InvoiceId')">
<span title="Invoice Details Toggle" class="fa fa-1x fa-angle-double-down"></span>
</a>
</td>
</tr>
<tr id="invoice_@invoice.InvoiceId" style="display:none;">
<td colspan="99" class="border-top-0">
<div style="margin-left: 15px; margin-bottom: 0;">
@* Leaving this as partial because it abstracts complexity of Invoice Payments *@
<partial name="ListInvoicesPaymentsPartial" model="(invoice.Details, true)" />
</div>
</td>
</tr>
</div>
}
else
{
<span class="pavpill pavpil-@invoice.Status.ToString().ToLower()">@invoice.StatusString</span>
}
</td>
<td style="text-align:right">@invoice.AmountCurrency</td>
<td style="text-align:right">
@if (invoice.ShowCheckout)
{
<span>
<a asp-action="Checkout" class="invoice-checkout-link" id="invoice-checkout-@invoice.InvoiceId" asp-route-invoiceId="@invoice.InvoiceId">Checkout</a>
<a href="javascript:btcpay.showInvoice('@invoice.InvoiceId')">[^]</a>
@if (!invoice.CanMarkStatus)
{
<span>-</span>
}
</span>
}
&nbsp;
<a asp-action="Invoice" class="invoice-details-link" asp-route-invoiceId="@invoice.InvoiceId">Details</a>
<a href="javascript:void(0);" onclick="detailsToggle(this, '@invoice.InvoiceId')">
<span title="Invoice Details Toggle" class="fa fa-1x fa-angle-double-down"></span>
</a>
</td>
</tr>
<tr id="invoice_@invoice.InvoiceId" style="display:none;">
<td colspan="99" class="border-top-0">
<div style="margin-left: 15px; margin-bottom: 0;">
@* Leaving this as partial because it abstracts complexity of Invoice Payments *@
<partial name="ListInvoicesPaymentsPartial" model="(invoice.Details, true)" />
</div>
</td>
</tr>
}
</tbody>
</table>
<partial name="_TableFooterPager" />
<vc:pager view-model="Model"></vc:pager>
</div>
</div>
</form>
@ -324,7 +324,7 @@
$(".export-link, a.dropdown-item").each(function () {
this.href = this.href.replace("timezoneoffset=0", "timezoneoffset=" + timezoneOffset);
});
});
function getDateStringWithOffset(hoursDiff) {

View file

@ -47,7 +47,7 @@
</div>
</div>
@{
var grouped = invoice.Payments.GroupBy(payment => payment.GetPaymentMethodId().PaymentType);
var grouped = invoice.Payments.GroupBy(payment => payment.GetPaymentMethodId()?.PaymentType).Where(entities => entities.Key!= null);
}
@foreach (var paymentGroup in grouped)
{

View file

@ -91,7 +91,7 @@
</tbody>
</table>
<partial name="_TableFooterPager" />
<vc:pager view-model="Model"></vc:pager>
</div>
</div>
</div>

View file

@ -1,27 +0,0 @@
<div></div>
@await Component.InvokeAsync("NotificationsDropdown")
<script>
var supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
if (supportsWebSockets) {
var ws_uri = "@Url.Action("SubscribeUpdates", "Notifications", new {}, Context.Request.Scheme)".replace("http", "ws");
var newDataEndpoint = "@Url.Action("GetNotificationDropdownUI", "Notifications", new {}, Context.Request.Scheme)";
try {
socket = new WebSocket(ws_uri);
socket.onmessage = function (e) {
$.get(newDataEndpoint, function(data){
$("#notifications-nav-item").replaceWith($(data));
});
};
socket.onerror = function (e) {
console.error("Error while connecting to websocket for notifications (callback)", e);
};
}
catch (e) {
console.error("Error while connecting to websocket for notifications", e);
}
}
</script>

View file

@ -5,10 +5,14 @@
@{
PayjoinInformation payjoinIformation = null;
var onchainPayments = Model.Where(entity => entity.GetPaymentMethodId().PaymentType == BitcoinPaymentType.Instance).Select(payment =>
var onchainPayments = Model.Where(entity => entity.GetPaymentMethodId()?.PaymentType == BitcoinPaymentType.Instance).Select(payment =>
{
var m = new OnchainPaymentViewModel();
var onChainPaymentData = payment.GetCryptoPaymentData() as BitcoinLikePaymentData;
if (onChainPaymentData is null)
{
return null;
}
m.Crypto = payment.GetPaymentMethodId().CryptoCode;
m.DepositAddress = onChainPaymentData.GetDestination();
@ -38,7 +42,7 @@
m.Replaced = !payment.Accounted;
m.CryptoPaymentData = onChainPaymentData;
return m;
});
}).Where(model => model != null);
}
@if (onchainPayments.Any())

View file

@ -3,15 +3,19 @@
@model IEnumerable<BTCPayServer.Services.Invoices.PaymentEntity>
@{
var offchainPayments = Model.Where(entity => entity.GetPaymentMethodId().PaymentType == LightningPaymentType.Instance).Select(payment =>
var offchainPayments = Model.Where(entity => entity.GetPaymentMethodId()?.PaymentType == LightningPaymentType.Instance).Select(payment =>
{
var offChainPaymentData = payment.GetCryptoPaymentData() as LightningLikePaymentData;
if (offChainPaymentData is null)
{
return null;
}
return new OffChainPaymentViewModel()
{
Crypto = payment.Network.CryptoCode,
BOLT11 = offChainPaymentData.BOLT11
};
});
}).Where(model => model != null);
}

View file

@ -2,37 +2,37 @@
Our Supporters
</h5>
<div class="row justify-content-center mb-2">
<div class="p-3 text-center">
<div class="p-3 text-center" style="flex-basis:105px;">
<a href="https://kraken.com" target="_blank" class="text-muted small">
<img src="~/img/kraken.svg" alt="Sponsor Kraken" height="50" asp-append-version="true"/>
<span class="d-block mt-3">Kraken</span>
</a>
</div>
<div class="p-3 text-center">
<div class="p-3 text-center" style="flex-basis:105px;">
<a href="https://twitter.com/sqcrypto" target="_blank" class="text-muted small">
<img src="~/img/squarecrypto.svg" alt="Sponsor Square Crypto" height="50" asp-append-version="true"/>
<span class="d-block mt-3">Square Crypto</span>
</a>
</div>
<div class="p-3 text-center">
<div class="p-3 text-center" style="flex-basis:105px;">
<a href="https://www.btse.com" target="_blank" class="text-muted small">
<img src="~/img/btse.svg" alt="Sponsor BTSE" height="50" asp-append-version="true"/>
<span class="d-block mt-3">BTSE</span>
</a>
</div>
<div class="p-3 text-center">
<div class="p-3 text-center" style="flex-basis:105px;">
<a href="https://www.dglab.com/en/" target="_blank" class="text-muted small">
<img src="~/img/dglab.svg" alt="Sponsor DG lab" height="50" asp-append-version="true"/>
<span class="d-block mt-3">DG Lab</span>
</a>
</div>
<div class="p-3 text-center">
<div class="p-3 text-center" style="flex-basis:105px;">
<a href="https://www.okcoin.com/" target="_blank" class="text-muted small">
<img src="~/img/okcoin.svg" alt="Sponsor OKCoin" height="50" asp-append-version="true"/>
<span class="d-block mt-3">OKCoin</span>
</a>
</div>
<div class="p-3 text-center">
<div class="p-3 text-center" style="flex-basis:105px;">
<a href="https://acinq.co/" target="_blank" class="text-muted small">
<img src="~/img/acinq-logo.svg" alt="Sponsor ACINQ" height="50" asp-append-version="true"/>
<span class="d-block mt-3">ACINQ</span>

View file

@ -1,4 +1,4 @@
@inject SignInManager<ApplicationUser> SignInManager
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
@inject RoleManager<IdentityRole> RoleManager
@inject BTCPayServer.Services.BTCPayServerEnvironment env
@ -52,12 +52,6 @@
<span class="badge badge-warning" style="font-size:10px;">@env.NetworkType.ToString()</span>
}
</a>
@if (env.OnionUrl != null)
{
<a class="onion" href="@env.OnionUrl" target="_blank">
<img src="~/img/icons/onion.svg" width="26" height="32" asp-append-version="true" />
</a>
}
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<svg class="navbar-toggler-icon" viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'><path stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22' /></svg>
</button>
@ -77,8 +71,7 @@
<li class="nav-item">
<a asp-area="" asp-controller="Manage" asp-action="Index" title="My settings" class="nav-link js-scroll-trigger" id="MySettings"><i class="fa fa-user"></i></a>
</li>
<partial name="LayoutPartials/NotificationsNavItem" />
<vc:notifications-dropdown></vc:notifications-dropdown>
<li class="nav-item">
<a asp-area="" asp-controller="Account" asp- asp-action="Logout" title="Logout" class="nav-link js-scroll-trigger" id="Logout"><i class="fa fa-sign-out"></i></a>

View file

@ -20,7 +20,7 @@
You are not an admin on this server. While you are able to import or generate a wallet via seed with your account, please understand that you are trusting the server admins not just with your <a href="https://docs.btcpayserver.org/ThirdPartyHosting/#privacy-concerns" target="_blank" class="alert-link">privacy</a> but also with <a href="https://docs.btcpayserver.org/ThirdPartyHosting/#trust-concerns" target="_blank" class="alert-link">trivial access to your funds.</a> If you NEED to use this feature, please reconsider hosting your own BTCPay Server instance.
</div>
}
<p>You may generate a wallet with a seed and import the xpub it into BTCPay. You can optionally also tell NBX to import the keys to the node wallet to be able to view & spend received funds from it.</p>
<p>You may generate a wallet with a seed and import the xpub into BTCPay. You can optionally also tell NBX to import the keys to the node wallet to be able to view & spend received funds from it.</p>
<div class="form-group">
<label asp-for="ExistingMnemonic">Existing Seed</label>

View file

@ -148,7 +148,7 @@
}
<td class="text-right">
<div class="dropdown d-inline-block" >
<span class="fa fa-tags" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="fa fa-tags cursor-pointer" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu">
<form asp-action="ModifyTransaction" method="post"
asp-route-walletId="@this.Context.GetRouteValue("walletId")">
@ -180,11 +180,11 @@
<div class="dropdown d-inline-block">
@if (string.IsNullOrEmpty(transaction.Comment))
{
<span class="fa fa-comment" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="fa fa-comment cursor-pointer" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
}
else
{
<span class="fa fa-commenting" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="fa fa-commenting cursor-pointer" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
}
<div class="dropdown-menu">
<form asp-action="ModifyTransaction" method="post"

View file

@ -10,3 +10,4 @@
@using Microsoft.AspNetCore.Routing;
@inject BTCPayServer.Services.Safe Safe
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, BTCPayServer

View file

@ -37,6 +37,8 @@
"inputFiles": [
"wwwroot/vendor/font-awesome/css/font-awesome.css",
"wwwroot/vendor/vex/css/vex.css",
"wwwroot/main/fonts/Roboto.css",
"wwwroot/main/fonts/RobotoMono.css",
"wwwroot/checkout/*/*.css",
"wwwroot/vendor/jquery-prettydropdowns/prettydropdowns.css"
]

View file

@ -1,280 +0,0 @@
/* cyrillic-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v16/ek4gzZ-GeXAPcSbHtCeQI_esZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v16/mErvLBYg_cXG3rLvUsKT_fesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v16/-2n2p-_Y08sg57CNWQfKNvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v16/u0TOpm082MNkS5K0Q4rhqvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v16/NdF9MtnOpLzo-noMoG0miPesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v16/Fcx7Wwv8OzT71A3E1XOAjvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v16/CWB0XYA8bzo0kSThX0UTuA.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
/* cyrillic-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v16/ZLqKeelYbATG60EpZBSDyxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v16/oHi30kwQWvpCWqAhzHcCSBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v16/rGvHdJnr2l75qb0YND9NyBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v16/mx9Uck6uB63VIKFYnEMXrRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v16/mbmhprMH69Zi6eEPBYVFhRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v16/oOeFwZNlrTefzLYmlVV1UBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v16/RxZJdnzeo3R5zSexge8UUVtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
/* cyrillic-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v16/77FXFjRbGzN4aCrSFhlh3hJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v16/isZ-wbCXNKAbnjo6_TwHThJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v16/UX6i4JxQDm3fVTc1CPuwqhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v16/jSN2CGVDbcVyCnfJfjSdfBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v16/PwZc-YbIL414wB9rB1IAPRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v16/97uahxiqZRoncBaCEI3aWxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v16/d-6IYplOFocCacKzxwXSOFtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
/* cyrillic-ext */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local('Roboto Italic'), local('Roboto-Italic'), url(https://fonts.gstatic.com/s/roboto/v16/WxrXJa0C3KdtC7lMafG4dRTbgVql8nDJpwnrE27mub0.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local('Roboto Italic'), local('Roboto-Italic'), url(https://fonts.gstatic.com/s/roboto/v16/OpXUqTo0UgQQhGj_SFdLWBTbgVql8nDJpwnrE27mub0.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local('Roboto Italic'), local('Roboto-Italic'), url(https://fonts.gstatic.com/s/roboto/v16/1hZf02POANh32k2VkgEoUBTbgVql8nDJpwnrE27mub0.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local('Roboto Italic'), local('Roboto-Italic'), url(https://fonts.gstatic.com/s/roboto/v16/cDKhRaXnQTOVbaoxwdOr9xTbgVql8nDJpwnrE27mub0.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local('Roboto Italic'), local('Roboto-Italic'), url(https://fonts.gstatic.com/s/roboto/v16/K23cxWVTrIFD6DJsEVi07RTbgVql8nDJpwnrE27mub0.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local('Roboto Italic'), local('Roboto-Italic'), url(https://fonts.gstatic.com/s/roboto/v16/vSzulfKSK0LLjjfeaxcREhTbgVql8nDJpwnrE27mub0.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local('Roboto Italic'), local('Roboto-Italic'), url(https://fonts.gstatic.com/s/roboto/v16/vPcynSL0qHq_6dX7lKVByfesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
/* cyrillic-ext */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
src: local('Roboto Mono'), local('RobotoMono-Regular'), url(https://fonts.gstatic.com/s/robotomono/v4/hMqPNLsu_dywMa4C_DEpYzTOQ_MqJVwkKsUn0wKzc2I.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
src: local('Roboto Mono'), local('RobotoMono-Regular'), url(https://fonts.gstatic.com/s/robotomono/v4/hMqPNLsu_dywMa4C_DEpYzUj_cnvWIuuBMVgbX098Mw.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
src: local('Roboto Mono'), local('RobotoMono-Regular'), url(https://fonts.gstatic.com/s/robotomono/v4/hMqPNLsu_dywMa4C_DEpY0bcKLIaa1LC45dFaAfauRA.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
src: local('Roboto Mono'), local('RobotoMono-Regular'), url(https://fonts.gstatic.com/s/robotomono/v4/hMqPNLsu_dywMa4C_DEpY2o_sUJ8uO4YLWRInS22T3Y.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
src: local('Roboto Mono'), local('RobotoMono-Regular'), url(https://fonts.gstatic.com/s/robotomono/v4/hMqPNLsu_dywMa4C_DEpY76up8jxqWt8HVA3mDhkV_0.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
src: local('Roboto Mono'), local('RobotoMono-Regular'), url(https://fonts.gstatic.com/s/robotomono/v4/hMqPNLsu_dywMa4C_DEpYyYE0-AqJ3nfInTTiDXDjU4.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
src: local('Roboto Mono'), local('RobotoMono-Regular'), url(https://fonts.gstatic.com/s/robotomono/v4/hMqPNLsu_dywMa4C_DEpY44P5ICox8Kq3LLUNMylGO4.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}

Some files were not shown because too many files have changed in this diff Show more