mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-22 06:21:44 +01:00
Merge branch 'master' into mwb-integration-email-qr
This commit is contained in:
commit
77f9b6a88c
60 changed files with 752 additions and 573 deletions
|
@ -2,7 +2,7 @@ version: 2
|
|||
jobs:
|
||||
fast_tests:
|
||||
machine:
|
||||
image: ubuntu-2004:202111-02
|
||||
image: ubuntu-2004:2024.11.1
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
|
@ -10,7 +10,7 @@ jobs:
|
|||
cd .circleci && ./run-tests.sh "Fast=Fast|ThirdParty=ThirdParty" && ./can-build.sh
|
||||
selenium_tests:
|
||||
machine:
|
||||
image: ubuntu-2004:202111-02
|
||||
image: ubuntu-2004:2024.11.1
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
|
@ -18,7 +18,7 @@ jobs:
|
|||
cd .circleci && ./run-tests.sh "Selenium=Selenium"
|
||||
integration_tests:
|
||||
machine:
|
||||
image: ubuntu-2004:202111-02
|
||||
image: ubuntu-2004:2024.11.1
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
|
@ -26,7 +26,7 @@ jobs:
|
|||
cd .circleci && ./run-tests.sh "Integration=Integration"
|
||||
trigger_docs_build:
|
||||
machine:
|
||||
image: ubuntu-2004:202111-02
|
||||
image: ubuntu-2004:2024.11.1
|
||||
steps:
|
||||
- run:
|
||||
command: |
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
set -e
|
||||
|
||||
cd ../BTCPayServer.Tests
|
||||
docker-compose -v
|
||||
docker-compose -f "docker-compose.altcoins.yml" down --v
|
||||
docker-compose --version
|
||||
docker-compose -f "docker-compose.altcoins.yml" down -v
|
||||
|
||||
# For some reason, docker-compose pull fails time to time, so we try several times
|
||||
n=0
|
||||
|
|
|
@ -32,8 +32,8 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlSanitizer" Version="8.0.838" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.11" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BTCPayServer.Client\BTCPayServer.Client.csproj" />
|
||||
|
|
|
@ -30,8 +30,8 @@
|
|||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.5.1" />
|
||||
<PackageReference Include="NBitcoin" Version="7.0.45" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.5.2" />
|
||||
<PackageReference Include="NBitcoin" Version="7.0.46" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -29,4 +29,10 @@ public partial class BTCPayServerClient
|
|||
if (request == null) throw new ArgumentNullException(nameof(request));
|
||||
await SendHttpRequest<StoreUserData>($"api/v1/stores/{storeId}/users", request, HttpMethod.Post, token);
|
||||
}
|
||||
|
||||
public virtual async Task UpdateStoreUser(string storeId, string userId, StoreUserData request, CancellationToken token = default)
|
||||
{
|
||||
if (request == null) throw new ArgumentNullException(nameof(request));
|
||||
await SendHttpRequest<StoreUserData>($"api/v1/stores/{storeId}/users/{userId}", request, HttpMethod.Put, token);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,25 @@ namespace BTCPayServer.Client.Models
|
|||
/// </summary>
|
||||
public string UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the store role of the user
|
||||
/// </summary>
|
||||
public string Role { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the email AND username of the user
|
||||
/// </summary>
|
||||
public string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the name of the user
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the image url of the user
|
||||
/// </summary>
|
||||
public string ImageUrl { get; set; }
|
||||
}
|
||||
|
||||
public class RoleData
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NBXplorer.Client" Version="4.3.4" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="4.3.6" />
|
||||
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class CustomThreadPool : IDisposable
|
||||
{
|
||||
readonly CancellationTokenSource _Cancel = new CancellationTokenSource();
|
||||
readonly TaskCompletionSource<bool> _Exited;
|
||||
int _ExitedCount = 0;
|
||||
readonly Thread[] _Threads;
|
||||
Exception _UnhandledException;
|
||||
readonly BlockingCollection<(Action, TaskCompletionSource<object>)> _Actions = new BlockingCollection<(Action, TaskCompletionSource<object>)>(new ConcurrentQueue<(Action, TaskCompletionSource<object>)>());
|
||||
|
||||
public CustomThreadPool(int threadCount, string threadName)
|
||||
{
|
||||
if (threadCount <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(threadCount));
|
||||
_Exited = new TaskCompletionSource<bool>();
|
||||
_Threads = Enumerable.Range(0, threadCount).Select(_ => new Thread(RunLoop) { Name = threadName }).ToArray();
|
||||
foreach (var t in _Threads)
|
||||
t.Start();
|
||||
}
|
||||
|
||||
public void Do(Action act)
|
||||
{
|
||||
DoAsync(act).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public T Do<T>(Func<T> act)
|
||||
{
|
||||
return DoAsync(act).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<T> DoAsync<T>(Func<T> act)
|
||||
{
|
||||
TaskCompletionSource<object> done = new TaskCompletionSource<object>();
|
||||
_Actions.Add((() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
done.TrySetResult(act());
|
||||
}
|
||||
catch (Exception ex) { done.TrySetException(ex); }
|
||||
}
|
||||
, done));
|
||||
return (T)(await done.Task.ConfigureAwait(false));
|
||||
}
|
||||
|
||||
public Task DoAsync(Action act)
|
||||
{
|
||||
return DoAsync<object>(() =>
|
||||
{
|
||||
act();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
void RunLoop()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var act in _Actions.GetConsumingEnumerable(_Cancel.Token))
|
||||
{
|
||||
act.Item1();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (_Cancel.IsCancellationRequested) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
_Cancel.Cancel();
|
||||
_UnhandledException = ex;
|
||||
}
|
||||
if (Interlocked.Increment(ref _ExitedCount) == _Threads.Length)
|
||||
{
|
||||
foreach (var action in _Actions)
|
||||
{
|
||||
try
|
||||
{
|
||||
action.Item2.TrySetCanceled();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
_Exited.TrySetResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_Cancel.Cancel();
|
||||
_Exited.Task.GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,11 +3,11 @@
|
|||
<Import Project="../Build/Common.csproj" />
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.11" />
|
||||
<PackageReference Include="NBitcoin.Altcoins" Version="3.0.31" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
||||
<PackageReference Include="NBitcoin" Version="7.0.45" />
|
||||
<PackageReference Include="NBitcoin" Version="7.0.46" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="1.0.4" />
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
39
BTCPayServer.Rating/Providers/BareBitcoinRateProvider.cs
Normal file
39
BTCPayServer.Rating/Providers/BareBitcoinRateProvider.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Rating;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
public class BareBitcoinRateProvider : IRateProvider
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public RateSourceInfo RateSourceInfo => new("barebitcoin", "Bare Bitcoin", "https://api.bb.no/price");
|
||||
|
||||
public BareBitcoinRateProvider(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
}
|
||||
|
||||
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using var response = await _httpClient.GetAsync(RateSourceInfo.Url, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
|
||||
|
||||
// Extract market and otc prices
|
||||
var market = jobj["market"].Value<decimal>();
|
||||
var buy = jobj["buy"].Value<decimal>();
|
||||
var sell = jobj["sell"].Value<decimal>();
|
||||
|
||||
// Create currency pair for BTC/NOK
|
||||
var pair = new CurrencyPair("BTC", "NOK");
|
||||
|
||||
// Return single pair rate with sell/buy as bid/ask
|
||||
return new[] { new PairRate(pair, new BidAsk(sell, buy)) };
|
||||
}
|
||||
}
|
||||
}
|
39
BTCPayServer.Rating/Providers/BitmyntRateProvider.cs
Normal file
39
BTCPayServer.Rating/Providers/BitmyntRateProvider.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Rating;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
public class BitmyntRateProvider : IRateProvider
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public RateSourceInfo RateSourceInfo => new("bitmynt", "Bitmynt", "https://ny.bitmynt.no/data/rates.json");
|
||||
|
||||
public BitmyntRateProvider(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
}
|
||||
|
||||
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using var response = await _httpClient.GetAsync(RateSourceInfo.Url, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
|
||||
|
||||
// Extract bid and ask prices from current_rate object
|
||||
var currentRate = jobj["current_rate"];
|
||||
var bid = currentRate["bid"].Value<decimal>();
|
||||
var ask = currentRate["ask"].Value<decimal>();
|
||||
|
||||
// Create currency pair for BTC/NOK
|
||||
var pair = new CurrencyPair("BTC", "NOK");
|
||||
|
||||
// Return single pair rate with bid/ask
|
||||
return new[] { new PairRate(pair, new BidAsk(bid, ask)) };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -93,16 +93,16 @@ namespace BTCPayServer.Tests
|
|||
Assert.IsType<ViewResult>(response);
|
||||
|
||||
// Get enabled state from settings
|
||||
response = controller.WalletSettings(user.StoreId, cryptoCode).GetAwaiter().GetResult();
|
||||
response = await controller.WalletSettings(user.StoreId, cryptoCode);
|
||||
var onchainSettingsModel = (WalletSettingsViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.NotNull(onchainSettingsModel?.DerivationScheme);
|
||||
Assert.True(onchainSettingsModel.Enabled);
|
||||
|
||||
// Disable wallet
|
||||
onchainSettingsModel.Enabled = false;
|
||||
response = controller.UpdateWalletSettings(onchainSettingsModel).GetAwaiter().GetResult();
|
||||
response = await controller.UpdateWalletSettings(onchainSettingsModel);
|
||||
Assert.IsType<RedirectToActionResult>(response);
|
||||
response = controller.WalletSettings(user.StoreId, cryptoCode).GetAwaiter().GetResult();
|
||||
response = await controller.WalletSettings(user.StoreId, cryptoCode);
|
||||
onchainSettingsModel = (WalletSettingsViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.NotNull(onchainSettingsModel?.DerivationScheme);
|
||||
Assert.False(onchainSettingsModel.Enabled);
|
||||
|
@ -124,7 +124,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode);
|
||||
|
||||
// Removing the derivation scheme, should redirect to store page
|
||||
response = controller.ConfirmDeleteWallet(user.StoreId, cryptoCode).GetAwaiter().GetResult();
|
||||
response = await controller.ConfirmDeleteWallet(user.StoreId, cryptoCode);
|
||||
Assert.IsType<RedirectToActionResult>(response);
|
||||
|
||||
// Setting it again should show the confirmation page
|
||||
|
@ -174,7 +174,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.Equal("ElectrumFile", settingsVm.Source);
|
||||
|
||||
// Now let's check that no data has been lost in the process
|
||||
var store = tester.PayTester.StoreRepository.FindStore(storeId).GetAwaiter().GetResult();
|
||||
var store = await tester.PayTester.StoreRepository.FindStore(storeId);
|
||||
var handlers = tester.PayTester.GetService<PaymentMethodHandlerDictionary>();
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
|
||||
var onchainBTC = store.GetPaymentMethodConfig<DerivationSchemeSettings>(pmi, handlers);
|
||||
|
@ -206,7 +206,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.Equal("paid", invoice.Status);
|
||||
});
|
||||
var wallet = tester.PayTester.GetController<UIWalletsController>();
|
||||
var psbt = wallet.CreatePSBT(btcNetwork, onchainBTC,
|
||||
var psbt = await wallet.CreatePSBT(btcNetwork, onchainBTC,
|
||||
new WalletSendModel()
|
||||
{
|
||||
Outputs = new List<WalletSendModel.TransactionOutput>
|
||||
|
@ -219,7 +219,7 @@ namespace BTCPayServer.Tests
|
|||
}
|
||||
},
|
||||
FeeSatoshiPerByte = 1
|
||||
}, default).GetAwaiter().GetResult();
|
||||
}, default);
|
||||
|
||||
Assert.NotNull(psbt);
|
||||
|
||||
|
@ -440,136 +440,135 @@ namespace BTCPayServer.Tests
|
|||
[Trait("Altcoins", "Altcoins")]
|
||||
public async Task CanPayWithTwoCurrencies()
|
||||
{
|
||||
using (var tester = CreateServerTester())
|
||||
using var tester = CreateServerTester();
|
||||
|
||||
tester.ActivateLTC();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
// First we try payment with a merchant having only BTC
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
{
|
||||
Price = 5000.0m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
var cashCow = tester.ExplorerNode;
|
||||
await cashCow.GenerateAsync(2); // get some money in case
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||
var firstPayment = Money.Coins(0.04m);
|
||||
await cashCow.SendToAddressAsync(invoiceAddress, firstPayment);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
tester.ActivateLTC();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
// First we try payment with a merchant having only BTC
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
{
|
||||
Price = 5000.0m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.True(invoice.BtcPaid == firstPayment);
|
||||
});
|
||||
|
||||
var cashCow = tester.ExplorerNode;
|
||||
await cashCow.GenerateAsync(2); // get some money in case
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||
var firstPayment = Money.Coins(0.04m);
|
||||
await cashCow.SendToAddressAsync(invoiceAddress, firstPayment);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.True(invoice.BtcPaid == firstPayment);
|
||||
});
|
||||
Assert.Single(invoice.CryptoInfo); // Only BTC should be presented
|
||||
|
||||
Assert.Single(invoice.CryptoInfo); // Only BTC should be presented
|
||||
|
||||
var controller = tester.PayTester.GetController<UIInvoiceController>(null);
|
||||
var checkout =
|
||||
(Models.InvoicingModels.CheckoutModel)((JsonResult)controller.GetStatus(invoice.Id, null)
|
||||
.GetAwaiter().GetResult()).Value;
|
||||
Assert.Single(checkout.AvailablePaymentMethods);
|
||||
Assert.Equal("BTC", checkout.PaymentMethodCurrency);
|
||||
|
||||
Assert.Single(invoice.PaymentCodes);
|
||||
Assert.Single(invoice.SupportedTransactionCurrencies);
|
||||
Assert.Single(invoice.SupportedTransactionCurrencies);
|
||||
Assert.Single(invoice.PaymentSubtotals);
|
||||
Assert.Single(invoice.PaymentTotals);
|
||||
Assert.True(invoice.PaymentCodes.ContainsKey("BTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("BTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies["BTC"].Enabled);
|
||||
Assert.True(invoice.PaymentSubtotals.ContainsKey("BTC"));
|
||||
Assert.True(invoice.PaymentTotals.ContainsKey("BTC"));
|
||||
//////////////////////
|
||||
|
||||
// Retry now with LTC enabled
|
||||
user.RegisterDerivationScheme("LTC");
|
||||
invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
{
|
||||
Price = 5000.0m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
cashCow = tester.ExplorerNode;
|
||||
invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||
firstPayment = Money.Coins(0.04m);
|
||||
await cashCow.SendToAddressAsync(invoiceAddress, firstPayment);
|
||||
TestLogs.LogInformation("First payment sent to " + invoiceAddress);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.True(invoice.BtcPaid == firstPayment);
|
||||
});
|
||||
|
||||
cashCow = tester.LTCExplorerNode;
|
||||
var ltcCryptoInfo = invoice.CryptoInfo.FirstOrDefault(c => c.CryptoCode == "LTC");
|
||||
Assert.NotNull(ltcCryptoInfo);
|
||||
invoiceAddress = BitcoinAddress.Create(ltcCryptoInfo.Address, cashCow.Network);
|
||||
var secondPayment = Money.Coins(decimal.Parse(ltcCryptoInfo.Due, CultureInfo.InvariantCulture));
|
||||
await cashCow.GenerateAsync(4); // LTC is not worth a lot, so just to make sure we have money...
|
||||
await cashCow.SendToAddressAsync(invoiceAddress, secondPayment);
|
||||
TestLogs.LogInformation("Second payment sent to " + invoiceAddress);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.Equal(Money.Zero, invoice.BtcDue);
|
||||
var ltcPaid = invoice.CryptoInfo.First(c => c.CryptoCode == "LTC");
|
||||
Assert.Equal(Money.Zero, ltcPaid.Due);
|
||||
Assert.Equal(secondPayment, ltcPaid.CryptoPaid);
|
||||
Assert.Equal("paid", invoice.Status);
|
||||
Assert.False((bool)((JValue)invoice.ExceptionStatus).Value);
|
||||
});
|
||||
|
||||
controller = tester.PayTester.GetController<UIInvoiceController>(null);
|
||||
checkout = (Models.InvoicingModels.CheckoutModel)((JsonResult)controller.GetStatus(invoice.Id, "LTC")
|
||||
var controller = tester.PayTester.GetController<UIInvoiceController>(null);
|
||||
var checkout =
|
||||
(Models.InvoicingModels.CheckoutModel)((JsonResult)controller.GetStatus(invoice.Id, null)
|
||||
.GetAwaiter().GetResult()).Value;
|
||||
Assert.Equal(2, checkout.AvailablePaymentMethods.Count);
|
||||
Assert.Equal("LTC", checkout.PaymentMethodCurrency);
|
||||
Assert.Single(checkout.AvailablePaymentMethods);
|
||||
Assert.Equal("BTC", checkout.PaymentMethodCurrency);
|
||||
|
||||
Assert.Equal(2, invoice.PaymentCodes.Count());
|
||||
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count());
|
||||
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count());
|
||||
Assert.Equal(2, invoice.PaymentSubtotals.Count());
|
||||
Assert.Equal(2, invoice.PaymentTotals.Count());
|
||||
Assert.True(invoice.PaymentCodes.ContainsKey("LTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("LTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies["LTC"].Enabled);
|
||||
Assert.True(invoice.PaymentSubtotals.ContainsKey("LTC"));
|
||||
Assert.True(invoice.PaymentTotals.ContainsKey("LTC"));
|
||||
Assert.Single(invoice.PaymentCodes);
|
||||
Assert.Single(invoice.SupportedTransactionCurrencies);
|
||||
Assert.Single(invoice.SupportedTransactionCurrencies);
|
||||
Assert.Single(invoice.PaymentSubtotals);
|
||||
Assert.Single(invoice.PaymentTotals);
|
||||
Assert.True(invoice.PaymentCodes.ContainsKey("BTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("BTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies["BTC"].Enabled);
|
||||
Assert.True(invoice.PaymentSubtotals.ContainsKey("BTC"));
|
||||
Assert.True(invoice.PaymentTotals.ContainsKey("BTC"));
|
||||
//////////////////////
|
||||
|
||||
// Check if we can disable LTC
|
||||
invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
// Retry now with LTC enabled
|
||||
user.RegisterDerivationScheme("LTC");
|
||||
invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
{
|
||||
Price = 5000.0m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
cashCow = tester.ExplorerNode;
|
||||
invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||
firstPayment = Money.Coins(0.04m);
|
||||
await cashCow.SendToAddressAsync(invoiceAddress, firstPayment);
|
||||
TestLogs.LogInformation("First payment sent to " + invoiceAddress);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.True(invoice.BtcPaid == firstPayment);
|
||||
});
|
||||
|
||||
cashCow = tester.LTCExplorerNode;
|
||||
var ltcCryptoInfo = invoice.CryptoInfo.FirstOrDefault(c => c.CryptoCode == "LTC");
|
||||
Assert.NotNull(ltcCryptoInfo);
|
||||
invoiceAddress = BitcoinAddress.Create(ltcCryptoInfo.Address, cashCow.Network);
|
||||
var secondPayment = Money.Coins(decimal.Parse(ltcCryptoInfo.Due, CultureInfo.InvariantCulture));
|
||||
await cashCow.GenerateAsync(4); // LTC is not worth a lot, so just to make sure we have money...
|
||||
await cashCow.SendToAddressAsync(invoiceAddress, secondPayment);
|
||||
TestLogs.LogInformation("Second payment sent to " + invoiceAddress);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.Equal(Money.Zero, invoice.BtcDue);
|
||||
var ltcPaid = invoice.CryptoInfo.First(c => c.CryptoCode == "LTC");
|
||||
Assert.Equal(Money.Zero, ltcPaid.Due);
|
||||
Assert.Equal(secondPayment, ltcPaid.CryptoPaid);
|
||||
Assert.Equal("paid", invoice.Status);
|
||||
Assert.False((bool)((JValue)invoice.ExceptionStatus).Value);
|
||||
});
|
||||
|
||||
controller = tester.PayTester.GetController<UIInvoiceController>(null);
|
||||
checkout = (Models.InvoicingModels.CheckoutModel)((JsonResult)controller.GetStatus(invoice.Id, "LTC")
|
||||
.GetAwaiter().GetResult()).Value;
|
||||
Assert.Equal(2, checkout.AvailablePaymentMethods.Count);
|
||||
Assert.Equal("LTC", checkout.PaymentMethodCurrency);
|
||||
|
||||
Assert.Equal(2, invoice.PaymentCodes.Count());
|
||||
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count());
|
||||
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count());
|
||||
Assert.Equal(2, invoice.PaymentSubtotals.Count());
|
||||
Assert.Equal(2, invoice.PaymentTotals.Count());
|
||||
Assert.True(invoice.PaymentCodes.ContainsKey("LTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("LTC"));
|
||||
Assert.True(invoice.SupportedTransactionCurrencies["LTC"].Enabled);
|
||||
Assert.True(invoice.PaymentSubtotals.ContainsKey("LTC"));
|
||||
Assert.True(invoice.PaymentTotals.ContainsKey("LTC"));
|
||||
|
||||
// Check if we can disable LTC
|
||||
invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
{
|
||||
Price = 5000.0m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true,
|
||||
SupportedTransactionCurrencies = new Dictionary<string, InvoiceSupportedTransactionCurrency>()
|
||||
{
|
||||
Price = 5000.0m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true,
|
||||
SupportedTransactionCurrencies = new Dictionary<string, InvoiceSupportedTransactionCurrency>()
|
||||
{
|
||||
{"BTC", new InvoiceSupportedTransactionCurrency() {Enabled = true}}
|
||||
}
|
||||
}, Facade.Merchant);
|
||||
{"BTC", new InvoiceSupportedTransactionCurrency() {Enabled = true}}
|
||||
}
|
||||
}, Facade.Merchant);
|
||||
|
||||
Assert.Single(invoice.CryptoInfo.Where(c => c.CryptoCode == "BTC"));
|
||||
Assert.Empty(invoice.CryptoInfo.Where(c => c.CryptoCode == "LTC"));
|
||||
}
|
||||
Assert.Single(invoice.CryptoInfo, c => c.CryptoCode == "BTC");
|
||||
Assert.DoesNotContain(invoice.CryptoInfo, c => c.CryptoCode == "LTC");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -744,7 +743,7 @@ noninventoryitem:
|
|||
invoices = user.BitPay.GetInvoices();
|
||||
Assert.Equal(2, invoices.Count(invoice => invoice.ItemCode.Equals("noninventoryitem")));
|
||||
var inventoryItemInvoice =
|
||||
Assert.Single(invoices.Where(invoice => invoice.ItemCode.Equals("inventoryitem")));
|
||||
Assert.Single(invoices, invoice => invoice.ItemCode.Equals("inventoryitem"));
|
||||
Assert.NotNull(inventoryItemInvoice);
|
||||
|
||||
//let's mark the inventoryitem invoice as invalid, this should return the item to back in stock
|
||||
|
|
|
@ -39,13 +39,14 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.15" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.16" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.11" />
|
||||
<PackageReference Include="Selenium.Support" Version="4.1.1" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="4.22.0" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="128.0.6613.11900" />
|
||||
<PackageReference Include="xunit" Version="2.6.6" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
|
|
@ -263,7 +263,7 @@ namespace BTCPayServer.Tests
|
|||
});
|
||||
|
||||
TestLogs.LogInformation("Because UseAllStoreInvoices is true, let's make sure the invoice is tagged");
|
||||
var invoiceEntity = tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id).GetAwaiter().GetResult();
|
||||
var invoiceEntity = await tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id);
|
||||
Assert.True(invoiceEntity.Version >= InvoiceEntity.InternalTagSupport_Version);
|
||||
Assert.Contains(AppService.GetAppInternalTag(app.Id), invoiceEntity.InternalTags);
|
||||
|
||||
|
@ -281,7 +281,7 @@ namespace BTCPayServer.Tests
|
|||
TransactionSpeed = "high",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
invoiceEntity = tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id).GetAwaiter().GetResult();
|
||||
invoiceEntity = await tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id);
|
||||
Assert.DoesNotContain(AppService.GetAppInternalTag(app.Id), invoiceEntity.InternalTags);
|
||||
|
||||
TestLogs.LogInformation("After turning setting a softcap, let's check that only actual payments are counted");
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM mcr.microsoft.com/dotnet/sdk:8.0.101-bookworm-slim AS builder
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0.404-bookworm-slim AS builder
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends chromium-driver \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
|
|
@ -174,10 +174,10 @@ namespace BTCPayServer.Tests
|
|||
public void CanRandomizeByPercentage()
|
||||
{
|
||||
var generated = Enumerable.Range(0, 1000).Select(_ => MempoolSpaceFeeProvider.RandomizeByPercentage(100.0m, 10.0m)).ToArray();
|
||||
Assert.Empty(generated.Where(g => g < 90m));
|
||||
Assert.Empty(generated.Where(g => g > 110m));
|
||||
Assert.NotEmpty(generated.Where(g => g < 91m));
|
||||
Assert.NotEmpty(generated.Where(g => g > 109m));
|
||||
Assert.DoesNotContain(generated, g => g < 90m);
|
||||
Assert.DoesNotContain(generated, g => g > 110m);
|
||||
Assert.Contains(generated, g => g < 91m);
|
||||
Assert.Contains(generated, g => g > 109m);
|
||||
}
|
||||
|
||||
private void CanParseDecimalsCore(string str, decimal expected)
|
||||
|
@ -793,9 +793,9 @@ namespace BTCPayServer.Tests
|
|||
}), BTCPayLogs);
|
||||
await tor.Refresh();
|
||||
|
||||
Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.BTCPayServer));
|
||||
Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.P2P));
|
||||
Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.RPC));
|
||||
Assert.Single(tor.Services, t => t.ServiceType == TorServiceType.BTCPayServer);
|
||||
Assert.Single(tor.Services, t => t.ServiceType == TorServiceType.P2P);
|
||||
Assert.Single(tor.Services, t => t.ServiceType == TorServiceType.RPC);
|
||||
Assert.True(tor.Services.Count(t => t.ServiceType == TorServiceType.Other) > 1);
|
||||
|
||||
tor = new TorServices(CreateNetworkProvider(ChainName.Regtest),
|
||||
|
@ -806,24 +806,24 @@ namespace BTCPayServer.Tests
|
|||
}), BTCPayLogs);
|
||||
await Task.WhenAll(tor.StartAsync(CancellationToken.None));
|
||||
|
||||
var btcpayS = Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.BTCPayServer));
|
||||
var btcpayS = Assert.Single(tor.Services, t => t.ServiceType == TorServiceType.BTCPayServer);
|
||||
Assert.Null(btcpayS.Network);
|
||||
Assert.Equal("host.onion", btcpayS.OnionHost);
|
||||
Assert.Equal(80, btcpayS.VirtualPort);
|
||||
|
||||
var p2p = Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.P2P));
|
||||
var p2p = Assert.Single(tor.Services, t => t.ServiceType == TorServiceType.P2P);
|
||||
Assert.NotNull(p2p.Network);
|
||||
Assert.Equal("BTC", p2p.Network.CryptoCode);
|
||||
Assert.Equal("host2.onion", p2p.OnionHost);
|
||||
Assert.Equal(81, p2p.VirtualPort);
|
||||
|
||||
var rpc = Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.RPC));
|
||||
var rpc = Assert.Single(tor.Services, t => t.ServiceType == TorServiceType.RPC);
|
||||
Assert.NotNull(p2p.Network);
|
||||
Assert.Equal("BTC", rpc.Network.CryptoCode);
|
||||
Assert.Equal("host3.onion", rpc.OnionHost);
|
||||
Assert.Equal(82, rpc.VirtualPort);
|
||||
|
||||
var unknown = Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.Other));
|
||||
var unknown = Assert.Single(tor.Services, t => t.ServiceType == TorServiceType.Other);
|
||||
Assert.Null(unknown.Network);
|
||||
Assert.Equal("host4.onion", unknown.OnionHost);
|
||||
Assert.Equal(83, unknown.VirtualPort);
|
||||
|
|
|
@ -3867,7 +3867,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
void VerifyLightning(GenericPaymentMethodData[] methods)
|
||||
{
|
||||
var m = Assert.Single(methods.Where(m => m.PaymentMethodId == "BTC-LN"));
|
||||
var m = Assert.Single(methods, m => m.PaymentMethodId == "BTC-LN");
|
||||
Assert.Equal("Internal Node", m.Config["internalNodeRef"].Value<string>());
|
||||
}
|
||||
|
||||
|
@ -3879,7 +3879,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
void VerifyOnChain(GenericPaymentMethodData[] dictionary)
|
||||
{
|
||||
var m = Assert.Single(methods.Where(m => m.PaymentMethodId == "BTC-CHAIN"));
|
||||
var m = Assert.Single(methods, m => m.PaymentMethodId == "BTC-CHAIN");
|
||||
var paymentMethodBaseData = Assert.IsType<JObject>(m.Config);
|
||||
Assert.Equal(wallet.Config.AccountDerivation, paymentMethodBaseData["accountDerivation"].Value<string>());
|
||||
}
|
||||
|
@ -3985,7 +3985,12 @@ namespace BTCPayServer.Tests
|
|||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync(true);
|
||||
|
||||
var client = await user.CreateClient(Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings);
|
||||
var client = await user.CreateClient(Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings, Policies.CanModifyProfile);
|
||||
await client.UpdateCurrentUser(new UpdateApplicationUserRequest
|
||||
{
|
||||
Name = "The Admin",
|
||||
ImageUrl = "avatar.jpg"
|
||||
});
|
||||
|
||||
var roles = await client.GetServerRoles();
|
||||
Assert.Equal(4, roles.Count);
|
||||
|
@ -3999,6 +4004,9 @@ namespace BTCPayServer.Tests
|
|||
var storeUser = Assert.Single(users);
|
||||
Assert.Equal(user.UserId, storeUser.UserId);
|
||||
Assert.Equal(ownerRole.Id, storeUser.Role);
|
||||
Assert.Equal(user.Email, storeUser.Email);
|
||||
Assert.Equal("The Admin", storeUser.Name);
|
||||
Assert.Equal("avatar.jpg", storeUser.ImageUrl);
|
||||
var manager = tester.NewAccount();
|
||||
await manager.GrantAccessAsync();
|
||||
var employee = tester.NewAccount();
|
||||
|
@ -4029,7 +4037,14 @@ namespace BTCPayServer.Tests
|
|||
// add users to store
|
||||
await client.AddStoreUser(user.StoreId, new StoreUserData { Role = managerRole.Id, UserId = manager.UserId });
|
||||
await client.AddStoreUser(user.StoreId, new StoreUserData { Role = employeeRole.Id, UserId = employee.UserId });
|
||||
await client.AddStoreUser(user.StoreId, new StoreUserData { Role = guestRole.Id, UserId = guest.UserId });
|
||||
|
||||
// add with email
|
||||
await client.AddStoreUser(user.StoreId, new StoreUserData { Role = guestRole.Id, UserId = guest.Email });
|
||||
|
||||
// test unknown user
|
||||
await AssertAPIError("user-not-found", async () => await client.AddStoreUser(user.StoreId, new StoreUserData { Role = managerRole.Id, UserId = "unknown" }));
|
||||
await AssertAPIError("user-not-found", async () => await client.UpdateStoreUser(user.StoreId, "unknown", new StoreUserData { Role = ownerRole.Id }));
|
||||
await AssertAPIError("user-not-found", async () => await client.RemoveStoreUser(user.StoreId, "unknown"));
|
||||
|
||||
//test no access to api for employee
|
||||
await AssertPermissionError(Policies.CanViewStoreSettings, async () => await employeeClient.GetStore(user.StoreId));
|
||||
|
@ -4050,9 +4065,14 @@ namespace BTCPayServer.Tests
|
|||
await AssertPermissionError(Policies.CanModifyStoreSettings, async () => await managerClient.RemoveStoreUser(user.StoreId, user.UserId));
|
||||
|
||||
// updates
|
||||
await client.UpdateStoreUser(user.StoreId, employee.UserId, new StoreUserData { Role = ownerRole.Id });
|
||||
await employeeClient.GetStore(user.StoreId);
|
||||
|
||||
// remove
|
||||
await client.RemoveStoreUser(user.StoreId, employee.UserId);
|
||||
await AssertHttpError(403, async () => await employeeClient.GetStore(user.StoreId));
|
||||
|
||||
// test duplicate add
|
||||
await client.AddStoreUser(user.StoreId, new StoreUserData { Role = ownerRole.Id, UserId = employee.UserId });
|
||||
await AssertAPIError("duplicate-store-user-role", async () =>
|
||||
await client.AddStoreUser(user.StoreId, new StoreUserData { Role = ownerRole.Id, UserId = employee.UserId }));
|
||||
|
@ -4412,8 +4432,8 @@ namespace BTCPayServer.Tests
|
|||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
|
||||
Assert.Equal(3, payouts.Length);
|
||||
Assert.Empty(payouts.Where(data => data.State == PayoutState.AwaitingApproval));
|
||||
Assert.Empty(payouts.Where(data => data.PayoutAmount is null));
|
||||
Assert.DoesNotContain(payouts, data => data.State == PayoutState.AwaitingApproval);
|
||||
Assert.DoesNotContain(payouts, data => data.PayoutAmount is null);
|
||||
|
||||
Assert.Empty(await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC"));
|
||||
|
||||
|
@ -4456,7 +4476,7 @@ namespace BTCPayServer.Tests
|
|||
{
|
||||
Assert.Equal(2, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count());
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
Assert.Single(payouts.Where(data => data.State == PayoutState.InProgress));
|
||||
Assert.Single(payouts, data => data.State == PayoutState.InProgress);
|
||||
});
|
||||
|
||||
uint256 txid = null;
|
||||
|
@ -4470,7 +4490,7 @@ namespace BTCPayServer.Tests
|
|||
{
|
||||
Assert.Equal(4, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count());
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
Assert.Empty(payouts.Where(data => data.State != PayoutState.InProgress));
|
||||
Assert.DoesNotContain(payouts, data => data.State != PayoutState.InProgress);
|
||||
});
|
||||
|
||||
// settings that were added later
|
||||
|
@ -4536,7 +4556,7 @@ namespace BTCPayServer.Tests
|
|||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
try
|
||||
{
|
||||
Assert.Single(payouts.Where(data => data.State == PayoutState.InProgress && data.Id == payoutThatShouldBeProcessedStraightAway.Id));
|
||||
Assert.Single(payouts, data => data.State == PayoutState.InProgress && data.Id == payoutThatShouldBeProcessedStraightAway.Id);
|
||||
}
|
||||
catch (SingleException)
|
||||
{
|
||||
|
@ -4580,7 +4600,7 @@ namespace BTCPayServer.Tests
|
|||
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
Assert.Single(payouts.Where(data => data.State == PayoutState.AwaitingPayment && data.Id == payoutThatShouldNotBeProcessedStraightAway.Id));
|
||||
Assert.Single(payouts, data => data.State == PayoutState.AwaitingPayment && data.Id == payoutThatShouldNotBeProcessedStraightAway.Id);
|
||||
|
||||
beforeHookTcs = new TaskCompletionSource();
|
||||
afterHookTcs = new TaskCompletionSource();
|
||||
|
@ -4613,7 +4633,7 @@ namespace BTCPayServer.Tests
|
|||
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
Assert.Empty(payouts.Where(data => data.State != PayoutState.InProgress));
|
||||
Assert.DoesNotContain(payouts, data => data.State != PayoutState.InProgress);
|
||||
|
||||
}
|
||||
|
||||
|
@ -4690,11 +4710,11 @@ namespace BTCPayServer.Tests
|
|||
await client.AddOrUpdateOnChainWalletObject(admin.StoreId, "BTC", new AddOnChainWalletObjectRequest() { Type = "newtype", Id = test1.Id });
|
||||
|
||||
testObj = await client.GetOnChainWalletObject(admin.StoreId, "BTC", test);
|
||||
Assert.Single(testObj.Links.Where(l => l.Id == "test1" && l.LinkData["testData"]?.Value<string>() == "lol"));
|
||||
Assert.Single(testObj.Links.Where(l => l.Id == "test1" && l.ObjectData["testData"]?.Value<string>() == "test1"));
|
||||
Assert.Single(testObj.Links, l => l.Id == "test1" && l.LinkData["testData"]?.Value<string>() == "lol");
|
||||
Assert.Single(testObj.Links, l => l.Id == "test1" && l.ObjectData["testData"]?.Value<string>() == "test1");
|
||||
testObj = await client.GetOnChainWalletObject(admin.StoreId, "BTC", test, false);
|
||||
Assert.Single(testObj.Links.Where(l => l.Id == "test1" && l.LinkData["testData"]?.Value<string>() == "lol"));
|
||||
Assert.Single(testObj.Links.Where(l => l.Id == "test1" && l.ObjectData is null));
|
||||
Assert.Single(testObj.Links, l => l.Id == "test1" && l.LinkData["testData"]?.Value<string>() == "lol");
|
||||
Assert.Single(testObj.Links, l => l.Id == "test1" && l.ObjectData is null);
|
||||
|
||||
async Task TestWalletRepository()
|
||||
{
|
||||
|
|
|
@ -385,10 +385,6 @@ namespace BTCPayServer.Tests
|
|||
s.Driver.FindElement(By.Id("ConfirmPassword")).SendKeys("123456");
|
||||
s.ClickPagePrimary();
|
||||
Assert.Contains("Account successfully created.", s.FindAlertMessage().Text);
|
||||
|
||||
s.Driver.FindElement(By.Id("Email")).SendKeys(usr);
|
||||
s.Driver.FindElement(By.Id("Password")).SendKeys("123456");
|
||||
s.Driver.FindElement(By.Id("LoginButton")).Click();
|
||||
|
||||
// We should be logged in now
|
||||
s.GoToHome();
|
||||
|
|
|
@ -208,6 +208,18 @@ namespace BTCPayServer.Tests
|
|||
e => e.CurrencyPair == new CurrencyPair("BTC", "LBP") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1 BTC will always be more than 1 LBP (I hope)
|
||||
}
|
||||
else if (name == "bitmynt")
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[name],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "NOK") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1 BTC will always be more than 1 NOK
|
||||
}
|
||||
else if (name == "barebitcoin")
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[name],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "NOK") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1 BTC will always be more than 1 NOK
|
||||
}
|
||||
else
|
||||
{
|
||||
if (name == "kraken")
|
||||
|
@ -234,7 +246,7 @@ namespace BTCPayServer.Tests
|
|||
}
|
||||
|
||||
// Kraken emit one request only after first GetRates
|
||||
factory.Providers["kraken"].GetRatesAsync(default).GetAwaiter().GetResult();
|
||||
await factory.Providers["kraken"].GetRatesAsync(default);
|
||||
|
||||
var p = new KrakenExchangeRateProvider();
|
||||
var rates = await p.GetRatesAsync(default);
|
||||
|
@ -339,7 +351,7 @@ retry:
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void CanSolveTheDogesRatesOnKraken()
|
||||
public async Task CanSolveTheDogesRatesOnKraken()
|
||||
{
|
||||
var factory = FastTests.CreateBTCPayRateFactory();
|
||||
var fetcher = new RateFetcher(factory);
|
||||
|
@ -347,7 +359,7 @@ retry:
|
|||
Assert.True(RateRules.TryParse("X_X=kraken(X_BTC) * kraken(BTC_X)", out var rule));
|
||||
foreach (var pair in new[] { "DOGE_USD", "DOGE_CAD" })
|
||||
{
|
||||
var result = fetcher.FetchRate(CurrencyPair.Parse(pair), rule, null, default).GetAwaiter().GetResult();
|
||||
var result = await fetcher.FetchRate(CurrencyPair.Parse(pair), rule, null, default);
|
||||
Assert.NotNull(result.BidAsk);
|
||||
Assert.Empty(result.Errors);
|
||||
}
|
||||
|
@ -601,7 +613,7 @@ retry:
|
|||
|
||||
foreach (var rate in rates)
|
||||
{
|
||||
Assert.Single(rates.Where(r => r == rate));
|
||||
Assert.Single(rates, r => r == rate);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -194,7 +194,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async void CanStoreArbitrarySettingsWithStore()
|
||||
public async Task CanStoreArbitrarySettingsWithStore()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
|
@ -446,25 +446,25 @@ namespace BTCPayServer.Tests
|
|||
Assert.IsType<ViewResult>(storeResponse);
|
||||
Assert.IsType<ViewResult>(storeController.SetupLightningNode(user.StoreId, "BTC"));
|
||||
|
||||
storeController.SetupLightningNode(user.StoreId, new LightningNodeViewModel
|
||||
await storeController.SetupLightningNode(user.StoreId, new LightningNodeViewModel
|
||||
{
|
||||
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();
|
||||
}, "test", "BTC");
|
||||
Assert.False(storeController.TempData.ContainsKey(WellKnownTempData.ErrorMessage));
|
||||
storeController.TempData.Clear();
|
||||
Assert.True(storeController.ModelState.IsValid);
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(storeController.SetupLightningNode(user.StoreId,
|
||||
Assert.IsType<RedirectToActionResult>(await storeController.SetupLightningNode(user.StoreId,
|
||||
new LightningNodeViewModel
|
||||
{
|
||||
ConnectionString = $"type=charge;server={tester.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true"
|
||||
}, "save", "BTC").GetAwaiter().GetResult());
|
||||
}, "save", "BTC"));
|
||||
|
||||
// Make sure old connection string format does not work
|
||||
Assert.IsType<RedirectToActionResult>(storeController.SetupLightningNode(user.StoreId,
|
||||
Assert.IsType<RedirectToActionResult>(await storeController.SetupLightningNode(user.StoreId,
|
||||
new LightningNodeViewModel { ConnectionString = tester.MerchantCharge.Client.Uri.AbsoluteUri },
|
||||
"save", "BTC").GetAwaiter().GetResult());
|
||||
"save", "BTC"));
|
||||
|
||||
storeResponse = storeController.LightningSettings(user.StoreId, "BTC");
|
||||
var storeVm =
|
||||
|
@ -1099,7 +1099,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async void CheckCORSSetOnBitpayAPI()
|
||||
public async Task CheckCORSSetOnBitpayAPI()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
|
@ -1143,9 +1143,8 @@ namespace BTCPayServer.Tests
|
|||
|
||||
// Test request pairing code client side
|
||||
var storeController = user.GetController<UIStoresController>();
|
||||
storeController
|
||||
.CreateToken(user.StoreId, new CreateTokenViewModel() { Label = "test2", StoreId = user.StoreId })
|
||||
.GetAwaiter().GetResult();
|
||||
await storeController
|
||||
.CreateToken(user.StoreId, new CreateTokenViewModel() { Label = "test2", StoreId = user.StoreId });
|
||||
Assert.NotNull(storeController.GeneratedPairingCode);
|
||||
|
||||
|
||||
|
@ -1169,17 +1168,17 @@ namespace BTCPayServer.Tests
|
|||
|
||||
// Can generate API Key
|
||||
var repo = tester.PayTester.GetService<TokenRepository>();
|
||||
Assert.Empty(repo.GetLegacyAPIKeys(user.StoreId).GetAwaiter().GetResult());
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>()
|
||||
.GenerateAPIKey(user.StoreId).GetAwaiter().GetResult());
|
||||
Assert.Empty(await repo.GetLegacyAPIKeys(user.StoreId));
|
||||
Assert.IsType<RedirectToActionResult>(await user.GetController<UIStoresController>()
|
||||
.GenerateAPIKey(user.StoreId));
|
||||
|
||||
var apiKey = Assert.Single(repo.GetLegacyAPIKeys(user.StoreId).GetAwaiter().GetResult());
|
||||
var apiKey = Assert.Single(await repo.GetLegacyAPIKeys(user.StoreId));
|
||||
///////
|
||||
|
||||
// Generating a new one remove the previous
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>()
|
||||
.GenerateAPIKey(user.StoreId).GetAwaiter().GetResult());
|
||||
var apiKey2 = Assert.Single(repo.GetLegacyAPIKeys(user.StoreId).GetAwaiter().GetResult());
|
||||
Assert.IsType<RedirectToActionResult>(await user.GetController<UIStoresController>()
|
||||
.GenerateAPIKey(user.StoreId));
|
||||
var apiKey2 = Assert.Single(await repo.GetLegacyAPIKeys(user.StoreId));
|
||||
Assert.NotEqual(apiKey, apiKey2);
|
||||
////////
|
||||
|
||||
|
@ -1193,7 +1192,7 @@ namespace BTCPayServer.Tests
|
|||
var invoice = new Invoice() { Price = 5000.0m, Currency = "USD" };
|
||||
message.Content = new StringContent(JsonConvert.SerializeObject(invoice), Encoding.UTF8,
|
||||
"application/json");
|
||||
var result = client.SendAsync(message).GetAwaiter().GetResult();
|
||||
var result = await client.SendAsync(message);
|
||||
result.EnsureSuccessStatusCode();
|
||||
/////////////////////
|
||||
|
||||
|
@ -1207,7 +1206,7 @@ namespace BTCPayServer.Tests
|
|||
mess.Headers.Add("x-identity",
|
||||
"04b4d82095947262dd70f94c0a0e005ec3916e3f5f2181c176b8b22a52db22a8c436c4703f43a9e8884104854a11e1eb30df8fdf116e283807a1f1b8fe4c182b99");
|
||||
mess.Method = HttpMethod.Get;
|
||||
result = client.SendAsync(mess).GetAwaiter().GetResult();
|
||||
result = await client.SendAsync(mess);
|
||||
Assert.Equal(System.Net.HttpStatusCode.Unauthorized, result.StatusCode);
|
||||
|
||||
//
|
||||
|
@ -1539,7 +1538,7 @@ namespace BTCPayServer.Tests
|
|||
// We allow BTC and LN, but not BTC under 5 USD, so only LN should be in the invoice
|
||||
var vm = await user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModelAsync<CheckoutAppearanceViewModel>();
|
||||
Assert.Equal(2, vm.PaymentMethodCriteria.Count);
|
||||
var criteria = Assert.Single(vm.PaymentMethodCriteria.Where(m => m.PaymentMethod == btcMethod.ToString()));
|
||||
var criteria = Assert.Single(vm.PaymentMethodCriteria, m => m.PaymentMethod == btcMethod.ToString());
|
||||
Assert.Equal(btcMethod.ToString(), criteria.PaymentMethod);
|
||||
criteria.Value = "5 USD";
|
||||
criteria.Type = PaymentMethodCriteriaViewModel.CriteriaType.GreaterThan;
|
||||
|
@ -2175,19 +2174,19 @@ namespace BTCPayServer.Tests
|
|||
Assert.Equal(0, invoice.CryptoInfo[0].TxCount);
|
||||
Assert.True(invoice.MinerFees.ContainsKey("BTC"));
|
||||
Assert.Contains(Math.Round(invoice.MinerFees["BTC"].SatoshiPerBytes), new[] { 100.0m, 20.0m });
|
||||
TestUtils.Eventually(() =>
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var textSearchResult = tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
var textSearchResult = await tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
StoreId = new[] { user.StoreId },
|
||||
TextSearch = invoice.OrderId
|
||||
}).GetAwaiter().GetResult();
|
||||
});
|
||||
Assert.Single(textSearchResult);
|
||||
textSearchResult = tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
textSearchResult = await tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
StoreId = new[] { user.StoreId },
|
||||
TextSearch = invoice.Id
|
||||
}).GetAwaiter().GetResult();
|
||||
});
|
||||
|
||||
Assert.Single(textSearchResult);
|
||||
});
|
||||
|
@ -2215,11 +2214,11 @@ namespace BTCPayServer.Tests
|
|||
Assert.True(IsMapped(invoice, ctx));
|
||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||
|
||||
var invoiceEntity = repo.GetInvoice(invoice.Id, true).GetAwaiter().GetResult();
|
||||
var invoiceEntity = await repo.GetInvoice(invoice.Id, true);
|
||||
|
||||
Money secondPayment = Money.Zero;
|
||||
|
||||
TestUtils.Eventually(() =>
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("new", localInvoice.Status);
|
||||
|
@ -2231,7 +2230,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.True(IsMapped(invoice, ctx));
|
||||
Assert.True(IsMapped(localInvoice, ctx));
|
||||
|
||||
invoiceEntity = repo.GetInvoice(invoice.Id, true).GetAwaiter().GetResult();
|
||||
invoiceEntity = await repo.GetInvoice(invoice.Id, true);
|
||||
invoiceAddress = BitcoinAddress.Create(localInvoice.BitcoinAddress, cashCow.Network);
|
||||
secondPayment = localInvoice.BtcDue;
|
||||
});
|
||||
|
@ -2274,18 +2273,18 @@ namespace BTCPayServer.Tests
|
|||
|
||||
var txId = await cashCow.SendToAddressAsync(invoiceAddress, invoice.BtcDue + Money.Coins(1));
|
||||
|
||||
TestUtils.Eventually(() =>
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("paid", localInvoice.Status);
|
||||
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
||||
Assert.Equal("paidOver", (string)((JValue)localInvoice.ExceptionStatus).Value);
|
||||
|
||||
var textSearchResult = tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
var textSearchResult = await tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
StoreId = new[] { user.StoreId },
|
||||
TextSearch = txId.ToString()
|
||||
}).GetAwaiter().GetResult();
|
||||
});
|
||||
Assert.Single(textSearchResult);
|
||||
});
|
||||
|
||||
|
@ -2421,7 +2420,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async void CheckOnionlocationForNonOnionHtmlRequests()
|
||||
public async Task CheckOnionlocationForNonOnionHtmlRequests()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
|
@ -3048,7 +3047,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async void CanUseLocalProviderFiles()
|
||||
public async Task CanUseLocalProviderFiles()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
|
@ -3273,7 +3272,7 @@ namespace BTCPayServer.Tests
|
|||
var fullyPaidIndex = report.GetIndex("FullyPaid");
|
||||
var completedIndex = report.GetIndex("Completed");
|
||||
var limitIndex = report.GetIndex("Limit");
|
||||
var d = Assert.Single(report.Data.Where(d => d[report.GetIndex("InvoiceId")].Value<string>() == inv.Id));
|
||||
var d = Assert.Single(report.Data, d => d[report.GetIndex("InvoiceId")].Value<string>() == inv.Id);
|
||||
Assert.Equal(fullyPaid, (bool)d[fullyPaidIndex]);
|
||||
Assert.Equal(currency, d[currencyIndex].Value<string>());
|
||||
Assert.Equal(completed, GetAmount(completedIndex, d));
|
||||
|
|
|
@ -98,7 +98,7 @@ services:
|
|||
custom:
|
||||
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.5.12
|
||||
image: nicolasdorier/nbxplorer:2.5.14
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
|
|
|
@ -95,7 +95,7 @@ services:
|
|||
custom:
|
||||
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.5.12
|
||||
image: nicolasdorier/nbxplorer:2.5.14
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"maxParallelThreads": 4,
|
||||
"longRunningTestSeconds": 60,
|
||||
"diagnosticMessages": true,
|
||||
"methodDisplay": "method"
|
||||
}
|
||||
|
|
|
@ -8,6 +8,19 @@
|
|||
<RunAnalyzersDuringLiveAnalysis>False</RunAnalyzersDuringLiveAnalysis>
|
||||
<RunAnalyzersDuringBuild>False</RunAnalyzersDuringBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Pre-compiling views should only be done for Release builds without dotnet watch or design time build .-->
|
||||
<!-- Runtime compiling is only useful for debugging with hot reload of the views -->
|
||||
<PropertyGroup Condition="'$(RazorCompileOnBuild)'=='' AND ('$(Configuration)' == 'Debug' OR '$(DotNetWatchBuild)' == 'true' OR '$(DesignTimeBuild)' == 'true')">
|
||||
<RazorCompileOnBuild>false</RazorCompileOnBuild>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(RazorCompileOnBuild)'==''">
|
||||
<RazorCompileOnBuild>true</RazorCompileOnBuild>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(RazorCompileOnBuild)' == 'true'">
|
||||
<DefineConstants>$(DefineConstants);RAZOR_COMPILE_ON_BUILD</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Condition="'$(GitCommit)' != ''" Include="BTCPayServer.GitCommitAttribute">
|
||||
<_Parameter1>$(GitCommit)</_Parameter1>
|
||||
|
@ -37,17 +50,17 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BTCPayServer.NTag424" Version="1.0.24" />
|
||||
<PackageReference Include="BTCPayServer.NTag424" Version="1.0.25" />
|
||||
<PackageReference Include="YamlDotNet" Version="8.0.0" />
|
||||
<PackageReference Include="BIP78.Sender" Version="0.2.2" />
|
||||
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.2" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.6.7" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.6.9" />
|
||||
<PackageReference Include="CsvHelper" Version="32.0.3" />
|
||||
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||
<PackageReference Include="Fido2" Version="2.0.2" />
|
||||
<PackageReference Include="Fido2.AspNet" Version="2.0.2" />
|
||||
<PackageReference Include="LNURL" Version="0.0.34" />
|
||||
<PackageReference Include="MailKit" Version="3.3.0" />
|
||||
<PackageReference Include="Fido2" Version="3.0.1" />
|
||||
<PackageReference Include="Fido2.AspNet" Version="3.0.1" />
|
||||
<PackageReference Include="LNURL" Version="0.0.36" />
|
||||
<PackageReference Include="MailKit" Version="4.8.0" />
|
||||
<PackageReference Include="BTCPayServer.NETCore.Plugins.Mvc" Version="1.4.4" />
|
||||
<PackageReference Include="QRCoder" Version="1.6.0" />
|
||||
<PackageReference Include="System.IO.Pipelines" Version="8.0.0" />
|
||||
|
@ -60,13 +73,14 @@
|
|||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.1-dev-00968" />
|
||||
<PackageReference Include="SSH.NET" Version="2023.0.0" />
|
||||
<PackageReference Include="TwentyTwenty.Storage" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Amazon" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Azure" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Google" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Local" Version="2.12.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.7" />
|
||||
<PackageReference Include="TwentyTwenty.Storage" Version="2.24.2" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Amazon" Version="2.24.2" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Azure" Version="2.24.2" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Google" Version="2.24.2" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Local" Version="2.24.2" />
|
||||
<PackageReference Condition="'$(RazorCompileOnBuild)' == 'false'" Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.11" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Authorization;
|
|||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
|
@ -30,10 +31,10 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/users")]
|
||||
public IActionResult GetStoreUsers()
|
||||
public async Task<IActionResult> GetStoreUsers()
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
return store == null ? StoreNotFound() : Ok(FromModel(store));
|
||||
return store == null ? StoreNotFound() : Ok(await FromModel(store));
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
|
@ -41,31 +42,28 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
public async Task<IActionResult> RemoveStoreUser(string storeId, string idOrEmail)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
{
|
||||
return StoreNotFound();
|
||||
}
|
||||
if (store == null) return StoreNotFound();
|
||||
|
||||
var userId = await _userManager.FindByIdOrEmail(idOrEmail);
|
||||
if (userId != null && await _storeRepository.RemoveStoreUser(storeId, idOrEmail))
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
return this.CreateAPIError(409, "store-user-role-orphaned", "Removing this user would result in the store having no owner.");
|
||||
var user = await _userManager.FindByIdOrEmail(idOrEmail);
|
||||
if (user == null) return UserNotFound();
|
||||
|
||||
return await _storeRepository.RemoveStoreUser(storeId, user.Id)
|
||||
? Ok()
|
||||
: this.CreateAPIError(409, "store-user-role-orphaned", "Removing this user would result in the store having no owner.");
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/stores/{storeId}/users")]
|
||||
public async Task<IActionResult> AddStoreUser(string storeId, StoreUserData request)
|
||||
[HttpPut("~/api/v1/stores/{storeId}/users/{idOrEmail?}")]
|
||||
public async Task<IActionResult> AddOrUpdateStoreUser(string storeId, StoreUserData request, string idOrEmail = null)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
{
|
||||
return StoreNotFound();
|
||||
}
|
||||
StoreRoleId roleId = null;
|
||||
if (store == null) return StoreNotFound();
|
||||
|
||||
var user = await _userManager.FindByIdOrEmail(idOrEmail ?? request.UserId);
|
||||
if (user == null) return UserNotFound();
|
||||
|
||||
StoreRoleId roleId = null;
|
||||
if (request.Role is not null)
|
||||
{
|
||||
roleId = await _storeRepository.ResolveStoreRoleId(storeId, request.Role);
|
||||
|
@ -76,21 +74,42 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
|
||||
if (await _storeRepository.AddStoreUser(storeId, request.UserId, roleId))
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
return this.CreateAPIError(409, "duplicate-store-user-role", "The user is already added to the store");
|
||||
var result = string.IsNullOrEmpty(idOrEmail)
|
||||
? await _storeRepository.AddStoreUser(storeId, user.Id, roleId)
|
||||
: await _storeRepository.AddOrUpdateStoreUser(storeId, user.Id, roleId);
|
||||
return result
|
||||
? Ok()
|
||||
: this.CreateAPIError(409, "duplicate-store-user-role", "The user is already added to the store");
|
||||
}
|
||||
|
||||
private IEnumerable<StoreUserData> FromModel(Data.StoreData data)
|
||||
private async Task<IEnumerable<StoreUserData>> FromModel(StoreData data)
|
||||
{
|
||||
return data.UserStores.Select(store => new StoreUserData() { UserId = store.ApplicationUserId, Role = store.StoreRoleId });
|
||||
var storeUsers = new List<StoreUserData>();
|
||||
foreach (var storeUser in data.UserStores)
|
||||
{
|
||||
var user = await _userManager.FindByIdOrEmail(storeUser.ApplicationUserId);
|
||||
var blob = user?.GetBlob();
|
||||
storeUsers.Add(new StoreUserData
|
||||
{
|
||||
UserId = storeUser.ApplicationUserId,
|
||||
Role = storeUser.StoreRoleId,
|
||||
Email = user?.Email,
|
||||
Name = blob?.Name,
|
||||
ImageUrl = blob?.ImageUrl,
|
||||
|
||||
});
|
||||
}
|
||||
return storeUsers;
|
||||
}
|
||||
|
||||
private IActionResult StoreNotFound()
|
||||
{
|
||||
return this.CreateAPIError(404, "store-not-found", "The store was not found");
|
||||
}
|
||||
|
||||
private IActionResult UserNotFound()
|
||||
{
|
||||
return this.CreateAPIError(404, "user-not-found", "The user was not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -985,17 +985,22 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
return GetFromActionResult(await GetController<GreenfieldUsersController>().GetUsers());
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<StoreUserData>> GetStoreUsers(string storeId,
|
||||
public override async Task<IEnumerable<StoreUserData>> GetStoreUsers(string storeId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(
|
||||
GetFromActionResult<IEnumerable<StoreUserData>>(GetController<GreenfieldStoreUsersController>().GetStoreUsers()));
|
||||
return GetFromActionResult<IEnumerable<StoreUserData>>(await GetController<GreenfieldStoreUsersController>().GetStoreUsers());
|
||||
}
|
||||
|
||||
public override async Task AddStoreUser(string storeId, StoreUserData request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
HandleActionResult(await GetController<GreenfieldStoreUsersController>().AddStoreUser(storeId, request));
|
||||
HandleActionResult(await GetController<GreenfieldStoreUsersController>().AddOrUpdateStoreUser(storeId, request));
|
||||
}
|
||||
|
||||
public override async Task UpdateStoreUser(string storeId, string userId, StoreUserData request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
HandleActionResult(await GetController<GreenfieldStoreUsersController>().AddOrUpdateStoreUser(storeId, request, userId));
|
||||
}
|
||||
|
||||
public override async Task RemoveStoreUser(string storeId, string userId, CancellationToken token = default)
|
||||
|
|
|
@ -273,7 +273,7 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
return new LoginWithFido2ViewModel
|
||||
{
|
||||
Data = r,
|
||||
Data = System.Text.Json.JsonSerializer.Serialize(r, r.GetType()),
|
||||
UserId = user.Id,
|
||||
RememberMe = rememberMe
|
||||
};
|
||||
|
@ -385,7 +385,7 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
try
|
||||
{
|
||||
if (await _fido2Service.CompleteLogin(viewModel.UserId, JObject.Parse(viewModel.Response).ToObject<AuthenticatorAssertionRawResponse>()))
|
||||
if (await _fido2Service.CompleteLogin(viewModel.UserId, System.Text.Json.JsonSerializer.Deserialize<AuthenticatorAssertionRawResponse>(viewModel.Response)))
|
||||
{
|
||||
await _signInManager.SignInAsync(user!, viewModel.RememberMe, "FIDO2");
|
||||
_logger.LogInformation("User {Email} logged in with FIDO2", user.Email);
|
||||
|
@ -650,6 +650,7 @@ namespace BTCPayServer.Controllers
|
|||
if (logon)
|
||||
{
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
_logger.LogInformation("User {Email} logged in", user.Email);
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
}
|
||||
|
@ -793,7 +794,7 @@ namespace BTCPayServer.Controllers
|
|||
[HttpPost("/login/set-password")]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> SetPassword(SetPasswordViewModel model)
|
||||
public async Task<IActionResult> SetPassword(SetPasswordViewModel model, string returnUrl = null)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
|
@ -802,9 +803,11 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
var user = await _userManager.FindByEmailAsync(model.Email);
|
||||
var hasPassword = user != null && await _userManager.HasPasswordAsync(user);
|
||||
if (!UserService.TryCanLogin(user, out _))
|
||||
var needsInitialPassword = user != null && !await _userManager.HasPasswordAsync(user);
|
||||
// Let unapproved users set a password. Otherwise, don't reveal that the user does not exist.
|
||||
if (!UserService.TryCanLogin(user, out var message) && !needsInitialPassword || user == null)
|
||||
{
|
||||
// Don't reveal that the user does not exist
|
||||
_logger.LogWarning("User {Email} tried to reset password, but failed: {Message}", user?.Email ?? "(NO EMAIL)", message);
|
||||
return RedirectToAction(nameof(Login));
|
||||
}
|
||||
|
||||
|
@ -818,7 +821,19 @@ namespace BTCPayServer.Controllers
|
|||
? StringLocalizer["Password successfully set."].Value
|
||||
: StringLocalizer["Account successfully created."].Value
|
||||
});
|
||||
|
||||
if (!hasPassword) await FinalizeInvitationIfApplicable(user);
|
||||
|
||||
// see if we can sign in user after accepting an invitation and setting the password
|
||||
if (needsInitialPassword && UserService.TryCanLogin(user, out _))
|
||||
{
|
||||
var signInResult = await _signInManager.PasswordSignInAsync(user.Email!, model.Password, true, true);
|
||||
if (signInResult.Succeeded)
|
||||
{
|
||||
_logger.LogInformation("User {Email} logged in", user.Email);
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
}
|
||||
return RedirectToAction(nameof(Login));
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,6 @@ using NBitcoin;
|
|||
using Newtonsoft.Json.Linq;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
using Serilog.Filters;
|
||||
using PeterO.Numbers;
|
||||
using BTCPayServer.Payouts;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ using BTCPayServer.Services.Rates;
|
|||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using static Org.BouncyCastle.Math.EC.ECCurve;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
|
|
|
@ -11,6 +11,7 @@ using Fido2NetLib.Objects;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using static BTCPayServer.Fido2.Models.Fido2CredentialBlob;
|
||||
|
||||
namespace BTCPayServer.Fido2
|
||||
{
|
||||
|
@ -45,7 +46,7 @@ namespace BTCPayServer.Fido2
|
|||
var existingKeys =
|
||||
user.Fido2Credentials
|
||||
.Where(credential => credential.Type == Fido2Credential.CredentialType.FIDO2)
|
||||
.Select(c => c.GetFido2Blob().Descriptor).ToList();
|
||||
.Select(c => c.GetFido2Blob().Descriptor?.ToFido2()).ToList();
|
||||
|
||||
// 3. Create options
|
||||
var authenticatorSelection = new AuthenticatorSelection
|
||||
|
@ -57,14 +58,7 @@ namespace BTCPayServer.Fido2
|
|||
var exts = new AuthenticationExtensionsClientInputs()
|
||||
{
|
||||
Extensions = true,
|
||||
UserVerificationIndex = true,
|
||||
Location = true,
|
||||
UserVerificationMethod = true,
|
||||
BiometricAuthenticatorPerformanceBounds = new AuthenticatorBiometricPerfBounds
|
||||
{
|
||||
FAR = float.MaxValue,
|
||||
FRR = float.MaxValue
|
||||
},
|
||||
UserVerificationMethod = true
|
||||
};
|
||||
|
||||
var options = _fido2.RequestNewCredential(
|
||||
|
@ -81,7 +75,7 @@ namespace BTCPayServer.Fido2
|
|||
try
|
||||
{
|
||||
|
||||
var attestationResponse = JObject.Parse(data).ToObject<AuthenticatorAttestationRawResponse>();
|
||||
var attestationResponse = System.Text.Json.JsonSerializer.Deserialize<AuthenticatorAttestationRawResponse>(data);
|
||||
await using var dbContext = _contextFactory.CreateContext();
|
||||
var user = await dbContext.Users.Include(applicationUser => applicationUser.Fido2Credentials)
|
||||
.FirstOrDefaultAsync(applicationUser => applicationUser.Id == userId);
|
||||
|
@ -92,14 +86,14 @@ namespace BTCPayServer.Fido2
|
|||
|
||||
// 2. Verify and make the credentials
|
||||
var success =
|
||||
await _fido2.MakeNewCredentialAsync(attestationResponse, options, args => Task.FromResult(true));
|
||||
await _fido2.MakeNewCredentialAsync(attestationResponse, options, (args, cancellation) => Task.FromResult(true));
|
||||
|
||||
// 3. Store the credentials in db
|
||||
var newCredential = new Fido2Credential() { Name = name, ApplicationUserId = userId };
|
||||
|
||||
newCredential.SetBlob(new Fido2CredentialBlob()
|
||||
{
|
||||
Descriptor = new PublicKeyCredentialDescriptor(success.Result.CredentialId),
|
||||
Descriptor = new DescriptorClass(success.Result.CredentialId),
|
||||
PublicKey = success.Result.PublicKey,
|
||||
UserHandle = success.Result.User.Id,
|
||||
SignatureCounter = success.Result.Counter,
|
||||
|
@ -158,21 +152,13 @@ namespace BTCPayServer.Fido2
|
|||
}
|
||||
var existingCredentials = user.Fido2Credentials
|
||||
.Where(credential => credential.Type == Fido2Credential.CredentialType.FIDO2)
|
||||
.Select(c => c.GetFido2Blob().Descriptor)
|
||||
.Select(c => c.GetFido2Blob().Descriptor?.ToFido2())
|
||||
.ToList();
|
||||
var exts = new AuthenticationExtensionsClientInputs()
|
||||
{
|
||||
SimpleTransactionAuthorization = "FIDO",
|
||||
GenericTransactionAuthorization = new TxAuthGenericArg
|
||||
{
|
||||
ContentType = "text/plain",
|
||||
Content = new byte[] { 0x46, 0x49, 0x44, 0x4F }
|
||||
},
|
||||
UserVerificationIndex = true,
|
||||
Location = true,
|
||||
UserVerificationMethod = true,
|
||||
Extensions = true,
|
||||
AppID = _fido2Configuration.Origin
|
||||
AppID = _fido2Configuration.Origins.First()
|
||||
};
|
||||
|
||||
// 3. Create options
|
||||
|
@ -206,7 +192,7 @@ namespace BTCPayServer.Fido2
|
|||
|
||||
// 5. Make the assertion
|
||||
var res = await _fido2.MakeAssertionAsync(response, options, credential.Item2.PublicKey,
|
||||
credential.Item2.SignatureCounter, x => Task.FromResult(true));
|
||||
credential.Item2.SignatureCounter, (x, cancellationToken) => Task.FromResult(true));
|
||||
|
||||
// 6. Store the updated counter
|
||||
credential.Item2.SignatureCounter = res.Counter;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using Fido2NetLib;
|
||||
using Fido2NetLib.Objects;
|
||||
using Newtonsoft.Json;
|
||||
|
@ -6,7 +7,84 @@ namespace BTCPayServer.Fido2.Models
|
|||
{
|
||||
public class Fido2CredentialBlob
|
||||
{
|
||||
public PublicKeyCredentialDescriptor Descriptor { get; set; }
|
||||
public class Base64UrlConverter : JsonConverter<byte[]>
|
||||
{
|
||||
private readonly Required _requirement = Required.DisallowNull;
|
||||
|
||||
public Base64UrlConverter()
|
||||
{
|
||||
}
|
||||
|
||||
public Base64UrlConverter(Required required = Required.DisallowNull)
|
||||
{
|
||||
_requirement = required;
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, byte[] value, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteValue(Base64Url.Encode(value));
|
||||
}
|
||||
|
||||
public override byte[] ReadJson(JsonReader reader, Type objectType, byte[] existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
byte[] ret = null;
|
||||
|
||||
if (null == reader.Value && _requirement == Required.AllowNull)
|
||||
return ret;
|
||||
|
||||
if (null == reader.Value)
|
||||
throw new Fido2VerificationException("json value must not be null");
|
||||
if (Type.GetType("System.String") != reader.ValueType)
|
||||
throw new Fido2VerificationException("json valuetype must be string");
|
||||
try
|
||||
{
|
||||
ret = Base64Url.Decode((string)reader.Value);
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
throw new Fido2VerificationException("json value must be valid base64 encoded string", ex);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
public class DescriptorClass
|
||||
{
|
||||
public DescriptorClass(byte[] credentialId)
|
||||
{
|
||||
Id = credentialId;
|
||||
}
|
||||
|
||||
public DescriptorClass()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This member contains the type of the public key credential the caller is referring to.
|
||||
/// </summary>
|
||||
[JsonProperty("type")]
|
||||
public string Type { get; set; } = "public-key";
|
||||
|
||||
/// <summary>
|
||||
/// This member contains the credential ID of the public key credential the caller is referring to.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(Base64UrlConverter))]
|
||||
[JsonProperty("id")]
|
||||
public byte[] Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This OPTIONAL member contains a hint as to how the client might communicate with the managing authenticator of the public key credential the caller is referring to.
|
||||
/// </summary>
|
||||
[JsonProperty("transports", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string[] Transports { get; set; }
|
||||
|
||||
public PublicKeyCredentialDescriptor ToFido2()
|
||||
{
|
||||
var str = JsonConvert.SerializeObject(this);
|
||||
return System.Text.Json.JsonSerializer.Deserialize<PublicKeyCredentialDescriptor>(str);
|
||||
}
|
||||
}
|
||||
public DescriptorClass Descriptor { get; set; }
|
||||
[JsonConverter(typeof(Base64UrlConverter))]
|
||||
public byte[] PublicKey { get; set; }
|
||||
[JsonConverter(typeof(Base64UrlConverter))]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Fido2NetLib;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Fido2.Models
|
||||
{
|
||||
|
@ -7,7 +8,7 @@ namespace BTCPayServer.Fido2.Models
|
|||
public string UserId { get; set; }
|
||||
|
||||
public bool RememberMe { get; set; }
|
||||
public AssertionOptions Data { get; set; }
|
||||
public string Data { get; set; }
|
||||
public string Response { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ using BTCPayServer.Services.Invoices;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.WindowsAzure.Storage.Table;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
|
|
@ -532,7 +532,8 @@ o.GetRequiredService<IEnumerable<IPaymentLinkExtension>>().ToDictionary(o => o.P
|
|||
{ "TRY", "btcturk" },
|
||||
{ "UGX", "yadio"},
|
||||
{ "RSD", "bitpay"},
|
||||
{ "NGN", "bitnob"}
|
||||
{ "NGN", "bitnob"},
|
||||
{ "NOK", "barebitcoin"}
|
||||
})
|
||||
{
|
||||
var r = new DefaultRules.Recommendation(rule.Key, rule.Value);
|
||||
|
@ -587,6 +588,8 @@ o.GetRequiredService<IEnumerable<IPaymentLinkExtension>>().ToDictionary(o => o.P
|
|||
services.AddRateProvider<YadioRateProvider>();
|
||||
services.AddRateProvider<BtcTurkRateProvider>();
|
||||
services.AddRateProvider<FreeCurrencyRatesRateProvider>();
|
||||
services.AddRateProvider<BitmyntRateProvider>();
|
||||
services.AddRateProvider<BareBitcoinRateProvider>();
|
||||
|
||||
services.AddSingleton<InvoiceBlobMigratorHostedService>();
|
||||
services.AddSingleton<IHostedService, InvoiceBlobMigratorHostedService>(o => o.GetRequiredService<InvoiceBlobMigratorHostedService>());
|
||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
|
@ -22,14 +23,15 @@ using BTCPayServer.Services.Invoices;
|
|||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Storage.Models;
|
||||
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration;
|
||||
using Fido2NetLib.Cbor;
|
||||
using Fido2NetLib.Objects;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PeterO.Cbor;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using static BTCPayServer.Fido2.Models.Fido2CredentialBlob;
|
||||
using LightningAddressData = BTCPayServer.Data.LightningAddressData;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
|
@ -738,9 +740,9 @@ WHERE cte.""Id""=p.""Id""
|
|||
fido2.SetBlob(new Fido2CredentialBlob()
|
||||
{
|
||||
SignatureCounter = (uint)u2FDevice.Counter,
|
||||
PublicKey = CreatePublicKeyFromU2fRegistrationData(u2FDevice.PublicKey).EncodeToBytes(),
|
||||
PublicKey = CreatePublicKeyFromU2fRegistrationData(u2FDevice.PublicKey).Encode(),
|
||||
UserHandle = u2FDevice.KeyHandle,
|
||||
Descriptor = new PublicKeyCredentialDescriptor(u2FDevice.KeyHandle),
|
||||
Descriptor = new DescriptorClass(u2FDevice.KeyHandle),
|
||||
CredType = "u2f"
|
||||
});
|
||||
|
||||
|
@ -751,27 +753,29 @@ WHERE cte.""Id""=p.""Id""
|
|||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
//from https://github.com/abergs/fido2-net-lib/blob/0fa7bb4b4a1f33f46c5f7ca4ee489b47680d579b/Test/ExistingU2fRegistrationDataTests.cs#L70
|
||||
private static CBORObject CreatePublicKeyFromU2fRegistrationData(byte[] publicKeyData)
|
||||
private static CborMap CreatePublicKeyFromU2fRegistrationData(byte[] publicKeyData)
|
||||
{
|
||||
if (publicKeyData.Length != 65)
|
||||
{
|
||||
throw new ArgumentException("u2f public key must be 65 bytes", nameof(publicKeyData));
|
||||
}
|
||||
var x = new byte[32];
|
||||
var y = new byte[32];
|
||||
Buffer.BlockCopy(publicKeyData, 1, x, 0, 32);
|
||||
Buffer.BlockCopy(publicKeyData, 33, y, 0, 32);
|
||||
|
||||
var point = new ECPoint
|
||||
{
|
||||
X = x,
|
||||
Y = y,
|
||||
};
|
||||
|
||||
var coseKey = CBORObject.NewMap();
|
||||
var coseKey = new CborMap
|
||||
{
|
||||
{ (long)COSE.KeyCommonParameter.KeyType, (long)COSE.KeyType.EC2 },
|
||||
{ (long)COSE.KeyCommonParameter.Alg, -7L },
|
||||
|
||||
coseKey.Add(COSE.KeyCommonParameter.KeyType, COSE.KeyType.EC2);
|
||||
coseKey.Add(COSE.KeyCommonParameter.Alg, -7);
|
||||
{ (long)COSE.KeyTypeParameter.Crv, (long)COSE.EllipticCurve.P256 },
|
||||
|
||||
coseKey.Add(COSE.KeyTypeParameter.Crv, COSE.EllipticCurve.P256);
|
||||
|
||||
coseKey.Add(COSE.KeyTypeParameter.X, x);
|
||||
coseKey.Add(COSE.KeyTypeParameter.Y, y);
|
||||
{ (long)COSE.KeyTypeParameter.X, point.X },
|
||||
{ (long)COSE.KeyTypeParameter.Y, point.Y }
|
||||
};
|
||||
|
||||
return coseKey;
|
||||
}
|
||||
|
|
|
@ -122,8 +122,7 @@ namespace BTCPayServer.Hosting
|
|||
})
|
||||
.AddCachedMetadataService(config =>
|
||||
{
|
||||
//They'll be used in a "first match wins" way in the order registered
|
||||
config.AddStaticMetadataRepository();
|
||||
config.AddFidoMetadataRepository();
|
||||
});
|
||||
var descriptor = services.Single(descriptor => descriptor.ServiceType == typeof(Fido2Configuration));
|
||||
services.Remove(descriptor);
|
||||
|
@ -133,7 +132,7 @@ namespace BTCPayServer.Hosting
|
|||
return new Fido2Configuration()
|
||||
{
|
||||
ServerName = "BTCPay Server",
|
||||
Origin = $"{httpContext.HttpContext.Request.Scheme}://{httpContext.HttpContext.Request.Host}",
|
||||
Origins = new[] { $"{httpContext.HttpContext.Request.Scheme}://{httpContext.HttpContext.Request.Host}" }.ToHashSet(),
|
||||
ServerDomain = httpContext.HttpContext.Request.Host.Host
|
||||
};
|
||||
});
|
||||
|
@ -141,7 +140,7 @@ namespace BTCPayServer.Hosting
|
|||
services.AddSingleton<UserLoginCodeService>();
|
||||
services.AddSingleton<LnurlAuthService>();
|
||||
services.AddSingleton<LightningAddressService>();
|
||||
services.AddMvc(o =>
|
||||
var mvcBuilder = services.AddMvc(o =>
|
||||
{
|
||||
o.Filters.Add(new XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.Deny));
|
||||
o.Filters.Add(new XContentTypeOptionsAttribute("nosniff"));
|
||||
|
@ -167,11 +166,14 @@ namespace BTCPayServer.Hosting
|
|||
o.PageViewLocationFormats.Add("/{0}.cshtml");
|
||||
})
|
||||
.AddNewtonsoftJson()
|
||||
.AddRazorRuntimeCompilation()
|
||||
.AddPlugins(services, Configuration, LoggerFactory, bootstrapServiceProvider)
|
||||
.AddDataAnnotationsLocalization()
|
||||
.AddControllersAsServices();
|
||||
|
||||
#if !RAZOR_COMPILE_ON_BUILD
|
||||
mvcBuilder.AddRazorRuntimeCompilation();
|
||||
#endif
|
||||
|
||||
services.AddServerSideBlazor();
|
||||
|
||||
LowercaseTransformer.Register(services);
|
||||
|
|
|
@ -67,16 +67,14 @@ namespace BTCPayServer.PaymentRequest
|
|||
var invoices = await _paymentRequestRepository.GetInvoicesForPaymentRequest(pr.Id);
|
||||
var contributions = _invoiceRepository.GetContributionsByPaymentMethodId(blob.Currency, invoices, true);
|
||||
|
||||
if (contributions.Total >= blob.Amount)
|
||||
{
|
||||
currentStatus = contributions.TotalSettled >= blob.Amount
|
||||
? Client.Models.PaymentRequestData.PaymentRequestStatus.Completed
|
||||
: Client.Models.PaymentRequestData.PaymentRequestStatus.Processing;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentStatus = Client.Models.PaymentRequestData.PaymentRequestStatus.Pending;
|
||||
}
|
||||
currentStatus =
|
||||
(PaidEnough: contributions.Total >= blob.Amount,
|
||||
SettledEnough: contributions.TotalSettled >= blob.Amount) switch
|
||||
{
|
||||
{ SettledEnough: true } => Client.Models.PaymentRequestData.PaymentRequestStatus.Completed,
|
||||
{ PaidEnough: true } => Client.Models.PaymentRequestData.PaymentRequestStatus.Processing,
|
||||
_ => Client.Models.PaymentRequestData.PaymentRequestStatus.Pending
|
||||
};
|
||||
}
|
||||
|
||||
if (currentStatus != pr.Status)
|
||||
|
@ -100,7 +98,7 @@ namespace BTCPayServer.PaymentRequest
|
|||
var amountDue = blob.Amount - paymentStats.Total;
|
||||
var pendingInvoice = invoices.OrderByDescending(entity => entity.InvoiceTime)
|
||||
.FirstOrDefault(entity => entity.Status == InvoiceStatus.New);
|
||||
|
||||
|
||||
return new ViewPaymentRequestViewModel(pr)
|
||||
{
|
||||
Archived = pr.Archived,
|
||||
|
@ -121,8 +119,7 @@ namespace BTCPayServer.PaymentRequest
|
|||
var state = entity.GetInvoiceState();
|
||||
var payments = ViewPaymentRequestViewModel.PaymentRequestInvoicePayment.GetViewModels(entity, _displayFormatter, _transactionLinkProviders, _handlers);
|
||||
|
||||
if (state.Status == InvoiceStatus.Invalid ||
|
||||
state.Status == InvoiceStatus.Expired && !payments.Any())
|
||||
if (state.Status is InvoiceStatus.Invalid or InvoiceStatus.Expired && payments.Count is 0)
|
||||
return null;
|
||||
|
||||
return new ViewPaymentRequestViewModel.PaymentRequestInvoice
|
||||
|
|
|
@ -23,7 +23,6 @@ using NBXplorer.DerivationStrategy;
|
|||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using static Org.BouncyCastle.Math.EC.ECCurve;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Payments.Bitcoin
|
||||
|
|
|
@ -17,7 +17,6 @@ using BTCPayServer.Services;
|
|||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using crypto;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using NBitcoin;
|
||||
|
|
|
@ -144,6 +144,8 @@ retry:
|
|||
return _memoryCache.GetOrCreateAsync(GetCacheKey(invoiceId), async (cacheEntry) =>
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
||||
if (invoice is null)
|
||||
return null;
|
||||
cacheEntry.AbsoluteExpiration = GetExpiration(invoice);
|
||||
return invoice;
|
||||
})!;
|
||||
|
|
|
@ -2,8 +2,10 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using Dapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using Npgsql;
|
||||
|
||||
namespace BTCPayServer.Payments.PayJoin
|
||||
{
|
||||
|
@ -16,20 +18,6 @@ namespace BTCPayServer.Payments.PayJoin
|
|||
_dbContextFactory = dbContextFactory;
|
||||
}
|
||||
|
||||
public async Task<bool> TryLock(OutPoint outpoint)
|
||||
{
|
||||
using var ctx = _dbContextFactory.CreateContext();
|
||||
ctx.PayjoinLocks.Add(new PayjoinLock() { Id = outpoint.ToString() });
|
||||
try
|
||||
{
|
||||
return await ctx.SaveChangesAsync() == 1;
|
||||
}
|
||||
catch (DbUpdateException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> TryUnlock(params OutPoint[] outPoints)
|
||||
{
|
||||
using var ctx = _dbContextFactory.CreateContext();
|
||||
|
@ -48,29 +36,33 @@ namespace BTCPayServer.Payments.PayJoin
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<bool> TryLockInputs(OutPoint[] outPoints)
|
||||
|
||||
|
||||
|
||||
|
||||
private async Task<bool> TryLockInputs(string[] ids)
|
||||
{
|
||||
using var ctx = _dbContextFactory.CreateContext();
|
||||
foreach (OutPoint outPoint in outPoints)
|
||||
{
|
||||
ctx.PayjoinLocks.Add(new PayjoinLock()
|
||||
{
|
||||
// Random flag so it does not lock same id
|
||||
// as the lock utxo
|
||||
Id = "K-" + outPoint.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
var connection = ctx.Database.GetDbConnection();
|
||||
try
|
||||
{
|
||||
return await ctx.SaveChangesAsync() == outPoints.Length;
|
||||
await connection.ExecuteAsync("""
|
||||
INSERT INTO "PayjoinLocks"("Id")
|
||||
SELECT * FROM unnest(@ids)
|
||||
""", new { ids });
|
||||
return true;
|
||||
}
|
||||
catch (DbUpdateException)
|
||||
catch (Npgsql.PostgresException ex) when (ex.SqlState == PostgresErrorCodes.UniqueViolation)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<bool> TryLock(OutPoint outpoint)
|
||||
=> TryLockInputs([outpoint.ToString()]);
|
||||
public Task<bool> TryLockInputs(OutPoint[] outPoints)
|
||||
=> TryLockInputs(outPoints.Select(o => "K-" + o.ToString()).ToArray());
|
||||
|
||||
public async Task<HashSet<OutPoint>> FindLocks(OutPoint[] outpoints)
|
||||
{
|
||||
var outPointsStr = outpoints.Select(o => o.ToString());
|
||||
|
|
|
@ -43,6 +43,10 @@ namespace BTCPayServer.Services.Apps
|
|||
private readonly DisplayFormatter _displayFormatter;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
public CurrencyNameTable Currencies => _Currencies;
|
||||
private readonly string[] _paidStatuses = [
|
||||
InvoiceStatus.Processing.ToString(),
|
||||
InvoiceStatus.Settled.ToString()
|
||||
];
|
||||
|
||||
public AppService(
|
||||
IEnumerable<AppBaseType> apps,
|
||||
|
@ -86,11 +90,7 @@ namespace BTCPayServer.Services.Apps
|
|||
{
|
||||
if (GetAppType(appData.AppType) is not IHasItemStatsAppType salesType)
|
||||
throw new InvalidOperationException("This app isn't a SalesAppBaseType");
|
||||
var paidInvoices = await GetInvoicesForApp(_InvoiceRepository, appData, null,
|
||||
[
|
||||
InvoiceStatus.Processing.ToString(),
|
||||
InvoiceStatus.Settled.ToString()
|
||||
]);
|
||||
var paidInvoices = await GetInvoicesForApp(_InvoiceRepository, appData, null, _paidStatuses);
|
||||
return await salesType.GetItemStats(appData, paidInvoices);
|
||||
}
|
||||
|
||||
|
@ -132,8 +132,7 @@ namespace BTCPayServer.Services.Apps
|
|||
{
|
||||
if (GetAppType(app.AppType) is not IHasSaleStatsAppType salesType)
|
||||
throw new InvalidOperationException("This app isn't a SalesAppBaseType");
|
||||
var paidInvoices = await GetInvoicesForApp(_InvoiceRepository, app, DateTimeOffset.UtcNow - TimeSpan.FromDays(numberOfDays));
|
||||
|
||||
var paidInvoices = await GetInvoicesForApp(_InvoiceRepository, app, DateTimeOffset.UtcNow - TimeSpan.FromDays(numberOfDays), _paidStatuses);
|
||||
return await salesType.GetSalesStats(app, paidInvoices, numberOfDays);
|
||||
}
|
||||
|
||||
|
|
|
@ -267,9 +267,9 @@ namespace BTCPayServer.Services.Invoices
|
|||
}
|
||||
public const int InternalTagSupport_Version = 1;
|
||||
public const int GreenfieldInvoices_Version = 2;
|
||||
public const int LeanInvoices_Version = 3;
|
||||
public const int Lastest_Version = 3;
|
||||
public int Version { get; set; }
|
||||
public const int LeanInvoices_Version = 3;
|
||||
public const int Lastest_Version = 3;
|
||||
public int Version { get; set; }
|
||||
[JsonIgnore]
|
||||
public string Id { get; set; }
|
||||
[JsonIgnore]
|
||||
|
@ -349,7 +349,7 @@ namespace BTCPayServer.Services.Invoices
|
|||
ArgumentNullException.ThrowIfNull(pair);
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (pair.Right == Currency && Rates.TryGetValue(pair.Left, out var rate)) // Fast lane
|
||||
return rate;
|
||||
return rate;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
var rule = GetRateRules().GetRuleFor(pair);
|
||||
rule.Reevaluate();
|
||||
|
@ -802,33 +802,40 @@ namespace BTCPayServer.Services.Invoices
|
|||
}
|
||||
public record InvoiceState(InvoiceStatus Status, InvoiceExceptionStatus ExceptionStatus)
|
||||
{
|
||||
public InvoiceState(string status, string exceptionStatus):
|
||||
public InvoiceState(string status, string exceptionStatus) :
|
||||
this(Enum.Parse<InvoiceStatus>(status), exceptionStatus switch { "None" or "" or null => InvoiceExceptionStatus.None, _ => Enum.Parse<InvoiceExceptionStatus>(exceptionStatus) })
|
||||
{
|
||||
}
|
||||
|
||||
public bool CanMarkComplete()
|
||||
public bool CanMarkComplete() => (Status, ExceptionStatus) is
|
||||
{
|
||||
return Status is InvoiceStatus.New or InvoiceStatus.Processing or InvoiceStatus.Expired or InvoiceStatus.Invalid ||
|
||||
(Status != InvoiceStatus.Settled && ExceptionStatus == InvoiceExceptionStatus.Marked);
|
||||
Status: InvoiceStatus.New or InvoiceStatus.Processing or InvoiceStatus.Expired or InvoiceStatus.Invalid
|
||||
}
|
||||
or
|
||||
{
|
||||
Status: not InvoiceStatus.Settled,
|
||||
ExceptionStatus: InvoiceExceptionStatus.Marked
|
||||
};
|
||||
|
||||
public bool CanMarkInvalid()
|
||||
public bool CanMarkInvalid() => (Status, ExceptionStatus) is
|
||||
{
|
||||
return Status is InvoiceStatus.New or InvoiceStatus.Processing or InvoiceStatus.Expired ||
|
||||
(Status != InvoiceStatus.Invalid && ExceptionStatus == InvoiceExceptionStatus.Marked);
|
||||
Status: InvoiceStatus.New or InvoiceStatus.Processing or InvoiceStatus.Expired
|
||||
}
|
||||
or
|
||||
{
|
||||
Status: not InvoiceStatus.Invalid,
|
||||
ExceptionStatus: InvoiceExceptionStatus.Marked
|
||||
};
|
||||
|
||||
public bool CanRefund()
|
||||
public bool CanRefund() => (Status, ExceptionStatus) is
|
||||
{
|
||||
return
|
||||
Status == InvoiceStatus.Settled ||
|
||||
(Status == InvoiceStatus.Expired &&
|
||||
(ExceptionStatus == InvoiceExceptionStatus.PaidLate ||
|
||||
ExceptionStatus == InvoiceExceptionStatus.PaidOver ||
|
||||
ExceptionStatus == InvoiceExceptionStatus.PaidPartial)) ||
|
||||
Status == InvoiceStatus.Invalid;
|
||||
Status: InvoiceStatus.Settled or InvoiceStatus.Invalid
|
||||
}
|
||||
or
|
||||
{
|
||||
Status: InvoiceStatus.Expired,
|
||||
ExceptionStatus: InvoiceExceptionStatus.PaidLate or InvoiceExceptionStatus.PaidOver or InvoiceExceptionStatus.PaidPartial
|
||||
};
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.WindowsAzure.Storage;
|
||||
|
||||
namespace BTCPayServer.Storage.Services.Providers.AzureBlobStorage.Configuration
|
||||
{
|
||||
|
@ -10,7 +9,7 @@ namespace BTCPayServer.Storage.Services.Providers.AzureBlobStorage.Configuration
|
|||
{
|
||||
try
|
||||
{
|
||||
CloudStorageAccount.Parse(value as string);
|
||||
new Azure.Storage.Blobs.BlobClient(value as string, "unusedcontainer", "unusedblob");
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
|
@ -32,7 +32,8 @@
|
|||
<div class="sticky-header">
|
||||
<h2 text-translate="true">@ViewData["Title"]</h2>
|
||||
<div>
|
||||
<button id="page-primary" type="submit" class="btn btn-primary order-sm-1">Save</button>
|
||||
<button id="page-primary" type="submit" class="btn btn-primary order-sm-1">Save</button>
|
||||
<a class="btn btn-secondary" asp-action="ListInvoices" asp-controller="UIInvoice" asp-route-storeId="@Model.StoreId" asp-route-searchterm="@Model.SearchTerm">Invoices</a>
|
||||
@if (Model.Archived)
|
||||
{
|
||||
<button type="submit" class="btn btn-outline-secondary" name="Archived" value="False" text-translate="true">Unarchive</button>
|
||||
|
@ -345,7 +346,6 @@
|
|||
</form>
|
||||
|
||||
<div class="d-grid d-sm-flex flex-wrap gap-3 mt-3">
|
||||
<a class="btn btn-secondary" asp-action="ListInvoices" asp-controller="UIInvoice" asp-route-storeId="@Model.StoreId" asp-route-searchterm="@Model.SearchTerm">Invoices</a>
|
||||
<form method="post" asp-controller="UIApps" asp-action="ToggleArchive" asp-route-appId="@Model.AppId" permission="@Policies.CanModifyStoreSettings">
|
||||
<button type="submit" class="w-100 btn btn-outline-secondary" id="btn-archive-toggle">
|
||||
@if (Model.Archived)
|
||||
|
|
|
@ -31,7 +31,8 @@
|
|||
<div class="sticky-header">
|
||||
<h2 text-translate="true">@ViewData["Title"]</h2>
|
||||
<div>
|
||||
<button id="page-primary" type="submit" class="btn btn-primary order-sm-1">Save</button>
|
||||
<button id="page-primary" type="submit" class="btn btn-primary order-sm-1">Save</button>
|
||||
<a class="btn btn-secondary" asp-action="ListInvoices" asp-controller="UIInvoice" asp-route-storeId="@Model.StoreId" asp-route-searchterm="@Model.SearchTerm" text-translate="true">Invoices</a>
|
||||
@if (Model.Archived)
|
||||
{
|
||||
<button type="submit" class="btn btn-outline-secondary" name="Archived" value="False" text-translate="true">Unarchive</button>
|
||||
|
@ -293,7 +294,6 @@
|
|||
</form>
|
||||
|
||||
<div class="d-grid d-sm-flex flex-wrap gap-3 mt-3">
|
||||
<a class="btn btn-secondary" asp-action="ListInvoices" asp-controller="UIInvoice" asp-route-storeId="@Model.StoreId" asp-route-searchterm="@Model.SearchTerm" text-translate="true">Invoices</a>
|
||||
<form method="post" asp-controller="UIApps" asp-action="ToggleArchive" asp-route-appId="@Model.Id">
|
||||
<button type="submit" class="w-100 btn btn-outline-secondary" id="btn-archive-toggle" permission="@Policies.CanModifyStoreSettings">
|
||||
@if (Model.Archived)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
@using Newtonsoft.Json.Linq
|
||||
@model BTCPayServer.Fido2.Models.LoginWithFido2ViewModel
|
||||
|
||||
<div class="twoFaBox">
|
||||
|
@ -24,7 +25,7 @@
|
|||
<script>
|
||||
document.getElementById('btn-retry').addEventListener('click', () => window.location.reload())
|
||||
// send to server for registering
|
||||
window.makeAssertionOptions = @Safe.Json(Model.Data);
|
||||
window.makeAssertionOptions = @Safe.Json(JObject.Parse(Model.Data));
|
||||
</script>
|
||||
<script src="~/js/webauthn/helpers.js" asp-append-version="true"></script>
|
||||
<script src="~/js/webauthn/login.js" asp-append-version="true"></script>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
@using Newtonsoft.Json.Linq
|
||||
@model Fido2NetLib.CredentialCreateOptions
|
||||
@{
|
||||
ViewData.SetActivePage(ManageNavPages.TwoFactorAuthentication, StringLocalizer["Register your security device"]);
|
||||
|
@ -42,7 +43,7 @@
|
|||
<script>
|
||||
document.getElementById('btn-retry').addEventListener('click', function () { window.location.reload() });
|
||||
// send to server for registering
|
||||
window.makeCredentialOptions = @Json.Serialize(Model);
|
||||
window.makeCredentialOptions = @Json.Serialize(JToken.Parse(Model.ToJson()));
|
||||
</script>
|
||||
<script src="~/js/webauthn/helpers.js"></script>
|
||||
<script src="~/js/webauthn/register.js"></script>
|
||||
|
|
|
@ -37,11 +37,6 @@
|
|||
<input asp-for="Bucket" class="form-control" />
|
||||
<span asp-validation-for="Bucket" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Email" class="form-label"></label>
|
||||
<input asp-for="Email" class="form-control" />
|
||||
<span asp-validation-for="Email" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" name="command" value="Save" text-translate="true">Save</button>
|
||||
<a asp-action="Storage" asp-route-forceChoice="true" class="ms-2" text-translate="true">Change Storage provider</a>
|
||||
|
|
|
@ -33,16 +33,12 @@
|
|||
: ''
|
||||
});
|
||||
delegate('click', '#Presets_InStore', e => {
|
||||
$("#CheckoutV2Settings").addClass('show');
|
||||
$("#ClassicCheckoutSettings").removeClass('show');
|
||||
$("#CheckNFC").removeClass('d-none');
|
||||
$("#PlaySoundOnPayment").prop('checked', true);
|
||||
$("#ShowPayInWalletButton").prop('checked', false);
|
||||
$("#ShowStoreHeader").prop('checked', false);
|
||||
});
|
||||
delegate('click', '#Presets_Online', e => {
|
||||
$("#CheckoutV2Settings").addClass('show');
|
||||
$("#ClassicCheckoutSettings").removeClass('show');
|
||||
$("#CheckNFC").addClass('d-none');
|
||||
$("#PlaySoundOnPayment").prop('checked', false);
|
||||
$("#ShowPayInWalletButton").prop('checked', true);
|
||||
|
@ -135,7 +131,7 @@
|
|||
<div id="CheckNFC" class="form-group d-none">
|
||||
<button type="button" class="btn btn-outline-secondary" text-translate="true">Check if NFC is supported and enabled on this device</button>
|
||||
</div>
|
||||
<div class="checkout-settings collapse show" id="CheckoutV2Settings">
|
||||
<div class="checkout-settings">
|
||||
<div class="form-group">
|
||||
<label asp-for="DisplayExpirationTimer" class="form-label"></label>
|
||||
<div class="input-group">
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
@model WalletPSBTReadyViewModel
|
||||
|
||||
<script src="~/js/wallet/wallet-camera-scanner.js" asp-append-version="true"></script>
|
||||
<script src="~/js/wallet/WalletSend.js" asp-append-version="true"></script>
|
||||
@if (Model.CanCalculateBalance)
|
||||
{
|
||||
<p class="lead text-center text-secondary">
|
||||
|
|
|
@ -40,7 +40,7 @@ async function verifyAssertionWithServer(assertedCredential) {
|
|||
extensions: assertedCredential.getClientExtensionResults(),
|
||||
response: {
|
||||
authenticatorData: coerceToBase64Url(authData),
|
||||
clientDataJson: coerceToBase64Url(clientDataJSON),
|
||||
clientDataJSON: coerceToBase64Url(clientDataJSON),
|
||||
signature: coerceToBase64Url(sig)
|
||||
}
|
||||
};
|
||||
|
|
|
@ -49,8 +49,8 @@ async function registerNewCredential(newCredential) {
|
|||
type: newCredential.type,
|
||||
extensions: newCredential.getClientExtensionResults(),
|
||||
response: {
|
||||
AttestationObject: coerceToBase64Url(attestationObject),
|
||||
clientDataJson: coerceToBase64Url(clientDataJSON)
|
||||
attestationObject: coerceToBase64Url(attestationObject),
|
||||
clientDataJSON: coerceToBase64Url(clientDataJSON)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -47,13 +47,7 @@
|
|||
"description": "Revoke the API key of a target user so that it cannot be used anymore",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "idOrEmail",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The target user's id or email",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
"$ref": "#/components/parameters/UserIdOrEmail"
|
||||
},
|
||||
{
|
||||
"name": "apikey",
|
||||
|
@ -244,13 +238,7 @@
|
|||
"description": "Create a new API Key for a user",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "idOrEmail",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The target user's id or email",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
"$ref": "#/components/parameters/UserIdOrEmail"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
|
@ -42,6 +42,15 @@
|
|||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"UserIdOrEmail": {
|
||||
"name": "idOrEmail",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The user's id or email",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schemas": {
|
||||
|
|
|
@ -25,10 +25,10 @@
|
|||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden to view the specified store"
|
||||
"description": "If you are authenticated but forbidden to view the specified store's users"
|
||||
},
|
||||
"404": {
|
||||
"description": "The key is not found for this store"
|
||||
"description": "The store could not be found"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
|
@ -69,7 +69,7 @@
|
|||
"description": "The user was added"
|
||||
},
|
||||
"400": {
|
||||
"description": "A list of errors that occurred when creating the store",
|
||||
"description": "A list of errors that occurred when adding the store user",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
|
@ -79,7 +79,10 @@
|
|||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden to add new stores"
|
||||
"description": "If you are authenticated but forbidden to add new store users"
|
||||
},
|
||||
"404": {
|
||||
"description": "The store or user could not be found"
|
||||
},
|
||||
"409": {
|
||||
"description": "Error code: `duplicate-store-user-role`. Removing this user would result in the store having no owner.",
|
||||
|
@ -103,6 +106,63 @@
|
|||
}
|
||||
},
|
||||
"/api/v1/stores/{storeId}/users/{idOrEmail}": {
|
||||
"put": {
|
||||
"tags": [
|
||||
"Stores (Users)"
|
||||
],
|
||||
"summary": "Updates a store user",
|
||||
"description": "Updates a store user",
|
||||
"operationId": "Stores_UpdateStoreUser",
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/components/parameters/StoreId"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/UserIdOrEmail"
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"x-name": "request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/StoreUserData"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true,
|
||||
"x-position": 1
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The user was updated"
|
||||
},
|
||||
"400": {
|
||||
"description": "A list of errors that occurred when updating the store user",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ValidationProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden to update store users"
|
||||
},
|
||||
"404": {
|
||||
"description": "The store or user could not be found"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"API_Key": [
|
||||
"btcpay.store.canmodifystoresettings"
|
||||
],
|
||||
"Basic": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"tags": [
|
||||
"Stores (Users)"
|
||||
|
@ -115,13 +175,7 @@
|
|||
"$ref": "#/components/parameters/StoreId"
|
||||
},
|
||||
{
|
||||
"name": "idOrEmail",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The user's id or email",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
"$ref": "#/components/parameters/UserIdOrEmail"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
@ -129,7 +183,7 @@
|
|||
"description": "The user has been removed"
|
||||
},
|
||||
"400": {
|
||||
"description": "A list of errors that occurred when removing the store",
|
||||
"description": "A list of errors that occurred when removing the store user",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
|
@ -149,10 +203,10 @@
|
|||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden to remove the specified store"
|
||||
"description": "If you are authenticated but forbidden to remove the specified store user"
|
||||
},
|
||||
"404": {
|
||||
"description": "The key is not found for this store"
|
||||
"description": "The store or user could not be found"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
|
@ -186,8 +240,23 @@
|
|||
},
|
||||
"role": {
|
||||
"type": "string",
|
||||
"description": "The role of the user. Default roles are `Owner` and `Guest`",
|
||||
"description": "The role of the user. Default roles are `Owner`, `Manager`, `Employee` and `Guest`",
|
||||
"nullable": false
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "The email of the user",
|
||||
"nullable": true
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the user",
|
||||
"nullable": true
|
||||
},
|
||||
"imageUrl": {
|
||||
"type": "string",
|
||||
"description": "The profile picture URL of the user",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -330,13 +330,7 @@
|
|||
"description": "Get 1 user by ID or Email.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "idOrEmail",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The ID or email of the user to load",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
"$ref": "#/components/parameters/UserIdOrEmail"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
@ -378,13 +372,7 @@
|
|||
"description": "Delete a user.\n\nMust be an admin to perform this operation.\n\nAttempting to delete the only admin user will not succeed.\n\nAll data associated with the user will be deleted as well if the operation succeeds.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "idOrEmail",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The ID or email of the user to be deleted",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
"$ref": "#/components/parameters/UserIdOrEmail"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
@ -421,13 +409,7 @@
|
|||
"description": "Lock or unlock a user.\n\nMust be an admin to perform this operation.\n\nAttempting to lock the only admin user will not succeed.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "idOrEmail",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The ID of the user to be un/locked",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
"$ref": "#/components/parameters/UserIdOrEmail"
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
|
@ -474,13 +456,7 @@
|
|||
"description": "Approve or unapprove a user.\n\nMust be an admin to perform this operation.\n\nAttempting to (un)approve a user for which this requirement does not exist will not succeed.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "idOrEmail",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The ID of the user to be un/approved",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
"$ref": "#/components/parameters/UserIdOrEmail"
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
|
|
|
@ -12,8 +12,4 @@
|
|||
<Configurations>Debug;Release</Configurations>
|
||||
<Platforms>AnyCPU</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug' And '$(RazorCompileOnBuild)' != 'true' And '$(DotNetWatchBuild)' != 'true' And '$(DesignTimeBuild)' != 'true'">
|
||||
<RazorCompileOnBuild>false</RazorCompileOnBuild>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0.203-bookworm-slim AS builder
|
||||
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0.404-bookworm-slim AS builder
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
WORKDIR /source
|
||||
COPY nuget.config nuget.config
|
||||
|
@ -21,7 +21,7 @@ ARG CONFIGURATION_NAME=Release
|
|||
ARG GIT_COMMIT
|
||||
RUN cd BTCPayServer && dotnet publish -p:GitCommit=${GIT_COMMIT} --output /app/ --configuration ${CONFIGURATION_NAME}
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0.3-bookworm-slim
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0.11-bookworm-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends iproute2 openssh-client \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
|
Loading…
Add table
Reference in a new issue