Compare commits
145 Commits
v2.0.2
...
v2.0.5-pay
Author | SHA1 | Date | |
---|---|---|---|
54dd1df55f | |||
6ae36825d5 | |||
f1719ed3d2 | |||
79c5ff9ed0 | |||
76a880a30e | |||
08835895e9 | |||
4ee12b41b1 | |||
4fb43cbbad | |||
adce1dffb1 | |||
0f049eee1b | |||
cfc2b9c236 | |||
637c06fc01 | |||
1ef177ba0f | |||
8acf1c2d62 | |||
44dc6499cd | |||
f5a420a272 | |||
d24e0cd1a2 | |||
b3bc11c19d | |||
fe3bccf3ce | |||
7829a93251 | |||
d4b76823a2 | |||
00cc16455c | |||
6e222c573b | |||
6f9e0dca51 | |||
d2c3c37f4d | |||
b797cc9af8 | |||
cc915df10e | |||
fe53424710 | |||
6cfd4cb633 | |||
4d38f91bd5 | |||
4f63f08aeb | |||
e7e7fab1a9 | |||
1214367503 | |||
d24adda700 | |||
eb89f4636b | |||
7da247ffd6 | |||
cadcf27ebc | |||
61b882d426 | |||
e4d780417d | |||
4d01e3a16a | |||
52a1627d81 | |||
d5b9d91a75 | |||
cd507b10e0 | |||
f7a96272c1 | |||
a8d1f55544 | |||
fb8ca19327 | |||
341bea48f8 | |||
aa336e7d8a | |||
77f9b6a88c | |||
3b94c2798c | |||
70f97382a4 | |||
e7b98fbae3 | |||
ad3a635f59 | |||
eea9ba10fd | |||
f2dc033067 | |||
bf37ee9cf6 | |||
9b4fe5930e | |||
a4e950343a | |||
af54e7ebc2 | |||
8edb4eae3b | |||
694b8e111c | |||
cccaf0e72c | |||
7d65817acd | |||
43f159cd81 | |||
6e2f355aa6 | |||
e2f868df52 | |||
898f0f4481 | |||
615b7842db | |||
2bc6499f1b | |||
9175af4abe | |||
3046fe0f0b | |||
6822608c14 | |||
177ddb4117 | |||
3b876413c1 | |||
6397c8e07c | |||
9fce9fcb7b | |||
0eec3eefd6 | |||
83ce5a2107 | |||
4ea6a03b4b | |||
bdf12aab0f | |||
299527fe16 | |||
e14c086fed | |||
fbf707cde2 | |||
45a6bc8ae2 | |||
71c578f866 | |||
5cf3819743 | |||
d3315c2fa6 | |||
f7f3244b3d | |||
d6211327db | |||
76f87011a2 | |||
c6fc0302d2 | |||
843c813f86 | |||
57742ad871 | |||
7db510b5ca | |||
1490fbb721 | |||
dd4400f8dd | |||
4279ee1962 | |||
558f45a48c | |||
6a76167637 | |||
f56ad3408c | |||
8e688b7f28 | |||
6d737ce119 | |||
2075e16767 | |||
2c4b1798ad | |||
b040f78f70 | |||
c221cb7ccb | |||
8a51e21310 | |||
31b73f0d82 | |||
777748d79e | |||
d0f97e85d2 | |||
2eff7523c3 | |||
12d4803c5c | |||
fdbee350b8 | |||
78f47516b8 | |||
088c0c7f85 | |||
809e475e29 | |||
8b776ed9e5 | |||
984475b9d0 | |||
894f68f2ae | |||
796d8d4406 | |||
7ac6cad18c | |||
b3abc8f161 | |||
e80296fa85 | |||
d0779b88e0 | |||
acd7765159 | |||
0a51031334 | |||
0f7c0341c5 | |||
d40669c7bd | |||
fe8360e870 | |||
72fd2ec424 | |||
7e7d4086cd | |||
f5d26a1555 | |||
540fb6c9f6 | |||
12681eda36 | |||
a05dbef337 | |||
a8581510e5 | |||
bbba7551b7 | |||
a129114603 | |||
392ec623c0 | |||
641bdcff31 | |||
b3945d758a | |||
4272ea97db | |||
5e438b84e1 | |||
ff79a31066 | |||
225264a283 |
@ -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: |
|
||||
@ -47,7 +47,6 @@ jobs:
|
||||
docker buildx create --use
|
||||
DOCKER_BUILDX_OPTS="--platform linux/amd64,linux/arm64,linux/arm/v7 --build-arg GIT_COMMIT=${GIT_COMMIT} --push"
|
||||
docker buildx build $DOCKER_BUILDX_OPTS -t $DOCKERHUB_REPO:$LATEST_TAG .
|
||||
docker buildx build $DOCKER_BUILDX_OPTS -t $DOCKERHUB_REPO:$LATEST_TAG-altcoins --build-arg CONFIGURATION_NAME=Altcoins-Release .
|
||||
workflows:
|
||||
version: 2
|
||||
build_and_test:
|
||||
|
@ -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" />
|
||||
|
@ -106,8 +106,8 @@ public static class HttpRequestExtensions
|
||||
|
||||
/// <summary>
|
||||
/// Will return an absolute URL.
|
||||
/// If `relativeOrAsbolute` is absolute, returns it.
|
||||
/// If `relativeOrAsbolute` is relative, send absolute url based on the HOST of this request (without PathBase)
|
||||
/// If `relativeOrAbsolute` is absolute, returns it.
|
||||
/// If `relativeOrAbsolute` is relative, send absolute url based on the HOST of this request (without PathBase)
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="relativeOrAbsolte"></param>
|
||||
|
139
BTCPayServer.Client/App/IBTCPayAppHubClient.cs
Normal file
139
BTCPayServer.Client/App/IBTCPayAppHubClient.cs
Normal file
@ -0,0 +1,139 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Lightning;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Client.App;
|
||||
|
||||
//methods available on the hub in the client
|
||||
public interface IBTCPayAppHubClient
|
||||
{
|
||||
Task NotifyServerEvent(ServerEvent ev);
|
||||
Task NotifyNetwork(string network);
|
||||
Task NotifyServerNode(string nodeInfo);
|
||||
Task TransactionDetected(TransactionDetectedRequest request);
|
||||
Task NewBlock(string block);
|
||||
Task StartListen(string key);
|
||||
|
||||
Task<LightningInvoice> CreateInvoice(string key, CreateLightningInvoiceRequest createLightningInvoiceRequest);
|
||||
Task<LightningInvoice?> GetLightningInvoice(string key, uint256 paymentHash);
|
||||
Task<LightningPayment?> GetLightningPayment(string key, uint256 paymentHash);
|
||||
Task CancelInvoice(string key, uint256 paymentHash);
|
||||
Task<List<LightningPayment>> GetLightningPayments(string key, ListPaymentsParams request);
|
||||
Task<List<LightningInvoice>> GetLightningInvoices(string key, ListInvoicesParams request);
|
||||
Task<PayResponse> PayInvoice(string key, string bolt11, long? amountMilliSatoshi);
|
||||
Task MasterUpdated(long? deviceIdentifier);
|
||||
Task<LightningNodeInformation> GetLightningNodeInfo(string key);
|
||||
Task<LightningNodeBalance> GetLightningBalance(string key);
|
||||
}
|
||||
|
||||
//methods available on the hub in the server
|
||||
public interface IBTCPayAppHubServer
|
||||
{
|
||||
Task<bool> DeviceMasterSignal(long deviceIdentifier, bool active);
|
||||
|
||||
Task<Dictionary<string,string>> Pair(PairRequest request);
|
||||
Task<AppHandshakeResponse> Handshake(AppHandshake request);
|
||||
Task<bool> BroadcastTransaction(string tx);
|
||||
Task<decimal> GetFeeRate(int blockTarget);
|
||||
Task<BestBlockResponse> GetBestBlock();
|
||||
Task<TxInfoResponse> FetchTxsAndTheirBlockHeads(string identifier, string[] txIds, string[] outpoints);
|
||||
Task<string> DeriveScript(string identifier);
|
||||
Task TrackScripts(string identifier, string[] scripts);
|
||||
Task<string> UpdatePsbt(string[] identifiers, string psbt);
|
||||
Task<CoinResponse[]> GetUTXOs(string[] identifiers);
|
||||
Task<Dictionary<string, TxResp[]>> GetTransactions(string[] identifiers);
|
||||
Task SendInvoiceUpdate(LightningInvoice lightningInvoice);
|
||||
Task<long?> GetCurrentMaster();
|
||||
}
|
||||
|
||||
public class ServerEvent
|
||||
{
|
||||
public string? Type { get; set; }
|
||||
public string? StoreId { get; set; }
|
||||
public string? UserId { get; set; }
|
||||
public string? AppId { get; set; }
|
||||
public string? InvoiceId { get; set; }
|
||||
public string? Detail { get; set; }
|
||||
}
|
||||
|
||||
public record TxResp
|
||||
{
|
||||
public TxResp(long confirmations, long? height, decimal balanceChange, DateTimeOffset timestamp, string transactionId)
|
||||
{
|
||||
Confirmations = confirmations;
|
||||
Height = height;
|
||||
BalanceChange = balanceChange;
|
||||
Timestamp = timestamp;
|
||||
TransactionId = transactionId;
|
||||
}
|
||||
|
||||
public long Confirmations { get; set; }
|
||||
public long? Height { get; set; }
|
||||
public decimal BalanceChange { get; set; }
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
public string TransactionId { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{{ Confirmations = {Confirmations}, Height = {Height}, BalanceChange = {BalanceChange}, Timestamp = {Timestamp}, TransactionId = {TransactionId} }}";
|
||||
}
|
||||
}
|
||||
|
||||
public class TransactionDetectedRequest
|
||||
{
|
||||
public string? Identifier { get; set; }
|
||||
public string? TxId { get; set; }
|
||||
public string[]? SpentScripts { get; set; }
|
||||
public string[]? ReceivedScripts { get; set; }
|
||||
public bool Confirmed { get; set; }
|
||||
}
|
||||
|
||||
public class CoinResponse
|
||||
{
|
||||
public string? Identifier{ get; set; }
|
||||
public bool Confirmed { get; set; }
|
||||
public string? Script { get; set; }
|
||||
public string? Outpoint { get; set; }
|
||||
public decimal Value { get; set; }
|
||||
public string? Path { get; set; }
|
||||
}
|
||||
|
||||
public class TxInfoResponse
|
||||
{
|
||||
public Dictionary<string,TransactionResponse>? Txs { get; set; }
|
||||
public Dictionary<string,string>? BlockHeaders { get; set; }
|
||||
public Dictionary<string,int>? BlockHeghts { get; set; }
|
||||
}
|
||||
|
||||
public class TransactionResponse
|
||||
{
|
||||
public string? BlockHash { get; set; }
|
||||
public string? Transaction { get; set; }
|
||||
}
|
||||
|
||||
public class BestBlockResponse
|
||||
{
|
||||
public string? BlockHash { get; set; }
|
||||
public int BlockHeight { get; set; }
|
||||
public string? BlockHeader { get; set; }
|
||||
}
|
||||
|
||||
public class AppHandshake
|
||||
{
|
||||
public string[]? Identifiers { get; set; }
|
||||
}
|
||||
|
||||
public class AppHandshakeResponse
|
||||
{
|
||||
//response about identifiers being tracked successfully
|
||||
public string[]? IdentifiersAcknowledged { get; set; }
|
||||
}
|
||||
|
||||
public class PairRequest
|
||||
{
|
||||
public Dictionary<string, string?> Derivations { get; set; } = new();
|
||||
}
|
8
BTCPayServer.Client/App/Models/AcceptInviteRequest.cs
Normal file
8
BTCPayServer.Client/App/Models/AcceptInviteRequest.cs
Normal file
@ -0,0 +1,8 @@
|
||||
#nullable enable
|
||||
namespace BTCPayServer.Client.App.Models;
|
||||
|
||||
public class AcceptInviteRequest
|
||||
{
|
||||
public string? UserId { get; set; }
|
||||
public string? Code { get; set; }
|
||||
}
|
10
BTCPayServer.Client/App/Models/AcceptInviteResult.cs
Normal file
10
BTCPayServer.Client/App/Models/AcceptInviteResult.cs
Normal file
@ -0,0 +1,10 @@
|
||||
#nullable enable
|
||||
namespace BTCPayServer.Client.App.Models;
|
||||
|
||||
public class AcceptInviteResult
|
||||
{
|
||||
public string? Email { get; set; }
|
||||
public bool? RequiresUserApproval { get; set; }
|
||||
public bool? EmailHasBeenConfirmed { get; set; }
|
||||
public string? PasswordSetCode { get; set; }
|
||||
}
|
10
BTCPayServer.Client/App/Models/AccessTokenResult.cs
Normal file
10
BTCPayServer.Client/App/Models/AccessTokenResult.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer.Client.App.Models;
|
||||
|
||||
public class AccessTokenResult
|
||||
{
|
||||
public string AccessToken { get; set; }
|
||||
public string RefreshToken { get; set; }
|
||||
public DateTimeOffset Expiry { get; set; }
|
||||
}
|
13
BTCPayServer.Client/App/Models/AppInstanceInfo.cs
Normal file
13
BTCPayServer.Client/App/Models/AppInstanceInfo.cs
Normal file
@ -0,0 +1,13 @@
|
||||
#nullable enable
|
||||
namespace BTCPayServer.Client.App.Models;
|
||||
|
||||
public class AppInstanceInfo
|
||||
{
|
||||
public string BaseUrl { get; set; } = null!;
|
||||
public string ServerName { get; set; } = null!;
|
||||
public string? ContactUrl { get; set; }
|
||||
public string? LogoUrl { get; set; }
|
||||
public string? CustomThemeCssUrl { get; set; }
|
||||
public string? CustomThemeExtension { get; set; }
|
||||
public bool RegistrationEnabled { get; set; }
|
||||
}
|
44
BTCPayServer.Client/App/Models/AppUserInfo.cs
Normal file
44
BTCPayServer.Client/App/Models/AppUserInfo.cs
Normal file
@ -0,0 +1,44 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Client.App.Models;
|
||||
|
||||
public class AppUserInfo
|
||||
{
|
||||
public string? UserId { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? ImageUrl { get; set; }
|
||||
public IEnumerable<string>? Roles { get; set; }
|
||||
public IEnumerable<AppUserStoreInfo>? Stores { get; set; }
|
||||
|
||||
public void SetInfo(string email, string? name, string? imageUrl)
|
||||
{
|
||||
Email = email;
|
||||
Name = name;
|
||||
ImageUrl = imageUrl;
|
||||
}
|
||||
|
||||
public static bool Equals(AppUserInfo? x, AppUserInfo? y)
|
||||
{
|
||||
if (ReferenceEquals(x, y)) return true;
|
||||
if (ReferenceEquals(x, null)) return false;
|
||||
if (ReferenceEquals(y, null)) return false;
|
||||
if (x.GetType() != y.GetType()) return false;
|
||||
return x.UserId == y.UserId && x.Email == y.Email &&
|
||||
x.Name == y.Name && x.ImageUrl == y.ImageUrl &&
|
||||
Equals(x.Roles, y.Roles) && Equals(x.Stores, y.Stores);
|
||||
}
|
||||
}
|
||||
|
||||
public class AppUserStoreInfo
|
||||
{
|
||||
public string Id { get; set; } = null!;
|
||||
public string? Name { get; set; }
|
||||
public string? LogoUrl { get; set; }
|
||||
public string? RoleId { get; set; }
|
||||
public string? PosAppId { get; set; }
|
||||
public string? DefaultCurrency { get; set; }
|
||||
public bool Archived { get; set; }
|
||||
public IEnumerable<string>? Permissions { get; set; }
|
||||
}
|
11
BTCPayServer.Client/App/Models/CreateStoreData.cs
Normal file
11
BTCPayServer.Client/App/Models/CreateStoreData.cs
Normal file
@ -0,0 +1,11 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Client.App.Models;
|
||||
|
||||
public class CreateStoreData
|
||||
{
|
||||
public string? DefaultCurrency { get; set; }
|
||||
public string? RecommendedExchangeId { get; set; }
|
||||
public Dictionary<string, string>? Exchanges { get; set; }
|
||||
}
|
@ -16,7 +16,7 @@
|
||||
<Platforms>AnyCPU</Platforms>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Version Condition=" '$(Version)' == '' ">2.0.0</Version>
|
||||
<Version Condition=" '$(Version)' == '' ">2.0.1</Version>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
@ -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.37" />
|
||||
<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>
|
||||
|
@ -78,4 +78,14 @@ public partial class BTCPayServerClient
|
||||
if (appId == null) throw new ArgumentNullException(nameof(appId));
|
||||
await SendHttpRequest($"api/v1/apps/{appId}", null, HttpMethod.Delete, token);
|
||||
}
|
||||
|
||||
public virtual async Task<FileData> UploadAppItemImage(string appId, string filePath, string mimeType, CancellationToken token = default)
|
||||
{
|
||||
return await UploadFileRequest<FileData>($"api/v1/apps/{appId}/image", filePath, mimeType, "file", HttpMethod.Post, token);
|
||||
}
|
||||
|
||||
public virtual async Task DeleteAppItemImage(string appId, string fileId, CancellationToken token = default)
|
||||
{
|
||||
await SendHttpRequest($"api/v1/apps/{appId}/image/{fileId}", null, HttpMethod.Delete, token);
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,13 @@ public partial class BTCPayServerClient
|
||||
return await SendHttpRequest<LightningNodeBalanceData>($"api/v1/server/lightning/{cryptoCode}/balance", null, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task<HistogramData> GetLightningNodeHistogram(string cryptoCode, HistogramType? type = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var queryPayload = type == null ? null : new Dictionary<string, object> { { "type", type.ToString() } };
|
||||
return await SendHttpRequest<HistogramData>($"api/v1/server/lightning/{cryptoCode}/histogram", queryPayload, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task ConnectToLightningNode(string cryptoCode, ConnectToNodeRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
|
@ -21,6 +21,13 @@ public partial class BTCPayServerClient
|
||||
return await SendHttpRequest<LightningNodeBalanceData>($"api/v1/stores/{storeId}/lightning/{cryptoCode}/balance", null, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task<HistogramData> GetLightningNodeHistogram(string storeId, string cryptoCode, HistogramType? type = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var queryPayload = type == null ? null : new Dictionary<string, object> { { "type", type.ToString() } };
|
||||
return await SendHttpRequest<HistogramData>($"api/v1/stores/{storeId}/lightning/{cryptoCode}/histogram", queryPayload, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task ConnectToLightningNode(string storeId, string cryptoCode, ConnectToNodeRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
|
@ -16,6 +16,14 @@ public partial class BTCPayServerClient
|
||||
{
|
||||
return await SendHttpRequest<OnChainWalletOverviewData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet", null, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task<HistogramData> GetOnChainWalletHistogram(string storeId, string cryptoCode, HistogramType? type = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var queryPayload = type == null ? null : new Dictionary<string, object> { { "type", type.ToString() } };
|
||||
return await SendHttpRequest<HistogramData>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/histogram", queryPayload, HttpMethod.Get, token);
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainWalletFeeRateData> GetOnChainFeeRate(string storeId, string cryptoCode, int? blockTarget = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ public partial class BTCPayServerClient
|
||||
protected virtual async Task<T> UploadFileRequest<T>(string apiPath, string filePath, string mimeType, string formFieldName, HttpMethod method = null, CancellationToken token = default)
|
||||
{
|
||||
using MultipartFormDataContent multipartContent = new();
|
||||
var fileContent = new StreamContent(File.OpenRead(filePath));
|
||||
using var fileContent = new StreamContent(File.OpenRead(filePath));
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse(mimeType);
|
||||
multipartContent.Add(fileContent, formFieldName, fileName);
|
||||
|
13
BTCPayServer.Client/Models/AppCartItem.cs
Normal file
13
BTCPayServer.Client/Models/AppCartItem.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class AppCartItem
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public int Count { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Price { get; set; }
|
||||
}
|
35
BTCPayServer.Client/Models/AppItem.cs
Normal file
35
BTCPayServer.Client/Models/AppItem.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public enum AppItemPriceType
|
||||
{
|
||||
Fixed,
|
||||
Topup,
|
||||
Minimum
|
||||
}
|
||||
|
||||
public class AppItem
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public bool Disabled { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string[] Categories { get; set; }
|
||||
public string Image { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public AppItemPriceType PriceType { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal? Price { get; set; }
|
||||
public string BuyButtonText { get; set; }
|
||||
public int? Inventory { get; set; }
|
||||
|
||||
[JsonExtensionData]
|
||||
public Dictionary<string, JToken> AdditionalData { get; set; }
|
||||
}
|
25
BTCPayServer.Client/Models/CreatePosInvoiceRequest.cs
Normal file
25
BTCPayServer.Client/Models/CreatePosInvoiceRequest.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class CreatePosInvoiceRequest
|
||||
{
|
||||
public string AppId { get; set; }
|
||||
public List<AppCartItem> Cart { get; set; }
|
||||
[JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))]
|
||||
public List<decimal> Amounts { get; set; }
|
||||
public int? DiscountPercent { get; set; }
|
||||
public int? TipPercent { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal? DiscountAmount { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal? Tip { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal? Subtotal { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal? Total { get; set; }
|
||||
public string PosData { get; set; }
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ public abstract class CrowdfundBaseData : AppBaseData
|
||||
|
||||
public class CrowdfundAppData : CrowdfundBaseData
|
||||
{
|
||||
public object? Perks { get; set; }
|
||||
public AppItem[]? Perks { get; set; }
|
||||
}
|
||||
|
||||
public class CrowdfundAppRequest : CrowdfundBaseData, IAppRequest
|
||||
|
30
BTCPayServer.Client/Models/HistogramData.cs
Normal file
30
BTCPayServer.Client/Models/HistogramData.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public enum HistogramType
|
||||
{
|
||||
Week,
|
||||
Month,
|
||||
YTD,
|
||||
Year,
|
||||
TwoYears,
|
||||
Day
|
||||
}
|
||||
|
||||
public class HistogramData
|
||||
{
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public HistogramType Type { get; set; }
|
||||
[JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))]
|
||||
public List<decimal> Series { get; set; }
|
||||
[JsonProperty(ItemConverterType = typeof(DateTimeToUnixTimeConverter))]
|
||||
public List<DateTimeOffset> Labels { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Balance { get; set; }
|
||||
}
|
@ -11,7 +11,7 @@ namespace BTCPayServer.Client.Models
|
||||
public string Body { get; set; }
|
||||
public string StoreId { get; set; }
|
||||
public bool Seen { get; set; }
|
||||
public Uri Link { get; set; }
|
||||
public string Link { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset CreatedTime { get; set; }
|
||||
|
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
@ -11,17 +10,17 @@ namespace BTCPayServer.Client.Models
|
||||
static PermissionMetadata()
|
||||
{
|
||||
Dictionary<string, PermissionMetadata> nodes = new Dictionary<string, PermissionMetadata>();
|
||||
foreach (var policy in Client.Policies.AllPolicies)
|
||||
foreach (var policy in Policies.AllPolicies)
|
||||
{
|
||||
nodes.Add(policy, new PermissionMetadata() { PermissionName = policy });
|
||||
}
|
||||
foreach (var n in nodes)
|
||||
{
|
||||
foreach (var policy in Client.Policies.AllPolicies)
|
||||
foreach (var policy in Policies.AllPolicies)
|
||||
{
|
||||
if (policy.Equals(n.Key, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
if (Client.Permission.Create(n.Key).Contains(Client.Permission.Create(policy)))
|
||||
if (Permission.Create(n.Key).Contains(Permission.Create(policy)))
|
||||
n.Value.SubPermissions.Add(policy);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
@ -31,7 +29,7 @@ public abstract class PointOfSaleBaseData : AppBaseData
|
||||
|
||||
public class PointOfSaleAppData : PointOfSaleBaseData
|
||||
{
|
||||
public object? Items { get; set; }
|
||||
public AppItem[]? Items { get; set; }
|
||||
}
|
||||
|
||||
public class PointOfSaleAppRequest : PointOfSaleBaseData, IAppRequest
|
||||
|
@ -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
|
||||
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
|
50
BTCPayServer.Client/PosDataParser.cs
Normal file
50
BTCPayServer.Client/PosDataParser.cs
Normal file
@ -0,0 +1,50 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client;
|
||||
|
||||
public static class PosDataParser
|
||||
{
|
||||
public static Dictionary<string, object> ParsePosData(JToken? posData)
|
||||
{
|
||||
var result = new Dictionary<string, object>();
|
||||
if (posData is JObject jobj)
|
||||
{
|
||||
foreach (var item in jobj)
|
||||
{
|
||||
ParsePosDataItem(item, ref result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void ParsePosDataItem(KeyValuePair<string, JToken?> item, ref Dictionary<string, object> result)
|
||||
{
|
||||
switch (item.Value?.Type)
|
||||
{
|
||||
case JTokenType.Array:
|
||||
var items = item.Value.AsEnumerable().ToList();
|
||||
var arrayResult = new List<object>();
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
arrayResult.Add(items[i] is JObject
|
||||
? ParsePosData(items[i])
|
||||
: items[i].ToString());
|
||||
}
|
||||
|
||||
result.TryAdd(item.Key, arrayResult);
|
||||
|
||||
break;
|
||||
case JTokenType.Object:
|
||||
result.TryAdd(item.Key, ParsePosData(item.Value));
|
||||
break;
|
||||
case null:
|
||||
break;
|
||||
default:
|
||||
result.TryAdd(item.Key, item.Value.ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -4,8 +4,6 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Common;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using NBXplorer.Models;
|
||||
|
@ -3,13 +3,9 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Logging;
|
||||
using Microsoft.AspNetCore.DataProtection.KeyManagement;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using StandardConfiguration;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
|
@ -1,13 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="4.3.1" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="4.3.6" />
|
||||
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Altcoins\" />
|
||||
</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();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -4,7 +4,6 @@ using System.Linq;
|
||||
using BTCPayServer.Logging;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Bson;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
|
@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
@ -61,6 +63,7 @@ namespace BTCPayServer.Data
|
||||
public DbSet<LightningAddressData> LightningAddresses { get; set; }
|
||||
public DbSet<PayoutProcessorData> PayoutProcessors { get; set; }
|
||||
public DbSet<FormData> Forms { get; set; }
|
||||
public DbSet<PendingTransaction> PendingTransactions { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
@ -106,7 +109,7 @@ namespace BTCPayServer.Data
|
||||
WebhookData.OnModelCreating(builder, Database);
|
||||
FormData.OnModelCreating(builder, Database);
|
||||
StoreRole.OnModelCreating(builder, Database);
|
||||
PendingTransaction.OnModelCreating(builder, Database);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,12 +3,12 @@
|
||||
<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="NBitcoin.Altcoins" Version="3.0.24" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.11" />
|
||||
<PackageReference Include="NBitcoin.Altcoins" Version="3.0.31" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
|
||||
|
63
BTCPayServer.Data/Data/PendingTransaction.cs
Normal file
63
BTCPayServer.Data/Data/PendingTransaction.cs
Normal file
@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
namespace BTCPayServer.Data;
|
||||
|
||||
public class PendingTransaction: IHasBlob<PendingTransactionBlob>
|
||||
{
|
||||
public string TransactionId { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string StoreId { get; set; }
|
||||
public StoreData Store { get; set; }
|
||||
public DateTimeOffset? Expiry { get; set; }
|
||||
public PendingTransactionState State { get; set; }
|
||||
public string[] OutpointsUsed { get; set; }
|
||||
|
||||
[NotMapped][Obsolete("Use Blob2 instead")]
|
||||
public byte[] Blob { get; set; }
|
||||
|
||||
public string Blob2 { get; set; }
|
||||
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
|
||||
{
|
||||
builder.Entity<PendingTransaction>()
|
||||
.HasOne(o => o.Store)
|
||||
.WithMany(i => i.PendingTransactions)
|
||||
.HasForeignKey(i => i.StoreId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<PendingTransaction>().HasKey(transaction => new {transaction.CryptoCode, transaction.TransactionId});
|
||||
|
||||
builder.Entity<PendingTransaction>()
|
||||
.Property(o => o.Blob2)
|
||||
.HasColumnType("JSONB");
|
||||
builder.Entity<PendingTransaction>()
|
||||
.Property(o => o.OutpointsUsed)
|
||||
.HasColumnType("text[]");
|
||||
}
|
||||
}
|
||||
public enum PendingTransactionState
|
||||
{
|
||||
Pending,
|
||||
Cancelled,
|
||||
Expired,
|
||||
Invalidated,
|
||||
Signed,
|
||||
Broadcast
|
||||
}
|
||||
|
||||
public class PendingTransactionBlob
|
||||
{
|
||||
public string PSBT { get; set; }
|
||||
public List<CollectedSignature> CollectedSignatures { get; set; } = new();
|
||||
}
|
||||
|
||||
public class CollectedSignature
|
||||
{
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
public string ReceivedPSBT { get; set; }
|
||||
}
|
@ -49,6 +49,7 @@ namespace BTCPayServer.Data
|
||||
public IEnumerable<FormData> Forms { get; set; }
|
||||
public IEnumerable<StoreRole> StoreRoles { get; set; }
|
||||
public bool Archived { get; set; }
|
||||
public IEnumerable<PendingTransaction> PendingTransactions { get; set; }
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
|
||||
{
|
||||
|
@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20241029163147_AddingPendingTransactionsTable")]
|
||||
public partial class AddingPendingTransactionsTable : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PendingTransactions",
|
||||
columns: table => new
|
||||
{
|
||||
TransactionId = table.Column<string>(type: "text", nullable: false),
|
||||
CryptoCode = table.Column<string>(type: "text", nullable: false),
|
||||
StoreId = table.Column<string>(type: "text", nullable: true),
|
||||
Expiry = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
State = table.Column<int>(type: "integer", nullable: false),
|
||||
OutpointsUsed = table.Column<string[]>(type: "text[]", nullable: true),
|
||||
Blob2 = table.Column<string>(type: "JSONB", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PendingTransactions", x => new { x.CryptoCode, x.TransactionId });
|
||||
table.ForeignKey(
|
||||
name: "FK_PendingTransactions_Stores_StoreId",
|
||||
column: x => x.StoreId,
|
||||
principalTable: "Stores",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PendingTransactions_StoreId",
|
||||
table: "PendingTransactions",
|
||||
column: "StoreId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "PendingTransactions");
|
||||
}
|
||||
}
|
||||
}
|
@ -637,6 +637,36 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("PayoutProcessors");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PendingTransaction", b =>
|
||||
{
|
||||
b.Property<string>("CryptoCode")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("TransactionId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Blob2")
|
||||
.HasColumnType("JSONB");
|
||||
|
||||
b.Property<DateTimeOffset?>("Expiry")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string[]>("OutpointsUsed")
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<int>("State")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("StoreId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("CryptoCode", "TransactionId");
|
||||
|
||||
b.HasIndex("StoreId");
|
||||
|
||||
b.ToTable("PendingTransactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PlannedTransaction", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -1324,6 +1354,16 @@ namespace BTCPayServer.Migrations
|
||||
b.Navigation("Store");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PendingTransaction", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "Store")
|
||||
.WithMany("PendingTransactions")
|
||||
.HasForeignKey("StoreId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("Store");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PullPaymentData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
@ -1582,6 +1622,8 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.Navigation("Payouts");
|
||||
|
||||
b.Navigation("PendingTransactions");
|
||||
|
||||
b.Navigation("PullPayments");
|
||||
|
||||
b.Navigation("Settings");
|
||||
|
@ -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.37" />
|
||||
<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>
|
||||
|
38
BTCPayServer.Rating/Providers/BareBitcoinRateProvider.cs
Normal file
38
BTCPayServer.Rating/Providers/BareBitcoinRateProvider.cs
Normal file
@ -0,0 +1,38 @@
|
||||
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/v1/price/nok");
|
||||
|
||||
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 bid/ask prices from JSON response
|
||||
var bid = (decimal)jobj["bid"];
|
||||
var ask = (decimal)jobj["ask"];
|
||||
|
||||
// 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)) };
|
||||
}
|
||||
}
|
||||
}
|
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)) };
|
||||
}
|
||||
}
|
||||
}
|
@ -2,11 +2,11 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
@ -25,7 +25,7 @@ using Newtonsoft.Json.Linq;
|
||||
using OpenQA.Selenium;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
using PosViewType = BTCPayServer.Plugins.PointOfSale.PosViewType;
|
||||
using WalletSettingsViewModel = BTCPayServer.Models.StoreViewModels.WalletSettingsViewModel;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
@ -94,16 +94,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);
|
||||
@ -125,7 +125,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
|
||||
@ -175,7 +175,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);
|
||||
@ -207,7 +207,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>
|
||||
@ -220,7 +220,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
},
|
||||
FeeSatoshiPerByte = 1
|
||||
}, default).GetAwaiter().GetResult();
|
||||
}, default);
|
||||
|
||||
Assert.NotNull(psbt);
|
||||
|
||||
@ -441,136 +441,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]
|
||||
@ -745,7 +744,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
|
||||
@ -759,39 +758,6 @@ noninventoryitem:
|
||||
AppService.Parse(vmpos.Template).Single(item => item.Id == "inventoryitem").Inventory);
|
||||
}, 10000);
|
||||
|
||||
//test payment methods option
|
||||
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
vmpos.Title = "hello";
|
||||
vmpos.Currency = "BTC";
|
||||
vmpos.Template = @"
|
||||
btconly:
|
||||
price: 1.0
|
||||
title: good apple
|
||||
payment_methods:
|
||||
- BTC
|
||||
normal:
|
||||
price: 1.0";
|
||||
vmpos.Template = AppService.SerializeTemplate(MigrationStartupTask.ParsePOSYML(vmpos.Template));
|
||||
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "btconly").Result);
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "normal").Result);
|
||||
invoices = user.BitPay.GetInvoices();
|
||||
var normalInvoice = invoices.Single(invoice => invoice.ItemCode == "normal");
|
||||
var btcOnlyInvoice = invoices.Single(invoice => invoice.ItemCode == "btconly");
|
||||
Assert.Single(btcOnlyInvoice.CryptoInfo);
|
||||
Assert.Equal("BTC",
|
||||
btcOnlyInvoice.CryptoInfo.First().CryptoCode);
|
||||
Assert.Equal("BTC-CHAIN",
|
||||
btcOnlyInvoice.CryptoInfo.First().PaymentType);
|
||||
|
||||
Assert.Equal(2, normalInvoice.CryptoInfo.Length);
|
||||
Assert.Contains(
|
||||
normalInvoice.CryptoInfo,
|
||||
s => "BTC-CHAIN" == s.PaymentType && new[] { "BTC", "LTC" }.Contains(
|
||||
s.CryptoCode));
|
||||
|
||||
//test topup option
|
||||
vmpos.Template = @"
|
||||
a:
|
||||
@ -821,13 +787,13 @@ g:
|
||||
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
Assert.DoesNotContain("custom", vmpos.Template);
|
||||
var items = AppService.Parse(vmpos.Template);
|
||||
Assert.Contains(items, item => item.Id == "a" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "b" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "c" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Minimum);
|
||||
Assert.Contains(items, item => item.Id == "d" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "e" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Minimum);
|
||||
Assert.Contains(items, item => item.Id == "f" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Topup);
|
||||
Assert.Contains(items, item => item.Id == "g" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Topup);
|
||||
Assert.Contains(items, item => item.Id == "a" && item.PriceType == AppItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "b" && item.PriceType == AppItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "c" && item.PriceType == AppItemPriceType.Minimum);
|
||||
Assert.Contains(items, item => item.Id == "d" && item.PriceType == AppItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "e" && item.PriceType == AppItemPriceType.Minimum);
|
||||
Assert.Contains(items, item => item.Id == "f" && item.PriceType == AppItemPriceType.Topup);
|
||||
Assert.Contains(items, item => item.Id == "g" && item.PriceType == AppItemPriceType.Topup);
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Static, choiceKey: "g").Result);
|
||||
@ -837,5 +803,65 @@ g:
|
||||
Assert.Equal("new", topupInvoice.Status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanUsePoSAppJsonEndpoint()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
var apps = user.GetController<UIAppsController>();
|
||||
var pos = user.GetController<UIPointOfSaleController>();
|
||||
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
|
||||
var appType = PointOfSaleAppType.AppType;
|
||||
vm.AppName = "test";
|
||||
vm.SelectedAppType = appType;
|
||||
var redirect = Assert.IsType<RedirectResult>(apps.CreateApp(user.StoreId, vm).Result);
|
||||
Assert.EndsWith("/settings/pos", redirect.Url);
|
||||
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
|
||||
var app = appList.Apps[0];
|
||||
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType };
|
||||
apps.HttpContext.SetAppData(appData);
|
||||
pos.HttpContext.SetAppData(appData);
|
||||
var vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
vmpos.Title = "App POS";
|
||||
vmpos.Currency = "EUR";
|
||||
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
|
||||
// Failing requests
|
||||
var (invoiceId1, error1) = await PosJsonRequest(tester, app.Id, "amount=-21&discount=10&tip=2");
|
||||
Assert.Null(invoiceId1);
|
||||
Assert.Equal("Negative amount is not allowed", error1);
|
||||
var (invoiceId2, error2) = await PosJsonRequest(tester, app.Id, "amount=21&discount=-10&tip=-2");
|
||||
Assert.Null(invoiceId2);
|
||||
Assert.Equal("Negative tip or discount is not allowed", error2);
|
||||
|
||||
// Successful request
|
||||
var (invoiceId3, error3) = await PosJsonRequest(tester, app.Id, "amount=21");
|
||||
Assert.NotNull(invoiceId3);
|
||||
Assert.Null(error3);
|
||||
|
||||
// Check generated invoice
|
||||
var invoices = await user.BitPay.GetInvoicesAsync();
|
||||
var invoice = invoices.First();
|
||||
Assert.Equal(invoiceId3, invoice.Id);
|
||||
Assert.Equal(21.00m, invoice.Price);
|
||||
Assert.Equal("EUR", invoice.Currency);
|
||||
}
|
||||
|
||||
private async Task<(string invoiceId, string error)> PosJsonRequest(ServerTester tester, string appId, string query)
|
||||
{
|
||||
var uriBuilder = new UriBuilder(tester.PayTester.ServerUri) { Path = $"/apps/{appId}/pos/light", Query = query };
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, uriBuilder.Uri);
|
||||
request.Headers.Add("Accept", "application/json");
|
||||
var response = await tester.PayTester.HttpClient.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
var json = JObject.Parse(content);
|
||||
return (json["invoiceId"]?.Value<string>(), json["error"]?.Value<string>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -7,12 +7,14 @@ using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Mails;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
@ -27,7 +29,6 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using AuthenticationSchemes = BTCPayServer.Abstractions.Constants.AuthenticationSchemes;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
|
@ -65,6 +65,11 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal($"bitcoin:{address}", clipboard);
|
||||
Assert.Equal($"bitcoin:{address.ToUpperInvariant()}", qrValue);
|
||||
s.Driver.ElementDoesNotExist(By.Id("Lightning_BTC-CHAIN"));
|
||||
|
||||
// Contact option
|
||||
var contactLink = s.Driver.FindElement(By.Id("ContactLink"));
|
||||
Assert.Equal("Contact us", contactLink.Text);
|
||||
Assert.Matches(supportUrl.Replace("{InvoiceId}", invoiceId), contactLink.GetAttribute("href"));
|
||||
|
||||
// Details should show exchange rate
|
||||
s.Driver.ToggleCollapse("PaymentDetails");
|
||||
@ -138,7 +143,6 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("resubmit a payment", expiredSection.Text);
|
||||
Assert.DoesNotContain("This invoice expired with partial payment", expiredSection.Text);
|
||||
});
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ContactLink")));
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ReceiptLink")));
|
||||
Assert.Equal(storeUrl, s.Driver.FindElement(By.Id("StoreLink")).GetAttribute("href"));
|
||||
|
||||
@ -172,9 +176,6 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("This invoice expired with partial payment", expiredSection.Text);
|
||||
Assert.DoesNotContain("resubmit a payment", expiredSection.Text);
|
||||
});
|
||||
var contactLink = s.Driver.FindElement(By.Id("ContactLink"));
|
||||
Assert.Equal("Contact us", contactLink.Text);
|
||||
Assert.Matches(supportUrl.Replace("{InvoiceId}", invoiceId), contactLink.GetAttribute("href"));
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ReceiptLink")));
|
||||
Assert.Equal(storeUrl, s.Driver.FindElement(By.Id("StoreLink")).GetAttribute("href"));
|
||||
|
||||
@ -243,7 +244,6 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
s.Driver.FindElement(By.Id("confetti"));
|
||||
s.Driver.FindElement(By.Id("ReceiptLink"));
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ContactLink")));
|
||||
Assert.Equal(storeUrl, s.Driver.FindElement(By.Id("StoreLink")).GetAttribute("href"));
|
||||
|
||||
// BIP21
|
||||
|
@ -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/*
|
||||
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
@ -11,7 +10,6 @@ using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
@ -20,40 +18,28 @@ using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Plugins;
|
||||
using BTCPayServer.Plugins.Bitcoin;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Fees;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Labels;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Validation;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Configuration.Memory;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitcoin.Scripting.Parser;
|
||||
using NBXplorer;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -117,11 +103,13 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
var compose1 = File.ReadAllText(Path.Combine(TestUtils.TryGetSolutionDirectoryInfo().FullName, "BTCPayServer.Tests", "docker-compose.yml"));
|
||||
var compose2 = File.ReadAllText(Path.Combine(TestUtils.TryGetSolutionDirectoryInfo().FullName, "BTCPayServer.Tests", "docker-compose.altcoins.yml"));
|
||||
var compose3 = File.ReadAllText(Path.Combine(TestUtils.TryGetSolutionDirectoryInfo().FullName, "BTCPayServer.Tests", "docker-compose.mutinynet.yml"));
|
||||
var compose4 = File.ReadAllText(Path.Combine(TestUtils.TryGetSolutionDirectoryInfo().FullName, "BTCPayServer.Tests", "docker-compose.testnet.yml"));
|
||||
|
||||
List<DockerImage> GetImages(string content)
|
||||
{
|
||||
List<DockerImage> images = new List<DockerImage>();
|
||||
foreach (var line in content.Split(new[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries))
|
||||
var images = new List<DockerImage>();
|
||||
foreach (var line in content.Split(["\n", "\r\n"], StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var l = line.Trim();
|
||||
if (l.StartsWith("image:", StringComparison.OrdinalIgnoreCase))
|
||||
@ -134,13 +122,15 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var img1 = GetImages(compose1);
|
||||
var img2 = GetImages(compose2);
|
||||
var groups = img1.Concat(img2).GroupBy(g => g.Name);
|
||||
var img3 = GetImages(compose3);
|
||||
var img4 = GetImages(compose4);
|
||||
var groups = img1.Concat(img2).Concat(img3).Concat(img4).GroupBy(g => g.Name);
|
||||
foreach (var g in groups)
|
||||
{
|
||||
var tags = new HashSet<String>(g.Select(o => o.Tag));
|
||||
var tags = new HashSet<string>(g.Select(o => o.Tag));
|
||||
if (tags.Count != 1)
|
||||
{
|
||||
Assert.Fail($"All docker images '{g.Key}' in docker-compose.yml and docker-compose.altcoins.yml should have the same tags. (Found {string.Join(',', tags)})");
|
||||
Assert.Fail($"All docker images '{g.Key}' across the docker-compose.yml files should have the same tags. (Found {string.Join(',', tags)})");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -188,10 +178,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)
|
||||
@ -807,9 +797,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),
|
||||
@ -820,24 +810,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);
|
||||
@ -868,6 +858,13 @@ namespace BTCPayServer.Tests
|
||||
Assert.IsType<MultisigDerivationStrategy>(((P2WSHDerivationStrategy)strategyBase).Inner);
|
||||
Assert.Equal(expected, strategyBase.ToString());
|
||||
|
||||
foreach (var space in new[] { "\r\n", " ", "\t" })
|
||||
{
|
||||
var expectedWithNewLines = $"2-of-tpubDDXgATYzdQkHHhZZCMcNJj8BGDENvzMVou5v9NdxiP4rxDLj33nS233dGFW4htpVZSJ6zds9eVqAV9RyRHHiKtwQKX8eR4n4KN3Dwmj7A3h-{space}tpubDC8a54NFtQtMQAZ97VhoU9V6jVTvi9w4Y5SaAXJSBYETKg3AoX5CCKndznhPWxJUBToPCpT44s86QbKdGpKAnSjcMTGW4kE6UQ8vpBjcybW-tpubDChjnP9LXNrJp43biqjY7FH93wgRRNrNxB4Q8pH7PPRy8UPcH2S6V46WGVJ47zVGF7SyBJNCpnaogsFbsybVQckGtVhCkng3EtFn8qmxptS";
|
||||
strategyBase = parser.Parse(expectedWithNewLines);
|
||||
Assert.Equal(expected, strategyBase.ToString());
|
||||
}
|
||||
|
||||
var inner = (MultisigDerivationStrategy)((P2WSHDerivationStrategy)strategyBase).Inner;
|
||||
Assert.False(inner.IsLegacy);
|
||||
Assert.Equal(3, inner.Keys.Count);
|
||||
@ -884,7 +881,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(((DirectDerivationStrategy)strategyBase).Segwit);
|
||||
|
||||
// Failure cases
|
||||
Assert.Throws<FormatException>(() => { parser.Parse("xpub 661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw"); }); // invalid format because of space
|
||||
Assert.Throws<FormatException>(() => { parser.Parse("xpubZ661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw"); });
|
||||
Assert.Throws<ParsingException>(() => { parser.ParseOutputDescriptor("invalid"); }); // invalid in general
|
||||
Assert.Throws<ParsingException>(() => { parser.ParseOutputDescriptor("wpkh([8b60afd1/49h/0h/0h]xpub661MyMwAFXkMnyoBjyHndD3QwRbcGVBsTGeNZN6QGVHcfz4MPzBUxjSevweNFQx7SqmMHLdSA4FteGsRrEriu4pnVZMZWnruFFAYZATtcDw/0/*)#9x4vkw48"); }); // invalid checksum
|
||||
}
|
||||
@ -1664,7 +1661,7 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
||||
|
||||
testCases.ForEach(tuple =>
|
||||
{
|
||||
Assert.Equal(tuple.expectedOutput, UIInvoiceController.PosDataParser.ParsePosData(string.IsNullOrEmpty(tuple.input) ? null : JToken.Parse(tuple.input)));
|
||||
Assert.Equal(tuple.expectedOutput, PosDataParser.ParsePosData(string.IsNullOrEmpty(tuple.input) ? null : JToken.Parse(tuple.input)));
|
||||
});
|
||||
}
|
||||
[Fact]
|
||||
|
@ -318,6 +318,20 @@ namespace BTCPayServer.Tests
|
||||
Assert.Empty(await client.GetFiles());
|
||||
storeData = await client.GetStore(store.Id);
|
||||
Assert.Null(storeData.LogoUrl);
|
||||
|
||||
// App Item Image
|
||||
var app = await client.CreatePointOfSaleApp(store.Id, new PointOfSaleAppRequest { AppName = "Test App" });
|
||||
await AssertValidationError(["file"],
|
||||
async () => await client.UploadAppItemImage(app.Id, filePath, "text/csv")
|
||||
);
|
||||
|
||||
var fileData = await client.UploadAppItemImage(app.Id, logoPath, "image/png");
|
||||
Assert.Equal("logo.png", fileData.OriginalName);
|
||||
files = await client.GetFiles();
|
||||
Assert.Single(files);
|
||||
|
||||
await client.DeleteAppItemImage(app.Id, fileData.Id);
|
||||
Assert.Empty(await client.GetFiles());
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
@ -745,9 +759,9 @@ namespace BTCPayServer.Tests
|
||||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
var client = await user.CreateClient();
|
||||
|
||||
var item1 = new ViewPointOfSaleViewModel.Item { Id = "item1", Title = "Item 1", Price = 1, PriceType = ViewPointOfSaleViewModel.ItemPriceType.Fixed };
|
||||
var item2 = new ViewPointOfSaleViewModel.Item { Id = "item2", Title = "Item 2", Price = 2, PriceType = ViewPointOfSaleViewModel.ItemPriceType.Fixed };
|
||||
var item3 = new ViewPointOfSaleViewModel.Item { Id = "item3", Title = "Item 3", Price = 3, PriceType = ViewPointOfSaleViewModel.ItemPriceType.Fixed };
|
||||
var item1 = new AppItem { Id = "item1", Title = "Item 1", Price = 1, PriceType = AppItemPriceType.Fixed };
|
||||
var item2 = new AppItem { Id = "item2", Title = "Item 2", Price = 2, PriceType = AppItemPriceType.Fixed };
|
||||
var item3 = new AppItem { Id = "item3", Title = "Item 3", Price = 3, PriceType = AppItemPriceType.Fixed };
|
||||
var posItems = AppService.SerializeTemplate([item1, item2, item3]);
|
||||
var posApp = await client.CreatePointOfSaleApp(user.StoreId, new PointOfSaleAppRequest { AppName = "test pos", Template = posItems, });
|
||||
var crowdfundApp = await client.CreateCrowdfundApp(user.StoreId, new CrowdfundAppRequest { AppName = "test crowdfund" });
|
||||
@ -985,7 +999,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("ServerAdmin", admin.Roles);
|
||||
Assert.NotNull(admin.Created);
|
||||
Assert.True((DateTimeOffset.Now - admin.Created).Value.Seconds < 10);
|
||||
|
||||
|
||||
// Creating a new user without proper creds is now impossible (unauthorized)
|
||||
// Because if registration are locked and that an admin exists, we don't accept unauthenticated connection
|
||||
var ex = await AssertAPIError("unauthenticated",
|
||||
@ -1037,7 +1051,14 @@ namespace BTCPayServer.Tests
|
||||
Password = "afewfoiewiou",
|
||||
IsAdministrator = true
|
||||
});
|
||||
|
||||
// Create user without password
|
||||
await adminClient.CreateUser(new CreateApplicationUserRequest
|
||||
{
|
||||
Email = "nopassword@gmail.com"
|
||||
});
|
||||
|
||||
// Regular user
|
||||
var user1Acc = tester.NewAccount();
|
||||
user1Acc.UserId = user1.Id;
|
||||
user1Acc.IsAdmin = false;
|
||||
@ -1400,10 +1421,17 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(0, card.Version);
|
||||
var card1keys = new[] { card.K0, card.K1, card.K2, card.K3, card.K4 };
|
||||
Assert.DoesNotContain(null, card1keys);
|
||||
|
||||
var card2 = await client.RegisterBoltcard(test4.Id, new RegisterBoltcardRequest()
|
||||
{
|
||||
UID = uid
|
||||
});
|
||||
Assert.Equal(0, card2.Version);
|
||||
card2 = await client.RegisterBoltcard(test4.Id, new RegisterBoltcardRequest()
|
||||
{
|
||||
UID = uid,
|
||||
OnExisting = OnExistingBehavior.UpdateVersion
|
||||
});
|
||||
Assert.Equal(1, card2.Version);
|
||||
Assert.StartsWith("lnurlw://", card2.LNURLW);
|
||||
Assert.EndsWith("/boltcard", card2.LNURLW);
|
||||
@ -3002,6 +3030,18 @@ namespace BTCPayServer.Tests
|
||||
Assert.Single(info.NodeURIs);
|
||||
Assert.NotEqual(0, info.BlockHeight);
|
||||
|
||||
// Disable for now see #6518
|
||||
//// balance
|
||||
//await TestUtils.EventuallyAsync(async () =>
|
||||
//{
|
||||
// var balance = await client.GetLightningNodeBalance(user.StoreId, "BTC");
|
||||
// var localBalance = balance.OffchainBalance.Local.ToDecimal(LightMoneyUnit.BTC);
|
||||
// var histogram = await client.GetLightningNodeHistogram(user.StoreId, "BTC");
|
||||
// Assert.Equal(histogram.Balance, histogram.Series.Last());
|
||||
// Assert.Equal(localBalance, histogram.Balance);
|
||||
// Assert.Equal(localBalance, histogram.Series.Last());
|
||||
//});
|
||||
|
||||
// As admin, can use the internal node through our store.
|
||||
await user.MakeAdmin(true);
|
||||
await user.RegisterInternalLightningNodeAsync("BTC");
|
||||
@ -3023,6 +3063,10 @@ namespace BTCPayServer.Tests
|
||||
client = await guest.CreateClient(Policies.CanUseLightningNodeInStore);
|
||||
// Can use lightning node is only granted to store's owner
|
||||
await AssertPermissionError("btcpay.store.canuselightningnode", () => client.GetLightningNodeInfo(user.StoreId, "BTC"));
|
||||
|
||||
// balance and histogram should not be accessible with view only clients
|
||||
await AssertPermissionError("btcpay.store.canuselightningnode", () => client.GetLightningNodeBalance(user.StoreId, "BTC"));
|
||||
await AssertPermissionError("btcpay.store.canuselightningnode", () => client.GetLightningNodeHistogram(user.StoreId, "BTC"));
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 20 * 1000)]
|
||||
@ -3538,8 +3582,7 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
var overview = await client.ShowOnChainWalletOverview(walletId.StoreId, walletId.CryptoCode);
|
||||
Assert.Equal(0m, overview.Balance);
|
||||
|
||||
|
||||
|
||||
var fee = await client.GetOnChainFeeRate(walletId.StoreId, walletId.CryptoCode);
|
||||
Assert.NotNull(fee.FeeRate);
|
||||
|
||||
@ -3585,6 +3628,17 @@ namespace BTCPayServer.Tests
|
||||
overview = await client.ShowOnChainWalletOverview(walletId.StoreId, walletId.CryptoCode);
|
||||
Assert.Equal(0.01m, overview.Balance);
|
||||
|
||||
// histogram should not be accessible with view only clients
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.GetOnChainWalletHistogram(walletId.StoreId, walletId.CryptoCode);
|
||||
});
|
||||
var histogram = await client.GetOnChainWalletHistogram(walletId.StoreId, walletId.CryptoCode);
|
||||
Assert.Equal(histogram.Balance, histogram.Series.Last());
|
||||
Assert.Equal(0.01m, histogram.Balance);
|
||||
Assert.Equal(0.01m, histogram.Series.Last());
|
||||
Assert.Equal(0, histogram.Series.First());
|
||||
|
||||
//the simplest request:
|
||||
var nodeAddress = await tester.ExplorerNode.GetNewAddressAsync();
|
||||
var createTxRequest = new CreateOnChainTransactionRequest()
|
||||
@ -3776,7 +3830,6 @@ namespace BTCPayServer.Tests
|
||||
|
||||
await tester.WaitForEvent<NewBlockEvent>(async () =>
|
||||
{
|
||||
|
||||
await tester.ExplorerNode.GenerateAsync(1);
|
||||
}, bevent => bevent.PaymentMethodId == PaymentTypes.CHAIN.GetPaymentMethodId("BTC"));
|
||||
|
||||
@ -3814,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>());
|
||||
}
|
||||
|
||||
@ -3826,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>());
|
||||
}
|
||||
@ -3932,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);
|
||||
@ -3946,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();
|
||||
@ -3976,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));
|
||||
@ -3997,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 }));
|
||||
@ -4122,6 +4195,11 @@ namespace BTCPayServer.Tests
|
||||
Assert.True((await adminClient.GetUserByIdOrEmail(unapprovedUser.UserId)).Approved);
|
||||
Assert.True((await unapprovedUserApiKeyClient.GetCurrentUser()).Approved);
|
||||
Assert.True((await unapprovedUserBasicAuthClient.GetCurrentUser()).Approved);
|
||||
var err = await AssertAPIError("invalid-state", async () =>
|
||||
{
|
||||
await adminClient.ApproveUser(unapprovedUser.UserId, true, CancellationToken.None);
|
||||
});
|
||||
Assert.Equal("User is already approved", err.APIError.Message);
|
||||
|
||||
// un-approve
|
||||
Assert.True(await adminClient.ApproveUser(unapprovedUser.UserId, false, CancellationToken.None));
|
||||
@ -4134,6 +4212,11 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await unapprovedUserBasicAuthClient.GetCurrentUser();
|
||||
});
|
||||
err = await AssertAPIError("invalid-state", async () =>
|
||||
{
|
||||
await adminClient.ApproveUser(unapprovedUser.UserId, false, CancellationToken.None);
|
||||
});
|
||||
Assert.Equal("User is already unapproved", err.APIError.Message);
|
||||
|
||||
// reset policies to not require approval
|
||||
await settings.UpdateSetting(new PoliciesSettings { LockSubscription = false, RequiresUserApproval = false });
|
||||
@ -4150,10 +4233,11 @@ namespace BTCPayServer.Tests
|
||||
Assert.Single(await adminClient.GetNotifications(false));
|
||||
|
||||
// try unapproving user which does not have the RequiresApproval flag
|
||||
await AssertAPIError("invalid-state", async () =>
|
||||
err = await AssertAPIError("invalid-state", async () =>
|
||||
{
|
||||
await adminClient.ApproveUser(newUser.UserId, false, CancellationToken.None);
|
||||
});
|
||||
Assert.Equal("Unapproving user failed: No approval required", err.APIError.Message);
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
@ -4348,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"));
|
||||
|
||||
@ -4392,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;
|
||||
@ -4406,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
|
||||
@ -4472,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)
|
||||
{
|
||||
@ -4516,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();
|
||||
@ -4549,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);
|
||||
|
||||
}
|
||||
|
||||
@ -4626,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()
|
||||
{
|
||||
|
@ -74,9 +74,8 @@ namespace BTCPayServer.Tests
|
||||
tester.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
|
||||
tester.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
|
||||
text = tester.Driver.PageSource;
|
||||
Assert.DoesNotContain("Select-English (Custom)", text);
|
||||
Assert.Contains("English (Custom) deleted", text);
|
||||
Assert.Contains("Dictionary English (Custom) deleted", tester.FindAlertMessage().Text);
|
||||
Assert.DoesNotContain("Select-English (Custom)", tester.Driver.PageSource);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
|
@ -15,7 +15,7 @@ namespace BTCPayServer.Tests.Mocks
|
||||
|
||||
public string Action(UrlActionContext actionContext)
|
||||
{
|
||||
return $"{_BaseUrl}mock";
|
||||
return $"/mock";
|
||||
}
|
||||
|
||||
public string Content(string contentPath)
|
||||
|
@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Plugins.Crowdfund.Models;
|
||||
using BTCPayServer.Plugins.PointOfSale;
|
||||
using BTCPayServer.Plugins.PointOfSale.Controllers;
|
||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||
@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using static BTCPayServer.Tests.UnitTest1;
|
||||
using PosViewType = BTCPayServer.Plugins.PointOfSale.PosViewType;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -76,9 +77,8 @@ fruit tea:
|
||||
Assert.Null( parsedDefault[0].BuyButtonText);
|
||||
Assert.Equal( "~/img/pos-sample/green-tea.jpg" ,parsedDefault[0].Image);
|
||||
Assert.Equal( 1 ,parsedDefault[0].Price);
|
||||
Assert.Equal( ViewPointOfSaleViewModel.ItemPriceType.Fixed ,parsedDefault[0].PriceType);
|
||||
Assert.Equal( AppItemPriceType.Fixed ,parsedDefault[0].PriceType);
|
||||
Assert.Null( parsedDefault[0].AdditionalData);
|
||||
Assert.Null( parsedDefault[0].PaymentMethods);
|
||||
|
||||
|
||||
Assert.Equal( "Herbal Tea" ,parsedDefault[4].Title);
|
||||
@ -87,9 +87,8 @@ fruit tea:
|
||||
Assert.Null( parsedDefault[4].BuyButtonText);
|
||||
Assert.Equal( "~/img/pos-sample/herbal-tea.jpg" ,parsedDefault[4].Image);
|
||||
Assert.Equal( 1.8m ,parsedDefault[4].Price);
|
||||
Assert.Equal( ViewPointOfSaleViewModel.ItemPriceType.Minimum ,parsedDefault[4].PriceType);
|
||||
Assert.Equal( AppItemPriceType.Minimum ,parsedDefault[4].PriceType);
|
||||
Assert.Null( parsedDefault[4].AdditionalData);
|
||||
Assert.Null( parsedDefault[4].PaymentMethods);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -404,10 +404,10 @@ namespace BTCPayServer.Tests
|
||||
Assert.False(paymentValueRowColumn.Text.Contains("payjoin",
|
||||
StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
s.GoToWallet(receiverWalletId, WalletsNavPages.Transactions);
|
||||
s.Driver.WaitForElement(By.CssSelector("#WalletTransactionsList tr"));
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.GoToWallet(receiverWalletId, WalletsNavPages.Transactions);
|
||||
s.Driver.WaitForElement(By.CssSelector("#WalletTransactionsList tr"));
|
||||
Assert.Contains("payjoin", s.Driver.PageSource);
|
||||
// Either the invoice id or the payjoin-exposed label, depending on the input having been used
|
||||
Assert.Matches(new Regex($"({invoiceId}|payjoin-exposed)"), s.Driver.PageSource);
|
||||
|
@ -643,7 +643,15 @@ retry:
|
||||
GoToUrl(url);
|
||||
Assert.DoesNotMatch("404 - Page not found</h", Driver.PageSource);
|
||||
if (shouldHaveAccess)
|
||||
{
|
||||
Assert.DoesNotMatch("- Denied</h", Driver.PageSource);
|
||||
// check associated link is active if present
|
||||
var sidebarLink = Driver.FindElements(By.CssSelector($"#mainNav a[href=\"{url}\"]")).FirstOrDefault();
|
||||
if (sidebarLink != null)
|
||||
{
|
||||
Assert.Contains("active", sidebarLink.GetAttribute("class"));
|
||||
}
|
||||
}
|
||||
else
|
||||
Assert.Contains("- Denied</h", Driver.PageSource);
|
||||
}
|
||||
|
@ -95,7 +95,6 @@ namespace BTCPayServer.Tests
|
||||
|
||||
s.Driver.FindElement(By.CssSelector("button[type='submit']")).Click();
|
||||
|
||||
Assert.Contains("Enter your email", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Name("buyerEmail")).SendKeys("aa@aa.com");
|
||||
s.Driver.FindElement(By.CssSelector("input[type='submit']")).Click();
|
||||
|
||||
@ -385,10 +384,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();
|
||||
@ -1244,15 +1239,17 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Title")).SendKeys("Tea shop");
|
||||
s.Driver.FindElement(By.CssSelector("label[for='DefaultView_Cart']")).Click();
|
||||
s.Driver.FindElement(By.CssSelector(".template-item:nth-of-type(1)")).Click();
|
||||
s.Driver.WaitUntilAvailable(By.Id("BuyButtonText"));
|
||||
s.Driver.FindElement(By.Id("BuyButtonText")).SendKeys("Take my money");
|
||||
s.Driver.FindElement(By.Id("EditorCategories-ts-control")).SendKeys("Drinks");
|
||||
s.Driver.FindElement(By.CssSelector(".offcanvas-header button")).Click();
|
||||
s.Driver.WaitUntilAvailable(By.Id("CodeTabButton"));
|
||||
s.Driver.ScrollTo(By.Id("CodeTabButton"));
|
||||
s.Driver.FindElement(By.Id("CodeTabButton")).Click();
|
||||
var template = s.Driver.FindElement(By.Id("TemplateConfig")).GetAttribute("value");
|
||||
Assert.Contains("\"buyButtonText\": \"Take my money\"", template);
|
||||
Assert.Matches("\"categories\": \\[\r?\n\\s*\"Drinks\"\\s*\\]", template);
|
||||
|
||||
|
||||
s.ClickPagePrimary();
|
||||
Assert.Contains("App updated", s.FindAlertMessage().Text);
|
||||
|
||||
@ -1457,12 +1454,15 @@ namespace BTCPayServer.Tests
|
||||
s.GoToUrl(editUrl);
|
||||
s.Driver.ScrollTo(By.Id("btAddItem"));
|
||||
s.Driver.FindElement(By.Id("btAddItem")).Click();
|
||||
s.Driver.WaitUntilAvailable(By.Id("EditorTitle"));
|
||||
s.Driver.FindElement(By.Id("EditorTitle")).SendKeys("Perk 1");
|
||||
s.Driver.FindElement(By.Id("EditorAmount")).SendKeys("20");
|
||||
// Test autogenerated ID
|
||||
Assert.Equal("perk-1", s.Driver.FindElement(By.Id("EditorId")).GetAttribute("value"));
|
||||
s.Driver.FindElement(By.Id("EditorId")).Clear();
|
||||
s.Driver.FindElement(By.Id("EditorId")).SendKeys("Perk-1");
|
||||
s.Driver.FindElement(By.CssSelector(".offcanvas-header button")).Click();
|
||||
s.Driver.WaitUntilAvailable(By.Id("CodeTabButton"));
|
||||
s.Driver.ScrollTo(By.Id("CodeTabButton"));
|
||||
s.Driver.FindElement(By.Id("CodeTabButton")).Click();
|
||||
var template = s.Driver.FindElement(By.Id("TemplateConfig")).GetAttribute("value");
|
||||
@ -1874,19 +1874,13 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("test-label", s.Driver.PageSource);
|
||||
});
|
||||
|
||||
// Let's try to remove a label
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
s.Driver.WaitForElement(By.CssSelector("[data-value='test-label']")).Click();
|
||||
await Task.Delay(500);
|
||||
s.Driver.ExecuteJavaScript("document.querySelector('[data-value=\"test-label\"]').nextSibling.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Delete', keyCode: 46}));");
|
||||
|
||||
});
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
Assert.DoesNotContain("test-label", s.Driver.PageSource);
|
||||
});
|
||||
// Remove a label
|
||||
s.Driver.WaitForElement(By.CssSelector("[data-value='test-label']")).Click();
|
||||
await Task.Delay(500);
|
||||
s.Driver.ExecuteJavaScript("var l=document.querySelector('[data-value=\"test-label\"]');l.click();l.nextSibling.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Delete', keyCode: 8}));");
|
||||
await Task.Delay(500);
|
||||
await s.Driver.Navigate().RefreshAsync();
|
||||
Assert.DoesNotContain("test-label", s.Driver.PageSource);
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("GoBack")));
|
||||
|
||||
//send money to addr and ensure it changed
|
||||
@ -2181,6 +2175,56 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("PP1 Edited", s.Driver.PageSource);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
public async Task CanUseAwaitProgressForInProgressPayout()
|
||||
{
|
||||
using var s = CreateSeleniumTester();
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser(true);
|
||||
s.CreateNewStore();
|
||||
s.GenerateWallet(isHotWallet: true);
|
||||
await s.FundStoreWallet(denomination: 50.0m);
|
||||
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PayoutProcessors);
|
||||
s.Driver.FindElement(By.Id("Configure-BTC-CHAIN")).Click();
|
||||
s.Driver.SetCheckbox(By.Id("ProcessNewPayoutsInstantly"), true);
|
||||
s.ClickPagePrimary();
|
||||
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
s.ClickPagePrimary();
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
|
||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0");
|
||||
s.Driver.SetCheckbox(By.Id("AutoApproveClaims"), true);
|
||||
s.ClickPagePrimary();
|
||||
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
|
||||
var address = await s.Server.ExplorerNode.GetNewAddressAsync();
|
||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString() + Keys.Enter);
|
||||
s.GoToStore(s.StoreId, StoreNavPages.Payouts);
|
||||
s.Driver.FindElement(By.Id("InProgress-view")).Click();
|
||||
|
||||
// Waiting for the payment processor to process the payment
|
||||
int i = 0;
|
||||
while (!s.Driver.PageSource.Contains("mass-action-select-all"))
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
i++;
|
||||
Thread.Sleep(1000);
|
||||
if (i > 10)
|
||||
break;
|
||||
}
|
||||
s.Driver.TakeScreenshot().SaveAsFile("C:\\Users\\NicolasDorier\\AppData\\Local\\Temp\\1109191644\\1.png");
|
||||
s.Driver.FindElement(By.ClassName("mass-action-select-all")).Click();
|
||||
|
||||
s.Driver.FindElement(By.Id("InProgress-mark-awaiting-payment")).Click();
|
||||
s.Driver.FindElement(By.Id("AwaitingPayment-view")).Click();
|
||||
Assert.Contains("PP1", s.Driver.PageSource);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
@ -2909,6 +2953,16 @@ namespace BTCPayServer.Tests
|
||||
// Unauthenticated user can't access recent transactions
|
||||
s.GoToUrl(keypadUrl);
|
||||
s.Driver.ElementDoesNotExist(By.Id("RecentTransactionsToggle"));
|
||||
|
||||
// But they can generate invoices
|
||||
s.Driver.FindElement(By.CssSelector(".keypad [data-key='1']")).Click();
|
||||
s.Driver.FindElement(By.CssSelector(".keypad [data-key='2']")).Click();
|
||||
s.Driver.FindElement(By.CssSelector(".keypad [data-key='3']")).Click();
|
||||
s.Driver.FindElement(By.Id("pay-button")).Click();
|
||||
s.Driver.WaitUntilAvailable(By.Id("Checkout"));
|
||||
s.Driver.FindElement(By.Id("DetailsToggle")).Click();
|
||||
s.Driver.WaitForElement(By.Id("PaymentDetails-TotalFiat"));
|
||||
Assert.Contains("1,23 €", s.Driver.FindElement(By.Id("PaymentDetails-TotalFiat")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -3783,7 +3837,7 @@ retry:
|
||||
(_, string posId) = s.CreateApp("PointOfSale");
|
||||
(_, string crowdfundId) = s.CreateApp("Crowdfund");
|
||||
|
||||
string GetStorePath(string subPath) => $"/stores/{storeId}/{subPath}";
|
||||
string GetStorePath(string subPath) => $"/stores/{storeId}" + (string.IsNullOrEmpty(subPath) ? "" : $"/{subPath}");
|
||||
|
||||
// Owner access
|
||||
s.AssertPageAccess(true, GetStorePath(""));
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
@ -15,8 +14,6 @@ using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Models.AccountViewModels;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
@ -26,10 +23,8 @@ using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
|
@ -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,15 +351,15 @@ retry:
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanSolveTheDogesRatesOnKraken()
|
||||
public async Task CanSolveTheDogesRatesOnKraken()
|
||||
{
|
||||
var factory = FastTests.CreateBTCPayRateFactory();
|
||||
var fetcher = new RateFetcher(factory);
|
||||
|
||||
Assert.True(RateRules.TryParse("X_X=kraken(X_BTC) * kraken(BTC_X)", out var rule));
|
||||
foreach (var pair in new[] { "DOGE_USD", "DOGE_CAD", "DASH_CAD", "DASH_USD", "DASH_EUR" })
|
||||
foreach (var pair in new[] { "DOGE_USD", "DOGE_CAD" })
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,10 +38,8 @@ using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Payments.PayJoin.Sender;
|
||||
using BTCPayServer.Plugins.PayButton;
|
||||
using BTCPayServer.Plugins.PointOfSale;
|
||||
using BTCPayServer.Plugins.PointOfSale.Controllers;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
using BTCPayServer.Security.Greenfield;
|
||||
using BTCPayServer.Services;
|
||||
@ -70,7 +68,6 @@ using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Schema;
|
||||
using Npgsql;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
@ -197,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();
|
||||
@ -449,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 =
|
||||
@ -841,6 +838,8 @@ namespace BTCPayServer.Tests
|
||||
AssertSearchInvoice(acc, false, invoice.Id, "exceptionstatus:paidOver");
|
||||
AssertSearchInvoice(acc, true, invoice.Id, "unusual:true");
|
||||
AssertSearchInvoice(acc, false, invoice.Id, "unusual:false");
|
||||
AssertSearchInvoice(acc, true, invoice.Id, "status:settled,exceptionstatus:paidPartial");
|
||||
AssertSearchInvoice(acc, true, invoice.Id, "status:settled,status:invalid,exceptionstatus:paidPartial,exceptionstatus:paidOver");
|
||||
|
||||
var time = invoice.InvoiceTime;
|
||||
AssertSearchInvoice(acc, true, invoice.Id, $"startdate:{time.ToString("yyyy-MM-dd HH:mm:ss")}");
|
||||
@ -1102,7 +1101,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();
|
||||
@ -1146,9 +1145,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);
|
||||
|
||||
|
||||
@ -1172,17 +1170,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);
|
||||
////////
|
||||
|
||||
@ -1196,7 +1194,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();
|
||||
/////////////////////
|
||||
|
||||
@ -1210,7 +1208,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);
|
||||
|
||||
//
|
||||
@ -1542,7 +1540,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;
|
||||
@ -2178,19 +2176,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);
|
||||
});
|
||||
@ -2218,11 +2216,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);
|
||||
@ -2234,7 +2232,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;
|
||||
});
|
||||
@ -2277,18 +2275,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);
|
||||
});
|
||||
|
||||
@ -2424,7 +2422,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();
|
||||
@ -3051,7 +3049,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async void CanUseLocalProviderFiles()
|
||||
public async Task CanUseLocalProviderFiles()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
@ -3250,8 +3248,14 @@ namespace BTCPayServer.Tests
|
||||
var date2018 = new DateTimeOffset(2018, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
report = await GetReport(acc, new() { ViewName = "Payments", TimePeriod = new TimePeriod() { From = date2018, To = date2018 + TimeSpan.FromDays(365) } });
|
||||
var invoiceIdIndex = report.GetIndex("InvoiceId");
|
||||
var invoiceCurrencyAmountIndex = report.GetIndex("InvoiceCurrencyAmount");
|
||||
var rateIndex = report.GetIndex("Rate");
|
||||
var oldPaymentsCount = report.Data.Count(d => d[invoiceIdIndex].Value<string>() == "Q7RqoHLngK9svM4MgRyi9y");
|
||||
Assert.Equal(9, oldPaymentsCount); // 11 payments, but 2 unaccounted
|
||||
Assert.Single(report.Data, d =>
|
||||
d[invoiceIdIndex].Value<string>() == "Q7RqoHLngK9svM4MgRyi9y" &&
|
||||
GetAmount(rateIndex, d) == 6596.35m &&
|
||||
GetAmount(invoiceCurrencyAmountIndex, d) == 1.18m);
|
||||
|
||||
var addr = await tester.ExplorerNode.GetNewAddressAsync();
|
||||
// Two invoices get refunded
|
||||
@ -3270,12 +3274,12 @@ 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, (((JObject)d[completedIndex])["v"]).Value<decimal>());
|
||||
Assert.Equal(awaiting, (((JObject)d[awaitingIndex])["v"]).Value<decimal>());
|
||||
Assert.Equal(limit, (((JObject)d[limitIndex])["v"]).Value<decimal>());
|
||||
Assert.Equal(completed, GetAmount(completedIndex, d));
|
||||
Assert.Equal(awaiting, GetAmount(awaitingIndex, d));
|
||||
Assert.Equal(limit, GetAmount(limitIndex, d));
|
||||
}
|
||||
|
||||
await AssertData("USD", awaiting: 0.0m, limit: 10.0m, completed: 0.0m, fullyPaid: false);
|
||||
@ -3296,6 +3300,12 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private static decimal GetAmount(int idx, JArray d)
|
||||
{
|
||||
var jobj = (JObject)d[idx];
|
||||
return Math.Round(jobj["v"].Value<decimal>(), jobj["d"].Value<int>());
|
||||
}
|
||||
|
||||
private async Task<StoreReportResponse> GetReport(TestAccount acc, StoreReportRequest req)
|
||||
{
|
||||
var controller = acc.GetController<UIReportsController>();
|
||||
|
@ -377,7 +377,7 @@ retry:
|
||||
}
|
||||
|
||||
}
|
||||
defaultTranslatedKeys = defaultTranslatedKeys.Select(d => d.Trim()).Distinct().OrderBy(o => o).ToList();
|
||||
defaultTranslatedKeys = defaultTranslatedKeys.Select(d => d.Trim().Replace("\r\n", "\n")).Distinct().OrderBy(o => o).ToList();
|
||||
JObject obj = new JObject();
|
||||
foreach (var v in defaultTranslatedKeys)
|
||||
{
|
||||
|
@ -98,7 +98,7 @@ services:
|
||||
custom:
|
||||
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.5.4
|
||||
image: nicolasdorier/nbxplorer:2.5.16
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
@ -180,6 +180,7 @@ services:
|
||||
dev-bitcoind-poll=1
|
||||
ports:
|
||||
- "30992:9835" # api port
|
||||
- "30892:9735" # server port
|
||||
expose:
|
||||
- "9735" # server port
|
||||
- "9835" # api port
|
||||
@ -208,6 +209,7 @@ services:
|
||||
dev-bitcoind-poll=1
|
||||
ports:
|
||||
- "30993:9835" # api port
|
||||
- "30893:9735" # server port
|
||||
expose:
|
||||
- "9735" # server port
|
||||
- "9835" # api port
|
||||
@ -252,9 +254,12 @@ services:
|
||||
no-rest-tls=1
|
||||
ports:
|
||||
- "35531:8080"
|
||||
- "30894:9735"
|
||||
- "53280:10009"
|
||||
expose:
|
||||
- "8080"
|
||||
- "9735"
|
||||
- "10009"
|
||||
volumes:
|
||||
- "merchant_lnd_datadir:/data"
|
||||
- "bitcoin_datadir:/deps/.bitcoin"
|
||||
@ -287,8 +292,10 @@ services:
|
||||
no-rest-tls=1
|
||||
ports:
|
||||
- "35532:8080"
|
||||
- "30895:9735"
|
||||
expose:
|
||||
- "8080"
|
||||
- "9735"
|
||||
- "10009"
|
||||
volumes:
|
||||
- "customer_lnd_datadir:/root/.lnd"
|
||||
|
298
BTCPayServer.Tests/docker-compose.mutinynet.yml
Normal file
298
BTCPayServer.Tests/docker-compose.mutinynet.yml
Normal file
@ -0,0 +1,298 @@
|
||||
version: "3"
|
||||
|
||||
# Run `docker-compose up dev` for bootstrapping your development environment
|
||||
# Doing so will expose NBXplorer, Bitcoind RPC and postgres port to the host so that tests can Run,
|
||||
# The Visual Studio launch setting `Docker-signet` is configured to use this environment.
|
||||
services:
|
||||
|
||||
# The dev container is not actually used, it is just handy to run `docker-compose up dev` to start all services
|
||||
dev:
|
||||
image: alpine:3.7
|
||||
command: [ "/bin/sh", "-c", "trap : TERM INT; while :; do echo Ready to code and debug like a rockstar!!!; sleep 2073600; done & wait" ]
|
||||
depends_on:
|
||||
- nbxplorer
|
||||
- postgres
|
||||
- customer_lightningd
|
||||
- merchant_lightningd
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
- sshd
|
||||
- tor
|
||||
|
||||
sshd:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: sshd.Dockerfile
|
||||
ports:
|
||||
- "21622:22"
|
||||
expose:
|
||||
- 22
|
||||
volumes:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
|
||||
devlnd:
|
||||
image: btcpayserver/mutinynet:c23afab47fbe
|
||||
environment:
|
||||
BITCOIN_NETWORK: signet
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
BITCOIN_EXTRA_ARGS: |
|
||||
deprecatedrpc=signrawtransaction
|
||||
connect=bitcoind:39388
|
||||
fallbackfee=0.0002
|
||||
rpcallowip=0.0.0.0/0
|
||||
[signet]
|
||||
signetchallenge=512102f7561d208dd9ae99bf497273e16f389bdbd6c4742ddb8e6b216e64fa2928ad8f51ae
|
||||
addnode=45.79.52.207:38333
|
||||
dnsseed=0
|
||||
signetblocktime=30
|
||||
depends_on:
|
||||
- nbxplorer
|
||||
- postgres
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
|
||||
selenium:
|
||||
image: selenium/standalone-chrome:125.0
|
||||
extra_hosts:
|
||||
- "tests:172.23.0.18"
|
||||
expose:
|
||||
- "4444"
|
||||
networks:
|
||||
default:
|
||||
custom:
|
||||
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.5.16
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
expose:
|
||||
- "32838"
|
||||
environment:
|
||||
NBXPLORER_NETWORK: signet
|
||||
NBXPLORER_CHAINS: "btc"
|
||||
NBXPLORER_BTCRPCURL: http://bitcoind:43782/
|
||||
NBXPLORER_BTCNODEENDPOINT: bitcoind:39388
|
||||
NBXPLORER_BTCRPCUSER: ceiwHEbqWI83
|
||||
NBXPLORER_BTCRPCPASSWORD: DwubwWsoo3
|
||||
NBXPLORER_BIND: 0.0.0.0:32838
|
||||
NBXPLORER_MINGAPSIZE: 5
|
||||
NBXPLORER_MAXGAPSIZE: 10
|
||||
NBXPLORER_VERBOSE: 1
|
||||
NBXPLORER_POSTGRES: User ID=postgres;Include Error Detail=true;Host=postgres;Port=5432;Database=nbxplorer_mutinynet
|
||||
NBXPLORER_EXPOSERPC: 1
|
||||
NBXPLORER_NOAUTH: 1
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
bitcoind:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/mutinynet:c23afab47fbe
|
||||
environment:
|
||||
BITCOIN_NETWORK: signet
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
BITCOIN_EXTRA_ARGS: |-
|
||||
rpcuser=ceiwHEbqWI83
|
||||
rpcpassword=DwubwWsoo3
|
||||
rpcport=43782
|
||||
rpcbind=0.0.0.0:43782
|
||||
rpcallowip=0.0.0.0/0
|
||||
port=39388
|
||||
whitelist=0.0.0.0/0
|
||||
zmqpubrawblock=tcp://0.0.0.0:28332
|
||||
zmqpubrawtx=tcp://0.0.0.0:28333
|
||||
deprecatedrpc=signrawtransaction
|
||||
fallbackfee=0.0002
|
||||
[signet]
|
||||
signetchallenge=512102f7561d208dd9ae99bf497273e16f389bdbd6c4742ddb8e6b216e64fa2928ad8f51ae
|
||||
addnode=45.79.52.207:38333
|
||||
dnsseed=0
|
||||
signetblocktime=30
|
||||
ports:
|
||||
- "43782:43782"
|
||||
- "39388:39388"
|
||||
expose:
|
||||
- "43782" # RPC
|
||||
- "39388" # P2P
|
||||
- "28332" # ZMQ
|
||||
- "28333" # ZMQ
|
||||
volumes:
|
||||
- "bitcoin_datadir:/data"
|
||||
|
||||
customer_lightningd:
|
||||
image: btcpayserver/lightning:v24.08.2
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
LIGHTNINGD_CHAIN: "btc"
|
||||
LIGHTNINGD_NETWORK: "signet"
|
||||
LIGHTNINGD_OPT: |
|
||||
developer
|
||||
bitcoin-datadir=/etc/bitcoin
|
||||
bitcoin-rpcconnect=bitcoind
|
||||
announce-addr=customer_lightningd:9735
|
||||
log-level=debug
|
||||
funding-confirms=1
|
||||
dev-fast-gossip
|
||||
dev-bitcoind-poll=1
|
||||
ports:
|
||||
- "30992:9835" # api port
|
||||
- "30892:9735" # server port
|
||||
expose:
|
||||
- "9735" # server port
|
||||
- "9835" # api port
|
||||
volumes:
|
||||
- "bitcoin_datadir:/etc/bitcoin"
|
||||
- "customer_lightningd_datadir:/root/.lightning"
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
merchant_lightningd:
|
||||
image: btcpayserver/lightning:v24.08.2
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
LIGHTNINGD_CHAIN: "btc"
|
||||
LIGHTNINGD_NETWORK: "signet"
|
||||
LIGHTNINGD_OPT: |
|
||||
developer
|
||||
bitcoin-datadir=/etc/bitcoin
|
||||
bitcoin-rpcconnect=bitcoind
|
||||
announce-addr=merchant_lightningd:9735
|
||||
funding-confirms=1
|
||||
log-level=debug
|
||||
dev-fast-gossip
|
||||
dev-bitcoind-poll=1
|
||||
ports:
|
||||
- "30993:9835" # api port
|
||||
- "30893:9735" # server port
|
||||
expose:
|
||||
- "9735" # server port
|
||||
- "9835" # api port
|
||||
volumes:
|
||||
- "bitcoin_datadir:/etc/bitcoin"
|
||||
- "merchant_lightningd_datadir:/root/.lightning"
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
postgres:
|
||||
image: postgres:13.13
|
||||
environment:
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
ports:
|
||||
- "39372:5432"
|
||||
expose:
|
||||
- "5432"
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.18.3-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
LND_ENVIRONMENT: "signet"
|
||||
LND_EXPLORERURL: "http://nbxplorer:32838/"
|
||||
LND_REST_LISTEN_HOST: http://merchant_lnd:8080
|
||||
LND_EXTRA_ARGS: |
|
||||
restlisten=merchant_lnd:8080
|
||||
rpclisten=127.0.0.1:10008
|
||||
rpclisten=merchant_lnd:10009
|
||||
bitcoin.node=bitcoind
|
||||
bitcoind.rpchost=bitcoind:43782
|
||||
bitcoind.rpcuser=ceiwHEbqWI83
|
||||
bitcoind.rpcpass=DwubwWsoo3
|
||||
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
|
||||
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
|
||||
externalip=merchant_lnd:9735
|
||||
bitcoin.defaultchanconfs=1
|
||||
no-macaroons=1
|
||||
debuglevel=debug
|
||||
trickledelay=1000
|
||||
no-rest-tls=1
|
||||
ports:
|
||||
- "35531:8080"
|
||||
- "30894:9735"
|
||||
- "53280:10009"
|
||||
expose:
|
||||
- "8080"
|
||||
- "9735"
|
||||
- "10009"
|
||||
volumes:
|
||||
- "merchant_lnd_datadir:/data"
|
||||
- "bitcoin_datadir:/deps/.bitcoin"
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.18.3-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
LND_ENVIRONMENT: "signet"
|
||||
LND_EXPLORERURL: "http://nbxplorer:32838/"
|
||||
LND_REST_LISTEN_HOST: http://customer_lnd:8080
|
||||
LND_EXTRA_ARGS: |
|
||||
restlisten=customer_lnd:8080
|
||||
rpclisten=127.0.0.1:10008
|
||||
rpclisten=customer_lnd:10009
|
||||
bitcoin.node=bitcoind
|
||||
bitcoind.rpchost=bitcoind:43782
|
||||
bitcoind.rpcuser=ceiwHEbqWI83
|
||||
bitcoind.rpcpass=DwubwWsoo3
|
||||
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
|
||||
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
|
||||
externalip=customer_lnd:9735
|
||||
bitcoin.defaultchanconfs=1
|
||||
no-macaroons=1
|
||||
debuglevel=debug
|
||||
trickledelay=1000
|
||||
no-rest-tls=1
|
||||
ports:
|
||||
- "35532:8080"
|
||||
- "30895:9735"
|
||||
expose:
|
||||
- "8080"
|
||||
- "9735"
|
||||
- "10009"
|
||||
volumes:
|
||||
- "customer_lnd_datadir:/root/.lnd"
|
||||
- "bitcoin_datadir:/deps/.bitcoin"
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
tor:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/tor:0.4.6.5
|
||||
container_name: tor
|
||||
environment:
|
||||
TOR_PASSWORD: btcpayserver
|
||||
ports:
|
||||
- "9050:9050" # SOCKS
|
||||
- "9051:9051" # Tor Control
|
||||
volumes:
|
||||
- "tor_datadir:/home/tor/.tor"
|
||||
- "torrcdir:/usr/local/etc/tor"
|
||||
- "tor_servicesdir:/var/lib/tor/hidden_services"
|
||||
|
||||
volumes:
|
||||
sshd_datadir:
|
||||
bitcoin_datadir:
|
||||
elementsd_liquid_datadir:
|
||||
customer_lightningd_datadir:
|
||||
merchant_lightningd_datadir:
|
||||
lightning_charge_datadir:
|
||||
customer_lnd_datadir:
|
||||
merchant_lnd_datadir:
|
||||
tor_datadir:
|
||||
torrcdir:
|
||||
tor_servicesdir:
|
||||
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
custom:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.23.0.0/16
|
288
BTCPayServer.Tests/docker-compose.testnet.yml
Normal file
288
BTCPayServer.Tests/docker-compose.testnet.yml
Normal file
@ -0,0 +1,288 @@
|
||||
version: "3"
|
||||
|
||||
# Run `docker-compose up dev` for bootstrapping your development environment
|
||||
# Doing so will expose NBXplorer, Bitcoind RPC and postgres port to the host so that tests can Run,
|
||||
# The Visual Studio launch setting `Docker-testnet` is configured to use this environment.
|
||||
services:
|
||||
|
||||
# The dev container is not actually used, it is just handy to run `docker-compose up dev` to start all services
|
||||
dev:
|
||||
image: alpine:3.7
|
||||
command: [ "/bin/sh", "-c", "trap : TERM INT; while :; do echo Ready to code and debug like a rockstar!!!; sleep 2073600; done & wait" ]
|
||||
depends_on:
|
||||
- nbxplorer
|
||||
- postgres
|
||||
- customer_lightningd
|
||||
- merchant_lightningd
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
- sshd
|
||||
- tor
|
||||
|
||||
sshd:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: sshd.Dockerfile
|
||||
ports:
|
||||
- "21622:22"
|
||||
expose:
|
||||
- 22
|
||||
volumes:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
|
||||
devlnd:
|
||||
image: btcpayserver/bitcoin:26.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: testnet
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
BITCOIN_EXTRA_ARGS: |
|
||||
deprecatedrpc=signrawtransaction
|
||||
connect=bitcoind:39388
|
||||
fallbackfee=0.0002
|
||||
rpcallowip=0.0.0.0/0
|
||||
depends_on:
|
||||
- nbxplorer
|
||||
- postgres
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
|
||||
selenium:
|
||||
image: selenium/standalone-chrome:125.0
|
||||
extra_hosts:
|
||||
- "tests:172.23.0.18"
|
||||
expose:
|
||||
- "4444"
|
||||
networks:
|
||||
default:
|
||||
custom:
|
||||
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.5.16
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
expose:
|
||||
- "32838"
|
||||
environment:
|
||||
NBXPLORER_NETWORK: testnet
|
||||
NBXPLORER_CHAINS: "btc"
|
||||
NBXPLORER_BTCRPCURL: http://bitcoind:43782/
|
||||
NBXPLORER_BTCNODEENDPOINT: bitcoind:39388
|
||||
NBXPLORER_BTCRPCUSER: ceiwHEbqWI83
|
||||
NBXPLORER_BTCRPCPASSWORD: DwubwWsoo3
|
||||
NBXPLORER_BIND: 0.0.0.0:32838
|
||||
NBXPLORER_MINGAPSIZE: 5
|
||||
NBXPLORER_MAXGAPSIZE: 10
|
||||
NBXPLORER_VERBOSE: 1
|
||||
NBXPLORER_POSTGRES: User ID=postgres;Include Error Detail=true;Host=postgres;Port=5432;Database=nbxplorer_testnet
|
||||
NBXPLORER_EXPOSERPC: 1
|
||||
NBXPLORER_NOAUTH: 1
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
bitcoind:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/bitcoin:26.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: testnet
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
BITCOIN_EXTRA_ARGS: |-
|
||||
rpcuser=ceiwHEbqWI83
|
||||
rpcpassword=DwubwWsoo3
|
||||
rpcport=43782
|
||||
rpcbind=0.0.0.0:43782
|
||||
rpcallowip=0.0.0.0/0
|
||||
port=39388
|
||||
whitelist=0.0.0.0/0
|
||||
zmqpubrawblock=tcp://0.0.0.0:28332
|
||||
zmqpubrawtx=tcp://0.0.0.0:28333
|
||||
deprecatedrpc=signrawtransaction
|
||||
fallbackfee=0.0002
|
||||
ports:
|
||||
- "43782:43782"
|
||||
- "39388:39388"
|
||||
expose:
|
||||
- "43782" # RPC
|
||||
- "39388" # P2P
|
||||
- "28332" # ZMQ
|
||||
- "28333" # ZMQ
|
||||
volumes:
|
||||
- "bitcoin_datadir:/data"
|
||||
|
||||
customer_lightningd:
|
||||
image: btcpayserver/lightning:v24.08.2
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
LIGHTNINGD_CHAIN: "btc"
|
||||
LIGHTNINGD_NETWORK: "testnet"
|
||||
LIGHTNINGD_OPT: |
|
||||
developer
|
||||
bitcoin-datadir=/etc/bitcoin
|
||||
bitcoin-rpcconnect=bitcoind
|
||||
announce-addr=customer_lightningd:9735
|
||||
log-level=debug
|
||||
funding-confirms=1
|
||||
dev-fast-gossip
|
||||
dev-bitcoind-poll=1
|
||||
ports:
|
||||
- "30992:9835" # api port
|
||||
- "30892:9735" # server port
|
||||
expose:
|
||||
- "9735" # server port
|
||||
- "9835" # api port
|
||||
volumes:
|
||||
- "bitcoin_datadir:/etc/bitcoin"
|
||||
- "customer_lightningd_datadir:/root/.lightning"
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
merchant_lightningd:
|
||||
image: btcpayserver/lightning:v24.08.2
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
LIGHTNINGD_CHAIN: "btc"
|
||||
LIGHTNINGD_NETWORK: "testnet"
|
||||
LIGHTNINGD_OPT: |
|
||||
developer
|
||||
bitcoin-datadir=/etc/bitcoin
|
||||
bitcoin-rpcconnect=bitcoind
|
||||
announce-addr=merchant_lightningd:9735
|
||||
funding-confirms=1
|
||||
log-level=debug
|
||||
dev-fast-gossip
|
||||
dev-bitcoind-poll=1
|
||||
ports:
|
||||
- "30993:9835" # api port
|
||||
- "30893:9735" # server port
|
||||
expose:
|
||||
- "9735" # server port
|
||||
- "9835" # api port
|
||||
volumes:
|
||||
- "bitcoin_datadir:/etc/bitcoin"
|
||||
- "merchant_lightningd_datadir:/root/.lightning"
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
postgres:
|
||||
image: postgres:13.13
|
||||
environment:
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
ports:
|
||||
- "39372:5432"
|
||||
expose:
|
||||
- "5432"
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.18.3-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
LND_ENVIRONMENT: "testnet"
|
||||
LND_EXPLORERURL: "http://nbxplorer:32838/"
|
||||
LND_REST_LISTEN_HOST: http://merchant_lnd:8080
|
||||
LND_EXTRA_ARGS: |
|
||||
restlisten=merchant_lnd:8080
|
||||
rpclisten=127.0.0.1:10008
|
||||
rpclisten=merchant_lnd:10009
|
||||
bitcoin.node=bitcoind
|
||||
bitcoind.rpchost=bitcoind:43782
|
||||
bitcoind.rpcuser=ceiwHEbqWI83
|
||||
bitcoind.rpcpass=DwubwWsoo3
|
||||
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
|
||||
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
|
||||
externalip=merchant_lnd:9735
|
||||
bitcoin.defaultchanconfs=1
|
||||
no-macaroons=1
|
||||
debuglevel=debug
|
||||
trickledelay=1000
|
||||
no-rest-tls=1
|
||||
ports:
|
||||
- "35531:8080"
|
||||
- "30894:9735"
|
||||
- "53280:10009"
|
||||
expose:
|
||||
- "8080"
|
||||
- "9735"
|
||||
- "10009"
|
||||
volumes:
|
||||
- "merchant_lnd_datadir:/data"
|
||||
- "bitcoin_datadir:/deps/.bitcoin"
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.18.3-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
LND_ENVIRONMENT: "testnet"
|
||||
LND_EXPLORERURL: "http://nbxplorer:32838/"
|
||||
LND_REST_LISTEN_HOST: http://customer_lnd:8080
|
||||
LND_EXTRA_ARGS: |
|
||||
restlisten=customer_lnd:8080
|
||||
rpclisten=127.0.0.1:10008
|
||||
rpclisten=customer_lnd:10009
|
||||
bitcoin.node=bitcoind
|
||||
bitcoind.rpchost=bitcoind:43782
|
||||
bitcoind.rpcuser=ceiwHEbqWI83
|
||||
bitcoind.rpcpass=DwubwWsoo3
|
||||
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
|
||||
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
|
||||
externalip=customer_lnd:9735
|
||||
bitcoin.defaultchanconfs=1
|
||||
no-macaroons=1
|
||||
debuglevel=debug
|
||||
trickledelay=1000
|
||||
no-rest-tls=1
|
||||
ports:
|
||||
- "35532:8080"
|
||||
- "30895:9735"
|
||||
expose:
|
||||
- "8080"
|
||||
- "9735"
|
||||
- "10009"
|
||||
volumes:
|
||||
- "customer_lnd_datadir:/root/.lnd"
|
||||
- "bitcoin_datadir:/deps/.bitcoin"
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
tor:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/tor:0.4.6.5
|
||||
container_name: tor
|
||||
environment:
|
||||
TOR_PASSWORD: btcpayserver
|
||||
ports:
|
||||
- "9050:9050" # SOCKS
|
||||
- "9051:9051" # Tor Control
|
||||
volumes:
|
||||
- "tor_datadir:/home/tor/.tor"
|
||||
- "torrcdir:/usr/local/etc/tor"
|
||||
- "tor_servicesdir:/var/lib/tor/hidden_services"
|
||||
|
||||
volumes:
|
||||
sshd_datadir:
|
||||
bitcoin_datadir:
|
||||
elementsd_liquid_datadir:
|
||||
customer_lightningd_datadir:
|
||||
merchant_lightningd_datadir:
|
||||
lightning_charge_datadir:
|
||||
customer_lnd_datadir:
|
||||
merchant_lnd_datadir:
|
||||
tor_datadir:
|
||||
torrcdir:
|
||||
tor_servicesdir:
|
||||
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
custom:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.23.0.0/16
|
@ -95,7 +95,7 @@ services:
|
||||
custom:
|
||||
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.5.4
|
||||
image: nicolasdorier/nbxplorer:2.5.16
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
@ -166,6 +166,7 @@ services:
|
||||
dev-bitcoind-poll=1
|
||||
ports:
|
||||
- "30992:9835" # api port
|
||||
- "30892:9735" # server port
|
||||
expose:
|
||||
- "9735" # server port
|
||||
- "9835" # api port
|
||||
@ -194,6 +195,7 @@ services:
|
||||
dev-bitcoind-poll=1
|
||||
ports:
|
||||
- "30993:9835" # api port
|
||||
- "30893:9735" # server port
|
||||
expose:
|
||||
- "9735" # server port
|
||||
- "9835" # api port
|
||||
@ -239,6 +241,7 @@ services:
|
||||
ports:
|
||||
- "35531:8080"
|
||||
- "53280:10009"
|
||||
- "30894:9735"
|
||||
expose:
|
||||
- "8080"
|
||||
- "9735"
|
||||
@ -275,6 +278,7 @@ services:
|
||||
no-rest-tls=1
|
||||
ports:
|
||||
- "35532:8080"
|
||||
- "30895:9735"
|
||||
expose:
|
||||
- "8080"
|
||||
- "9735"
|
||||
|
@ -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,19 +50,19 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BTCPayServer.NTag424" Version="1.0.23" />
|
||||
<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.6" />
|
||||
<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.4.3" />
|
||||
<PackageReference Include="QRCoder" Version="1.6.0" />
|
||||
<PackageReference Include="System.IO.Pipelines" Version="8.0.0" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.39" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
@ -60,16 +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" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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>
|
||||
|
@ -110,5 +110,10 @@ namespace BTCPayServer
|
||||
{
|
||||
return ColorTranslator.FromHtml(html);
|
||||
}
|
||||
|
||||
public string ToHtml(Color color)
|
||||
{
|
||||
return ColorTranslator.ToHtml(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,5 @@
|
||||
using System;
|
||||
using System.Security.AccessControl;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Services.Apps;
|
||||
|
||||
namespace BTCPayServer.Components.AppSales;
|
||||
|
||||
|
@ -42,11 +42,19 @@ if (!window.appSales) {
|
||||
render(data, period);
|
||||
}
|
||||
};
|
||||
|
||||
function addEventListeners() {
|
||||
delegate('change', `#${id} [name="AppSalesPeriod-${appId}"]`, async e => {
|
||||
const type = e.target.value;
|
||||
await update(type);
|
||||
});
|
||||
}
|
||||
|
||||
delegate('change', `#${id} [name="AppSalesPeriod-${appId}"]`, async e => {
|
||||
const type = e.target.value;
|
||||
await update(type);
|
||||
});
|
||||
if (document.readyState === "loading") {
|
||||
window.addEventListener("DOMContentLoaded", addEventListeners);
|
||||
} else {
|
||||
addEventListeners();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -45,31 +45,31 @@
|
||||
</li>
|
||||
@if (ViewData.IsPageActive([StoreNavPages.General, StoreNavPages.Rates, StoreNavPages.CheckoutAppearance, StoreNavPages.Tokens, StoreNavPages.Users, StoreNavPages.Roles, StoreNavPages.Webhooks, StoreNavPages.PayoutProcessors, StoreNavPages.Emails, StoreNavPages.Forms]))
|
||||
{
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.Rates))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Rates)" asp-controller="UIStores" asp-action="Rates" asp-route-storeId="@Model.Store.Id" text-translate="true">Rates</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.CheckoutAppearance))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.CheckoutAppearance)" asp-controller="UIStores" asp-action="CheckoutAppearance" asp-route-storeId="@Model.Store.Id" text-translate="true">Checkout Appearance</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.Tokens))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Tokens)" asp-controller="UIStores" asp-action="ListTokens" asp-route-storeId="@Model.Store.Id" text-translate="true">Access Tokens</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.Users))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Users)" asp-controller="UIStores" asp-action="StoreUsers" asp-route-storeId="@Model.Store.Id" text-translate="true">Users</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.Roles))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Roles)" asp-controller="UIStores" asp-action="ListRoles" asp-route-storeId="@Model.Store.Id" text-translate="true">Roles</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.Webhooks))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Webhooks)" asp-controller="UIStores" asp-action="Webhooks" asp-route-storeId="@Model.Store.Id" text-translate="true">Webhooks</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.PayoutProcessors))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.PayoutProcessors)" asp-controller="UIPayoutProcessors" asp-action="ConfigureStorePayoutProcessors" asp-route-storeId="@Model.Store.Id" text-translate="true">Payout Processors</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.Emails))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Emails)" asp-controller="UIStores" asp-action="StoreEmailSettings" asp-route-storeId="@Model.Store.Id" text-translate="true">Emails</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
|
||||
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
|
||||
<a id="StoreNav-@(nameof(StoreNavPages.Forms))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Forms)" asp-controller="UIForms" asp-action="FormsList" asp-route-storeId="@Model.Store.Id" text-translate="true">Forms</a>
|
||||
</li>
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
||||
|
@ -1,9 +1,13 @@
|
||||
@using BTCPayServer.Abstractions.TagHelpers
|
||||
@using BTCPayServer.Client.Models
|
||||
@using BTCPayServer.TagHelpers
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@model BTCPayServer.Components.StoreLightningBalance.StoreLightningBalanceViewModel
|
||||
@if(!Model.InitialRendering && Model.Balance == null)
|
||||
@if (!Model.InitialRendering && Model.Balance == null)
|
||||
{
|
||||
return;
|
||||
return;
|
||||
}
|
||||
<div id="StoreLightningBalance-@Model.Store.Id" class="widget store-lightning-balance">
|
||||
<div id="StoreLightningBalance-@Model.StoreId" class="widget store-lightning-balance">
|
||||
<div class="d-flex gap-3 align-items-center justify-content-between mb-2">
|
||||
<h6 text-translate="true">Lightning Balance</h6>
|
||||
@if (Model.CryptoCode != Model.DefaultCurrency && Model.Balance != null)
|
||||
@ -128,13 +132,30 @@
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (Model.Balance.OffchainBalance != null && Model.Balance.OnchainBalance != null)
|
||||
{
|
||||
<button class="d-inline-flex align-items-center btn btn-link text-primary fw-semibold p-0 mt-3 ms-n1" type="button" data-bs-toggle="collapse" data-bs-target=".balance-details" aria-expanded="false" aria-controls="balanceDetailsOffchain balanceDetailsOnchain">
|
||||
<vc:icon symbol="caret-down"/>
|
||||
<span class="ms-1" text-translate="true">Details</span>
|
||||
</button>
|
||||
}
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3 @(Model.Series != null ? "my-3" : "mt-3")">
|
||||
@if (Model.Balance.OffchainBalance != null && Model.Balance.OnchainBalance != null)
|
||||
{
|
||||
<button class="d-inline-flex align-items-center btn btn-link text-primary fw-semibold p-0 ms-n1" type="button" data-bs-toggle="collapse" data-bs-target=".balance-details" aria-expanded="false" aria-controls="balanceDetailsOffchain balanceDetailsOnchain">
|
||||
<vc:icon symbol="caret-down" />
|
||||
<span class="ms-1" text-translate="true">Details</span>
|
||||
</button>
|
||||
}
|
||||
@if (Model.Series != null)
|
||||
{
|
||||
<div class="btn-group only-for-js mt-1" role="group" aria-label="Period">
|
||||
<input type="radio" class="btn-check" name="StoreLightningBalancePeriod-@Model.StoreId" id="StoreLightningBalancePeriodWeek-@Model.StoreId" value="@HistogramType.Week" @(Model.Type == HistogramType.Week ? "checked" : "")>
|
||||
<label class="btn btn-link" for="StoreLightningBalancePeriodWeek-@Model.StoreId">1W</label>
|
||||
<input type="radio" class="btn-check" name="StoreLightningBalancePeriod-@Model.StoreId" id="StoreLightningBalancePeriodMonth-@Model.StoreId" value="@HistogramType.Month" @(Model.Type == HistogramType.Month ? "checked" : "")>
|
||||
<label class="btn btn-link" for="StoreLightningBalancePeriodMonth-@Model.StoreId">1M</label>
|
||||
<input type="radio" class="btn-check" name="StoreLightningBalancePeriod-@Model.StoreId" id="StoreLightningBalancePeriodYear-@Model.StoreId" value="@HistogramType.Year" @(Model.Type == HistogramType.Year ? "checked" : "")>
|
||||
<label class="btn btn-link" for="StoreLightningBalancePeriodYear-@Model.StoreId">1Y</label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="ct-chart"></div>
|
||||
<template>
|
||||
@Safe.Json(Model)
|
||||
</template>
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -143,47 +164,18 @@
|
||||
<span class="visually-hidden" text-translate="true">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<script src="~/Components/StoreLightningBalance/Default.cshtml.js" asp-append-version="true"></script>
|
||||
<script>
|
||||
(async () => {
|
||||
const url = @Safe.Json(Url.Action("LightningBalance", "UIStores", new { storeId = Model.Store.Id, cryptoCode = Model.CryptoCode }));
|
||||
const storeId = @Safe.Json(Model.Store.Id);
|
||||
const url = @Safe.Json(Model.DataUrl);
|
||||
const storeId = @Safe.Json(Model.StoreId);
|
||||
const response = await fetch(url);
|
||||
if (response.ok) {
|
||||
document.getElementById(`StoreLightningBalance-${storeId}`).outerHTML = await response.text();
|
||||
const data = document.querySelector(`#StoreLightningBalance-${storeId} template`);
|
||||
if (data) window.storeLightningBalance.dataLoaded(JSON.parse(data.innerHTML));
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
}
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
const storeId = @Safe.Json(Model.Store.Id);
|
||||
const cryptoCode = @Safe.Json(Model.CryptoCode);
|
||||
const defaultCurrency = @Safe.Json(Model.DefaultCurrency);
|
||||
const divisibility = @Safe.Json(Model.CurrencyData.Divisibility);
|
||||
const id = `StoreLightningBalance-${storeId}`;
|
||||
|
||||
const render = rate => {
|
||||
const currency = rate ? defaultCurrency : cryptoCode;
|
||||
document.querySelectorAll(`#${id} .currency`).forEach(c => c.innerText = currency)
|
||||
document.querySelectorAll(`#${id} [data-balance]`).forEach(c => {
|
||||
const value = Number.parseFloat(c.dataset.balance);
|
||||
c.innerText = rate
|
||||
? DashboardUtils.displayDefaultCurrency(value, rate, currency, divisibility)
|
||||
: value
|
||||
});
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
delegate('change', `#${id} .currency-toggle input`, async e => {
|
||||
const { target } = e;
|
||||
if (target.value === defaultCurrency) {
|
||||
const rate = await DashboardUtils.fetchRate(`${cryptoCode}_${defaultCurrency}`);
|
||||
if (rate) render(rate);
|
||||
} else {
|
||||
render(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
@ -0,0 +1,94 @@
|
||||
if (!window.storeLightningBalance) {
|
||||
window.storeLightningBalance = {
|
||||
dataLoaded (model) {
|
||||
const { storeId, cryptoCode, defaultCurrency, currencyData: { divisibility } } = model;
|
||||
const id = `StoreLightningBalance-${storeId}`;
|
||||
const valueTransform = value => rate ? DashboardUtils.displayDefaultCurrency(value, rate, defaultCurrency, divisibility) : value
|
||||
const labelCount = 6
|
||||
const tooltip = Chartist.plugins.tooltip2({
|
||||
template: '<div class="chartist-tooltip-value">{{value}}</div><div class="chartist-tooltip-line"></div>',
|
||||
offset: {
|
||||
x: 0,
|
||||
y: -16
|
||||
},
|
||||
valueTransformFunction(value, label) {
|
||||
return valueTransform(value) + ' ' + (rate ? defaultCurrency : cryptoCode)
|
||||
}
|
||||
})
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
|
||||
const dateFormatter = new Intl.DateTimeFormat('default', { month: 'short', day: 'numeric' })
|
||||
const chartOpts = {
|
||||
fullWidth: true,
|
||||
showArea: true,
|
||||
axisY: {
|
||||
showLabel: false,
|
||||
offset: 0
|
||||
},
|
||||
plugins: [tooltip]
|
||||
};
|
||||
const baseUrl = model.dataUrl;
|
||||
let data = model;
|
||||
let rate = null;
|
||||
|
||||
const render = data => {
|
||||
let { series, labels } = data;
|
||||
const currency = rate ? defaultCurrency : cryptoCode;
|
||||
document.querySelectorAll(`#${id} .currency`).forEach(c => c.innerText = currency)
|
||||
document.querySelectorAll(`#${id} [data-balance]`).forEach(c => {
|
||||
const value = Number.parseFloat(c.dataset.balance);
|
||||
c.innerText = valueTransform(value)
|
||||
});
|
||||
if (!series) return;
|
||||
|
||||
const min = Math.min(...series);
|
||||
const max = Math.max(...series);
|
||||
const low = Math.max(min - ((max - min) / 5), 0);
|
||||
const renderOpts = Object.assign({}, chartOpts, { low, axisX: {
|
||||
labelInterpolationFnc(date, i) {
|
||||
return i % labelEvery == 0 ? dateFormatter.format(new Date(date)) : null
|
||||
}
|
||||
} });
|
||||
const pointCount = series.length;
|
||||
const labelEvery = pointCount / labelCount;
|
||||
const chart = new Chartist.Line(`#${id} .ct-chart`, {
|
||||
labels: labels,
|
||||
series: [series]
|
||||
}, renderOpts);
|
||||
};
|
||||
|
||||
const update = async type => {
|
||||
const url = `${baseUrl}/${type}`;
|
||||
const response = await fetch(url);
|
||||
if (response.ok) {
|
||||
data = await response.json();
|
||||
render(data);
|
||||
}
|
||||
};
|
||||
|
||||
render(data);
|
||||
|
||||
function addEventListeners() {
|
||||
delegate('change', `#${id} [name="StoreLightningBalancePeriod-${storeId}"]`, async e => {
|
||||
const type = e.target.value;
|
||||
await update(type);
|
||||
})
|
||||
delegate('change', `#${id} .currency-toggle input`, async e => {
|
||||
const { target } = e;
|
||||
if (target.value === defaultCurrency) {
|
||||
rate = await DashboardUtils.fetchRate(`${cryptoCode}_${defaultCurrency}`);
|
||||
if (rate) render(data);
|
||||
} else {
|
||||
rate = null;
|
||||
render(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
window.addEventListener("DOMContentLoaded", addEventListeners);
|
||||
} else {
|
||||
addEventListeners();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -1,13 +1,11 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Security;
|
||||
@ -18,11 +16,14 @@ using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Components.StoreLightningBalance;
|
||||
|
||||
public class StoreLightningBalance : ViewComponent
|
||||
{
|
||||
private const HistogramType DefaultType = HistogramType.Week;
|
||||
|
||||
private readonly StoreRepository _storeRepo;
|
||||
private readonly CurrencyNameTable _currencies;
|
||||
private readonly BTCPayServerOptions _btcpayServerOptions;
|
||||
@ -32,6 +33,7 @@ public class StoreLightningBalance : ViewComponent
|
||||
private readonly IOptions<ExternalServicesOptions> _externalServiceOptions;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly LightningHistogramService _lnHistogramService;
|
||||
|
||||
public StoreLightningBalance(
|
||||
StoreRepository storeRepo,
|
||||
@ -42,7 +44,8 @@ public class StoreLightningBalance : ViewComponent
|
||||
IOptions<LightningNetworkOptions> lightningNetworkOptions,
|
||||
IOptions<ExternalServicesOptions> externalServiceOptions,
|
||||
IAuthorizationService authorizationService,
|
||||
PaymentMethodHandlerDictionary handlers)
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
LightningHistogramService lnHistogramService)
|
||||
{
|
||||
_storeRepo = storeRepo;
|
||||
_currencies = currencies;
|
||||
@ -53,31 +56,32 @@ public class StoreLightningBalance : ViewComponent
|
||||
_handlers = handlers;
|
||||
_lightningClientFactory = lightningClientFactory;
|
||||
_lightningNetworkOptions = lightningNetworkOptions;
|
||||
_lnHistogramService = lnHistogramService;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(StoreLightningBalanceViewModel vm)
|
||||
public async Task<IViewComponentResult> InvokeAsync(StoreData store, string cryptoCode, bool initialRendering)
|
||||
{
|
||||
if (vm.Store == null)
|
||||
throw new ArgumentNullException(nameof(vm.Store));
|
||||
if (vm.CryptoCode == null)
|
||||
throw new ArgumentNullException(nameof(vm.CryptoCode));
|
||||
var defaultCurrency = store.GetStoreBlob().DefaultCurrency;
|
||||
var vm = new StoreLightningBalanceViewModel
|
||||
{
|
||||
StoreId = store.Id,
|
||||
CryptoCode = cryptoCode,
|
||||
InitialRendering = initialRendering,
|
||||
DefaultCurrency = defaultCurrency,
|
||||
CurrencyData = _currencies.GetCurrencyData(defaultCurrency, true),
|
||||
DataUrl = Url.Action("LightningBalanceDashboard", "UIStores", new { storeId = store.Id, cryptoCode })
|
||||
};
|
||||
|
||||
vm.DefaultCurrency = vm.Store.GetStoreBlob().DefaultCurrency;
|
||||
vm.CurrencyData = _currencies.GetCurrencyData(vm.DefaultCurrency, true);
|
||||
if (vm.InitialRendering)
|
||||
return View(vm);
|
||||
|
||||
try
|
||||
{
|
||||
var lightningClient = await GetLightningClient(vm.Store, vm.CryptoCode);
|
||||
if (lightningClient == null)
|
||||
{
|
||||
vm.InitialRendering = false;
|
||||
return View(vm);
|
||||
}
|
||||
var lightningClient = await GetLightningClient(store, vm.CryptoCode);
|
||||
|
||||
if (vm.InitialRendering)
|
||||
return View(vm);
|
||||
|
||||
var balance = await lightningClient.GetBalance();
|
||||
// balance
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||
var balance = await lightningClient.GetBalance(cts.Token);
|
||||
vm.Balance = balance;
|
||||
vm.TotalOnchain = balance.OnchainBalance != null
|
||||
? (balance.OnchainBalance.Confirmed ?? 0L) + (balance.OnchainBalance.Reserved ?? 0L) +
|
||||
@ -87,8 +91,16 @@ public class StoreLightningBalance : ViewComponent
|
||||
? (balance.OffchainBalance.Opening ?? 0) + (balance.OffchainBalance.Local ?? 0) +
|
||||
(balance.OffchainBalance.Closing ?? 0)
|
||||
: null;
|
||||
|
||||
// histogram
|
||||
var data = await _lnHistogramService.GetHistogram(lightningClient, DefaultType, cts.Token);
|
||||
if (data != null)
|
||||
{
|
||||
vm.Type = data.Type;
|
||||
vm.Series = data.Series;
|
||||
vm.Labels = data.Labels;
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception ex) when (ex is NotImplementedException or NotSupportedException)
|
||||
{
|
||||
// not all implementations support balance fetching
|
||||
@ -102,7 +114,7 @@ public class StoreLightningBalance : ViewComponent
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
private async Task<ILightningClient> GetLightningClient(StoreData store, string cryptoCode )
|
||||
private async Task<ILightningClient> GetLightningClient(StoreData store, string cryptoCode)
|
||||
{
|
||||
var network = _networkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var id = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||
|
@ -1,4 +1,6 @@
|
||||
using BTCPayServer.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using NBitcoin;
|
||||
@ -7,13 +9,17 @@ namespace BTCPayServer.Components.StoreLightningBalance;
|
||||
|
||||
public class StoreLightningBalanceViewModel
|
||||
{
|
||||
public string StoreId { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string DefaultCurrency { get; set; }
|
||||
public CurrencyData CurrencyData { get; set; }
|
||||
public StoreData Store { get; set; }
|
||||
public Money TotalOnchain { get; set; }
|
||||
public LightMoney TotalOffchain { get; set; }
|
||||
public LightningNodeBalance Balance { get; set; }
|
||||
public string ProblemDescription { get; set; }
|
||||
public bool InitialRendering { get; set; }
|
||||
public bool InitialRendering { get; set; } = true;
|
||||
public HistogramType Type { get; set; }
|
||||
public IList<DateTimeOffset> Labels { get; set; }
|
||||
public IList<decimal> Series { get; set; }
|
||||
public string DataUrl { get; set; }
|
||||
}
|
||||
|
@ -2,14 +2,14 @@
|
||||
|
||||
@if (Model.Services != null && Model.Services.Any())
|
||||
{
|
||||
<div id="StoreLightningServices-@Model.Store.Id" class="widget store-lightning-services">
|
||||
<div id="StoreLightningServices-@Model.StoreId" class="widget store-lightning-services">
|
||||
<header class="mb-4">
|
||||
<h6 text-translate="true">Lightning Services</h6>
|
||||
<a
|
||||
asp-controller="UIPublicLightningNodeInfo"
|
||||
asp-action="ShowLightningNodeInfo"app-top-items
|
||||
asp-route-cryptoCode="@Model.CryptoCode"
|
||||
asp-route-storeId="@Model.Store.Id"
|
||||
asp-route-storeId="@Model.StoreId"
|
||||
target="_blank"
|
||||
id="PublicNodeInfo"
|
||||
text-translate="true">
|
||||
|
@ -2,15 +2,16 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
@ -20,24 +21,38 @@ public class StoreLightningServices : ViewComponent
|
||||
{
|
||||
private readonly BTCPayServerOptions _btcpayServerOptions;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
|
||||
private readonly IOptions<ExternalServicesOptions> _externalServiceOptions;
|
||||
|
||||
public StoreLightningServices(
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
BTCPayServerOptions btcpayServerOptions,
|
||||
IAuthorizationService authorizationService,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
IOptions<LightningNetworkOptions> lightningNetworkOptions,
|
||||
IOptions<ExternalServicesOptions> externalServiceOptions)
|
||||
{
|
||||
_networkProvider = networkProvider;
|
||||
_btcpayServerOptions = btcpayServerOptions;
|
||||
_lightningNetworkOptions = lightningNetworkOptions;
|
||||
_externalServiceOptions = externalServiceOptions;
|
||||
_authorizationService = authorizationService;
|
||||
_handlers = handlers;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(StoreLightningServicesViewModel vm)
|
||||
public async Task<IViewComponentResult> InvokeAsync(StoreData store, string cryptoCode)
|
||||
{
|
||||
if (vm.Store == null)
|
||||
throw new ArgumentNullException(nameof(vm.Store));
|
||||
if (vm.CryptoCode == null)
|
||||
throw new ArgumentNullException(nameof(vm.CryptoCode));
|
||||
var vm = new StoreLightningServicesViewModel { StoreId = store.Id, CryptoCode = cryptoCode };
|
||||
var id = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||
var existing = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(id, _handlers);
|
||||
if (existing?.IsInternalNode is true && _lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(cryptoCode, out _))
|
||||
{
|
||||
var result = await _authorizationService.AuthorizeAsync(HttpContext.User, null, new PolicyRequirement(Policies.CanUseInternalLightningNode));
|
||||
vm.LightningNodeType = result.Succeeded ? LightningNodeType.Internal : null;
|
||||
}
|
||||
|
||||
if (vm.LightningNodeType != LightningNodeType.Internal)
|
||||
return View(vm);
|
||||
if (!User.IsInRole(Roles.ServerAdmin))
|
||||
|
@ -8,8 +8,8 @@ namespace BTCPayServer.Components.StoreLightningServices;
|
||||
|
||||
public class StoreLightningServicesViewModel
|
||||
{
|
||||
public string StoreId { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public StoreData Store { get; set; }
|
||||
public LightningNodeType LightningNodeType { get; set; }
|
||||
public LightningNodeType? LightningNodeType { get; set; }
|
||||
public List<AdditionalServiceViewModel> Services { get; set; }
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
@using BTCPayServer.Client
|
||||
@model BTCPayServer.Components.StoreNumbers.StoreNumbersViewModel
|
||||
|
||||
<div class="widget store-numbers" id="StoreNumbers-@Model.Store.Id">
|
||||
<div class="widget store-numbers" id="StoreNumbers-@Model.StoreId">
|
||||
@if (Model.InitialRendering)
|
||||
{
|
||||
<div class="loading d-flex justify-content-center p-3">
|
||||
@ -11,8 +11,8 @@
|
||||
</div>
|
||||
<script>
|
||||
(async () => {
|
||||
const url = @Safe.Json(Url.Action("StoreNumbers", "UIStores", new { storeId = Model.Store.Id, cryptoCode = Model.CryptoCode }));
|
||||
const storeId = @Safe.Json(Model.Store.Id);
|
||||
const url = @Safe.Json(Url.Action("StoreNumbers", "UIStores", new { storeId = Model.StoreId, cryptoCode = Model.CryptoCode }));
|
||||
const storeId = @Safe.Json(Model.StoreId);
|
||||
const response = await fetch(url);
|
||||
if (response.ok) {
|
||||
document.getElementById(`StoreNumbers-${storeId}`).outerHTML = await response.text();
|
||||
@ -24,10 +24,10 @@
|
||||
{
|
||||
<div class="store-number">
|
||||
<header>
|
||||
<h6 text-translate="true">@ViewLocalizer["Paid invoices in the last {0} days", @Model.TimeframeDays]</h6>
|
||||
<h6 text-translate="true">@ViewLocalizer["Paid invoices in the last {0} days", Model.TimeframeDays]</h6>
|
||||
@if (Model.PaidInvoices > 0)
|
||||
{
|
||||
<a asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@Model.Store.Id" permission="@Policies.CanViewInvoices">View All</a>
|
||||
<a asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" permission="@Policies.CanViewInvoices" text-translate="true">View All</a>
|
||||
}
|
||||
</header>
|
||||
<div class="h3">@Model.PaidInvoices</div>
|
||||
@ -35,7 +35,7 @@
|
||||
<div class="store-number">
|
||||
<header>
|
||||
<h6 text-translate="true">Payouts Pending</h6>
|
||||
<a asp-controller="UIStorePullPayments" asp-action="Payouts" asp-route-storeId="@Model.Store.Id" permission="@Policies.CanManagePullPayments" text-translate="true">Manage</a>
|
||||
<a asp-controller="UIStorePullPayments" asp-action="Payouts" asp-route-storeId="@Model.StoreId" permission="@Policies.CanManagePullPayments" text-translate="true">Manage</a>
|
||||
</header>
|
||||
<div class="h3">@Model.PayoutsPending</div>
|
||||
</div>
|
||||
|
@ -1,19 +1,12 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Components.StoreRecentTransactions;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Dapper;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Npgsql;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Components.StoreNumbers;
|
||||
@ -34,14 +27,15 @@ public class StoreNumbers : ViewComponent
|
||||
_invoiceRepository = invoiceRepository;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(StoreNumbersViewModel vm)
|
||||
public async Task<IViewComponentResult> InvokeAsync(StoreData store, string cryptoCode, bool initialRendering)
|
||||
{
|
||||
if (vm.Store == null)
|
||||
throw new ArgumentNullException(nameof(vm.Store));
|
||||
if (vm.CryptoCode == null)
|
||||
throw new ArgumentNullException(nameof(vm.CryptoCode));
|
||||
|
||||
vm.WalletId = new WalletId(vm.Store.Id, vm.CryptoCode);
|
||||
var vm = new StoreNumbersViewModel
|
||||
{
|
||||
StoreId = store.Id,
|
||||
CryptoCode = cryptoCode,
|
||||
InitialRendering = initialRendering,
|
||||
WalletId = new WalletId(store.Id, cryptoCode)
|
||||
};
|
||||
|
||||
if (vm.InitialRendering)
|
||||
return View(vm);
|
||||
@ -50,12 +44,12 @@ public class StoreNumbers : ViewComponent
|
||||
var offset = DateTimeOffset.Now.AddDays(-vm.TimeframeDays).ToUniversalTime();
|
||||
|
||||
vm.PaidInvoices = await _invoiceRepository.GetInvoiceCount(
|
||||
new InvoiceQuery { StoreId = new [] { vm.Store.Id }, StartDate = offset, Status = new [] { "paid", "confirmed" } });
|
||||
new InvoiceQuery { StoreId = [store.Id], StartDate = offset, Status = ["paid", "confirmed"] });
|
||||
vm.PayoutsPending = await ctx.Payouts
|
||||
.Where(p => p.PullPaymentData.StoreId == vm.Store.Id && !p.PullPaymentData.Archived && p.State == PayoutState.AwaitingApproval)
|
||||
.Where(p => p.PullPaymentData.StoreId == store.Id && !p.PullPaymentData.Archived && p.State == PayoutState.AwaitingApproval)
|
||||
.CountAsync();
|
||||
vm.RefundsIssued = await ctx.Invoices
|
||||
.Where(i => i.StoreData.Id == vm.Store.Id && !i.Archived && i.Created >= offset)
|
||||
.Where(i => i.StoreData.Id == store.Id && !i.Archived && i.Created >= offset)
|
||||
.SelectMany(i => i.Refunds)
|
||||
.CountAsync();
|
||||
|
||||
|
@ -1,10 +1,8 @@
|
||||
using BTCPayServer.Data;
|
||||
|
||||
namespace BTCPayServer.Components.StoreNumbers;
|
||||
|
||||
public class StoreNumbersViewModel
|
||||
{
|
||||
public StoreData Store { get; set; }
|
||||
public string StoreId { get; set; }
|
||||
public WalletId WalletId { get; set; }
|
||||
public int PayoutsPending { get; set; }
|
||||
public int TimeframeDays { get; set; } = 7;
|
||||
|
@ -1,15 +1,14 @@
|
||||
@using BTCPayServer.Client.Models
|
||||
@using BTCPayServer.Services
|
||||
@using BTCPayServer.Services.Invoices
|
||||
@inject DisplayFormatter DisplayFormatter
|
||||
@model BTCPayServer.Components.StoreRecentInvoices.StoreRecentInvoicesViewModel
|
||||
|
||||
<div class="widget store-recent-invoices" id="StoreRecentInvoices-@Model.Store.Id">
|
||||
<div class="widget store-recent-invoices" id="StoreRecentInvoices-@Model.StoreId">
|
||||
<header>
|
||||
<h3 text-translate="true">Recent Invoices</h3>
|
||||
@if (Model.Invoices.Any())
|
||||
{
|
||||
<a asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@Model.Store.Id" text-translate="true">View All</a>
|
||||
<a asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" text-translate="true">View All</a>
|
||||
}
|
||||
</header>
|
||||
@if (Model.InitialRendering)
|
||||
@ -21,8 +20,8 @@
|
||||
</div>
|
||||
<script>
|
||||
(async () => {
|
||||
const url = @Safe.Json(Url.Action("RecentInvoices", "UIStores", new { storeId = Model.Store.Id, cryptoCode = Model.CryptoCode }));
|
||||
const storeId = @Safe.Json(Model.Store.Id);
|
||||
const url = @Safe.Json(Url.Action("RecentInvoices", "UIStores", new { storeId = Model.StoreId, cryptoCode = Model.CryptoCode }));
|
||||
const storeId = @Safe.Json(Model.StoreId);
|
||||
const response = await fetch(url);
|
||||
if (response.ok) {
|
||||
document.getElementById(`StoreRecentInvoices-${storeId}`).outerHTML = await response.text();
|
||||
@ -68,7 +67,7 @@
|
||||
<p class="text-secondary my-3" text-translate="true">
|
||||
There are no recent invoices.
|
||||
</p>
|
||||
<a asp-controller="UIInvoice" asp-action="CreateInvoice" asp-route-storeId="@Model.Store.Id" class="fw-semibold" text-translate="true">
|
||||
<a asp-controller="UIInvoice" asp-action="CreateInvoice" asp-route-storeId="@Model.StoreId" class="fw-semibold" text-translate="true">
|
||||
Create Invoice
|
||||
</a>
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
@ -9,7 +7,6 @@ using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Components.StoreRecentInvoices;
|
||||
|
||||
@ -35,12 +32,15 @@ public class StoreRecentInvoices : ViewComponent
|
||||
_dbContextFactory = dbContextFactory;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(StoreRecentInvoicesViewModel vm)
|
||||
public async Task<IViewComponentResult> InvokeAsync(StoreData store, string cryptoCode, bool initialRendering)
|
||||
{
|
||||
if (vm.Store == null)
|
||||
throw new ArgumentNullException(nameof(vm.Store));
|
||||
if (vm.CryptoCode == null)
|
||||
throw new ArgumentNullException(nameof(vm.CryptoCode));
|
||||
var vm = new StoreRecentInvoicesViewModel
|
||||
{
|
||||
StoreId = store.Id,
|
||||
CryptoCode = cryptoCode,
|
||||
InitialRendering = initialRendering
|
||||
};
|
||||
|
||||
if (vm.InitialRendering)
|
||||
return View(vm);
|
||||
|
||||
@ -48,7 +48,7 @@ public class StoreRecentInvoices : ViewComponent
|
||||
var invoiceEntities = await _invoiceRepo.GetInvoices(new InvoiceQuery
|
||||
{
|
||||
UserId = userId,
|
||||
StoreId = new[] { vm.Store.Id },
|
||||
StoreId = [store.Id],
|
||||
IncludeArchived = false,
|
||||
IncludeRefunds = true,
|
||||
Take = 5
|
||||
|
@ -5,7 +5,7 @@ namespace BTCPayServer.Components.StoreRecentInvoices;
|
||||
|
||||
public class StoreRecentInvoicesViewModel
|
||||
{
|
||||
public StoreData Store { get; set; }
|
||||
public string StoreId { get; set; }
|
||||
public IList<StoreRecentInvoiceViewModel> Invoices { get; set; } = new List<StoreRecentInvoiceViewModel>();
|
||||
public bool InitialRendering { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
|
@ -2,7 +2,7 @@
|
||||
@inject DisplayFormatter DisplayFormatter
|
||||
@model BTCPayServer.Components.StoreRecentTransactions.StoreRecentTransactionsViewModel
|
||||
|
||||
<div class="widget store-recent-transactions" id="StoreRecentTransactions-@Model.Store.Id">
|
||||
<div class="widget store-recent-transactions" id="StoreRecentTransactions-@Model.StoreId">
|
||||
<header>
|
||||
<h3 text-translate="true">Recent Transactions</h3>
|
||||
@if (Model.Transactions.Any())
|
||||
@ -19,8 +19,8 @@
|
||||
</div>
|
||||
<script>
|
||||
(async () => {
|
||||
const url = @Safe.Json(Url.Action("RecentTransactions", "UIStores", new { storeId = Model.Store.Id, cryptoCode = Model.CryptoCode }));
|
||||
const storeId = @Safe.Json(Model.Store.Id);
|
||||
const url = @Safe.Json(Url.Action("RecentTransactions", "UIStores", new { storeId = Model.StoreId, cryptoCode = Model.CryptoCode }));
|
||||
const storeId = @Safe.Json(Model.StoreId);
|
||||
const response = await fetch(url);
|
||||
if (response.ok) {
|
||||
document.getElementById(`StoreRecentTransactions-${storeId}`).outerHTML = await response.text();
|
||||
@ -36,7 +36,7 @@
|
||||
<tr>
|
||||
<th class="w-125px" text-translate="true">Date</th>
|
||||
<th text-translate="true">Transaction</th>
|
||||
<th text-translate="true">Labels</th>
|
||||
<th style="min-width:125px" text-translate="true">Labels</th>
|
||||
<th class="text-end" text-translate="true">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -1,27 +1,16 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Labels;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Dapper;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using NBXplorer.Client;
|
||||
using static BTCPayServer.Components.StoreRecentTransactions.StoreRecentTransactionsViewModel;
|
||||
|
||||
namespace BTCPayServer.Components.StoreRecentTransactions;
|
||||
|
||||
@ -47,26 +36,27 @@ public class StoreRecentTransactions : ViewComponent
|
||||
_transactionLinkProviders = transactionLinkProviders;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(StoreRecentTransactionsViewModel vm)
|
||||
public async Task<IViewComponentResult> InvokeAsync(StoreData store, string cryptoCode, bool initialRendering)
|
||||
{
|
||||
if (vm.Store == null)
|
||||
throw new ArgumentNullException(nameof(vm.Store));
|
||||
if (vm.CryptoCode == null)
|
||||
throw new ArgumentNullException(nameof(vm.CryptoCode));
|
||||
|
||||
vm.WalletId = new WalletId(vm.Store.Id, vm.CryptoCode);
|
||||
var vm = new StoreRecentTransactionsViewModel
|
||||
{
|
||||
StoreId = store.Id,
|
||||
CryptoCode = cryptoCode,
|
||||
InitialRendering = initialRendering,
|
||||
WalletId = new WalletId(store.Id, cryptoCode)
|
||||
};
|
||||
|
||||
if (vm.InitialRendering)
|
||||
return View(vm);
|
||||
|
||||
var derivationSettings = vm.Store.GetDerivationSchemeSettings(_handlers, vm.CryptoCode);
|
||||
var derivationSettings = store.GetDerivationSchemeSettings(_handlers, vm.CryptoCode);
|
||||
var transactions = new List<StoreRecentTransactionViewModel>();
|
||||
if (derivationSettings?.AccountDerivation is not null)
|
||||
{
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(vm.CryptoCode);
|
||||
var network = ((IHasNetwork)_handlers[pmi]).Network;
|
||||
var wallet = _walletProvider.GetWallet(network);
|
||||
var allTransactions = await wallet.FetchTransactionHistory(derivationSettings.AccountDerivation, 0, 5, TimeSpan.FromDays(31.0), cancellationToken: this.HttpContext.RequestAborted);
|
||||
var allTransactions = await wallet.FetchTransactionHistory(derivationSettings.AccountDerivation, 0, 5, TimeSpan.FromDays(31.0), cancellationToken: HttpContext.RequestAborted);
|
||||
var walletTransactionsInfo = await _walletRepository.GetWalletTransactionsInfo(vm.WalletId, allTransactions.Select(t => t.TransactionId.ToString()).ToArray());
|
||||
|
||||
transactions = allTransactions
|
||||
|
@ -1,11 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Data;
|
||||
|
||||
namespace BTCPayServer.Components.StoreRecentTransactions;
|
||||
|
||||
public class StoreRecentTransactionsViewModel
|
||||
{
|
||||
public StoreData Store { get; set; }
|
||||
public string StoreId { get; set; }
|
||||
public IList<StoreRecentTransactionViewModel> Transactions { get; set; } = new List<StoreRecentTransactionViewModel>();
|
||||
public WalletId WalletId { get; set; }
|
||||
public bool InitialRendering { get; set; }
|
||||
|
@ -1,16 +1,11 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
||||
namespace BTCPayServer.Components.StoreSelector
|
||||
{
|
||||
@ -38,13 +33,11 @@ namespace BTCPayServer.Components.StoreSelector
|
||||
var archivedCount = stores.Count(s => s.Archived);
|
||||
var options = stores
|
||||
.Where(store => !store.Archived)
|
||||
.Select(store =>
|
||||
new StoreSelectorOption
|
||||
.Select(store => new StoreSelectorOption
|
||||
{
|
||||
Text = store.StoreName,
|
||||
Value = store.Id,
|
||||
Selected = store.Id == currentStore?.Id,
|
||||
Store = store
|
||||
Selected = store.Id == currentStore?.Id
|
||||
})
|
||||
.OrderBy(s => s.Text)
|
||||
.ToList();
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Data;
|
||||
|
||||
namespace BTCPayServer.Components.StoreSelector
|
||||
{
|
||||
@ -17,6 +16,5 @@ namespace BTCPayServer.Components.StoreSelector
|
||||
public bool Selected { get; set; }
|
||||
public string Text { get; set; }
|
||||
public string Value { get; set; }
|
||||
public StoreData Store { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
@using BTCPayServer.Services.Wallets
|
||||
@using BTCPayServer.Payments
|
||||
@using BTCPayServer.Abstractions.TagHelpers
|
||||
@using BTCPayServer.Client.Models
|
||||
@using BTCPayServer.TagHelpers
|
||||
@model BTCPayServer.Components.StoreWalletBalance.StoreWalletBalanceViewModel
|
||||
@inject BTCPayNetworkProvider NetworkProvider
|
||||
<div id="StoreWalletBalance-@Model.Store.Id" class="widget store-wallet-balance">
|
||||
<div id="StoreWalletBalance-@Model.StoreId" class="widget store-wallet-balance">
|
||||
<div class="d-flex gap-3 align-items-center justify-content-between mb-2">
|
||||
<h6 text-translate="true">Wallet Balance</h6>
|
||||
@if (Model.CryptoCode != Model.DefaultCurrency)
|
||||
@ -26,12 +26,12 @@
|
||||
@if (Model.Series != null)
|
||||
{
|
||||
<div class="btn-group only-for-js mt-1" role="group" aria-label="Period">
|
||||
<input type="radio" class="btn-check" name="StoreWalletBalancePeriod-@Model.Store.Id" id="StoreWalletBalancePeriodWeek-@Model.Store.Id" value="@WalletHistogramType.Week" @(Model.Type == WalletHistogramType.Week ? "checked" : "")>
|
||||
<label class="btn btn-link" for="StoreWalletBalancePeriodWeek-@Model.Store.Id">1W</label>
|
||||
<input type="radio" class="btn-check" name="StoreWalletBalancePeriod-@Model.Store.Id" id="StoreWalletBalancePeriodMonth-@Model.Store.Id" value="@WalletHistogramType.Month" @(Model.Type == WalletHistogramType.Month ? "checked" : "")>
|
||||
<label class="btn btn-link" for="StoreWalletBalancePeriodMonth-@Model.Store.Id">1M</label>
|
||||
<input type="radio" class="btn-check" name="StoreWalletBalancePeriod-@Model.Store.Id" id="StoreWalletBalancePeriodYear-@Model.Store.Id" value="@WalletHistogramType.Year" @(Model.Type == WalletHistogramType.Year ? "checked" : "")>
|
||||
<label class="btn btn-link" for="StoreWalletBalancePeriodYear-@Model.Store.Id">1Y</label>
|
||||
<input type="radio" class="btn-check" name="StoreWalletBalancePeriod-@Model.StoreId" id="StoreWalletBalancePeriodWeek-@Model.StoreId" value="@HistogramType.Week" @(Model.Type == HistogramType.Week ? "checked" : "")>
|
||||
<label class="btn btn-link" for="StoreWalletBalancePeriodWeek-@Model.StoreId">1W</label>
|
||||
<input type="radio" class="btn-check" name="StoreWalletBalancePeriod-@Model.StoreId" id="StoreWalletBalancePeriodMonth-@Model.StoreId" value="@HistogramType.Month" @(Model.Type == HistogramType.Month ? "checked" : "")>
|
||||
<label class="btn btn-link" for="StoreWalletBalancePeriodMonth-@Model.StoreId">1M</label>
|
||||
<input type="radio" class="btn-check" name="StoreWalletBalancePeriod-@Model.StoreId" id="StoreWalletBalancePeriodYear-@Model.StoreId" value="@HistogramType.Year" @(Model.Type == HistogramType.Year ? "checked" : "")>
|
||||
<label class="btn btn-link" for="StoreWalletBalancePeriodYear-@Model.StoreId">1Y</label>
|
||||
</div>
|
||||
}
|
||||
</header>
|
||||
@ -39,82 +39,79 @@
|
||||
{
|
||||
<div class="ct-chart"></div>
|
||||
}
|
||||
else if (Model.Store.GetPaymentMethodConfig(PaymentTypes.CHAIN.GetPaymentMethodId(Model.CryptoCode)) is null)
|
||||
else if (Model.MissingWalletConfig)
|
||||
{
|
||||
<p>
|
||||
We would like to show you a chart of your balance but you have not yet <a href="@Url.Action("SetupWallet", "UIStores", new {storeId = Model.Store.Id, cryptoCode = Model.CryptoCode})">configured a wallet</a>.
|
||||
We would like to show you a chart of your balance but you have not yet <a href="@Url.Action("SetupWallet", "UIStores", new { storeId = Model.StoreId, cryptoCode = Model.CryptoCode })">configured a wallet</a>.
|
||||
</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>
|
||||
We would like to show you a chart of your balance.
|
||||
Please <a href="https://github.com/dgarage/NBXplorer/blob/master/docs/Postgres-Migration.md" target="_blank" rel="noreferrer noopener">migrate to the new NBXplorer backend</a>
|
||||
for that data to become available.
|
||||
We would like to show you a chart of your balance. Please set <a href="https://docs.btcpayserver.org/Deployment/ManualDeploymentExtended/#3-create-a-configuration-file" target="_blank" rel="noreferrer noopener">NBXPlorer's PostgreSQL connection string</a> to make this data available.
|
||||
</p>
|
||||
}
|
||||
<script>
|
||||
(function () {
|
||||
const storeId = @Safe.Json(Model.Store.Id);
|
||||
const storeId = @Safe.Json(Model.StoreId);
|
||||
const cryptoCode = @Safe.Json(Model.CryptoCode);
|
||||
const defaultCurrency = @Safe.Json(Model.DefaultCurrency);
|
||||
const divisibility = @Safe.Json(Model.CurrencyData.Divisibility);
|
||||
let data = { series: @Safe.Json(Model.Series), labels: @Safe.Json(Model.Labels), balance: @Safe.Json(Model.Balance) };
|
||||
let rate = null;
|
||||
|
||||
|
||||
const id = `StoreWalletBalance-${storeId}`;
|
||||
const baseUrl = @Safe.Json(Url.Action("WalletHistogram", "UIWallets", new { walletId = Model.WalletId, type = WalletHistogramType.Week }));
|
||||
const valueTransform = value => rate
|
||||
? DashboardUtils.displayDefaultCurrency(value, rate, defaultCurrency, divisibility).toString()
|
||||
: value
|
||||
const baseUrl = @Safe.Json(Url.Action("WalletHistogram", "UIWallets", new { walletId = Model.WalletId, type = HistogramType.Week }));
|
||||
const valueTransform = value => rate ? DashboardUtils.displayDefaultCurrency(value, rate, defaultCurrency, divisibility) : value
|
||||
const labelCount = 6
|
||||
const tooltip = Chartist.plugins.tooltip2({
|
||||
template: '<div class="chartist-tooltip-value">{{value}}</div><div class="chartist-tooltip-line"></div>',
|
||||
offset: {
|
||||
x: 0,
|
||||
y: -16
|
||||
},
|
||||
valueTransformFunction(value, label) {
|
||||
return valueTransform(value) + ' ' + (rate ? defaultCurrency : cryptoCode)
|
||||
}
|
||||
})
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
|
||||
const dateFormatter = new Intl.DateTimeFormat('default', { month: 'short', day: 'numeric' })
|
||||
const chartOpts = {
|
||||
fullWidth: true,
|
||||
showArea: true,
|
||||
axisY: {
|
||||
labelInterpolationFnc: valueTransform
|
||||
}
|
||||
showLabel: false,
|
||||
offset: 0
|
||||
},
|
||||
plugins: [tooltip]
|
||||
};
|
||||
|
||||
|
||||
const render = data => {
|
||||
let { series, labels } = data;
|
||||
const currency = rate ? defaultCurrency : cryptoCode;
|
||||
document.querySelectorAll(`#${id} .currency`).forEach(c => c.innerText = currency)
|
||||
document.querySelectorAll(`#${id} .currency`).forEach(c => c.innerText = currency)
|
||||
document.querySelectorAll(`#${id} [data-balance]`).forEach(c => {
|
||||
const value = Number.parseFloat(c.dataset.balance);
|
||||
c.innerText = valueTransform(value)
|
||||
});
|
||||
if (!series) return;
|
||||
|
||||
|
||||
const min = Math.min(...series);
|
||||
const max = Math.max(...series);
|
||||
const low = Math.max(min - ((max - min) / 5), 0);
|
||||
const tooltip = Chartist.plugins.tooltip2({
|
||||
template: '<div class="chartist-tooltip-value">{{value}}</div><div class="chartist-tooltip-line"></div>',
|
||||
offset: {
|
||||
x: 0,
|
||||
y: -16
|
||||
},
|
||||
valueTransformFunction: valueTransform
|
||||
})
|
||||
const renderOpts = Object.assign({}, chartOpts, { low, plugins: [tooltip] });
|
||||
const chart = new Chartist.Line(`#${id} .ct-chart`, {
|
||||
labels,
|
||||
const renderOpts = Object.assign({}, chartOpts, { low, axisX: {
|
||||
labelInterpolationFnc(date, i) {
|
||||
return i % labelEvery === 0 ? dateFormatter.format(new Date(date)) : null
|
||||
}
|
||||
} });
|
||||
const pointCount = series.length;
|
||||
const labelEvery = pointCount / labelCount;
|
||||
new Chartist.Line(`#${id} .ct-chart`, {
|
||||
labels: labels,
|
||||
series: [series]
|
||||
}, renderOpts);
|
||||
|
||||
// prevent y-axis labels from getting cut off
|
||||
window.setTimeout(() => {
|
||||
const yLabels = [...document.querySelectorAll('.ct-label.ct-vertical.ct-start')];
|
||||
if (yLabels) {
|
||||
const width = Math.max(...(yLabels.map(l => l.innerText.length * 7.5)));
|
||||
const opts = Object.assign({}, renderOpts, {
|
||||
axisY: Object.assign({}, renderOpts.axisY, { offset: width })
|
||||
});
|
||||
chart.update(null, opts);
|
||||
}
|
||||
}, 0)
|
||||
};
|
||||
|
||||
|
||||
const update = async type => {
|
||||
const url = baseUrl.replace(/\/week$/gi, `/${type}`);
|
||||
const response = await fetch(url);
|
||||
@ -123,10 +120,10 @@
|
||||
render(data);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
render(data);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
function addEventListeners() {
|
||||
delegate('change', `#${id} [name="StoreWalletBalancePeriod-${storeId}"]`, async e => {
|
||||
const type = e.target.value;
|
||||
await update(type);
|
||||
@ -141,7 +138,13 @@
|
||||
render(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
window.addEventListener("DOMContentLoaded", addEventListeners);
|
||||
} else {
|
||||
addEventListeners();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
|
@ -1,29 +1,21 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Dapper;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using NBXplorer.Client;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Components.StoreWalletBalance;
|
||||
|
||||
public class StoreWalletBalance : ViewComponent
|
||||
{
|
||||
private const WalletHistogramType DefaultType = WalletHistogramType.Week;
|
||||
private const HistogramType DefaultType = HistogramType.Week;
|
||||
|
||||
private readonly StoreRepository _storeRepo;
|
||||
private readonly CurrencyNameTable _currencies;
|
||||
@ -57,7 +49,7 @@ public class StoreWalletBalance : ViewComponent
|
||||
|
||||
var vm = new StoreWalletBalanceViewModel
|
||||
{
|
||||
Store = store,
|
||||
StoreId = store.Id,
|
||||
CryptoCode = cryptoCode,
|
||||
CurrencyData = _currencies.GetCurrencyData(defaultCurrency, true),
|
||||
DefaultCurrency = defaultCurrency,
|
||||
@ -82,6 +74,10 @@ public class StoreWalletBalance : ViewComponent
|
||||
var balance = await wallet.GetBalance(derivation.AccountDerivation, cts.Token);
|
||||
vm.Balance = balance.Available.GetValue(network);
|
||||
}
|
||||
else
|
||||
{
|
||||
vm.MissingWalletConfig = true;
|
||||
}
|
||||
}
|
||||
|
||||
return View(vm);
|
||||
|
@ -1,19 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
|
||||
namespace BTCPayServer.Components.StoreWalletBalance;
|
||||
|
||||
public class StoreWalletBalanceViewModel
|
||||
{
|
||||
public string StoreId { get; set; }
|
||||
public decimal? Balance { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string DefaultCurrency { get; set; }
|
||||
public CurrencyData CurrencyData { get; set; }
|
||||
public StoreData Store { get; set; }
|
||||
public WalletId WalletId { get; set; }
|
||||
public WalletHistogramType Type { get; set; }
|
||||
public IList<string> Labels { get; set; }
|
||||
public HistogramType Type { get; set; }
|
||||
public IList<DateTimeOffset> Labels { get; set; }
|
||||
public IList<decimal> Series { get; set; }
|
||||
public bool MissingWalletConfig { get; set; }
|
||||
}
|
||||
|
@ -62,9 +62,6 @@ namespace BTCPayServer.Configuration
|
||||
|
||||
Logs.Configuration.LogInformation("Network: " + NetworkType.ToString());
|
||||
|
||||
if (conf.GetOrDefault<bool>("launchsettings", false) && NetworkType != ChainName.Regtest)
|
||||
throw new ConfigException($"You need to run BTCPayServer with the run.sh or run.ps1 script");
|
||||
|
||||
if (conf.GetOrDefault<string>("POSTGRES", null) == null)
|
||||
{
|
||||
var allowDeprecated = conf.GetOrDefault<bool>("DEPRECATED", false);
|
||||
|
@ -10,10 +10,8 @@ using BTCPayServer.Data;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
|
@ -5,13 +5,11 @@ using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Security.Greenfield;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
|
||||
|
@ -3,7 +3,9 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
@ -15,6 +17,7 @@ using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
@ -33,13 +36,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly CurrencyNameTable _currencies;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly IFileService _fileService;
|
||||
|
||||
public GreenfieldAppsController(
|
||||
AppService appService,
|
||||
UriResolver uriResolver,
|
||||
StoreRepository storeRepository,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
CurrencyNameTable currencies,
|
||||
IFileService fileService,
|
||||
UserManager<ApplicationUser> userManager
|
||||
)
|
||||
{
|
||||
@ -47,6 +51,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
_uriResolver = uriResolver;
|
||||
_storeRepository = storeRepository;
|
||||
_currencies = currencies;
|
||||
_fileService = fileService;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
@ -190,30 +195,17 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
|
||||
[HttpGet("~/api/v1/apps/pos/{appId}")]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> GetPosApp(string appId)
|
||||
{
|
||||
var app = await _appService.GetApp(appId, PointOfSaleAppType.AppType, includeArchived: true);
|
||||
if (app == null)
|
||||
{
|
||||
return AppNotFound();
|
||||
}
|
||||
|
||||
return Ok(ToPointOfSaleModel(app));
|
||||
return app == null ? AppNotFound() : Ok(ToPointOfSaleModel(app));
|
||||
}
|
||||
|
||||
[HttpGet("~/api/v1/apps/crowdfund/{appId}")]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> GetCrowdfundApp(string appId)
|
||||
{
|
||||
var app = await _appService.GetApp(appId, CrowdfundAppType.AppType, includeArchived: true);
|
||||
if (app == null)
|
||||
{
|
||||
return AppNotFound();
|
||||
}
|
||||
|
||||
var model = await ToCrowdfundModel(app);
|
||||
return Ok(model);
|
||||
return app == null ? AppNotFound() : Ok(await ToCrowdfundModel(app));
|
||||
}
|
||||
|
||||
[HttpDelete("~/api/v1/apps/{appId}")]
|
||||
@ -221,10 +213,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
public async Task<IActionResult> DeleteApp(string appId)
|
||||
{
|
||||
var app = await _appService.GetApp(appId, null, includeArchived: true);
|
||||
if (app == null)
|
||||
{
|
||||
return AppNotFound();
|
||||
}
|
||||
if (app == null) return AppNotFound();
|
||||
|
||||
await _appService.DeleteApp(app);
|
||||
|
||||
@ -254,6 +243,57 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
var items = stats.GetRange(offset, max);
|
||||
return Ok(items);
|
||||
}
|
||||
|
||||
[HttpPost("~/api/v1/apps/{appId}/image")]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> UploadAppItemImage(string appId, IFormFile? file)
|
||||
{
|
||||
var app = await _appService.GetApp(appId, null, includeArchived: true);
|
||||
var userId = _userManager.GetUserId(User);
|
||||
if (app == null || userId == null) return AppNotFound();
|
||||
|
||||
UploadImageResultModel? upload = null;
|
||||
if (file is null)
|
||||
ModelState.AddModelError(nameof(file), "Invalid file");
|
||||
else
|
||||
{
|
||||
upload = await _fileService.UploadImage(file, userId, 500_000);
|
||||
if (!upload.Success)
|
||||
ModelState.AddModelError(nameof(file), upload.Response);
|
||||
}
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
|
||||
try
|
||||
{
|
||||
var storedFile = upload!.StoredFile!;
|
||||
var fileData = new FileData
|
||||
{
|
||||
Id = storedFile.Id,
|
||||
UserId = storedFile.ApplicationUserId,
|
||||
Url = await _fileService.GetFileUrl(Request.GetAbsoluteRootUri(), storedFile.Id),
|
||||
OriginalName = storedFile.FileName,
|
||||
StorageName = storedFile.StorageFileName,
|
||||
CreatedAt = storedFile.Timestamp
|
||||
};
|
||||
return Ok(fileData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return this.CreateAPIError(404, "file-upload-failed", e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpDelete("~/api/v1/apps/{appId}/image/{fileId}")]
|
||||
public async Task<IActionResult> DeleteAppItemImage(string appId, string fileId)
|
||||
{
|
||||
var app = await _appService.GetApp(appId, null, includeArchived: true);
|
||||
var userId = _userManager.GetUserId(User);
|
||||
if (app == null || userId == null) return AppNotFound();
|
||||
if (!string.IsNullOrEmpty(fileId)) await _fileService.RemoveFile(fileId, userId);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
private IActionResult AppNotFound()
|
||||
{
|
||||
@ -355,6 +395,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
var settings = appData.GetSettings<PointOfSaleSettings>();
|
||||
Enum.TryParse<PosViewType>(settings.DefaultView.ToString(), true, out var defaultView);
|
||||
var items = AppService.Parse(settings.Template);
|
||||
|
||||
return new PointOfSaleAppData
|
||||
{
|
||||
@ -382,16 +423,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
RedirectUrl = settings.RedirectUrl,
|
||||
Description = settings.Description,
|
||||
RedirectAutomatically = settings.RedirectAutomatically,
|
||||
Items = JsonConvert.DeserializeObject(
|
||||
JsonConvert.SerializeObject(
|
||||
AppService.Parse(settings.Template),
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver =
|
||||
new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
|
||||
}
|
||||
)
|
||||
)
|
||||
Items = items
|
||||
};
|
||||
}
|
||||
|
||||
@ -420,6 +452,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
var settings = appData.GetSettings<CrowdfundSettings>();
|
||||
Enum.TryParse<CrowdfundResetEvery>(settings.ResetEvery.ToString(), true, out var resetEvery);
|
||||
var perks = AppService.Parse(settings.PerksTemplate);
|
||||
|
||||
return new CrowdfundAppData
|
||||
{
|
||||
@ -451,15 +484,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
SortPerksByPopularity = settings.SortPerksByPopularity,
|
||||
Sounds = settings.Sounds,
|
||||
AnimationColors = settings.AnimationColors,
|
||||
Perks = JsonConvert.DeserializeObject(
|
||||
JsonConvert.SerializeObject(
|
||||
AppService.Parse(settings.PerksTemplate),
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
|
||||
}
|
||||
)
|
||||
)
|
||||
Perks = perks
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -12,10 +12,8 @@ using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Security.Greenfield;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
@ -25,9 +23,6 @@ using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using CreateInvoiceRequest = BTCPayServer.Client.Models.CreateInvoiceRequest;
|
||||
using InvoiceData = BTCPayServer.Client.Models.InvoiceData;
|
||||
@ -600,12 +595,13 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
statuses.Add(InvoiceStatus.Settled);
|
||||
}
|
||||
|
||||
if (state.CanMarkInvalid())
|
||||
{
|
||||
statuses.Add(InvoiceStatus.Invalid);
|
||||
}
|
||||
return new InvoiceData()
|
||||
var store = request?.HttpContext.GetStoreData();
|
||||
var receipt = store == null ? entity.ReceiptOptions : InvoiceDataBase.ReceiptOptions.Merge(store.GetStoreBlob().ReceiptOptions, entity.ReceiptOptions);
|
||||
return new InvoiceData
|
||||
{
|
||||
StoreId = entity.StoreId,
|
||||
ExpirationTime = entity.ExpirationTime,
|
||||
@ -621,7 +617,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
Archived = entity.Archived,
|
||||
Metadata = entity.Metadata.ToJObject(),
|
||||
AvailableStatusesForManualMarking = statuses.ToArray(),
|
||||
Checkout = new CreateInvoiceRequest.CheckoutOptions()
|
||||
Checkout = new InvoiceDataBase.CheckoutOptions
|
||||
{
|
||||
Expiration = entity.ExpirationTime - entity.InvoiceTime,
|
||||
Monitoring = entity.MonitoringExpiration - entity.ExpirationTime,
|
||||
@ -634,7 +630,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
RedirectAutomatically = entity.RedirectAutomatically,
|
||||
RedirectURL = entity.RedirectURLTemplate
|
||||
},
|
||||
Receipt = entity.ReceiptOptions
|
||||
Receipt = receipt
|
||||
};
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user