Compare commits
511 Commits
v1.0.3.164
...
torproxy
Author | SHA1 | Date | |
---|---|---|---|
effbf169d3 | |||
74b6aa7353 | |||
0e0297d650 | |||
8dd6ecc0b8 | |||
e84d4d3cb5 | |||
da54154ae7 | |||
a9dbbe1955 | |||
3fbe86c286 | |||
0022e71dd4 | |||
c74121fefc | |||
cf00784096 | |||
db209af787 | |||
30bd94ee43 | |||
f313a5f221 | |||
b9ef5af5d7 | |||
56283df05a | |||
ef271a088a | |||
6043b7c75d | |||
c338779f0e | |||
67758609a7 | |||
f2bb24f6ab | |||
44ee7c66ce | |||
117622200b | |||
340ba5c714 | |||
9e16b506e5 | |||
e865d85c2c | |||
284e66c730 | |||
587f244f1d | |||
379e5741e7 | |||
ec4c346e0a | |||
5099f98d2a | |||
ebc99adc58 | |||
048dff7e9e | |||
a70934938a | |||
d9a93f996f | |||
3456d87bb5 | |||
4947fa4d45 | |||
37f4b34b5e | |||
66206399e1 | |||
1e3f62718d | |||
114ab98059 | |||
47baa219fd | |||
96509717cb | |||
89bbcb6092 | |||
95dbb65612 | |||
7db8d52624 | |||
77073fa78c | |||
9e315f99d0 | |||
16ad3ee7fe | |||
559015c70d | |||
15d02dab4f | |||
332a1da167 | |||
e12aa9e657 | |||
9503e07dc8 | |||
9b44370832 | |||
568015c58f | |||
58f56eac90 | |||
03532e4063 | |||
de6081c2dc | |||
197b46880c | |||
b7ec22ee89 | |||
fad53a9fa2 | |||
5b4fec11ed | |||
0ef4602a65 | |||
c8f182f13f | |||
36630d9586 | |||
32f2acee53 | |||
3be2ef5c91 | |||
7e5d269c39 | |||
6e0c090622 | |||
cccf3ca617 | |||
9e9b5945fe | |||
77f6019d82 | |||
35432d919c | |||
25e6f82aa3 | |||
d22993871f | |||
80563de587 | |||
b5102c4269 | |||
9d215ea27d | |||
cdf6886c39 | |||
79b034b505 | |||
bfba105aec | |||
1bbdaa1251 | |||
ca462bec84 | |||
eb5dcab32d | |||
ca3acdacdc | |||
33f63508e8 | |||
5033cb3186 | |||
3d1122be7c | |||
1d092a15fb | |||
501a21b89e | |||
b9006e4417 | |||
5b3b96b372 | |||
338a0f9251 | |||
29ba761d7c | |||
b96e668dfd | |||
de3753d04e | |||
2a030fe7fb | |||
f595d823b6 | |||
78d191f7d8 | |||
3b6dbe76c5 | |||
e3b348b55c | |||
cf012a7946 | |||
8d1ff01ee8 | |||
e9bda50054 | |||
8ca2824b00 | |||
f7d70daff3 | |||
42d27d69dc | |||
5a9d1e3257 | |||
9cc9a16bb9 | |||
a3efe9a9dc | |||
94b0b9498d | |||
bb322d6bf3 | |||
b0f820e95a | |||
da588380ed | |||
8fa65408ed | |||
2d68d0da63 | |||
2bc7fa0316 | |||
55f416c48c | |||
aff91f49ef | |||
ba02372d13 | |||
e5a3ef3e22 | |||
137c3ef2ce | |||
87352f0b62 | |||
2226884946 | |||
9d2cd46464 | |||
f3b2b350ce | |||
96c04481da | |||
dad2642fa7 | |||
59bdb943dd | |||
67da6ee379 | |||
9c9c102e74 | |||
26241be6fa | |||
2bb4dd5d01 | |||
5312bb1dee | |||
bf6f5aa335 | |||
4a58763f98 | |||
b8202da7aa | |||
edfc82ac75 | |||
b28fc85974 | |||
ab1b36bcdc | |||
5443ac4688 | |||
d92d8ba0e4 | |||
0f19d303eb | |||
1332f597e5 | |||
de004074b7 | |||
29741f39ac | |||
33ea8984fc | |||
85517b0344 | |||
74c574255e | |||
70d4e98dff | |||
f1900d30f2 | |||
463567cb07 | |||
53b0e675c3 | |||
d323bb35cc | |||
3e13e478ad | |||
5f421b0679 | |||
c99fe54db1 | |||
05a2985c5b | |||
47408498b9 | |||
e75b4ec6bf | |||
ff99ab1239 | |||
2841cd8498 | |||
519f4af867 | |||
68cc3aba21 | |||
3a2970a495 | |||
b31fb1a269 | |||
3801eeec43 | |||
94cdd399d5 | |||
c784144a07 | |||
b600e5777e | |||
e49074d797 | |||
02d26467f9 | |||
e68b45c76a | |||
f410f7d4d1 | |||
d4dbe6fe17 | |||
c7305ba5e1 | |||
e11963aca0 | |||
2d77426e04 | |||
7e0f9e1d28 | |||
18e181bb9f | |||
c3c9585a95 | |||
4b5b941761 | |||
79c70b31a3 | |||
072139f707 | |||
9d80db98c5 | |||
a5df029d43 | |||
47f16aadd5 | |||
f8b2b18c6e | |||
4be6c06af5 | |||
92c58eea7f | |||
5e6049bf3f | |||
7adaa146dc | |||
e3b51f593e | |||
e64094dfcc | |||
cb6fcadb86 | |||
297b84a18b | |||
b7c0e049b5 | |||
aeef160d0b | |||
34c1a304a9 | |||
4f1ae4733c | |||
e009c1a25a | |||
79f12a7058 | |||
deb197cfa5 | |||
2710130667 | |||
48163961ed | |||
8658cb5f29 | |||
a6a56e4791 | |||
22e39998e2 | |||
70d1056d48 | |||
3bd5c3e1b5 | |||
0a1a4fd3b5 | |||
e508b22d34 | |||
a7815f107e | |||
ded5670108 | |||
c1ffeb331b | |||
ad1148d3e2 | |||
a7b926d907 | |||
1f7a821c09 | |||
bf45edb5d8 | |||
686f5bf151 | |||
426fe793e6 | |||
aee55103a3 | |||
778bf97079 | |||
2dcb3111f8 | |||
03d1f98402 | |||
34755b32dc | |||
6679ee1ca2 | |||
8420c74b31 | |||
0077105a2d | |||
51db617584 | |||
514b695907 | |||
b470ce2dad | |||
161850150a | |||
b02cfa9d41 | |||
dfe655393d | |||
8c81dae167 | |||
de75d30f06 | |||
8ba99d4e7c | |||
c3bc25a7d4 | |||
42be03b560 | |||
e73aece9c3 | |||
69c57867b3 | |||
7434163848 | |||
00d1c4ebcc | |||
26067fbfe2 | |||
03458efea4 | |||
bd21bf9c0f | |||
5b7a20c33e | |||
c73c34dfaa | |||
a3a9361ba5 | |||
2f0e9569a1 | |||
511a0efa89 | |||
4ae91ba307 | |||
3a70f467eb | |||
4e09bb0b01 | |||
5ae18cf21f | |||
6bfb6a795e | |||
1d2540543b | |||
9efe6267d3 | |||
cb10551d2c | |||
b0073af5aa | |||
7ca7f53446 | |||
ad284a4b61 | |||
95e7d5ded9 | |||
55722b3191 | |||
5e34efc9f4 | |||
6f85ffd9df | |||
9783a76c38 | |||
05952f95f1 | |||
b9c97cc5d7 | |||
d5b088b924 | |||
fbd5673cfd | |||
dbb7ad083a | |||
ce7e4234cc | |||
d2b38fdfce | |||
fcdcc5e69b | |||
8d73606809 | |||
15a7c4d092 | |||
c205e41072 | |||
48c220b751 | |||
06ff268644 | |||
9ee920a816 | |||
a403363015 | |||
6274958409 | |||
d47e225dce | |||
841cf61c92 | |||
2d2c5b46af | |||
cc80e4636f | |||
bb24c95e71 | |||
c7a4158a39 | |||
ed0e423aa7 | |||
1c0d713b00 | |||
70c80f4d44 | |||
e3f6de8472 | |||
95644f8884 | |||
f57db12c09 | |||
0d821ff4db | |||
6927d81175 | |||
e183714475 | |||
6602823067 | |||
111feeb673 | |||
3bf1b78b33 | |||
751ccc333f | |||
624e6e4744 | |||
73b13c750d | |||
148b04e9ba | |||
32938479ac | |||
aae0086e68 | |||
1f4556bd9d | |||
d7bb15cac3 | |||
9a54445785 | |||
523edfef58 | |||
5a841216b8 | |||
d6d58a98db | |||
6a0cda69c9 | |||
24691e5290 | |||
b203d369fb | |||
b1cc30d25d | |||
72e64885be | |||
f4a47f5197 | |||
fe45152529 | |||
9a9773853e | |||
9d2ab8b154 | |||
ba2184e21a | |||
829b0dd5e2 | |||
9fc451c9ca | |||
452568e740 | |||
5503132ffc | |||
17a6b7d34f | |||
69ad9edc9a | |||
80bb959ac3 | |||
1e0587af26 | |||
16e35e8b55 | |||
8278926e42 | |||
1debbc3cdb | |||
65c99ead1d | |||
2693dacae6 | |||
7ce614f1c4 | |||
79a0f97abb | |||
670e0ee7df | |||
3f231a8894 | |||
f5dfee7642 | |||
ab120c5dcb | |||
f085a5618b | |||
d939baac84 | |||
41d70e8462 | |||
9af7edf8b8 | |||
01a8c20ee8 | |||
4c966e2a09 | |||
42aead3c89 | |||
a348960041 | |||
c737a25234 | |||
a01b2e4a83 | |||
5f838db281 | |||
08beffb005 | |||
76b919d887 | |||
c106ac2c42 | |||
4a1fb71e09 | |||
98e2baae19 | |||
963c69a0e0 | |||
fd026a9733 | |||
e76785a64e | |||
1a8f222e46 | |||
24d26d7a44 | |||
c86370c25a | |||
20cba1d3a1 | |||
3e2efc7f27 | |||
d2c29aaec6 | |||
bb1c5dead5 | |||
41cc79600a | |||
238d4fceea | |||
c6d75de3d7 | |||
9e1ae29600 | |||
d60b00e8cd | |||
49786f4195 | |||
7b6eae6053 | |||
a408541eb3 | |||
1ba25448cc | |||
4d2e59e1a1 | |||
7b4f686add | |||
ee0ef2881a | |||
22f79e9fe4 | |||
fdad5a47d5 | |||
e32f3cbf80 | |||
b56d026fdb | |||
64717328f6 | |||
065be9be64 | |||
10fcfab233 | |||
ff9865c516 | |||
59bae2c337 | |||
89d9793692 | |||
23b2f55b47 | |||
886510c2e1 | |||
2b11b43d6d | |||
d90ffb2254 | |||
fc88a867fa | |||
e4cb1a875b | |||
1a62ee9260 | |||
56d5e6f99f | |||
f1821636db | |||
2e3a0706ee | |||
89da4184ff | |||
1895e154d9 | |||
6d7b57ea3b | |||
39a8c3fe47 | |||
927c09ff7b | |||
08abda1522 | |||
d219ba5d32 | |||
afdee9d8a2 | |||
ac14f199e4 | |||
76818fa385 | |||
49be370e51 | |||
fbe89f1784 | |||
b7afcb90a2 | |||
a6ac67963e | |||
bde8ed7aa2 | |||
ca234838a3 | |||
d54d340bef | |||
a926a5eedf | |||
0df5e7d7a3 | |||
034fb4ec80 | |||
69482eb4fb | |||
10e52f08be | |||
5565d8dae5 | |||
c633402fe2 | |||
0688feea3c | |||
c906fd42df | |||
6468b39121 | |||
d0a95f5a69 | |||
e36338d903 | |||
e596513fc1 | |||
77588182b9 | |||
ca00caa4a4 | |||
36bd76248b | |||
f0f05acdfd | |||
6df7ffd7e2 | |||
91924512e6 | |||
7899c2d5c5 | |||
56ba834ca2 | |||
d57fdd4785 | |||
805e1f53b3 | |||
40953ef2c6 | |||
ff055c08fb | |||
f3d5cf3622 | |||
e48e8c34d9 | |||
98a48cd0a5 | |||
f8f358ebdb | |||
9d99c32305 | |||
478b1463ff | |||
7e7f0053e2 | |||
9a940a044e | |||
d2864ccd7c | |||
ad4dbdad6d | |||
094307d688 | |||
53e7c84e73 | |||
2a865284da | |||
4666238e38 | |||
b54a7b80e3 | |||
432d6bb261 | |||
fb36ed2cae | |||
55516a3253 | |||
a0e638d500 | |||
2def9e7bd3 | |||
0bfc12ae3d | |||
318d826694 | |||
44b3bb34a4 | |||
46edc281b6 | |||
d72139c2c1 | |||
29a807696b | |||
517c65f1fc | |||
8f18be727b | |||
d6c66d0c03 | |||
eac33d494a | |||
2105b44610 | |||
ab74013a05 | |||
967b02e373 | |||
8432cd5477 | |||
ccfca65c41 | |||
0a8abaf7d5 | |||
47c1164003 | |||
65d26ad8a1 | |||
0a0d8d53a4 | |||
e50e3f662d | |||
540a31207e | |||
132c36df7b | |||
e351e0c9ea | |||
8d7b9fcef2 | |||
6e1f3989e8 | |||
e99767c7e2 | |||
c85fb3e89f | |||
348934488d | |||
6c8918a308 | |||
ff2ea5815c | |||
cc0202ecb3 | |||
0c065df4bd | |||
b5664dac81 | |||
8173296c96 | |||
71a00c0e67 | |||
70b172addc | |||
2002c6750b | |||
786be9d1f5 | |||
233fa8a4a1 | |||
c74f52a61c | |||
245507f821 | |||
5495c4b5d3 | |||
afd2c8e3d7 | |||
c8e1db2102 | |||
95f859b6db | |||
6bf7ef0798 | |||
42152050a3 |
@ -119,22 +119,23 @@ workflows:
|
||||
# ignore any commit on any branch by default
|
||||
branches:
|
||||
ignore: /.*/
|
||||
# only act on version tags v1.0.0.88
|
||||
# only act on version tags v1.0.0.88 or v1.0.2-1
|
||||
# OR feature tags like vlndseedbackup
|
||||
tags:
|
||||
only: /(v[1-9]+(\.[0-9]+)*)|(v[a-z]+)/
|
||||
|
||||
only: /(v[1-9]+(\.[0-9]+)*(-[0-9]+)?)|(v[a-z]+)/
|
||||
- arm32v7:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /(v[1-9]+(\.[0-9]+)*)|(v[a-z]+)/
|
||||
only: /(v[1-9]+(\.[0-9]+)*(-[0-9]+)?)|(v[a-z]+)/
|
||||
- arm64v8:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /(v[1-9]+(\.[0-9]+)*)|(v[a-z]+)/
|
||||
only: /(v[1-9]+(\.[0-9]+)*(-[0-9]+)?)|(v[a-z]+)/
|
||||
- multiarch:
|
||||
requires:
|
||||
- amd64
|
||||
@ -144,4 +145,4 @@ workflows:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /(v[1-9]+(\.[0-9]+)*)|(v[a-z]+)/
|
||||
only: /(v[1-9]+(\.[0-9]+)*(-[0-9]+)?)|(v[a-z]+)/
|
||||
|
13
BTCPayServer.Client/BTCPayServer.Client.csproj
Normal file
13
BTCPayServer.Client/BTCPayServer.Client.csproj
Normal file
@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NBitcoin" Version="5.0.40" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
39
BTCPayServer.Client/BTCPayServerClient.APIKeys.cs
Normal file
39
BTCPayServer.Client/BTCPayServerClient.APIKeys.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<ApiKeyData> GetCurrentAPIKeyInfo(CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/api-keys/current"), token);
|
||||
return await HandleResponse<ApiKeyData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<ApiKeyData> CreateAPIKey(CreateApiKeyRequest request, CancellationToken token = default)
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/api-keys", bodyPayload: request, method: HttpMethod.Post), token);
|
||||
return await HandleResponse<ApiKeyData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task RevokeCurrentAPIKeyInfo(CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/api-keys/current", null, HttpMethod.Delete), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public virtual async Task RevokeAPIKey(string apikey, CancellationToken token = default)
|
||||
{
|
||||
if (apikey == null)
|
||||
throw new ArgumentNullException(nameof(apikey));
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/api-keys/{apikey}", null, HttpMethod.Delete), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
}
|
||||
}
|
24
BTCPayServer.Client/BTCPayServerClient.Authorization.cs
Normal file
24
BTCPayServer.Client/BTCPayServerClient.Authorization.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
|
||||
public static Uri GenerateAuthorizeUri(Uri btcpayHost, string[] permissions, bool strict = true,
|
||||
bool selectiveStores = false)
|
||||
{
|
||||
var result = new UriBuilder(btcpayHost);
|
||||
result.Path = "api-keys/authorize";
|
||||
|
||||
AppendPayloadToQuery(result,
|
||||
new Dictionary<string, object>()
|
||||
{
|
||||
{"strict", strict}, {"selectiveStores", selectiveStores}, {"permissions", permissions}
|
||||
});
|
||||
|
||||
return result.Uri;
|
||||
}
|
||||
}
|
||||
}
|
16
BTCPayServer.Client/BTCPayServerClient.Health.cs
Normal file
16
BTCPayServer.Client/BTCPayServerClient.Health.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<ApiHealthData> GetHealth(CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/health"), token);
|
||||
return await HandleResponse<ApiHealthData>(response);
|
||||
}
|
||||
}
|
||||
}
|
91
BTCPayServer.Client/BTCPayServerClient.Lightning.Internal.cs
Normal file
91
BTCPayServer.Client/BTCPayServerClient.Lightning.Internal.cs
Normal file
@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public async Task<LightningNodeInformationData> GetLightningNodeInfo(string cryptoCode,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/info",
|
||||
method: HttpMethod.Get), token);
|
||||
return await HandleResponse<LightningNodeInformationData>(response);
|
||||
}
|
||||
|
||||
public async Task ConnectToLightningNode(string cryptoCode, ConnectToNodeRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/connect", bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LightningChannelData>> GetLightningNodeChannels(string cryptoCode,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/channels",
|
||||
method: HttpMethod.Get), token);
|
||||
return await HandleResponse<IEnumerable<LightningChannelData>>(response);
|
||||
}
|
||||
|
||||
public async Task<string> OpenLightningChannel(string cryptoCode, OpenLightningChannelRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/channels", bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
return await HandleResponse<string>(response);
|
||||
}
|
||||
|
||||
public async Task<string> GetLightningDepositAddress(string cryptoCode, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/address", method: HttpMethod.Post), token);
|
||||
return await HandleResponse<string>(response);
|
||||
}
|
||||
|
||||
|
||||
public async Task PayLightningInvoice(string cryptoCode, PayLightningInvoiceRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/invoices/pay", bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public async Task<LightningInvoiceData> GetLightningInvoice(string cryptoCode,
|
||||
string invoiceId, CancellationToken token = default)
|
||||
{
|
||||
if (invoiceId == null)
|
||||
throw new ArgumentNullException(nameof(invoiceId));
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/invoices/{invoiceId}",
|
||||
method: HttpMethod.Get), token);
|
||||
return await HandleResponse<LightningInvoiceData>(response);
|
||||
}
|
||||
|
||||
public async Task<LightningInvoiceData> CreateLightningInvoice(string cryptoCode, CreateLightningInvoiceRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/invoices", bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
return await HandleResponse<LightningInvoiceData>(response);
|
||||
}
|
||||
}
|
||||
}
|
93
BTCPayServer.Client/BTCPayServerClient.Lightning.Store.cs
Normal file
93
BTCPayServer.Client/BTCPayServerClient.Lightning.Store.cs
Normal file
@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Lightning;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public async Task<LightningNodeInformationData> GetLightningNodeInfo(string storeId, string cryptoCode,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/info",
|
||||
method: HttpMethod.Get), token);
|
||||
return await HandleResponse<LightningNodeInformationData>(response);
|
||||
}
|
||||
|
||||
public async Task ConnectToLightningNode(string storeId, string cryptoCode, ConnectToNodeRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/connect", bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LightningChannelData>> GetLightningNodeChannels(string storeId, string cryptoCode,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/channels",
|
||||
method: HttpMethod.Get), token);
|
||||
return await HandleResponse<IEnumerable<LightningChannelData>>(response);
|
||||
}
|
||||
|
||||
public async Task<string> OpenLightningChannel(string storeId, string cryptoCode, OpenLightningChannelRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/channels", bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
return await HandleResponse<string>(response);
|
||||
}
|
||||
|
||||
public async Task<string> GetLightningDepositAddress(string storeId, string cryptoCode,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/address", method: HttpMethod.Post),
|
||||
token);
|
||||
return await HandleResponse<string>(response);
|
||||
}
|
||||
|
||||
public async Task PayLightningInvoice(string storeId, string cryptoCode, PayLightningInvoiceRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices/pay", bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public async Task<LightningInvoiceData> GetLightningInvoice(string storeId, string cryptoCode,
|
||||
string invoiceId, CancellationToken token = default)
|
||||
{
|
||||
if (invoiceId == null)
|
||||
throw new ArgumentNullException(nameof(invoiceId));
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices/{invoiceId}",
|
||||
method: HttpMethod.Get), token);
|
||||
return await HandleResponse<LightningInvoiceData>(response);
|
||||
}
|
||||
|
||||
public async Task<LightningInvoiceData> CreateLightningInvoice(string storeId, string cryptoCode,
|
||||
CreateLightningInvoiceRequest request, CancellationToken token = default)
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices", bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
return await HandleResponse<LightningInvoiceData>(response);
|
||||
}
|
||||
}
|
||||
}
|
59
BTCPayServer.Client/BTCPayServerClient.PaymentRequests.cs
Normal file
59
BTCPayServer.Client/BTCPayServerClient.PaymentRequests.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<IEnumerable<PaymentRequestData>> GetPaymentRequests(string storeId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/payment-requests"), token);
|
||||
return await HandleResponse<IEnumerable<PaymentRequestData>>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<PaymentRequestData> GetPaymentRequest(string storeId, string paymentRequestId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-requests/{paymentRequestId}"), token);
|
||||
return await HandleResponse<PaymentRequestData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task ArchivePaymentRequest(string storeId, string paymentRequestId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-requests/{paymentRequestId}",
|
||||
method: HttpMethod.Delete), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public virtual async Task<PaymentRequestData> CreatePaymentRequest(string storeId,
|
||||
CreatePaymentRequestRequest request, CancellationToken token = default)
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-requests", bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
return await HandleResponse<PaymentRequestData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<PaymentRequestData> UpdatePaymentRequest(string storeId, string paymentRequestId,
|
||||
UpdatePaymentRequestRequest request, CancellationToken token = default)
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-requests/{paymentRequestId}", bodyPayload: request,
|
||||
method: HttpMethod.Put), token);
|
||||
return await HandleResponse<PaymentRequestData>(response);
|
||||
}
|
||||
}
|
||||
}
|
16
BTCPayServer.Client/BTCPayServerClient.ServerInfo.cs
Normal file
16
BTCPayServer.Client/BTCPayServerClient.ServerInfo.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<ServerInfoData> GetServerInfo(CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/server/info"), token);
|
||||
return await HandleResponse<ServerInfoData>(response);
|
||||
}
|
||||
}
|
||||
}
|
51
BTCPayServer.Client/BTCPayServerClient.Stores.cs
Normal file
51
BTCPayServer.Client/BTCPayServerClient.Stores.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<IEnumerable<StoreData>> GetStores(CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/stores"), token);
|
||||
return await HandleResponse<IEnumerable<StoreData>>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<StoreData> GetStore(string storeId, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}"), token);
|
||||
return await HandleResponse<StoreData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task RemoveStore(string storeId, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}", method: HttpMethod.Delete), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public virtual async Task<StoreData> CreateStore(CreateStoreRequest request, CancellationToken token = default)
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/stores", bodyPayload: request, method: HttpMethod.Post), token);
|
||||
return await HandleResponse<StoreData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<StoreData> UpdateStore(string storeId, UpdateStoreRequest request, CancellationToken token = default)
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
if (storeId == null)
|
||||
throw new ArgumentNullException(nameof(storeId));
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}", bodyPayload: request, method: HttpMethod.Put), token);
|
||||
return await HandleResponse<StoreData>(response);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
23
BTCPayServer.Client/BTCPayServerClient.Users.cs
Normal file
23
BTCPayServer.Client/BTCPayServerClient.Users.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<ApplicationUserData> GetCurrentUser(CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/users/me"), token);
|
||||
return await HandleResponse<ApplicationUserData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<ApplicationUserData> CreateUser(CreateApplicationUserRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/users", null, request, HttpMethod.Post), token);
|
||||
return await HandleResponse<ApplicationUserData>(response);
|
||||
}
|
||||
}
|
||||
}
|
128
BTCPayServer.Client/BTCPayServerClient.cs
Normal file
128
BTCPayServer.Client/BTCPayServerClient.cs
Normal file
@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
private readonly string _apiKey;
|
||||
private readonly Uri _btcpayHost;
|
||||
private readonly string _username;
|
||||
private readonly string _password;
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public string APIKey => _apiKey;
|
||||
|
||||
public BTCPayServerClient(Uri btcpayHost, HttpClient httpClient = null)
|
||||
{
|
||||
if (btcpayHost == null)
|
||||
throw new ArgumentNullException(nameof(btcpayHost));
|
||||
_btcpayHost = btcpayHost;
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
}
|
||||
public BTCPayServerClient(Uri btcpayHost, string APIKey, HttpClient httpClient = null)
|
||||
{
|
||||
_apiKey = APIKey;
|
||||
_btcpayHost = btcpayHost;
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
}
|
||||
|
||||
public BTCPayServerClient(Uri btcpayHost, string username, string password, HttpClient httpClient = null)
|
||||
{
|
||||
_apiKey = APIKey;
|
||||
_btcpayHost = btcpayHost;
|
||||
_username = username;
|
||||
_password = password;
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
}
|
||||
|
||||
protected async Task HandleResponse(HttpResponseMessage message)
|
||||
{
|
||||
if (message.StatusCode == System.Net.HttpStatusCode.UnprocessableEntity)
|
||||
{
|
||||
var err = JsonConvert.DeserializeObject<Models.GreenfieldValidationError[]>(await message.Content.ReadAsStringAsync()); ;
|
||||
throw new GreenFieldValidationException(err);
|
||||
}
|
||||
else if (message.StatusCode == System.Net.HttpStatusCode.BadRequest)
|
||||
{
|
||||
var err = JsonConvert.DeserializeObject<Models.GreenfieldAPIError>(await message.Content.ReadAsStringAsync());
|
||||
throw new GreenFieldAPIException(err);
|
||||
}
|
||||
|
||||
message.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
protected async Task<T> HandleResponse<T>(HttpResponseMessage message)
|
||||
{
|
||||
await HandleResponse(message);
|
||||
return JsonConvert.DeserializeObject<T>(await message.Content.ReadAsStringAsync());
|
||||
}
|
||||
|
||||
protected virtual HttpRequestMessage CreateHttpRequest(string path,
|
||||
Dictionary<string, object> queryPayload = null,
|
||||
HttpMethod method = null)
|
||||
{
|
||||
UriBuilder uriBuilder = new UriBuilder(_btcpayHost) {Path = path};
|
||||
if (queryPayload != null && queryPayload.Any())
|
||||
{
|
||||
AppendPayloadToQuery(uriBuilder, queryPayload);
|
||||
}
|
||||
|
||||
var httpRequest = new HttpRequestMessage(method ?? HttpMethod.Get, uriBuilder.Uri);
|
||||
if (_apiKey != null)
|
||||
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("token", _apiKey);
|
||||
else if (!string.IsNullOrEmpty(_username))
|
||||
{
|
||||
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Basic", System.Convert.ToBase64String(Encoding.ASCII.GetBytes(_username + ":" + _password)));
|
||||
}
|
||||
|
||||
|
||||
return httpRequest;
|
||||
}
|
||||
|
||||
protected virtual HttpRequestMessage CreateHttpRequest<T>(string path,
|
||||
Dictionary<string, object> queryPayload = null,
|
||||
T bodyPayload = default, HttpMethod method = null)
|
||||
{
|
||||
var request = CreateHttpRequest(path, queryPayload, method);
|
||||
if (typeof(T).IsPrimitive || !EqualityComparer<T>.Default.Equals(bodyPayload, default(T)))
|
||||
{
|
||||
request.Content = new StringContent(JsonConvert.SerializeObject(bodyPayload), Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private static void AppendPayloadToQuery(UriBuilder uri, Dictionary<string, object> payload)
|
||||
{
|
||||
if (uri.Query.Length > 1)
|
||||
uri.Query += "&";
|
||||
foreach (KeyValuePair<string, object> keyValuePair in payload)
|
||||
{
|
||||
UriBuilder uriBuilder = uri;
|
||||
if (!(keyValuePair.Value is string) && keyValuePair.Value.GetType().GetInterfaces().Contains((typeof(IEnumerable))))
|
||||
{
|
||||
foreach (var item in (IEnumerable)keyValuePair.Value)
|
||||
{
|
||||
uriBuilder.Query = uriBuilder.Query + Uri.EscapeDataString(keyValuePair.Key) + "=" +
|
||||
Uri.EscapeDataString(item.ToString()) + "&";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uriBuilder.Query = uriBuilder.Query + Uri.EscapeDataString(keyValuePair.Key) + "=" +
|
||||
Uri.EscapeDataString(keyValuePair.Value.ToString()) + "&";
|
||||
}
|
||||
}
|
||||
|
||||
uri.Query = uri.Query.Trim('&');
|
||||
}
|
||||
}
|
||||
}
|
18
BTCPayServer.Client/GreenFieldAPIException.cs
Normal file
18
BTCPayServer.Client/GreenFieldAPIException.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public class GreenFieldAPIException : Exception
|
||||
{
|
||||
public GreenFieldAPIException(Models.GreenfieldAPIError error):base(error.Message)
|
||||
{
|
||||
if (error == null)
|
||||
throw new ArgumentNullException(nameof(error));
|
||||
APIError = error;
|
||||
}
|
||||
public Models.GreenfieldAPIError APIError { get; }
|
||||
}
|
||||
}
|
30
BTCPayServer.Client/GreenFieldValidationException.cs
Normal file
30
BTCPayServer.Client/GreenFieldValidationException.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public class GreenFieldValidationException : Exception
|
||||
{
|
||||
public GreenFieldValidationException(Models.GreenfieldValidationError[] errors) : base(BuildMessage(errors))
|
||||
{
|
||||
ValidationErrors = errors;
|
||||
}
|
||||
|
||||
private static string BuildMessage(GreenfieldValidationError[] errors)
|
||||
{
|
||||
if (errors == null)
|
||||
throw new ArgumentNullException(nameof(errors));
|
||||
StringBuilder builder = new StringBuilder();
|
||||
foreach (var error in errors)
|
||||
{
|
||||
builder.AppendLine($"{error.Path}: {error.Message}");
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public Models.GreenfieldValidationError[] ValidationErrors { get; }
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.JsonConverters
|
||||
{
|
||||
public class DecimalStringJsonConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return (objectType == typeof(decimal) || objectType == typeof(decimal?));
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
|
||||
JsonSerializer serializer)
|
||||
{
|
||||
JToken token = JToken.Load(reader);
|
||||
switch (token.Type)
|
||||
{
|
||||
case JTokenType.Float:
|
||||
case JTokenType.Integer:
|
||||
case JTokenType.String:
|
||||
return decimal.Parse(token.ToString(), CultureInfo.InvariantCulture);
|
||||
case JTokenType.Null when objectType == typeof(decimal?):
|
||||
return null;
|
||||
default:
|
||||
throw new JsonSerializationException("Unexpected token type: " +
|
||||
token.Type);
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
if (value != null)
|
||||
writer.WriteValue(((decimal)value).ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
using System.Globalization;
|
||||
using BTCPayServer.Lightning;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.JsonConverters
|
||||
{
|
||||
public class LightMoneyJsonConverter : BTCPayServer.Lightning.JsonConverters.LightMoneyJsonConverter
|
||||
{
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
if (value != null)
|
||||
writer.WriteValue(((LightMoney)value).MilliSatoshi.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
}
|
25
BTCPayServer.Client/JsonConverters/MoneyJsonConverter.cs
Normal file
25
BTCPayServer.Client/JsonConverters/MoneyJsonConverter.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.JsonConverters
|
||||
{
|
||||
public class MoneyJsonConverter : NBitcoin.JsonConverters.MoneyJsonConverter
|
||||
{
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.String)
|
||||
{
|
||||
return new Money( long.Parse((string) reader.Value));
|
||||
}
|
||||
return base.ReadJson(reader, objectType, existingValue, serializer);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
if (value != null)
|
||||
writer.WriteValue(((Money)value).Satoshi.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
}
|
29
BTCPayServer.Client/JsonConverters/NodeUriJsonConverter.cs
Normal file
29
BTCPayServer.Client/JsonConverters/NodeUriJsonConverter.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Lightning;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.JsonConverters
|
||||
{
|
||||
public class NodeUriJsonConverter : JsonConverter<NodeInfo>
|
||||
{
|
||||
public override NodeInfo ReadJson(JsonReader reader, Type objectType, [AllowNull] NodeInfo existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.String)
|
||||
throw new JsonObjectException(reader.Path, "Unexpected token type for NodeUri");
|
||||
if (NodeInfo.TryParse((string)reader.Value, out var info))
|
||||
return info;
|
||||
throw new JsonObjectException(reader.Path, "Invalid NodeUri");
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, [AllowNull] NodeInfo value, JsonSerializer serializer)
|
||||
{
|
||||
if (value is NodeInfo)
|
||||
writer.WriteValue(value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using NBitcoin.JsonConverters;
|
||||
|
||||
namespace BTCPayServer.Client.JsonConverters
|
||||
{
|
||||
public class PermissionJsonConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(Permission).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
return null;
|
||||
if (reader.TokenType != JsonToken.String)
|
||||
throw new JsonObjectException("Type 'Permission' is expected to be a 'String'", reader);
|
||||
if (reader.Value is String s && Permission.TryParse(s, out var permission))
|
||||
return permission;
|
||||
throw new JsonObjectException("Invalid 'Permission' String", reader);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
if (value is Permission v)
|
||||
writer.WriteValue(v.ToString());
|
||||
}
|
||||
}
|
||||
}
|
45
BTCPayServer.Client/JsonConverters/TimeSpanJsonConverter.cs
Normal file
45
BTCPayServer.Client/JsonConverters/TimeSpanJsonConverter.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.JsonConverters
|
||||
{
|
||||
public class TimeSpanJsonConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(TimeSpan) || objectType == typeof(TimeSpan?);
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
try
|
||||
{
|
||||
var nullable = objectType == typeof(TimeSpan?);
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
{
|
||||
if (nullable)
|
||||
return null;
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
if (reader.TokenType != JsonToken.Integer)
|
||||
throw new JsonObjectException("Invalid timespan, expected integer", reader);
|
||||
return TimeSpan.FromSeconds((long)reader.Value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new JsonObjectException("Invalid locktime", reader);
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
if (value is TimeSpan s)
|
||||
{
|
||||
writer.WriteValue((int)s.TotalSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
BTCPayServer.Client/Models/ApiHealthData.cs
Normal file
7
BTCPayServer.Client/Models/ApiHealthData.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class ApiHealthData
|
||||
{
|
||||
public bool Synchronized { get; set; }
|
||||
}
|
||||
}
|
14
BTCPayServer.Client/Models/ApiKeyData.cs
Normal file
14
BTCPayServer.Client/Models/ApiKeyData.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class ApiKeyData
|
||||
{
|
||||
public string ApiKey { get; set; }
|
||||
public string Label { get; set; }
|
||||
|
||||
[JsonProperty(ItemConverterType = typeof(PermissionJsonConverter))]
|
||||
public Permission[] Permissions { get; set; }
|
||||
}
|
||||
}
|
25
BTCPayServer.Client/Models/ApplicationUserData.cs
Normal file
25
BTCPayServer.Client/Models/ApplicationUserData.cs
Normal file
@ -0,0 +1,25 @@
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class ApplicationUserData
|
||||
{
|
||||
/// <summary>
|
||||
/// the id of the user
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the email AND username of the user
|
||||
/// </summary>
|
||||
public string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the user has verified their email
|
||||
/// </summary>
|
||||
public bool EmailConfirmed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// whether the user needed to verify their email on account creation
|
||||
/// </summary>
|
||||
public bool RequiresEmailConfirmation { get; set; }
|
||||
}
|
||||
}
|
24
BTCPayServer.Client/Models/ConnectToNodeRequest.cs
Normal file
24
BTCPayServer.Client/Models/ConnectToNodeRequest.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.Lightning;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class ConnectToNodeRequest
|
||||
{
|
||||
public ConnectToNodeRequest()
|
||||
{
|
||||
|
||||
}
|
||||
public ConnectToNodeRequest(NodeInfo nodeInfo)
|
||||
{
|
||||
NodeURI = nodeInfo;
|
||||
}
|
||||
[JsonConverter(typeof(NodeUriJsonConverter))]
|
||||
[JsonProperty("nodeURI")]
|
||||
public NodeInfo NodeURI { get; set; }
|
||||
}
|
||||
}
|
13
BTCPayServer.Client/Models/CreateApiKeyRequest.cs
Normal file
13
BTCPayServer.Client/Models/CreateApiKeyRequest.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class CreateApiKeyRequest
|
||||
{
|
||||
public string Label { get; set; }
|
||||
|
||||
[JsonProperty(ItemConverterType = typeof(PermissionJsonConverter))]
|
||||
public Permission[] Permissions { get; set; }
|
||||
}
|
||||
}
|
20
BTCPayServer.Client/Models/CreateApplicationUserRequest.cs
Normal file
20
BTCPayServer.Client/Models/CreateApplicationUserRequest.cs
Normal file
@ -0,0 +1,20 @@
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class CreateApplicationUserRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// the email AND username of the new user
|
||||
/// </summary>
|
||||
public string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// password of the new user
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this user is an administrator. If left null and there are no admins in the system, the user will be created as an admin.
|
||||
/// </summary>
|
||||
public bool? IsAdministrator { get; set; }
|
||||
}
|
||||
}
|
29
BTCPayServer.Client/Models/CreateLightningInvoiceRequest.cs
Normal file
29
BTCPayServer.Client/Models/CreateLightningInvoiceRequest.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class CreateLightningInvoiceRequest
|
||||
{
|
||||
public CreateLightningInvoiceRequest()
|
||||
{
|
||||
|
||||
}
|
||||
public CreateLightningInvoiceRequest(LightMoney amount, string description, TimeSpan expiry)
|
||||
{
|
||||
Amount = amount;
|
||||
Description = description;
|
||||
Expiry = expiry;
|
||||
}
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
public LightMoney Amount { get; set; }
|
||||
public string Description { get; set; }
|
||||
[JsonConverter(typeof(JsonConverters.TimeSpanJsonConverter))]
|
||||
public TimeSpan Expiry { get; set; }
|
||||
public bool PrivateRouteHints { get; set; }
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class CreatePaymentRequestRequest : PaymentRequestBaseData
|
||||
{
|
||||
}
|
||||
}
|
6
BTCPayServer.Client/Models/CreateStoreRequest.cs
Normal file
6
BTCPayServer.Client/Models/CreateStoreRequest.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class CreateStoreRequest: StoreBaseData
|
||||
{
|
||||
}
|
||||
}
|
25
BTCPayServer.Client/Models/GreenfieldAPIError.cs
Normal file
25
BTCPayServer.Client/Models/GreenfieldAPIError.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class GreenfieldAPIError
|
||||
{
|
||||
public GreenfieldAPIError()
|
||||
{
|
||||
|
||||
}
|
||||
public GreenfieldAPIError(string code, string message)
|
||||
{
|
||||
if (code == null)
|
||||
throw new ArgumentNullException(nameof(code));
|
||||
if (message == null)
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
Code = code;
|
||||
Message = message;
|
||||
}
|
||||
public string Code { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
26
BTCPayServer.Client/Models/GreenfieldValidationError.cs
Normal file
26
BTCPayServer.Client/Models/GreenfieldValidationError.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class GreenfieldValidationError
|
||||
{
|
||||
public GreenfieldValidationError()
|
||||
{
|
||||
|
||||
}
|
||||
public GreenfieldValidationError(string path, string message)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
if (message == null)
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
Path = path;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public string Path { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
30
BTCPayServer.Client/Models/LightningInvoiceData.cs
Normal file
30
BTCPayServer.Client/Models/LightningInvoiceData.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.Lightning;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class LightningInvoiceData
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public LightningInvoiceStatus Status { get; set; }
|
||||
|
||||
[JsonProperty("BOLT11")]
|
||||
public string BOLT11 { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset? PaidAt { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset ExpiresAt { get; set; }
|
||||
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
public LightMoney Amount { get; set; }
|
||||
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
public LightMoney AmountReceived { get; set; }
|
||||
}
|
||||
}
|
33
BTCPayServer.Client/Models/LightningNodeInformationData.cs
Normal file
33
BTCPayServer.Client/Models/LightningNodeInformationData.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.Lightning;
|
||||
using NBitcoin;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class LightningNodeInformationData
|
||||
{
|
||||
[JsonProperty("nodeURIs", ItemConverterType = typeof(NodeUriJsonConverter))]
|
||||
public NodeInfo[] NodeURIs { get; set; }
|
||||
public int BlockHeight { get; set; }
|
||||
}
|
||||
|
||||
public class LightningChannelData
|
||||
{
|
||||
public string RemoteNode { get; set; }
|
||||
|
||||
public bool IsPublic { get; set; }
|
||||
|
||||
public bool IsActive { get; set; }
|
||||
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
public LightMoney Capacity { get; set; }
|
||||
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
public LightMoney LocalBalance { get; set; }
|
||||
|
||||
public string ChannelPoint { get; set; }
|
||||
}
|
||||
}
|
21
BTCPayServer.Client/Models/OpenLightningChannelRequest.cs
Normal file
21
BTCPayServer.Client/Models/OpenLightningChannelRequest.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.Lightning;
|
||||
using NBitcoin;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using MoneyJsonConverter = BTCPayServer.Client.JsonConverters.MoneyJsonConverter;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class OpenLightningChannelRequest
|
||||
{
|
||||
[JsonConverter(typeof(NodeUriJsonConverter))]
|
||||
[JsonProperty("nodeURI")]
|
||||
public NodeInfo NodeURI { get; set; }
|
||||
[JsonConverter(typeof(MoneyJsonConverter))]
|
||||
public Money ChannelAmount { get; set; }
|
||||
|
||||
[JsonConverter(typeof(FeeRateJsonConverter))]
|
||||
public FeeRate FeeRate { get; set; }
|
||||
}
|
||||
}
|
8
BTCPayServer.Client/Models/PayLightningInvoiceRequest.cs
Normal file
8
BTCPayServer.Client/Models/PayLightningInvoiceRequest.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class PayLightningInvoiceRequest
|
||||
{
|
||||
[Newtonsoft.Json.JsonProperty("BOLT11")]
|
||||
public string BOLT11 { get; set; }
|
||||
}
|
||||
}
|
26
BTCPayServer.Client/Models/PaymentRequestBaseData.cs
Normal file
26
BTCPayServer.Client/Models/PaymentRequestBaseData.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class PaymentRequestBaseData
|
||||
{
|
||||
[JsonProperty(ItemConverterType = typeof(DecimalStringJsonConverter))]
|
||||
public decimal Amount { get; set; }
|
||||
public string Currency { get; set; }
|
||||
public DateTime? ExpiryDate { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Email { get; set; }
|
||||
|
||||
public string EmbeddedCSS { get; set; }
|
||||
public string CustomCSSLink { get; set; }
|
||||
public bool AllowCustomPaymentAmounts { get; set; }
|
||||
|
||||
[JsonExtensionData]
|
||||
public IDictionary<string, JToken> AdditionalData { get; set; }
|
||||
}
|
||||
}
|
22
BTCPayServer.Client/Models/PaymentRequestData.cs
Normal file
22
BTCPayServer.Client/Models/PaymentRequestData.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class PaymentRequestData : PaymentRequestBaseData
|
||||
{
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public PaymentRequestData.PaymentRequestStatus Status { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
public string Id { get; set; }
|
||||
public bool Archived { get; set; }
|
||||
|
||||
public enum PaymentRequestStatus
|
||||
{
|
||||
Pending = 0,
|
||||
Completed = 1,
|
||||
Expired = 2
|
||||
}
|
||||
}
|
||||
}
|
47
BTCPayServer.Client/Models/ServerInfoData.cs
Normal file
47
BTCPayServer.Client/Models/ServerInfoData.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class ServerInfoData
|
||||
{
|
||||
/// <summary>
|
||||
/// the BTCPay Server version
|
||||
/// </summary>
|
||||
public string Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the Tor hostname
|
||||
/// </summary>
|
||||
public string Onion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the payment methods this server supports
|
||||
/// </summary>
|
||||
public IEnumerable<string> SupportedPaymentMethods { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// are all chains fully synched
|
||||
/// </summary>
|
||||
public bool FullySynched { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// detailed sync information per chain
|
||||
/// </summary>
|
||||
public IEnumerable<ServerInfoSyncStatusData> SyncStatus { get; set; }
|
||||
}
|
||||
|
||||
public class ServerInfoSyncStatusData
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
public int ChainHeight { get; set; }
|
||||
public int? SyncHeight { get; set; }
|
||||
public ServerInfoNodeData NodeInformation { get; set; }
|
||||
}
|
||||
|
||||
public class ServerInfoNodeData
|
||||
{
|
||||
public int Headers { get; set; }
|
||||
public int Blocks { get; set; }
|
||||
public double VerificationProgress { get; set; }
|
||||
}
|
||||
}
|
79
BTCPayServer.Client/Models/StoreBaseData.cs
Normal file
79
BTCPayServer.Client/Models/StoreBaseData.cs
Normal file
@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public abstract class StoreBaseData
|
||||
{
|
||||
/// <summary>
|
||||
/// the name of the store
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Website { get; set; }
|
||||
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter))]
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public TimeSpan InvoiceExpiration { get; set; } = TimeSpan.FromMinutes(15);
|
||||
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter))]
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public TimeSpan MonitoringExpiration { get; set; } = TimeSpan.FromMinutes(60);
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public SpeedPolicy SpeedPolicy { get; set; }
|
||||
public string LightningDescriptionTemplate { get; set; }
|
||||
public double PaymentTolerance { get; set; } = 0;
|
||||
public bool AnyoneCanCreateInvoice { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public bool ShowRecommendedFee { get; set; } = true;
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public int RecommendedFeeBlockTarget { get; set; } = 1;
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string DefaultLang { get; set; } = "en";
|
||||
public bool LightningAmountInSatoshi { get; set; }
|
||||
|
||||
public string CustomLogo { get; set; }
|
||||
|
||||
public string CustomCSS { get; set; }
|
||||
|
||||
public string HtmlTitle { get; set; }
|
||||
|
||||
public bool RedirectAutomatically { get; set; }
|
||||
|
||||
public bool RequiresRefundEmail { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public NetworkFeeMode NetworkFeeMode { get; set; } = NetworkFeeMode.Never;
|
||||
|
||||
public bool PayJoinEnabled { get; set; }
|
||||
public bool LightningPrivateRouteHints { get; set; }
|
||||
|
||||
|
||||
[JsonExtensionData]
|
||||
public IDictionary<string, JToken> AdditionalData { get; set; }
|
||||
}
|
||||
|
||||
public enum NetworkFeeMode
|
||||
{
|
||||
MultiplePaymentsOnly,
|
||||
Always,
|
||||
Never
|
||||
}
|
||||
|
||||
public enum SpeedPolicy
|
||||
{
|
||||
HighSpeed = 0,
|
||||
MediumSpeed = 1,
|
||||
LowSpeed = 2,
|
||||
LowMediumSpeed = 3
|
||||
}
|
||||
}
|
10
BTCPayServer.Client/Models/StoreData.cs
Normal file
10
BTCPayServer.Client/Models/StoreData.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class StoreData : StoreBaseData
|
||||
{
|
||||
/// <summary>
|
||||
/// the id of the store
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class UpdatePaymentRequestRequest : PaymentRequestBaseData
|
||||
{
|
||||
}
|
||||
}
|
6
BTCPayServer.Client/Models/UpdateStoreRequest.cs
Normal file
6
BTCPayServer.Client/Models/UpdateStoreRequest.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class UpdateStoreRequest : StoreBaseData
|
||||
{
|
||||
}
|
||||
}
|
202
BTCPayServer.Client/Permissions.cs
Normal file
202
BTCPayServer.Client/Permissions.cs
Normal file
@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public class Policies
|
||||
{
|
||||
public const string CanCreateLightningInvoiceInternalNode = "btcpay.server.cancreatelightninginvoiceinternalnode";
|
||||
public const string CanCreateLightningInvoiceInStore = "btcpay.store.cancreatelightninginvoice";
|
||||
public const string CanUseInternalLightningNode = "btcpay.server.canuseinternallightningnode";
|
||||
public const string CanUseLightningNodeInStore = "btcpay.store.canuselightningnode";
|
||||
public const string CanModifyServerSettings = "btcpay.server.canmodifyserversettings";
|
||||
public const string CanModifyStoreSettings = "btcpay.store.canmodifystoresettings";
|
||||
public const string CanViewStoreSettings = "btcpay.store.canviewstoresettings";
|
||||
public const string CanCreateInvoice = "btcpay.store.cancreateinvoice";
|
||||
public const string CanViewPaymentRequests = "btcpay.store.canviewpaymentrequests";
|
||||
public const string CanModifyPaymentRequests = "btcpay.store.canmodifypaymentrequests";
|
||||
public const string CanModifyProfile = "btcpay.user.canmodifyprofile";
|
||||
public const string CanViewProfile = "btcpay.user.canviewprofile";
|
||||
public const string CanCreateUser = "btcpay.server.cancreateuser";
|
||||
public const string Unrestricted = "unrestricted";
|
||||
public static IEnumerable<string> AllPolicies
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return CanCreateInvoice;
|
||||
yield return CanModifyServerSettings;
|
||||
yield return CanModifyStoreSettings;
|
||||
yield return CanViewStoreSettings;
|
||||
yield return CanViewPaymentRequests;
|
||||
yield return CanModifyPaymentRequests;
|
||||
yield return CanModifyProfile;
|
||||
yield return CanViewProfile;
|
||||
yield return CanCreateUser;
|
||||
yield return Unrestricted;
|
||||
yield return CanUseInternalLightningNode;
|
||||
yield return CanCreateLightningInvoiceInternalNode;
|
||||
yield return CanUseLightningNodeInStore;
|
||||
yield return CanCreateLightningInvoiceInStore;
|
||||
}
|
||||
}
|
||||
public static bool IsValidPolicy(string policy)
|
||||
{
|
||||
return AllPolicies.Any(p => p.Equals(policy, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public static bool IsStorePolicy(string policy)
|
||||
{
|
||||
return policy.StartsWith("btcpay.store", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static bool IsServerPolicy(string policy)
|
||||
{
|
||||
return policy.StartsWith("btcpay.server", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
public class Permission
|
||||
{
|
||||
public static Permission Create(string policy, string scope = null)
|
||||
{
|
||||
if (TryCreatePermission(policy, scope, out var r))
|
||||
return r;
|
||||
throw new ArgumentException("Invalid Permission");
|
||||
}
|
||||
|
||||
public static bool TryCreatePermission(string policy, string scope, out Permission permission)
|
||||
{
|
||||
permission = null;
|
||||
if (policy == null)
|
||||
throw new ArgumentNullException(nameof(policy));
|
||||
policy = policy.Trim().ToLowerInvariant();
|
||||
if (!Policies.IsValidPolicy(policy))
|
||||
return false;
|
||||
if (scope != null && !Policies.IsStorePolicy(policy))
|
||||
return false;
|
||||
permission = new Permission(policy, scope);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryParse(string str, out Permission permission)
|
||||
{
|
||||
permission = null;
|
||||
if (str == null)
|
||||
throw new ArgumentNullException(nameof(str));
|
||||
str = str.Trim();
|
||||
var separator = str.IndexOf(':');
|
||||
if (separator == -1)
|
||||
{
|
||||
str = str.ToLowerInvariant();
|
||||
if (!Policies.IsValidPolicy(str))
|
||||
return false;
|
||||
permission = new Permission(str, null);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var policy = str.Substring(0, separator).ToLowerInvariant();
|
||||
if (!Policies.IsValidPolicy(policy))
|
||||
return false;
|
||||
if (!Policies.IsStorePolicy(policy))
|
||||
return false;
|
||||
var storeId = str.Substring(separator + 1);
|
||||
if (storeId.Length == 0)
|
||||
return false;
|
||||
permission = new Permission(policy, storeId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal Permission(string policy, string scope)
|
||||
{
|
||||
Policy = policy;
|
||||
Scope = scope;
|
||||
}
|
||||
|
||||
public bool Contains(Permission subpermission)
|
||||
{
|
||||
if (subpermission is null)
|
||||
throw new ArgumentNullException(nameof(subpermission));
|
||||
|
||||
if (!ContainsPolicy(subpermission.Policy))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!Policies.IsStorePolicy(subpermission.Policy))
|
||||
return true;
|
||||
return Scope == null || subpermission.Scope == this.Scope;
|
||||
}
|
||||
|
||||
public static IEnumerable<Permission> ToPermissions(string[] permissions)
|
||||
{
|
||||
if (permissions == null)
|
||||
throw new ArgumentNullException(nameof(permissions));
|
||||
foreach (var p in permissions)
|
||||
{
|
||||
if (TryParse(p, out var pp))
|
||||
yield return pp;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ContainsPolicy(string subpolicy)
|
||||
{
|
||||
if (this.Policy == Policies.Unrestricted)
|
||||
return true;
|
||||
if (this.Policy == subpolicy)
|
||||
return true;
|
||||
switch (subpolicy)
|
||||
{
|
||||
case Policies.CanViewStoreSettings when this.Policy == Policies.CanModifyStoreSettings:
|
||||
case Policies.CanCreateInvoice when this.Policy == Policies.CanModifyStoreSettings:
|
||||
case Policies.CanViewProfile when this.Policy == Policies.CanModifyProfile:
|
||||
case Policies.CanModifyPaymentRequests when this.Policy == Policies.CanModifyStoreSettings:
|
||||
case Policies.CanViewPaymentRequests when this.Policy == Policies.CanModifyStoreSettings:
|
||||
case Policies.CanViewPaymentRequests when this.Policy == Policies.CanViewStoreSettings:
|
||||
case Policies.CanCreateLightningInvoiceInternalNode when this.Policy == Policies.CanUseInternalLightningNode:
|
||||
case Policies.CanCreateLightningInvoiceInStore when this.Policy == Policies.CanUseLightningNodeInStore:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public string Scope { get; }
|
||||
public string Policy { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (Scope != null)
|
||||
{
|
||||
return $"{Policy}:{Scope}";
|
||||
}
|
||||
return Policy;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
Permission item = obj as Permission;
|
||||
if (item == null)
|
||||
return false;
|
||||
return ToString().Equals(item.ToString());
|
||||
}
|
||||
public static bool operator ==(Permission a, Permission b)
|
||||
{
|
||||
if (System.Object.ReferenceEquals(a, b))
|
||||
return true;
|
||||
if (((object)a == null) || ((object)b == null))
|
||||
return false;
|
||||
return a.ToString() == b.ToString();
|
||||
}
|
||||
|
||||
public static bool operator !=(Permission a, Permission b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return ToString().GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ namespace BTCPayServer
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("0'") : new KeyPath("1'"),
|
||||
SupportRBF = true,
|
||||
SupportPayJoin = true,
|
||||
//https://github.com/spesmilo/electrum/blob/11733d6bc271646a00b69ff07657119598874da4/electrum/constants.py
|
||||
ElectrumMapping = NetworkType == NetworkType.Mainnet
|
||||
? new Dictionary<uint, DerivationType>()
|
||||
|
@ -28,7 +28,9 @@ namespace BTCPayServer
|
||||
CryptoImagePath = "imlegacy/groestlcoin.png",
|
||||
LightningImagePath = "imlegacy/groestlcoin-lightning.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("17'") : new KeyPath("1'")
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("17'") : new KeyPath("1'"),
|
||||
SupportRBF = true,
|
||||
SupportPayJoin = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,8 @@ namespace BTCPayServer
|
||||
CryptoImagePath = "imlegacy/liquid-tether.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
SupportRBF = true
|
||||
SupportRBF = true,
|
||||
SupportLightning = false
|
||||
});
|
||||
|
||||
Add(new ElementsBTCPayNetwork()
|
||||
@ -49,7 +50,31 @@ namespace BTCPayServer
|
||||
CryptoImagePath = "imlegacy/etb.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
SupportRBF = true
|
||||
SupportRBF = true,
|
||||
SupportLightning = false
|
||||
});
|
||||
|
||||
Add(new ElementsBTCPayNetwork()
|
||||
{
|
||||
CryptoCode = "LCAD",
|
||||
NetworkCryptoCode = "LBTC",
|
||||
ShowSyncSummary = false,
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"LCAD_CAD = 1",
|
||||
"LCAD_X = CAD_BTC * BTC_X",
|
||||
"LCAD_BTC = bylls(CAD_BTC)",
|
||||
},
|
||||
AssetId = new uint256("0e99c1a6da379d1f4151fb9df90449d40d0608f6cb33a5bcbfc8c265f42bab0a"),
|
||||
DisplayName = "Liquid CAD",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "liquidnetwork",
|
||||
CryptoImagePath = "imlegacy/lcad.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
SupportRBF = true,
|
||||
SupportLightning = false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -23,9 +23,38 @@ namespace BTCPayServer
|
||||
});
|
||||
}
|
||||
|
||||
public override GetTransactionsResponse FilterValidTransactions(GetTransactionsResponse response)
|
||||
{
|
||||
TransactionInformationSet Filter(TransactionInformationSet transactionInformationSet)
|
||||
{
|
||||
return new TransactionInformationSet()
|
||||
{
|
||||
Transactions =
|
||||
transactionInformationSet.Transactions.FindAll(information =>
|
||||
information.Outputs.Any(output =>
|
||||
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId) ||
|
||||
information.Inputs.Any(output =>
|
||||
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId))
|
||||
};
|
||||
}
|
||||
|
||||
return new GetTransactionsResponse()
|
||||
{
|
||||
Height = response.Height,
|
||||
ConfirmedTransactions = Filter(response.ConfirmedTransactions),
|
||||
ReplacedTransactions = Filter(response.ReplacedTransactions),
|
||||
UnconfirmedTransactions = Filter(response.UnconfirmedTransactions)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public override string GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
|
||||
{
|
||||
return $"{base.GenerateBIP21(cryptoInfoAddress, cryptoInfoDue)}&assetid={AssetId}";
|
||||
//precision 0: 10 = 0.00000010
|
||||
//precision 2: 10 = 0.00001000
|
||||
//precision 8: 10 = 10
|
||||
var money = new Money(cryptoInfoDue.ToDecimal(MoneyUnit.BTC) / decimal.Parse("1".PadRight(1 + 8 - Divisibility, '0')), MoneyUnit.BTC);
|
||||
return $"{base.GenerateBIP21(cryptoInfoAddress, money)}&assetid={AssetId}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
[JsonProperty("amount")] public long Amount { get; set; }
|
||||
[JsonProperty("confirmations")] public long Confirmations { get; set; }
|
||||
[JsonProperty("double_spend_seen")] public bool DoubleSpendSeen { get; set; }
|
||||
[JsonProperty("fee")] public long Fee { get; set; }
|
||||
[JsonProperty("height")] public long Height { get; set; }
|
||||
[JsonProperty("note")] public string Note { get; set; }
|
||||
[JsonProperty("payment_id")] public string PaymentId { get; set; }
|
||||
|
@ -18,7 +18,6 @@ namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
[JsonProperty("amount")] public long Amount { get; set; }
|
||||
[JsonProperty("confirmations")] public long Confirmations { get; set; }
|
||||
[JsonProperty("double_spend_seen")] public bool DoubleSpendSeen { get; set; }
|
||||
[JsonProperty("fee")] public long Fee { get; set; }
|
||||
[JsonProperty("height")] public long Height { get; set; }
|
||||
[JsonProperty("note")] public string Note { get; set; }
|
||||
[JsonProperty("payment_id")] public string PaymentId { get; set; }
|
||||
|
@ -61,6 +61,9 @@ namespace BTCPayServer
|
||||
|
||||
public int MaxTrackedConfirmation { get; internal set; } = 6;
|
||||
public string UriScheme { get; internal set; }
|
||||
public bool SupportPayJoin { get; set; } = false;
|
||||
public bool SupportLightning { get; set; } = true;
|
||||
|
||||
public KeyPath GetRootKeyPath(DerivationType type)
|
||||
{
|
||||
KeyPath baseKey;
|
||||
@ -117,6 +120,11 @@ namespace BTCPayServer
|
||||
{
|
||||
return $"{UriScheme}:{cryptoInfoAddress}?amount={cryptoInfoDue.ToString(false, true)}";
|
||||
}
|
||||
|
||||
public virtual GetTransactionsResponse FilterValidTransactions(GetTransactionsResponse response)
|
||||
{
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class BTCPayNetworkBase
|
||||
|
@ -4,6 +4,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="3.0.5" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="3.0.15" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -3,10 +3,16 @@
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.1" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.1.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.4" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.4" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BTCPayServer.Client\BTCPayServer.Client.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -22,18 +22,17 @@ namespace BTCPayServer.Data
|
||||
[MaxLength(50)] public string UserId { get; set; }
|
||||
|
||||
public APIKeyType Type { get; set; } = APIKeyType.Legacy;
|
||||
public string Permissions { get; set; }
|
||||
|
||||
public byte[] Blob { get; set; }
|
||||
public StoreData StoreData { get; set; }
|
||||
public ApplicationUser User { get; set; }
|
||||
public string Label { get; set; }
|
||||
public string[] GetPermissions() { return Permissions?.Split(';') ?? new string[0]; }
|
||||
}
|
||||
|
||||
public void SetPermissions(IEnumerable<string> permissions)
|
||||
{
|
||||
Permissions = string.Join(';',
|
||||
permissions?.Select(s => s.Replace(";", string.Empty)) ?? new string[0]);
|
||||
}
|
||||
public class APIKeyBlob
|
||||
{
|
||||
public string[] Permissions { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public enum APIKeyType
|
||||
|
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -35,6 +35,9 @@ namespace BTCPayServer.Data
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DbSet<PlannedTransaction> PlannedTransactions { get; set; }
|
||||
public DbSet<PayjoinLock> PayjoinLocks { get; set; }
|
||||
|
||||
public DbSet<AppData> Apps
|
||||
{
|
||||
get; set;
|
||||
@ -45,6 +48,8 @@ namespace BTCPayServer.Data
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DbSet<OffchainTransactionData> OffchainTransactions { get; set; }
|
||||
|
||||
public DbSet<HistoricalAddressInvoiceData> HistoricalAddressInvoices
|
||||
{
|
||||
get; set;
|
||||
|
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@ -79,6 +79,8 @@ namespace BTCPayServer.Data
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public bool Archived { get; set; }
|
||||
public List<PendingInvoiceData> PendingInvoices { get; set; }
|
||||
}
|
||||
}
|
||||
|
12
BTCPayServer.Data/Data/OffchainTransactionData.cs
Normal file
12
BTCPayServer.Data/Data/OffchainTransactionData.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class OffchainTransactionData
|
||||
{
|
||||
[Key]
|
||||
[MaxLength(32*2)]
|
||||
public string Id { get; set; }
|
||||
public byte[] Blob { get; set; }
|
||||
}
|
||||
}
|
16
BTCPayServer.Data/Data/PayjoinLock.cs
Normal file
16
BTCPayServer.Data/Data/PayjoinLock.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// We represent the locks of the PayjoinRepository
|
||||
/// with this table. (Both, our utxo we locked as part of a payjoin
|
||||
/// and the utxo of the payer which were used to pay us)
|
||||
/// </summary>
|
||||
public class PayjoinLock
|
||||
{
|
||||
[Key]
|
||||
[MaxLength(100)]
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
@ -12,34 +13,13 @@ namespace BTCPayServer.Data
|
||||
get; set;
|
||||
}
|
||||
public string StoreDataId { get; set; }
|
||||
public bool Archived { get; set; }
|
||||
|
||||
public StoreData StoreData { get; set; }
|
||||
|
||||
public PaymentRequestStatus Status { get; set; }
|
||||
public Client.Models.PaymentRequestData.PaymentRequestStatus Status { get; set; }
|
||||
|
||||
public byte[] Blob { get; set; }
|
||||
|
||||
public class PaymentRequestBlob
|
||||
{
|
||||
public decimal Amount { get; set; }
|
||||
public string Currency { get; set; }
|
||||
|
||||
public DateTime? ExpiryDate { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Email { get; set; }
|
||||
|
||||
public string EmbeddedCSS { get; set; }
|
||||
public string CustomCSSLink { get; set; }
|
||||
public bool AllowCustomPaymentAmounts { get; set; }
|
||||
}
|
||||
|
||||
public enum PaymentRequestStatus
|
||||
{
|
||||
Pending = 0,
|
||||
Completed = 1,
|
||||
Expired = 2
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
15
BTCPayServer.Data/Data/PlannedTransaction.cs
Normal file
15
BTCPayServer.Data/Data/PlannedTransaction.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class PlannedTransaction
|
||||
{
|
||||
[Key]
|
||||
[MaxLength(100)]
|
||||
// Id in the format [cryptocode]-[txid]
|
||||
public string Id { get; set; }
|
||||
public DateTimeOffset BroadcastAt { get; set; }
|
||||
public byte[] Blob { get; set; }
|
||||
}
|
||||
}
|
@ -1,98 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Security.Claims;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public enum SpeedPolicy
|
||||
{
|
||||
HighSpeed = 0,
|
||||
MediumSpeed = 1,
|
||||
LowSpeed = 2,
|
||||
LowMediumSpeed = 3
|
||||
}
|
||||
|
||||
public class StoreData
|
||||
{
|
||||
public string Id
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string Id { get; set; }
|
||||
public List<UserStore> UserStores { get; set; }
|
||||
|
||||
public List<UserStore> UserStores
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public List<AppData> Apps
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public List<PaymentRequestData> PaymentRequests
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public List<AppData> Apps { get; set; }
|
||||
|
||||
public List<PaymentRequestData> PaymentRequests { get; set; }
|
||||
|
||||
public List<InvoiceData> Invoices { get; set; }
|
||||
|
||||
[Obsolete("Use GetDerivationStrategies instead")]
|
||||
public string DerivationStrategy
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string DerivationStrategy { get; set; }
|
||||
|
||||
[Obsolete("Use GetDerivationStrategies instead")]
|
||||
public string DerivationStrategies
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string DerivationStrategies { get; set; }
|
||||
|
||||
public string StoreName
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string StoreName { get; set; }
|
||||
|
||||
public SpeedPolicy SpeedPolicy
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public SpeedPolicy SpeedPolicy { get; set; } = SpeedPolicy.MediumSpeed;
|
||||
|
||||
public string StoreWebsite
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string StoreWebsite { get; set; }
|
||||
|
||||
public byte[] StoreCertificate
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public byte[] StoreCertificate { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public string Role
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
[NotMapped] public string Role { get; set; }
|
||||
|
||||
public byte[] StoreBlob { get; set; }
|
||||
|
||||
public byte[] StoreBlob
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
[Obsolete("Use GetDefaultPaymentId instead")]
|
||||
public string DefaultCrypto { get; set; }
|
||||
|
||||
public List<PairedSINData> PairedSINs { get; set; }
|
||||
public IEnumerable<APIKeyData> APIKeys { get; set; }
|
||||
}
|
||||
|
||||
public enum NetworkFeeMode
|
||||
{
|
||||
MultiplePaymentsOnly,
|
||||
Always,
|
||||
Never
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
@ -16,69 +17,9 @@ namespace BTCPayServer.Data
|
||||
public byte[] Blob { get; set; }
|
||||
}
|
||||
|
||||
public class Label
|
||||
{
|
||||
public Label(string value, string color)
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
if (color == null)
|
||||
throw new ArgumentNullException(nameof(color));
|
||||
Value = value;
|
||||
Color = color;
|
||||
}
|
||||
|
||||
public string Value { get; }
|
||||
public string Color { get; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
Label item = obj as Label;
|
||||
if (item == null)
|
||||
return false;
|
||||
return Value.Equals(item.Value, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
public static bool operator ==(Label a, Label b)
|
||||
{
|
||||
if (System.Object.ReferenceEquals(a, b))
|
||||
return true;
|
||||
if (((object)a == null) || ((object)b == null))
|
||||
return false;
|
||||
return a.Value == b.Value;
|
||||
}
|
||||
|
||||
public static bool operator !=(Label a, Label b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Value.GetHashCode(StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
public class WalletBlobInfo
|
||||
{
|
||||
public Dictionary<string, string> LabelColors { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
public IEnumerable<Label> GetLabels(WalletTransactionInfo transactionInfo)
|
||||
{
|
||||
foreach (var label in transactionInfo.Labels)
|
||||
{
|
||||
if (LabelColors.TryGetValue(label, out var color))
|
||||
{
|
||||
yield return new Label(label, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Label> GetLabels()
|
||||
{
|
||||
foreach (var kv in LabelColors)
|
||||
{
|
||||
yield return new Label(kv.Key, kv.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,8 +114,8 @@ namespace BTCPayServer.Migrations
|
||||
name: "AspNetUserLogins",
|
||||
columns: table => new
|
||||
{
|
||||
LoginProvider = table.Column<string>(nullable: false),
|
||||
ProviderKey = table.Column<string>(nullable: false),
|
||||
LoginProvider = table.Column<string>(nullable: false, maxLength: 255),
|
||||
ProviderKey = table.Column<string>(nullable: false, maxLength: 255),
|
||||
ProviderDisplayName = table.Column<string>(nullable: true),
|
||||
UserId = table.Column<string>(nullable: false, maxLength: maxLength)
|
||||
},
|
||||
@ -159,8 +159,8 @@ namespace BTCPayServer.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
LoginProvider = table.Column<string>(nullable: false),
|
||||
Name = table.Column<string>(nullable: false),
|
||||
LoginProvider = table.Column<string>(nullable: false, maxLength: 64),
|
||||
Name = table.Column<string>(nullable: false, maxLength: 64),
|
||||
Value = table.Column<string>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
|
@ -22,7 +22,7 @@ namespace BTCPayServer.Migrations
|
||||
Label = table.Column<string>(nullable: true),
|
||||
Name = table.Column<string>(nullable: true),
|
||||
PairingTime = table.Column<DateTimeOffset>(nullable: false),
|
||||
SIN = table.Column<string>(nullable: true),
|
||||
SIN = table.Column<string>(nullable: true, maxLength: maxLength),
|
||||
StoreDataId = table.Column<string>(nullable: true, maxLength: maxLength)
|
||||
},
|
||||
constraints: table =>
|
||||
|
@ -23,7 +23,7 @@ namespace BTCPayServer.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
InvoiceDataId = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
Address = table.Column<string>(nullable: false),
|
||||
Address = table.Column<string>(nullable: false, maxLength: this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)512 : null),
|
||||
Assigned = table.Column<DateTimeOffset>(nullable: false),
|
||||
UnAssigned = table.Column<DateTimeOffset>(nullable: true)
|
||||
},
|
||||
|
42
BTCPayServer.Data/Migrations/20200402065615_AddApiKeyBlob.cs
Normal file
42
BTCPayServer.Data/Migrations/20200402065615_AddApiKeyBlob.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20200402065615_AddApiKeyBlob")]
|
||||
public partial class AddApiKeyBlob : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (this.SupportDropColumn(migrationBuilder.ActiveProvider))
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Permissions",
|
||||
table: "ApiKeys");
|
||||
}
|
||||
|
||||
migrationBuilder.AddColumn<byte[]>(
|
||||
name: "Blob",
|
||||
table: "ApiKeys",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (this.SupportDropColumn(migrationBuilder.ActiveProvider))
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Blob",
|
||||
table: "ApiKeys");
|
||||
}
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Permissions",
|
||||
table: "ApiKeys",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20200413052418_PlannedTransactions")]
|
||||
public partial class PlannedTransactions : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PlannedTransactions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(maxLength: 100, nullable: false),
|
||||
BroadcastAt = table.Column<DateTimeOffset>(nullable: false),
|
||||
Blob = table.Column<byte[]>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PlannedTransactions", x => x.Id);
|
||||
});
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PayjoinLocks",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(maxLength: 100, nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PayjoinLocks", x => x.Id);
|
||||
});
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OffchainTransactions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(maxLength: 64, nullable: false),
|
||||
Blob = table.Column<byte[]>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OffchainTransactions", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "PayjoinLocks");
|
||||
migrationBuilder.DropTable(
|
||||
name: "PlannedTransactions");
|
||||
migrationBuilder.DropTable(
|
||||
name: "OffchainTransactions");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20200507092343_AddArchivedToInvoice")]
|
||||
public class AddArchivedToInvoice : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "Archived",
|
||||
table: "Invoices",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "Archived",
|
||||
table: "PaymentRequests",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (this.SupportDropColumn(migrationBuilder.ActiveProvider))
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Archived",
|
||||
table: "Invoices");
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Archived",
|
||||
table: "PaymentRequests");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -22,10 +22,10 @@ namespace BTCPayServer.Migrations
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<string>("Label")
|
||||
.HasColumnType("TEXT");
|
||||
b.Property<byte[]>("Blob")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<string>("Permissions")
|
||||
b.Property<string>("Label")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StoreId")
|
||||
@ -190,6 +190,9 @@ namespace BTCPayServer.Migrations
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Archived")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
@ -240,6 +243,20 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("InvoiceEvents");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.OffchainTransactionData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(64);
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("OffchainTransactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -297,6 +314,17 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("PairingCodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PayjoinLock", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(100);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PayjoinLocks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -323,6 +351,9 @@ namespace BTCPayServer.Migrations
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Archived")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
@ -356,6 +387,23 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("PendingInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PlannedTransaction", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(100);
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<DateTimeOffset>("BroadcastAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PlannedTransactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
|
@ -4,14 +4,16 @@
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.4.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.6.0" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
|
||||
<PackageReference Include="NBitcoin" Version="5.0.40" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.6.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BTCPayServer.Common\BTCPayServer.Common.csproj" />
|
||||
<EmbeddedResource Include="Currencies.json" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
1276
BTCPayServer.Rating/Currencies.json
Normal file
1276
BTCPayServer.Rating/Currencies.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -6,35 +6,22 @@ using System.Reflection;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Globalization;
|
||||
using BTCPayServer.Rating;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
public class CurrencyData
|
||||
{
|
||||
public string Name
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
public string Code
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
public int Divisibility
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
public string Symbol
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
public string Name { get; set; }
|
||||
public string Code { get; set; }
|
||||
public int Divisibility { get; set; }
|
||||
public string Symbol { get; set; }
|
||||
public bool Crypto { get; set; }
|
||||
}
|
||||
public class CurrencyNameTable
|
||||
{
|
||||
public static CurrencyNameTable Instance = new CurrencyNameTable();
|
||||
public CurrencyNameTable()
|
||||
{
|
||||
_Currencies = LoadCurrency().ToDictionary(k => k.Code);
|
||||
@ -94,16 +81,10 @@ namespace BTCPayServer.Services.Rates
|
||||
catch { }
|
||||
}
|
||||
|
||||
foreach (var network in new BTCPayNetworkProvider(NetworkType.Mainnet).GetAll())
|
||||
foreach (var curr in _Currencies.Where(pair => pair.Value.Crypto))
|
||||
{
|
||||
AddCurrency(_CurrencyProviders, network.CryptoCode, network.Divisibility, network.CryptoCode);
|
||||
AddCurrency(_CurrencyProviders, curr.Key, curr.Value.Divisibility, curr.Value.Symbol?? curr.Value.Code);
|
||||
}
|
||||
|
||||
_CurrencyProviders.TryAdd("SATS",
|
||||
new NumberFormatInfo()
|
||||
{
|
||||
CurrencySymbol = "sats", CurrencyDecimalDigits = 0, CurrencyPositivePattern = 3
|
||||
});
|
||||
}
|
||||
return _CurrencyProviders.TryGet(currency.ToUpperInvariant());
|
||||
}
|
||||
@ -153,58 +134,15 @@ namespace BTCPayServer.Services.Rates
|
||||
|
||||
static CurrencyData[] LoadCurrency()
|
||||
{
|
||||
var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("BTCPayServer.Currencies.txt");
|
||||
var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("BTCPayServer.Rating.Currencies.json");
|
||||
string content = null;
|
||||
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
||||
{
|
||||
content = reader.ReadToEnd();
|
||||
}
|
||||
var currencies = content.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
Dictionary<string, CurrencyData> dico = new Dictionary<string, CurrencyData>();
|
||||
foreach (var currency in currencies)
|
||||
{
|
||||
var splitted = currency.Split(new[] { '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (splitted.Length < 3)
|
||||
continue;
|
||||
CurrencyData info = new CurrencyData();
|
||||
info.Name = splitted[0];
|
||||
info.Code = splitted[1];
|
||||
int divisibility;
|
||||
if (!int.TryParse(splitted[2], out divisibility))
|
||||
continue;
|
||||
info.Divisibility = divisibility;
|
||||
if (!dico.ContainsKey(info.Code))
|
||||
dico.Add(info.Code, info);
|
||||
if (splitted.Length >= 4)
|
||||
{
|
||||
info.Symbol = splitted[3];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var network in new BTCPayNetworkProvider(NetworkType.Mainnet).GetAll())
|
||||
{
|
||||
if (!dico.TryAdd(network.CryptoCode, new CurrencyData()
|
||||
{
|
||||
Code = network.CryptoCode,
|
||||
Divisibility = network.Divisibility,
|
||||
Name = network.CryptoCode,
|
||||
Crypto = true
|
||||
}))
|
||||
{
|
||||
dico[network.CryptoCode].Crypto = true;
|
||||
}
|
||||
}
|
||||
|
||||
dico.TryAdd("SATS", new CurrencyData()
|
||||
{
|
||||
Code = "SATS",
|
||||
Crypto = true,
|
||||
Divisibility = 0,
|
||||
Name = "Satoshis",
|
||||
Symbol = "Sats",
|
||||
});
|
||||
|
||||
return dico.Values.ToArray();
|
||||
var currencies = JsonConvert.DeserializeObject<CurrencyData[]>(content);
|
||||
return currencies;
|
||||
}
|
||||
|
||||
public CurrencyData GetCurrencyData(string currency, bool useFallback)
|
@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using BTCPayServer.Services.Rates;
|
||||
|
||||
namespace BTCPayServer.Rating
|
||||
{
|
||||
public class CurrencyPair
|
||||
{
|
||||
static readonly BTCPayNetworkProvider _NetworkProvider = new BTCPayNetworkProvider(NBitcoin.NetworkType.Mainnet);
|
||||
public CurrencyPair(string left, string right)
|
||||
{
|
||||
if (right == null)
|
||||
@ -47,13 +47,14 @@ namespace BTCPayServer.Rating
|
||||
value = new CurrencyPair(currencyPair.Substring(0,3), currencyPair.Substring(3, 3));
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 3; i < 5; i++)
|
||||
{
|
||||
var potentialCryptoName = currencyPair.Substring(0, i);
|
||||
var network = _NetworkProvider.GetNetwork<BTCPayNetworkBase>(potentialCryptoName);
|
||||
if (network != null)
|
||||
var currency = CurrencyNameTable.Instance.GetCurrencyData(potentialCryptoName, false);
|
||||
if (currency != null)
|
||||
{
|
||||
value = new CurrencyPair(network.CryptoCode, currencyPair.Substring(i));
|
||||
value = new CurrencyPair(currency.Code, currencyPair.Substring(i));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
25
BTCPayServer.Rating/Extensions.cs
Normal file
25
BTCPayServer.Rating/Extensions.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer.Rating
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static decimal RoundToSignificant(this decimal value, ref int divisibility)
|
||||
{
|
||||
if (value != 0m)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var rounded = decimal.Round(value, divisibility, MidpointRounding.AwayFromZero);
|
||||
if ((Math.Abs(rounded - value) / value) < 0.001m)
|
||||
{
|
||||
value = rounded;
|
||||
break;
|
||||
}
|
||||
divisibility++;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,10 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Rating;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using BTCPayServer.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using System.Reflection;
|
||||
using System.Globalization;
|
||||
@ -99,7 +96,7 @@ namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
LastRequested = LastRequested
|
||||
};
|
||||
if (_Latest is LatestFetch fetch)
|
||||
if (_Latest is LatestFetch fetch && fetch.Latest is PairRate[])
|
||||
{
|
||||
state.LastUpdated = fetch.Updated;
|
||||
state.Rates = fetch.Latest
|
||||
|
35
BTCPayServer.Rating/Providers/BitflyerRateProvider.cs
Normal file
35
BTCPayServer.Rating/Providers/BitflyerRateProvider.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Rating;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
public class BitflyerRateProvider : IRateProvider
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
public BitflyerRateProvider(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
}
|
||||
|
||||
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _httpClient.GetAsync("https://api.bitflyer.jp/v1/ticker", cancellationToken);
|
||||
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
|
||||
if (jobj.Property("error_message")?.Value?.Value<string>() is string err)
|
||||
{
|
||||
throw new Exception($"Error from bitflyer: {err}");
|
||||
}
|
||||
var bid = jobj.Property("best_bid").Value.Value<decimal>();
|
||||
var ask = jobj.Property("best_ask").Value.Value<decimal>();
|
||||
var rates = new PairRate[1];
|
||||
rates[0] = new PairRate(CurrencyPair.Parse(jobj.Property("product_code").Value.Value<string>()), new BidAsk(bid, ask));
|
||||
return rates;
|
||||
}
|
||||
}
|
||||
}
|
@ -530,7 +530,10 @@ namespace BTCPayServer.Rating
|
||||
var rewriter = new ReplaceExchangeRateRewriter();
|
||||
rewriter.Rates = ExchangeRates;
|
||||
var result = rewriter.Visit(this.expression);
|
||||
Errors.AddRange(rewriter.Errors);
|
||||
foreach (var item in rewriter.Errors)
|
||||
{
|
||||
Errors.Add(item);
|
||||
}
|
||||
_Evaluated = result.NormalizeWhitespace("", "\n").ToString();
|
||||
if (HasError)
|
||||
return false;
|
||||
@ -539,7 +542,10 @@ namespace BTCPayServer.Rating
|
||||
calculate.Visit(result);
|
||||
if (calculate.Values.Count != 1 || calculate.Errors.Count != 0)
|
||||
{
|
||||
Errors.AddRange(calculate.Errors);
|
||||
foreach (var item in calculate.Errors)
|
||||
{
|
||||
Errors.Add(item);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
_BidAsk = calculate.Values.Pop();
|
||||
|
@ -77,6 +77,7 @@ namespace BTCPayServer.Services.Rates
|
||||
yield return new AvailableRateProvider("kraken", "Kraken", "https://api.kraken.com/0/public/Ticker?pair=ATOMETH,ATOMEUR,ATOMUSD,ATOMXBT,BATETH,BATEUR,BATUSD,BATXBT,BCHEUR,BCHUSD,BCHXBT,DAIEUR,DAIUSD,DAIUSDT,DASHEUR,DASHUSD,DASHXBT,EOSETH,EOSXBT,ETHCHF,ETHDAI,ETHUSDC,ETHUSDT,GNOETH,GNOXBT,ICXETH,ICXEUR,ICXUSD,ICXXBT,LINKETH,LINKEUR,LINKUSD,LINKXBT,LSKETH,LSKEUR,LSKUSD,LSKXBT,NANOETH,NANOEUR,NANOUSD,NANOXBT,OMGETH,OMGEUR,OMGUSD,OMGXBT,PAXGETH,PAXGEUR,PAXGUSD,PAXGXBT,SCETH,SCEUR,SCUSD,SCXBT,USDCEUR,USDCUSD,USDCUSDT,USDTCAD,USDTEUR,USDTGBP,USDTZUSD,WAVESETH,WAVESEUR,WAVESUSD,WAVESXBT,XBTCHF,XBTDAI,XBTUSDC,XBTUSDT,XDGEUR,XDGUSD,XETCXETH,XETCXXBT,XETCZEUR,XETCZUSD,XETHXXBT,XETHZCAD,XETHZEUR,XETHZGBP,XETHZJPY,XETHZUSD,XLTCXXBT,XLTCZEUR,XLTCZUSD,XMLNXETH,XMLNXXBT,XMLNZEUR,XMLNZUSD,XREPXETH,XREPXXBT,XREPZEUR,XXBTZCAD,XXBTZEUR,XXBTZGBP,XXBTZJPY,XXBTZUSD,XXDGXXBT,XXLMXXBT,XXMRXXBT,XXMRZEUR,XXMRZUSD,XXRPXXBT,XXRPZEUR,XXRPZUSD,XZECXXBT,XZECZEUR,XZECZUSD");
|
||||
yield return new AvailableRateProvider("bylls", "Bylls", "https://bylls.com/api/price?from_currency=BTC&to_currency=CAD");
|
||||
yield return new AvailableRateProvider("bitbank", "Bitbank", "https://public.bitbank.cc/prices");
|
||||
yield return new AvailableRateProvider("bitflyer", "Bitflyer", "https://api.bitflyer.com/v1/ticker");
|
||||
yield return new AvailableRateProvider("bitpay", "Bitpay", "https://bitpay.com/rates");
|
||||
|
||||
yield return new AvailableRateProvider("polispay", "PolisPay", "https://obol.polispay.com/complex/btc/polis");
|
||||
@ -100,6 +101,7 @@ namespace BTCPayServer.Services.Rates
|
||||
Providers.Add("bylls", new ByllsRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BYLLS")));
|
||||
Providers.Add("bitbank", new BitbankRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITBANK")));
|
||||
Providers.Add("bitpay", new BitpayRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITPAY")));
|
||||
Providers.Add("bitflyer", new BitflyerRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITFLYER")));
|
||||
Providers.Add("polispay", new PolisRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_POLIS")));
|
||||
|
||||
|
||||
|
@ -4,15 +4,17 @@ using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Security.APIKeys;
|
||||
using BTCPayServer.Security.GreenField;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Views.Manage;
|
||||
using ExchangeSharp;
|
||||
using Newtonsoft.Json;
|
||||
using OpenQA.Selenium;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -23,7 +25,7 @@ namespace BTCPayServer.Tests
|
||||
public const string TestApiPath = "api/test/apikey";
|
||||
public ApiKeysTests(ITestOutputHelper helper)
|
||||
{
|
||||
Logs.Tester = new XUnitLog(helper) {Name = "Tests"};
|
||||
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
|
||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||
}
|
||||
|
||||
@ -42,61 +44,59 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
|
||||
await user.CreateStoreAsync();
|
||||
await user.MakeAdmin(false);
|
||||
s.GoToLogin();
|
||||
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
||||
s.GoToProfile(ManageNavPages.APIKeys);
|
||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||
if (!user.IsAdmin)
|
||||
{
|
||||
//not an admin, so this permission should not show
|
||||
Assert.DoesNotContain("ServerManagementPermission", s.Driver.PageSource);
|
||||
await user.MakeAdmin();
|
||||
s.Logout();
|
||||
s.GoToLogin();
|
||||
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
||||
s.GoToProfile(ManageNavPages.APIKeys);
|
||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||
}
|
||||
|
||||
//not an admin, so this permission should not show
|
||||
Assert.DoesNotContain("btcpay.server.canmodifyserversettings", s.Driver.PageSource);
|
||||
await user.MakeAdmin();
|
||||
s.Logout();
|
||||
s.GoToLogin();
|
||||
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
||||
s.GoToProfile(ManageNavPages.APIKeys);
|
||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||
Assert.Contains("btcpay.server.canmodifyserversettings", s.Driver.PageSource);
|
||||
|
||||
//server management should show now
|
||||
s.SetCheckbox(s, "ServerManagementPermission", true);
|
||||
s.SetCheckbox(s, "StoreManagementPermission", true);
|
||||
s.SetCheckbox(s, "btcpay.server.canmodifyserversettings", true);
|
||||
s.SetCheckbox(s, "btcpay.store.canmodifystoresettings", true);
|
||||
s.SetCheckbox(s, "btcpay.user.canviewprofile", true);
|
||||
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||
var superApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
||||
|
||||
//this api key has access to everything
|
||||
await TestApiAgainstAccessToken(superApiKey, tester, user, APIKeyConstants.Permissions.ServerManagement,
|
||||
APIKeyConstants.Permissions.StoreManagement);
|
||||
await TestApiAgainstAccessToken(superApiKey, tester, user, Policies.CanModifyServerSettings,Policies.CanModifyStoreSettings, Policies.CanViewProfile);
|
||||
|
||||
|
||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||
s.SetCheckbox(s, "ServerManagementPermission", true);
|
||||
s.SetCheckbox(s, "btcpay.server.canmodifyserversettings", true);
|
||||
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||
var serverOnlyApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
||||
await TestApiAgainstAccessToken(serverOnlyApiKey, tester, user,
|
||||
APIKeyConstants.Permissions.ServerManagement);
|
||||
Policies.CanModifyServerSettings);
|
||||
|
||||
|
||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||
s.SetCheckbox(s, "StoreManagementPermission", true);
|
||||
s.SetCheckbox(s, "btcpay.store.canmodifystoresettings", true);
|
||||
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||
var allStoreOnlyApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
||||
await TestApiAgainstAccessToken(allStoreOnlyApiKey, tester, user,
|
||||
APIKeyConstants.Permissions.StoreManagement);
|
||||
Policies.CanModifyStoreSettings);
|
||||
|
||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=change-store-mode]")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("button[value='btcpay.store.canmodifystoresettings:change-store-mode']")).Click();
|
||||
//there should be a store already by default in the dropdown
|
||||
var dropdown = s.Driver.FindElement(By.Name("SpecificStores[0]"));
|
||||
var dropdown = s.Driver.FindElement(By.Name("PermissionValues[2].SpecificStores[0]"));
|
||||
var option = dropdown.FindElement(By.TagName("option"));
|
||||
var storeId = option.GetAttribute("value");
|
||||
option.Click();
|
||||
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||
var selectiveStoreApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
||||
await TestApiAgainstAccessToken(selectiveStoreApiKey, tester, user,
|
||||
APIKeyConstants.Permissions.GetStorePermission(storeId));
|
||||
Permission.Create(Policies.CanModifyStoreSettings, storeId).ToString());
|
||||
|
||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||
@ -117,37 +117,14 @@ namespace BTCPayServer.Tests
|
||||
//permissions
|
||||
//strict
|
||||
//selectiveStores
|
||||
UriBuilder authorize = new UriBuilder(tester.PayTester.ServerUri);
|
||||
authorize.Path = "api-keys/authorize";
|
||||
|
||||
authorize.AppendPayloadToQuery(new Dictionary<string, object>()
|
||||
{
|
||||
{"redirect", "https://local.local/callback"},
|
||||
{"applicationName", "kukksappname"},
|
||||
{"strict", true},
|
||||
{"selectiveStores", false},
|
||||
{
|
||||
"permissions",
|
||||
new[]
|
||||
{
|
||||
APIKeyConstants.Permissions.StoreManagement,
|
||||
APIKeyConstants.Permissions.ServerManagement
|
||||
}
|
||||
},
|
||||
});
|
||||
var authUrl = authorize.ToString();
|
||||
var perms = new[]
|
||||
{
|
||||
APIKeyConstants.Permissions.StoreManagement, APIKeyConstants.Permissions.ServerManagement
|
||||
};
|
||||
authUrl = authUrl.Replace("permissions=System.String%5B%5D",
|
||||
string.Join("&", perms.Select(s1 => $"permissions={s1}")));
|
||||
var authUrl = BTCPayServerClient.GenerateAuthorizeUri(tester.PayTester.ServerUri,
|
||||
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }).ToString();
|
||||
s.Driver.Navigate().GoToUrl(authUrl);
|
||||
s.Driver.PageSource.Contains("kukksappname");
|
||||
Assert.NotNull(s.Driver.FindElement(By.Id("StoreManagementPermission")).GetAttribute("readonly"));
|
||||
Assert.True(s.Driver.FindElement(By.Id("StoreManagementPermission")).Selected);
|
||||
Assert.NotNull(s.Driver.FindElement(By.Id("ServerManagementPermission")).GetAttribute("readonly"));
|
||||
Assert.True(s.Driver.FindElement(By.Id("ServerManagementPermission")).Selected);
|
||||
Assert.Equal("hidden", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("type").ToLowerInvariant());
|
||||
Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("value").ToLowerInvariant());
|
||||
Assert.Equal("hidden", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("type").ToLowerInvariant());
|
||||
Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("value").ToLowerInvariant());
|
||||
Assert.DoesNotContain("change-store-mode", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Id("consent-yes")).Click();
|
||||
var url = s.Driver.Url;
|
||||
@ -155,109 +132,127 @@ namespace BTCPayServer.Tests
|
||||
.Select(s1 => new KeyValuePair<string, string>(s1.Split("=")[0], s1.Split("=")[1]));
|
||||
|
||||
var apiKeyRepo = s.Server.PayTester.GetService<APIKeyRepository>();
|
||||
|
||||
await TestApiAgainstAccessToken(results.Single(pair => pair.Key == "key").Value, tester, user,
|
||||
(await apiKeyRepo.GetKey(results.Single(pair => pair.Key == "key").Value)).GetPermissions());
|
||||
|
||||
authorize = new UriBuilder(tester.PayTester.ServerUri);
|
||||
authorize.Path = "api-keys/authorize";
|
||||
authorize.AppendPayloadToQuery(new Dictionary<string, object>()
|
||||
{
|
||||
{"strict", false},
|
||||
{"selectiveStores", true},
|
||||
{
|
||||
"permissions",
|
||||
new[]
|
||||
{
|
||||
APIKeyConstants.Permissions.StoreManagement,
|
||||
APIKeyConstants.Permissions.ServerManagement
|
||||
}
|
||||
}
|
||||
});
|
||||
authUrl = authorize.ToString();
|
||||
perms = new[]
|
||||
{
|
||||
APIKeyConstants.Permissions.StoreManagement, APIKeyConstants.Permissions.ServerManagement
|
||||
};
|
||||
authUrl = authUrl.Replace("permissions=System.String%5B%5D",
|
||||
string.Join("&", perms.Select(s1 => $"permissions={s1}")));
|
||||
await TestApiAgainstAccessToken(results.Single(pair => pair.Key == "key").Value, tester, user,
|
||||
(await apiKeyRepo.GetKey(results.Single(pair => pair.Key == "key").Value)).GetBlob().Permissions);
|
||||
|
||||
authUrl = BTCPayServerClient.GenerateAuthorizeUri(tester.PayTester.ServerUri,
|
||||
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, false, true).ToString();
|
||||
|
||||
s.Driver.Navigate().GoToUrl(authUrl);
|
||||
Assert.DoesNotContain("kukksappname", s.Driver.PageSource);
|
||||
|
||||
Assert.Null(s.Driver.FindElement(By.Id("StoreManagementPermission")).GetAttribute("readonly"));
|
||||
Assert.True(s.Driver.FindElement(By.Id("StoreManagementPermission")).Selected);
|
||||
Assert.Null(s.Driver.FindElement(By.Id("ServerManagementPermission")).GetAttribute("readonly"));
|
||||
Assert.True(s.Driver.FindElement(By.Id("ServerManagementPermission")).Selected);
|
||||
Assert.Equal("checkbox", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("type").ToLowerInvariant());
|
||||
Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("value").ToLowerInvariant());
|
||||
Assert.Equal("checkbox", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("type").ToLowerInvariant());
|
||||
Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("value").ToLowerInvariant());
|
||||
|
||||
s.SetCheckbox(s, "ServerManagementPermission", false);
|
||||
s.SetCheckbox(s, "btcpay.server.canmodifyserversettings", false);
|
||||
Assert.Contains("change-store-mode", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Id("consent-yes")).Click();
|
||||
url = s.Driver.Url;
|
||||
results = url.Split("?").Last().Split("&")
|
||||
.Select(s1 => new KeyValuePair<string, string>(s1.Split("=")[0], s1.Split("=")[1]));
|
||||
|
||||
|
||||
await TestApiAgainstAccessToken(results.Single(pair => pair.Key == "key").Value, tester, user,
|
||||
(await apiKeyRepo.GetKey(results.Single(pair => pair.Key == "key").Value)).GetPermissions());
|
||||
|
||||
(await apiKeyRepo.GetKey(results.Single(pair => pair.Key == "key").Value)).GetBlob().Permissions);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
async Task TestApiAgainstAccessToken(string accessToken, ServerTester tester, TestAccount testAccount,
|
||||
params string[] permissions)
|
||||
params string[] expectedPermissionsArr)
|
||||
{
|
||||
var resultUser =
|
||||
await TestApiAgainstAccessToken<string>(accessToken, $"{TestApiPath}/me/id",
|
||||
tester.PayTester.HttpClient);
|
||||
Assert.Equal(testAccount.UserId, resultUser);
|
||||
var expectedPermissions = Permission.ToPermissions(expectedPermissionsArr).ToArray();
|
||||
expectedPermissions ??= new Permission[0];
|
||||
var apikeydata = await TestApiAgainstAccessToken<ApiKeyData>(accessToken, $"api/v1/api-keys/current", tester.PayTester.HttpClient);
|
||||
var permissions = apikeydata.Permissions;
|
||||
Assert.Equal(expectedPermissions.Length, permissions.Length);
|
||||
foreach (var expectPermission in expectedPermissions)
|
||||
{
|
||||
Assert.True(permissions.Any(p => p == expectPermission), $"Missing expected permission {expectPermission}");
|
||||
}
|
||||
|
||||
if (permissions.Contains(Permission.Create(Policies.CanViewProfile)))
|
||||
{
|
||||
var resultUser = await TestApiAgainstAccessToken<string>(accessToken, $"{TestApiPath}/me/id", tester.PayTester.HttpClient);
|
||||
Assert.Equal(testAccount.UserId, resultUser);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||
{
|
||||
await TestApiAgainstAccessToken<string>(accessToken, $"{TestApiPath}/me/id", tester.PayTester.HttpClient);
|
||||
});
|
||||
}
|
||||
//create a second user to see if any of its data gets messed upin our results.
|
||||
var secondUser = tester.NewAccount();
|
||||
secondUser.GrantAccess();
|
||||
|
||||
var selectiveStorePermissions = APIKeyConstants.Permissions.ExtractStorePermissionsIds(permissions);
|
||||
if (permissions.Contains(APIKeyConstants.Permissions.StoreManagement) || selectiveStorePermissions.Any())
|
||||
var canModifyAllStores = Permission.Create(Policies.CanModifyStoreSettings, null);
|
||||
var canModifyServer = Permission.Create(Policies.CanModifyServerSettings, null);
|
||||
var unrestricted = Permission.Create(Policies.Unrestricted, null);
|
||||
var selectiveStorePermissions = permissions.Where(p => p.Scope != null && p.Policy == Policies.CanModifyStoreSettings);
|
||||
if (permissions.Contains(canModifyAllStores) || selectiveStorePermissions.Any())
|
||||
{
|
||||
var resultStores =
|
||||
await TestApiAgainstAccessToken<StoreData[]>(accessToken, $"{TestApiPath}/me/stores",
|
||||
tester.PayTester.HttpClient);
|
||||
|
||||
foreach (string selectiveStorePermission in selectiveStorePermissions)
|
||||
foreach (var selectiveStorePermission in selectiveStorePermissions)
|
||||
{
|
||||
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
|
||||
$"{TestApiPath}/me/stores/{selectiveStorePermission}/can-edit",
|
||||
$"{TestApiPath}/me/stores/{selectiveStorePermission.Scope}/can-edit",
|
||||
tester.PayTester.HttpClient));
|
||||
|
||||
Assert.Contains(resultStores,
|
||||
data => data.Id.Equals(selectiveStorePermission, StringComparison.InvariantCultureIgnoreCase));
|
||||
data => data.Id.Equals(selectiveStorePermission.Scope, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
if (permissions.Contains(APIKeyConstants.Permissions.StoreManagement))
|
||||
bool shouldBeAuthorized = false;
|
||||
if (permissions.Contains(canModifyAllStores) || selectiveStorePermissions.Contains(Permission.Create(Policies.CanViewStoreSettings, testAccount.StoreId)))
|
||||
{
|
||||
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
|
||||
$"{TestApiPath}/me/stores/actions",
|
||||
$"{TestApiPath}/me/stores/{testAccount.StoreId}/can-view",
|
||||
tester.PayTester.HttpClient));
|
||||
Assert.Contains(resultStores,
|
||||
data => data.Id.Equals(testAccount.StoreId, StringComparison.InvariantCultureIgnoreCase));
|
||||
shouldBeAuthorized = true;
|
||||
}
|
||||
if (permissions.Contains(canModifyAllStores) || selectiveStorePermissions.Contains(Permission.Create(Policies.CanModifyStoreSettings, testAccount.StoreId)))
|
||||
{
|
||||
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
|
||||
$"{TestApiPath}/me/stores/{testAccount.StoreId}/can-view",
|
||||
tester.PayTester.HttpClient));
|
||||
|
||||
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
|
||||
$"{TestApiPath}/me/stores/{testAccount.StoreId}/can-edit",
|
||||
tester.PayTester.HttpClient));
|
||||
Assert.Contains(resultStores,
|
||||
data => data.Id.Equals(testAccount.StoreId, StringComparison.InvariantCultureIgnoreCase));
|
||||
shouldBeAuthorized = true;
|
||||
}
|
||||
else
|
||||
|
||||
if (!shouldBeAuthorized)
|
||||
{
|
||||
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||
{
|
||||
await TestApiAgainstAccessToken<bool>(accessToken,
|
||||
$"{TestApiPath}/me/stores/actions",
|
||||
tester.PayTester.HttpClient);
|
||||
$"{TestApiPath}/me/stores/{testAccount.StoreId}/can-edit",
|
||||
tester.PayTester.HttpClient);
|
||||
});
|
||||
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||
{
|
||||
await TestApiAgainstAccessToken<bool>(accessToken,
|
||||
$"{TestApiPath}/me/stores/{testAccount.StoreId}/can-view",
|
||||
tester.PayTester.HttpClient);
|
||||
});
|
||||
Assert.DoesNotContain(resultStores,
|
||||
data => data.Id.Equals(testAccount.StoreId, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
Assert.DoesNotContain(resultStores,
|
||||
data => data.Id.Equals(secondUser.StoreId, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
else
|
||||
else if (!permissions.Contains(unrestricted))
|
||||
{
|
||||
|
||||
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||
{
|
||||
await TestApiAgainstAccessToken<bool>(accessToken,
|
||||
@ -265,19 +260,42 @@ namespace BTCPayServer.Tests
|
||||
tester.PayTester.HttpClient);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
await TestApiAgainstAccessToken<bool>(accessToken,
|
||||
$"{TestApiPath}/me/stores/{testAccount.StoreId}/can-edit",
|
||||
tester.PayTester.HttpClient);
|
||||
}
|
||||
|
||||
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||
if (!permissions.Contains(unrestricted))
|
||||
{
|
||||
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||
{
|
||||
await TestApiAgainstAccessToken<bool>(accessToken, $"{TestApiPath}/me/stores/{secondUser.StoreId}/can-edit",
|
||||
tester.PayTester.HttpClient);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
await TestApiAgainstAccessToken<bool>(accessToken, $"{TestApiPath}/me/stores/{secondUser.StoreId}/can-edit",
|
||||
tester.PayTester.HttpClient);
|
||||
});
|
||||
}
|
||||
|
||||
if (permissions.Contains(APIKeyConstants.Permissions.ServerManagement))
|
||||
if (permissions.Contains(canModifyServer))
|
||||
{
|
||||
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
|
||||
$"{TestApiPath}/me/is-admin",
|
||||
tester.PayTester.HttpClient));
|
||||
}
|
||||
else
|
||||
{
|
||||
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||
{
|
||||
await TestApiAgainstAccessToken<bool>(accessToken,
|
||||
$"{TestApiPath}/me/is-admin",
|
||||
tester.PayTester.HttpClient);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<T> TestApiAgainstAccessToken<T>(string apikey, string url, HttpClient client)
|
||||
|
@ -23,9 +23,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.13" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="80.0.3987.10600" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="83.0.4103.3900" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
@ -1,4 +1,6 @@
|
||||
using BTCPayServer.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using System.Linq;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Hosting;
|
||||
@ -16,7 +18,6 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
@ -92,10 +93,12 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
public bool MockRates { get; set; } = true;
|
||||
public string SocksEndpoint { get; set; }
|
||||
|
||||
public HashSet<string> Chains { get; set; } = new HashSet<string>(){"BTC"};
|
||||
public bool UseLightning { get; set; }
|
||||
|
||||
public bool AllowAdminRegistration { get; set; } = true;
|
||||
public bool DisableRegistration { get; set; } = false;
|
||||
public async Task StartAsync()
|
||||
{
|
||||
if (!Directory.Exists(_Directory))
|
||||
@ -137,11 +140,17 @@ namespace BTCPayServer.Tests
|
||||
config.AppendLine($"lbtc.explorer.url={LBTCNBXplorerUri.AbsoluteUri}");
|
||||
config.AppendLine($"lbtc.explorer.cookiefile=0");
|
||||
}
|
||||
config.AppendLine("allow-admin-registration=1");
|
||||
if (AllowAdminRegistration)
|
||||
config.AppendLine("allow-admin-registration=1");
|
||||
|
||||
config.AppendLine($"torrcfile={TestUtils.GetTestDataFullPath("Tor/torrc")}");
|
||||
config.AppendLine($"socksendpoint={SocksEndpoint}");
|
||||
config.AppendLine($"debuglog=debug.log");
|
||||
|
||||
if (SocksHTTPProxy is string v)
|
||||
{
|
||||
config.AppendLine($"sockshttpproxy={v}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(SSHPassword) && string.IsNullOrEmpty(SSHKeyFile))
|
||||
config.AppendLine($"sshpassword={SSHPassword}");
|
||||
@ -161,7 +170,7 @@ namespace BTCPayServer.Tests
|
||||
HttpClient = new HttpClient();
|
||||
HttpClient.BaseAddress = ServerUri;
|
||||
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development");
|
||||
var conf = new DefaultConfiguration() { Logger = Logs.LogProvider.CreateLogger("Console") }.CreateConfiguration(new[] { "--datadir", _Directory, "--conf", confPath, "--disable-registration", "false" });
|
||||
var conf = new DefaultConfiguration() { Logger = Logs.LogProvider.CreateLogger("Console") }.CreateConfiguration(new[] { "--datadir", _Directory, "--conf", confPath, "--disable-registration", DisableRegistration ? "true" : "false" });
|
||||
_Host = new WebHostBuilder()
|
||||
.UseConfiguration(conf)
|
||||
.UseContentRoot(FindBTCPayServerDirectory())
|
||||
@ -177,6 +186,10 @@ namespace BTCPayServer.Tests
|
||||
.AddProvider(Logs.LogProvider);
|
||||
});
|
||||
})
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.TryAddSingleton<IFeeProviderFactory>(new BTCPayServer.Services.Fees.FixedFeeProvider(new FeeRate(100L, 1)));
|
||||
})
|
||||
.UseKestrel()
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
@ -221,6 +234,10 @@ namespace BTCPayServer.Tests
|
||||
var bitfinex = new MockRateProvider();
|
||||
bitfinex.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("UST_BTC"), new BidAsk(0.000136m)));
|
||||
rateProvider.Providers.Add("bitfinex", bitfinex);
|
||||
|
||||
var bitpay = new MockRateProvider();
|
||||
bitpay.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("ETB_BTC"), new BidAsk(0.1m)));
|
||||
rateProvider.Providers.Add("bitpay", bitpay);
|
||||
}
|
||||
|
||||
|
||||
@ -278,6 +295,8 @@ namespace BTCPayServer.Tests
|
||||
public string SSHPassword { get; internal set; }
|
||||
public string SSHKeyFile { get; internal set; }
|
||||
public string SSHConnection { get; set; }
|
||||
public string SocksHTTPProxy { get; set; }
|
||||
|
||||
public T GetController<T>(string userId = null, string storeId = null, bool isAdmin = false) where T : Controller
|
||||
{
|
||||
var context = new DefaultHttpContext();
|
||||
|
@ -205,7 +205,8 @@ namespace BTCPayServer.Tests
|
||||
IWebElement closebutton = null;
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
var iframe = s.Driver.SwitchTo().Frame("btcpay");
|
||||
var frameElement = s.Driver.FindElement(By.Name("btcpay"));
|
||||
var iframe = s.Driver.SwitchTo().Frame(frameElement);
|
||||
closebutton = iframe.FindElement(By.ClassName("close-action"));
|
||||
Assert.True(closebutton.Displayed);
|
||||
});
|
||||
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
|
@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Configuration.Memory;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Payment;
|
||||
using NBitcoin.RPC;
|
||||
using NBitpayClient;
|
||||
using Xunit;
|
||||
@ -36,14 +37,13 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
tester.ActivateLBTC();
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("LBTC");
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
user.RegisterDerivationScheme("USDT");
|
||||
|
||||
Assert.Single(Assert.IsType<ListWalletsViewModel>(Assert.IsType<ViewResult>(await user.GetController<WalletsController>().ListWallets()).Model).Wallets);
|
||||
Assert.Equal(3, Assert.IsType<ListWalletsViewModel>(Assert.IsType<ViewResult>(await user.GetController<WalletsController>().ListWallets()).Model).Wallets.Count);
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,20 +79,28 @@ namespace BTCPayServer.Tests
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("LBTC");
|
||||
user.RegisterDerivationScheme("USDT");
|
||||
|
||||
user.RegisterDerivationScheme("ETB");
|
||||
await tester.LBTCExplorerNode.GenerateAsync(4);
|
||||
//no tether on our regtest, lets create it and set it
|
||||
var tether = tester.NetworkProvider.GetNetwork<ElementsBTCPayNetwork>("USDT");
|
||||
var lbtc = tester.NetworkProvider.GetNetwork<ElementsBTCPayNetwork>("LBTC");
|
||||
var etb = tester.NetworkProvider.GetNetwork<ElementsBTCPayNetwork>("ETB");
|
||||
var issueAssetResult = await tester.LBTCExplorerNode.SendCommandAsync("issueasset", 100000, 0);
|
||||
tether.AssetId = uint256.Parse(issueAssetResult.Result["asset"].ToString());
|
||||
((ElementsBTCPayNetwork)tester.PayTester.GetService<BTCPayWalletProvider>().GetWallet("USDT").Network)
|
||||
.AssetId = tether.AssetId;
|
||||
Logs.Tester.LogInformation($"Asset is {tether.AssetId}");
|
||||
Assert.Equal(tether.AssetId, tester.NetworkProvider.GetNetwork<ElementsBTCPayNetwork>("USDT").AssetId);
|
||||
Assert.Equal(tether.AssetId, ((ElementsBTCPayNetwork)tester.PayTester.GetService<BTCPayWalletProvider>().GetWallet("USDT").Network).AssetId);
|
||||
|
||||
var issueAssetResult2 = await tester.LBTCExplorerNode.SendCommandAsync("issueasset", 100000, 0);
|
||||
etb.AssetId = uint256.Parse(issueAssetResult2.Result["asset"].ToString());
|
||||
((ElementsBTCPayNetwork)tester.PayTester.GetService<BTCPayWalletProvider>().GetWallet("ETB").Network)
|
||||
.AssetId = etb.AssetId;
|
||||
|
||||
|
||||
//test: register 2 assets on the same elements network and make sure paying an invoice on one does not affect the other in any way
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.1m, "BTC"));
|
||||
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count);
|
||||
Assert.Equal(3, invoice.SupportedTransactionCurrencies.Count);
|
||||
var ci = invoice.CryptoInfo.Single(info => info.CryptoCode.Equals("LBTC"));
|
||||
//1 lbtc = 1 btc
|
||||
Assert.Equal(1, ci.Rate);
|
||||
@ -109,7 +117,7 @@ namespace BTCPayServer.Tests
|
||||
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.1m, "BTC"));
|
||||
|
||||
ci = invoice.CryptoInfo.Single(info => info.CryptoCode.Equals("USDT"));
|
||||
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count);
|
||||
Assert.Equal(3, invoice.SupportedTransactionCurrencies.Count);
|
||||
star = await tester.LBTCExplorerNode.SendCommandAsync("sendtoaddress", ci.Address, ci.Due, "", "", false, true,
|
||||
1, "UNSET", tether.AssetId);
|
||||
|
||||
@ -120,6 +128,14 @@ namespace BTCPayServer.Tests
|
||||
Assert.Single(localInvoice.CryptoInfo.Single(info => info.CryptoCode.Equals("USDT", StringComparison.InvariantCultureIgnoreCase)).Payments);
|
||||
});
|
||||
|
||||
//test precision based on https://github.com/ElementsProject/elements/issues/805#issuecomment-601277606
|
||||
var etbBip21 = new BitcoinUrlBuilder(invoice.CryptoInfo.Single(info => info.CryptoCode == "ETB").PaymentUrls.BIP21.Replace(etb.UriScheme, "bitcoin"), etb.NBitcoinNetwork);
|
||||
//precision = 2, 1ETB = 0.00000100
|
||||
Assert.Equal( 100,etbBip21.Amount.Satoshi);
|
||||
|
||||
var lbtcBip21 = new BitcoinUrlBuilder(invoice.CryptoInfo.Single(info => info.CryptoCode == "LBTC").PaymentUrls.BIP21.Replace(lbtc.UriScheme, "bitcoin"), lbtc.NBitcoinNetwork);
|
||||
//precision = 8, 0.1 = 0.1
|
||||
Assert.Equal( 0.1m,lbtcBip21.Amount.ToDecimal(MoneyUnit.BTC));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,6 +88,10 @@ namespace BTCPayServer.Tests
|
||||
if (!webElement.Displayed)
|
||||
return;
|
||||
}
|
||||
catch (NoSuchWindowException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
catch (NoSuchElementException)
|
||||
{
|
||||
return;
|
||||
|
74
BTCPayServer.Tests/FakeServer.cs
Normal file
74
BTCPayServer.Tests/FakeServer.cs
Normal file
@ -0,0 +1,74 @@
|
||||
using System.Threading.Channels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class FakeServer : IDisposable
|
||||
{
|
||||
IWebHost webHost;
|
||||
SemaphoreSlim semaphore;
|
||||
CancellationTokenSource cts = new CancellationTokenSource();
|
||||
public FakeServer()
|
||||
{
|
||||
_channel = Channel.CreateUnbounded<HttpContext>();
|
||||
semaphore = new SemaphoreSlim(0);
|
||||
}
|
||||
|
||||
Channel<HttpContext> _channel;
|
||||
public async Task Start()
|
||||
{
|
||||
webHost = new WebHostBuilder()
|
||||
.UseKestrel()
|
||||
.UseUrls("http://127.0.0.1:0")
|
||||
.Configure(appBuilder =>
|
||||
{
|
||||
appBuilder.Run(async ctx =>
|
||||
{
|
||||
await _channel.Writer.WriteAsync(ctx);
|
||||
await semaphore.WaitAsync(cts.Token);
|
||||
});
|
||||
})
|
||||
.Build();
|
||||
await webHost.StartAsync();
|
||||
var port = new Uri(webHost.ServerFeatures.Get<IServerAddressesFeature>().Addresses.First(), UriKind.Absolute)
|
||||
.Port;
|
||||
ServerUri = new Uri($"http://127.0.0.1:{port}/");
|
||||
}
|
||||
|
||||
public Uri ServerUri { get; set; }
|
||||
|
||||
public void Done()
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
|
||||
public async Task Stop()
|
||||
{
|
||||
await webHost.StopAsync();
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
cts.Dispose();
|
||||
webHost?.Dispose();
|
||||
semaphore.Dispose();
|
||||
}
|
||||
|
||||
public async Task<HttpContext> GetNextRequest()
|
||||
{
|
||||
return await _channel.Reader.ReadAsync();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +1,27 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Controllers.RestApi.ApiKeys;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Security.APIKeys;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenQA.Selenium;
|
||||
using Org.BouncyCastle.Utilities.Collections;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using CreateApplicationUserRequest = BTCPayServer.Client.Models.CreateApplicationUserRequest;
|
||||
using JsonReader = Newtonsoft.Json.JsonReader;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -26,7 +37,7 @@ namespace BTCPayServer.Tests
|
||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task ApiKeysControllerTests()
|
||||
{
|
||||
@ -36,36 +47,452 @@ namespace BTCPayServer.Tests
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
await user.MakeAdmin();
|
||||
string apiKey = await GenerateAPIKey(tester, user);
|
||||
|
||||
var client = await user.CreateClient(Policies.CanViewProfile);
|
||||
var clientBasic = await user.CreateClient();
|
||||
//Get current api key
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "api/v1/api-keys/current");
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("token", apiKey);
|
||||
var result = await tester.PayTester.HttpClient.SendAsync(request);
|
||||
Assert.True(result.IsSuccessStatusCode);
|
||||
var apiKeyData = JObject.Parse(await result.Content.ReadAsStringAsync()).ToObject<ApiKeyData>();
|
||||
var apiKeyData = await client.GetCurrentAPIKeyInfo();
|
||||
Assert.NotNull(apiKeyData);
|
||||
Assert.Equal(apiKey, apiKeyData.ApiKey);
|
||||
Assert.Equal(user.UserId, apiKeyData.UserId);
|
||||
Assert.Equal(2, apiKeyData.Permissions.Length);
|
||||
Assert.Equal(client.APIKey, apiKeyData.ApiKey);
|
||||
Assert.Single(apiKeyData.Permissions);
|
||||
|
||||
//a client using Basic Auth has no business here
|
||||
await AssertHttpError(401, async () => await clientBasic.GetCurrentAPIKeyInfo());
|
||||
|
||||
//revoke current api key
|
||||
await client.RevokeCurrentAPIKeyInfo();
|
||||
await AssertHttpError(401, async () => await client.GetCurrentAPIKeyInfo());
|
||||
//a client using Basic Auth has no business here
|
||||
await AssertHttpError(401, async () => await clientBasic.RevokeCurrentAPIKeyInfo());
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<string> GenerateAPIKey(ServerTester tester, TestAccount user)
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanCreateAndDeleteAPIKeyViaAPI()
|
||||
{
|
||||
var manageController = tester.PayTester.GetController<ManageController>(user.UserId, user.StoreId, user.IsAdmin);
|
||||
var x = Assert.IsType<RedirectToActionResult>(await manageController.AddApiKey(
|
||||
new ManageController.AddApiKeyViewModel()
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var acc = tester.NewAccount();
|
||||
await acc.GrantAccessAsync();
|
||||
var unrestricted = await acc.CreateClient();
|
||||
var apiKey = await unrestricted.CreateAPIKey(new CreateApiKeyRequest()
|
||||
{
|
||||
ServerManagementPermission = true,
|
||||
StoreManagementPermission = true,
|
||||
StoreMode = ManageController.AddApiKeyViewModel.ApiKeyStoreMode.AllStores
|
||||
}));
|
||||
var statusMessage = manageController.TempData.GetStatusMessageModel();
|
||||
Assert.NotNull(statusMessage);
|
||||
var apiKey = statusMessage.Html.Substring(statusMessage.Html.IndexOf("<code>") + 6);
|
||||
apiKey = apiKey.Substring(0, apiKey.IndexOf("</code>") );
|
||||
return apiKey;
|
||||
Label = "Hello world",
|
||||
Permissions = new Permission[] {Permission.Create(Policies.CanViewProfile)}
|
||||
});
|
||||
Assert.Equal("Hello world", apiKey.Label);
|
||||
var p = Assert.Single(apiKey.Permissions);
|
||||
Assert.Equal(Policies.CanViewProfile, p.Policy);
|
||||
|
||||
var restricted = acc.CreateClientFromAPIKey(apiKey.ApiKey);
|
||||
await AssertHttpError(403,
|
||||
async () => await restricted.CreateAPIKey(new CreateApiKeyRequest()
|
||||
{
|
||||
Label = "Hello world2",
|
||||
Permissions = new Permission[] {Permission.Create(Policies.CanViewProfile)}
|
||||
}));
|
||||
|
||||
await unrestricted.RevokeAPIKey(apiKey.ApiKey);
|
||||
await AssertHttpError(404, async () => await unrestricted.RevokeAPIKey(apiKey.ApiKey));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanCreateUsersViaAPI()
|
||||
{
|
||||
using (var tester = ServerTester.Create(newDb: true))
|
||||
{
|
||||
tester.PayTester.DisableRegistration = true;
|
||||
await tester.StartAsync();
|
||||
var unauthClient = new BTCPayServerClient(tester.PayTester.ServerUri);
|
||||
await AssertValidationError(new[] { "Email", "Password" },
|
||||
async () => await unauthClient.CreateUser(new CreateApplicationUserRequest()));
|
||||
await AssertValidationError(new[] { "Password" },
|
||||
async () => await unauthClient.CreateUser(
|
||||
new CreateApplicationUserRequest() {Email = "test@gmail.com"}));
|
||||
// Pass too simple
|
||||
await AssertValidationError(new[] { "Password" },
|
||||
async () => await unauthClient.CreateUser(
|
||||
new CreateApplicationUserRequest() {Email = "test3@gmail.com", Password = "a"}));
|
||||
|
||||
// We have no admin, so it should work
|
||||
var user1 = await unauthClient.CreateUser(
|
||||
new CreateApplicationUserRequest() {Email = "test@gmail.com", Password = "abceudhqw"});
|
||||
// We have no admin, so it should work
|
||||
var user2 = await unauthClient.CreateUser(
|
||||
new CreateApplicationUserRequest() {Email = "test2@gmail.com", Password = "abceudhqw"});
|
||||
|
||||
// Duplicate email
|
||||
await AssertValidationError(new[] { "Email" },
|
||||
async () => await unauthClient.CreateUser(
|
||||
new CreateApplicationUserRequest() {Email = "test2@gmail.com", Password = "abceudhqw"}));
|
||||
|
||||
// Let's make an admin
|
||||
var admin = await unauthClient.CreateUser(new CreateApplicationUserRequest()
|
||||
{
|
||||
Email = "admin@gmail.com", Password = "abceudhqw", IsAdministrator = true
|
||||
});
|
||||
|
||||
// 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
|
||||
await AssertHttpError(401,
|
||||
async () => await unauthClient.CreateUser(
|
||||
new CreateApplicationUserRequest() {Email = "test3@gmail.com", Password = "afewfoiewiou"}));
|
||||
|
||||
|
||||
// But should be ok with subscriptions unlocked
|
||||
var settings = tester.PayTester.GetService<SettingsRepository>();
|
||||
await settings.UpdateSetting<PoliciesSettings>(new PoliciesSettings() {LockSubscription = false});
|
||||
await unauthClient.CreateUser(
|
||||
new CreateApplicationUserRequest() {Email = "test3@gmail.com", Password = "afewfoiewiou"});
|
||||
|
||||
// But it should be forbidden to create an admin without being authenticated
|
||||
await AssertHttpError(403,
|
||||
async () => await unauthClient.CreateUser(new CreateApplicationUserRequest()
|
||||
{
|
||||
Email = "admin2@gmail.com", Password = "afewfoiewiou", IsAdministrator = true
|
||||
}));
|
||||
await settings.UpdateSetting<PoliciesSettings>(new PoliciesSettings() {LockSubscription = true});
|
||||
|
||||
var adminAcc = tester.NewAccount();
|
||||
adminAcc.UserId = admin.Id;
|
||||
adminAcc.IsAdmin = true;
|
||||
var adminClient = await adminAcc.CreateClient(Policies.CanModifyProfile);
|
||||
|
||||
// We should be forbidden to create a new user without proper admin permissions
|
||||
await AssertHttpError(403,
|
||||
async () => await adminClient.CreateUser(
|
||||
new CreateApplicationUserRequest() {Email = "test4@gmail.com", Password = "afewfoiewiou"}));
|
||||
await AssertHttpError(403,
|
||||
async () => await adminClient.CreateUser(new CreateApplicationUserRequest()
|
||||
{
|
||||
Email = "test4@gmail.com", Password = "afewfoiewiou", IsAdministrator = true
|
||||
}));
|
||||
|
||||
// However, should be ok with the unrestricted permissions of an admin
|
||||
adminClient = await adminAcc.CreateClient(Policies.Unrestricted);
|
||||
await adminClient.CreateUser(
|
||||
new CreateApplicationUserRequest() {Email = "test4@gmail.com", Password = "afewfoiewiou"});
|
||||
// Even creating new admin should be ok
|
||||
await adminClient.CreateUser(new CreateApplicationUserRequest()
|
||||
{
|
||||
Email = "admin4@gmail.com", Password = "afewfoiewiou", IsAdministrator = true
|
||||
});
|
||||
|
||||
var user1Acc = tester.NewAccount();
|
||||
user1Acc.UserId = user1.Id;
|
||||
user1Acc.IsAdmin = false;
|
||||
var user1Client = await user1Acc.CreateClient(Policies.CanModifyServerSettings);
|
||||
|
||||
// User1 trying to get server management would still fail to create user
|
||||
await AssertHttpError(403,
|
||||
async () => await user1Client.CreateUser(
|
||||
new CreateApplicationUserRequest() {Email = "test8@gmail.com", Password = "afewfoiewiou"}));
|
||||
|
||||
// User1 should be able to create user if subscription unlocked
|
||||
await settings.UpdateSetting<PoliciesSettings>(new PoliciesSettings() {LockSubscription = false});
|
||||
await user1Client.CreateUser(
|
||||
new CreateApplicationUserRequest() {Email = "test8@gmail.com", Password = "afewfoiewiou"});
|
||||
|
||||
// But not an admin
|
||||
await AssertHttpError(403,
|
||||
async () => await user1Client.CreateUser(new CreateApplicationUserRequest()
|
||||
{
|
||||
Email = "admin8@gmail.com", Password = "afewfoiewiou", IsAdministrator = true
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task StoresControllerTests()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
await user.MakeAdmin();
|
||||
var client = await user.CreateClient(Policies.Unrestricted);
|
||||
|
||||
//create store
|
||||
var newStore = await client.CreateStore(new CreateStoreRequest() {Name = "A"});
|
||||
|
||||
//update store
|
||||
var updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest() {Name = "B"});
|
||||
Assert.Equal("B", updatedStore.Name);
|
||||
Assert.Equal("B", (await client.GetStore(newStore.Id)).Name);
|
||||
|
||||
//list stores
|
||||
var stores = await client.GetStores();
|
||||
var storeIds = stores.Select(data => data.Id);
|
||||
var storeNames = stores.Select(data => data.Name);
|
||||
Assert.NotNull(stores);
|
||||
Assert.Equal(2, stores.Count());
|
||||
Assert.Contains(newStore.Id, storeIds);
|
||||
Assert.Contains(user.StoreId, storeIds);
|
||||
|
||||
//get store
|
||||
var store = await client.GetStore(user.StoreId);
|
||||
Assert.Equal(user.StoreId, store.Id);
|
||||
Assert.Contains(store.Name, storeNames);
|
||||
|
||||
//remove store
|
||||
await client.RemoveStore(newStore.Id);
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await client.GetStore(newStore.Id);
|
||||
});
|
||||
Assert.Single(await client.GetStores());
|
||||
|
||||
newStore = await client.CreateStore(new CreateStoreRequest() {Name = "A"});
|
||||
var scopedClient =
|
||||
await user.CreateClient(Permission.Create(Policies.CanViewStoreSettings, user.StoreId).ToString());
|
||||
Assert.Single(await scopedClient.GetStores());
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AssertValidationError(string[] fields, Func<Task> act)
|
||||
{
|
||||
var remainingFields = fields.ToHashSet();
|
||||
var ex = await Assert.ThrowsAsync<GreenFieldValidationException>(act);
|
||||
foreach (var field in fields)
|
||||
{
|
||||
Assert.Contains(field, ex.ValidationErrors.Select(e => e.Path).ToArray());
|
||||
remainingFields.Remove(field);
|
||||
}
|
||||
Assert.Empty(remainingFields);
|
||||
}
|
||||
|
||||
private async Task AssertHttpError(int code, Func<Task> act)
|
||||
{
|
||||
var ex = await Assert.ThrowsAsync<HttpRequestException>(act);
|
||||
Assert.Contains(code.ToString(), ex.Message);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task UsersControllerTests()
|
||||
{
|
||||
using (var tester = ServerTester.Create(newDb: true))
|
||||
{
|
||||
tester.PayTester.DisableRegistration = true;
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
await user.MakeAdmin();
|
||||
var clientProfile = await user.CreateClient(Policies.CanModifyProfile);
|
||||
var clientServer = await user.CreateClient(Policies.CanCreateUser, Policies.CanViewProfile);
|
||||
var clientInsufficient = await user.CreateClient(Policies.CanModifyStoreSettings);
|
||||
var clientBasic = await user.CreateClient();
|
||||
|
||||
|
||||
var apiKeyProfileUserData = await clientProfile.GetCurrentUser();
|
||||
Assert.NotNull(apiKeyProfileUserData);
|
||||
Assert.Equal(apiKeyProfileUserData.Id, user.UserId);
|
||||
Assert.Equal(apiKeyProfileUserData.Email, user.RegisterDetails.Email);
|
||||
|
||||
await Assert.ThrowsAsync<HttpRequestException>(async () => await clientInsufficient.GetCurrentUser());
|
||||
await clientServer.GetCurrentUser();
|
||||
await clientProfile.GetCurrentUser();
|
||||
await clientBasic.GetCurrentUser();
|
||||
|
||||
await Assert.ThrowsAsync<HttpRequestException>(async () =>
|
||||
await clientInsufficient.CreateUser(new CreateApplicationUserRequest()
|
||||
{
|
||||
Email = $"{Guid.NewGuid()}@g.com", Password = Guid.NewGuid().ToString()
|
||||
}));
|
||||
|
||||
var newUser = await clientServer.CreateUser(new CreateApplicationUserRequest()
|
||||
{
|
||||
Email = $"{Guid.NewGuid()}@g.com", Password = Guid.NewGuid().ToString()
|
||||
});
|
||||
Assert.NotNull(newUser);
|
||||
|
||||
var newUser2 = await clientBasic.CreateUser(new CreateApplicationUserRequest()
|
||||
{
|
||||
Email = $"{Guid.NewGuid()}@g.com", Password = Guid.NewGuid().ToString()
|
||||
});
|
||||
Assert.NotNull(newUser2);
|
||||
|
||||
await AssertValidationError(new[] { "Email" }, async () =>
|
||||
await clientServer.CreateUser(new CreateApplicationUserRequest()
|
||||
{
|
||||
Email = $"{Guid.NewGuid()}", Password = Guid.NewGuid().ToString()
|
||||
}));
|
||||
|
||||
await AssertValidationError(new[] { "Password" }, async () =>
|
||||
await clientServer.CreateUser(
|
||||
new CreateApplicationUserRequest() {Email = $"{Guid.NewGuid()}@g.com",}));
|
||||
|
||||
await AssertValidationError(new[] { "Email" }, async () =>
|
||||
await clientServer.CreateUser(
|
||||
new CreateApplicationUserRequest() {Password = Guid.NewGuid().ToString()}));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task HealthControllerTests()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var unauthClient = new BTCPayServerClient(tester.PayTester.ServerUri);
|
||||
|
||||
var apiHealthData = await unauthClient.GetHealth();
|
||||
Assert.NotNull(apiHealthData);
|
||||
Assert.True(apiHealthData.Synchronized);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task ServerInfoControllerTests()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var unauthClient = new BTCPayServerClient(tester.PayTester.ServerUri);
|
||||
await AssertHttpError(401, async () => await unauthClient.GetServerInfo());
|
||||
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
var clientBasic = await user.CreateClient();
|
||||
var serverInfoData = await clientBasic.GetServerInfo();
|
||||
Assert.NotNull(serverInfoData);
|
||||
Assert.NotNull(serverInfoData.Version);
|
||||
Assert.NotNull(serverInfoData.Onion);
|
||||
Assert.True(serverInfoData.FullySynched);
|
||||
Assert.Contains("BTC", serverInfoData.SupportedPaymentMethods);
|
||||
Assert.Contains("BTC_LightningLike", serverInfoData.SupportedPaymentMethods);
|
||||
Assert.NotNull(serverInfoData.SyncStatus);
|
||||
Assert.Single(serverInfoData.SyncStatus.Select(s => s.CryptoCode == "BTC"));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task PaymentControllerTests()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
await user.MakeAdmin();
|
||||
var client = await user.CreateClient(Policies.Unrestricted);
|
||||
var viewOnly = await user.CreateClient(Policies.CanViewPaymentRequests);
|
||||
|
||||
//create payment request
|
||||
|
||||
//validation errors
|
||||
await AssertValidationError(new[] { "Amount", "Currency" }, async () =>
|
||||
{
|
||||
await client.CreatePaymentRequest(user.StoreId, new CreatePaymentRequestRequest() {Title = "A"});
|
||||
});
|
||||
await AssertValidationError(new[] { "Amount" }, async () =>
|
||||
{
|
||||
await client.CreatePaymentRequest(user.StoreId,
|
||||
new CreatePaymentRequestRequest() {Title = "A", Currency = "BTC", Amount = 0});
|
||||
});
|
||||
await AssertValidationError(new[] { "Currency" }, async () =>
|
||||
{
|
||||
await client.CreatePaymentRequest(user.StoreId,
|
||||
new CreatePaymentRequestRequest() {Title = "A", Currency = "helloinvalid", Amount = 1});
|
||||
});
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnly.CreatePaymentRequest(user.StoreId,
|
||||
new CreatePaymentRequestRequest() {Title = "A", Currency = "helloinvalid", Amount = 1});
|
||||
});
|
||||
var newPaymentRequest = await client.CreatePaymentRequest(user.StoreId,
|
||||
new CreatePaymentRequestRequest() {Title = "A", Currency = "USD", Amount = 1});
|
||||
|
||||
//list payment request
|
||||
var paymentRequests = await viewOnly.GetPaymentRequests(user.StoreId);
|
||||
|
||||
Assert.NotNull(paymentRequests);
|
||||
Assert.Single(paymentRequests);
|
||||
Assert.Equal(newPaymentRequest.Id, paymentRequests.First().Id);
|
||||
|
||||
//get payment request
|
||||
var paymentRequest = await viewOnly.GetPaymentRequest(user.StoreId, newPaymentRequest.Id);
|
||||
Assert.Equal(newPaymentRequest.Title, paymentRequest.Title);
|
||||
|
||||
//update payment request
|
||||
var updateRequest = JObject.FromObject(paymentRequest).ToObject<UpdatePaymentRequestRequest>();
|
||||
updateRequest.Title = "B";
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnly.UpdatePaymentRequest(user.StoreId, paymentRequest.Id, updateRequest);
|
||||
});
|
||||
await client.UpdatePaymentRequest(user.StoreId, paymentRequest.Id, updateRequest);
|
||||
paymentRequest = await client.GetPaymentRequest(user.StoreId, newPaymentRequest.Id);
|
||||
Assert.Equal(updateRequest.Title, paymentRequest.Title);
|
||||
|
||||
//archive payment request
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnly.ArchivePaymentRequest(user.StoreId, paymentRequest.Id);
|
||||
});
|
||||
|
||||
await client.ArchivePaymentRequest(user.StoreId, paymentRequest.Id);
|
||||
Assert.DoesNotContain(paymentRequest.Id,
|
||||
(await client.GetPaymentRequests(user.StoreId)).Select(data => data.Id));
|
||||
|
||||
//let's test some payment stuff
|
||||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
var paymentTestPaymentRequest = await client.CreatePaymentRequest(user.StoreId,
|
||||
new CreatePaymentRequestRequest() {Amount = 0.1m, Currency = "BTC", Title = "Payment test title"});
|
||||
|
||||
var invoiceId = Assert.IsType<string>(Assert.IsType<OkObjectResult>(await user.GetController<PaymentRequestController>()
|
||||
.PayPaymentRequest(paymentTestPaymentRequest.Id, false)).Value);
|
||||
var invoice = user.BitPay.GetInvoice(invoiceId);
|
||||
await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
|
||||
{
|
||||
await tester.ExplorerNode.SendToAddressAsync(
|
||||
BitcoinAddress.Create(invoice.BitcoinAddress, tester.ExplorerNode.Network), invoice.BtcDue);
|
||||
});
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
Assert.Equal(Invoice.STATUS_PAID, user.BitPay.GetInvoice(invoiceId).Status);
|
||||
Assert.Equal(PaymentRequestData.PaymentRequestStatus.Completed, (await client.GetPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id)).Status);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void DecimalStringJsonConverterTests()
|
||||
{
|
||||
JsonReader Get(string val)
|
||||
{
|
||||
return new JsonTextReader(new StringReader(val));
|
||||
}
|
||||
|
||||
var jsonConverter = new DecimalStringJsonConverter();
|
||||
Assert.True(jsonConverter.CanConvert(typeof(decimal)));
|
||||
Assert.True(jsonConverter.CanConvert(typeof(decimal?)));
|
||||
Assert.False(jsonConverter.CanConvert(typeof(double)));
|
||||
Assert.False(jsonConverter.CanConvert(typeof(float)));
|
||||
Assert.False(jsonConverter.CanConvert(typeof(int)));
|
||||
Assert.False(jsonConverter.CanConvert(typeof(string)));
|
||||
|
||||
var numberJson = "1";
|
||||
var numberDecimalJson = "1.2";
|
||||
var stringJson = "\"1.2\"";
|
||||
Assert.Equal(1m, jsonConverter.ReadJson(Get(numberJson), typeof(decimal), null, null));
|
||||
Assert.Equal(1.2m, jsonConverter.ReadJson(Get(numberDecimalJson), typeof(decimal), null, null));
|
||||
Assert.Null(jsonConverter.ReadJson(Get("null"), typeof(decimal?), null, null));
|
||||
Assert.Throws<JsonSerializationException>(() =>
|
||||
{
|
||||
jsonConverter.ReadJson(Get("null"), typeof(decimal), null, null);
|
||||
});
|
||||
Assert.Equal(1.2m, jsonConverter.ReadJson(Get(stringJson), typeof(decimal), null, null));
|
||||
Assert.Equal(1.2m, jsonConverter.ReadJson(Get(stringJson), typeof(decimal?), null, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,10 +66,6 @@ namespace BTCPayServer.Tests
|
||||
FeeSatoshiPerByte = 1,
|
||||
CurrentBalance = 1.5m
|
||||
};
|
||||
var vmLedger = await walletController.WalletSend(walletId, sendModel, command: "ledger").AssertViewModelAsync<WalletSendLedgerModel>();
|
||||
PSBT.Parse(vmLedger.PSBT, user.SupportedNetwork.NBitcoinNetwork);
|
||||
BitcoinAddress.Create(vmLedger.HintChange, user.SupportedNetwork.NBitcoinNetwork);
|
||||
Assert.NotNull(vmLedger.WebsocketPath);
|
||||
|
||||
string redirectedPSBT = AssertRedirectedPSBT(await walletController.WalletSend(walletId, sendModel, command: "analyze-psbt"), nameof(walletController.WalletPSBT));
|
||||
var vmPSBT = await walletController.WalletPSBT(walletId, new WalletPSBTViewModel() { PSBT = redirectedPSBT }).AssertViewModelAsync<WalletPSBTViewModel>();
|
||||
@ -79,20 +75,25 @@ namespace BTCPayServer.Tests
|
||||
var filePSBT = (FileContentResult)(await walletController.WalletPSBT(walletId, vmPSBT, "save-psbt"));
|
||||
PSBT.Load(filePSBT.FileContents, user.SupportedNetwork.NBitcoinNetwork);
|
||||
|
||||
await walletController.WalletPSBT(walletId, vmPSBT, "ledger").AssertViewModelAsync<WalletSendLedgerModel>();
|
||||
var vmPSBT2 = await walletController.WalletPSBTReady(walletId, new WalletPSBTReadyViewModel()
|
||||
{
|
||||
PSBT = AssertRedirectedPSBT( await walletController.WalletPSBT(walletId, vmPSBT, "broadcast"), nameof(walletController.WalletPSBTReady))
|
||||
SigningContext = new SigningContextModel()
|
||||
{
|
||||
PSBT = AssertRedirectedPSBT( await walletController.WalletPSBT(walletId, vmPSBT, "broadcast"), nameof(walletController.WalletPSBTReady))
|
||||
}
|
||||
} ).AssertViewModelAsync<WalletPSBTReadyViewModel>();
|
||||
Assert.NotEmpty(vmPSBT2.Inputs.Where(i => i.Error != null));
|
||||
Assert.Equal(vmPSBT.PSBT, vmPSBT2.PSBT);
|
||||
Assert.Equal(vmPSBT.PSBT, vmPSBT2.SigningContext.PSBT);
|
||||
|
||||
var signedPSBT = unsignedPSBT.Clone();
|
||||
signedPSBT.SignAll(user.DerivationScheme, user.GenerateWalletResponseV.AccountHDKey, user.GenerateWalletResponseV.AccountKeyPath);
|
||||
vmPSBT.PSBT = signedPSBT.ToBase64();
|
||||
var psbtReady = await walletController.WalletPSBTReady(walletId, new WalletPSBTReadyViewModel()
|
||||
{
|
||||
PSBT = AssertRedirectedPSBT( await walletController.WalletPSBT(walletId, vmPSBT, "broadcast"), nameof(walletController.WalletPSBTReady))
|
||||
SigningContext = new SigningContextModel()
|
||||
{
|
||||
PSBT = AssertRedirectedPSBT(await walletController.WalletPSBT(walletId, vmPSBT, "broadcast"), nameof(walletController.WalletPSBTReady))
|
||||
}
|
||||
} ).AssertViewModelAsync<WalletPSBTReadyViewModel>();
|
||||
Assert.Equal(2 + 1, psbtReady.Destinations.Count); // The fee is a destination
|
||||
Assert.Contains(psbtReady.Destinations, d => d.Destination == sendDestination && !d.Positive);
|
||||
@ -120,8 +121,11 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(signedPSBT2.TryFinalize(out _));
|
||||
Assert.Equal(signedPSBT, signedPSBT2);
|
||||
|
||||
var ready = (await walletController.WalletPSBTReady(walletId, signedPSBT.ToBase64())).AssertViewModel<WalletPSBTReadyViewModel>();
|
||||
Assert.Equal(signedPSBT.ToBase64(), ready.PSBT);
|
||||
var ready = (await walletController.WalletPSBTReady(walletId, new WalletPSBTReadyViewModel()
|
||||
{
|
||||
SigningContext = new SigningContextModel(signedPSBT)
|
||||
})).AssertViewModel<WalletPSBTReadyViewModel>();
|
||||
Assert.Equal(signedPSBT.ToBase64(), ready.SigningContext.PSBT);
|
||||
psbt = AssertRedirectedPSBT(await walletController.WalletPSBTReady(walletId, ready, command: "analyze-psbt"), nameof(walletController.WalletPSBT));
|
||||
Assert.Equal(signedPSBT.ToBase64(), psbt);
|
||||
redirect = Assert.IsType<RedirectToActionResult>(await walletController.WalletPSBTReady(walletId, ready, command: "broadcast"));
|
||||
@ -134,7 +138,7 @@ namespace BTCPayServer.Tests
|
||||
var postRedirectView = Assert.IsType<ViewResult>(view);
|
||||
var postRedirectViewModel = Assert.IsType<PostRedirectViewModel>(postRedirectView.Model);
|
||||
Assert.Equal(actionName, postRedirectViewModel.AspAction);
|
||||
var redirectedPSBT = postRedirectViewModel.Parameters.Single(p => p.Key == "psbt").Value;
|
||||
var redirectedPSBT = postRedirectViewModel.Parameters.Single(p => p.Key == "psbt" || p.Key == "SigningContext.PSBT").Value;
|
||||
return redirectedPSBT;
|
||||
}
|
||||
}
|
||||
|
1040
BTCPayServer.Tests/PayJoinTests.cs
Normal file
1040
BTCPayServer.Tests/PayJoinTests.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -6,10 +6,12 @@ using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Rating;
|
||||
using Logs = BTCPayServer.Tests.Logging.Logs;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -41,8 +43,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
currencyPairRateResult.Add(new CurrencyPair("USD", "BTC"), Task.FromResult(rateResultUSDBTC));
|
||||
currencyPairRateResult.Add(new CurrencyPair("BTC", "USD"), Task.FromResult(rateResultBTCUSD));
|
||||
|
||||
handlerBTC = new BitcoinLikePaymentHandler(null, networkProvider, null, null);
|
||||
InvoiceLogs logs = new InvoiceLogs();
|
||||
handlerBTC = new BitcoinLikePaymentHandler(null, networkProvider, null, null, null);
|
||||
handlerLN = new LightningLikePaymentHandler(null, null, networkProvider, null);
|
||||
|
||||
#pragma warning restore CS0618
|
||||
|
@ -61,34 +61,38 @@ namespace BTCPayServer.Tests
|
||||
Description = "description"
|
||||
};
|
||||
var id = (Assert
|
||||
.IsType<RedirectToActionResult>(paymentRequestController.EditPaymentRequest(null, request).Result).RouteValues.Values.First().ToString());
|
||||
.IsType<RedirectToActionResult>(await paymentRequestController.EditPaymentRequest(null, request)).RouteValues.Values.First().ToString());
|
||||
|
||||
|
||||
|
||||
//permission guard for guests editing
|
||||
Assert
|
||||
.IsType<NotFoundResult>(guestpaymentRequestController.EditPaymentRequest(id).Result);
|
||||
.IsType<NotFoundResult>(await guestpaymentRequestController.EditPaymentRequest(id));
|
||||
|
||||
request.Title = "update";
|
||||
Assert.IsType<RedirectToActionResult>(paymentRequestController.EditPaymentRequest(id, request).Result);
|
||||
Assert.IsType<RedirectToActionResult>(await paymentRequestController.EditPaymentRequest(id, request));
|
||||
|
||||
Assert.Equal(request.Title, Assert.IsType<ViewPaymentRequestViewModel>( Assert.IsType<ViewResult>(paymentRequestController.ViewPaymentRequest(id).Result).Model).Title);
|
||||
Assert.Equal(request.Title, Assert.IsType<ViewPaymentRequestViewModel>( Assert.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Title);
|
||||
|
||||
Assert.False(string.IsNullOrEmpty(id));
|
||||
|
||||
Assert.IsType<ViewPaymentRequestViewModel>(Assert
|
||||
.IsType<ViewResult>(paymentRequestController.ViewPaymentRequest(id).Result).Model);
|
||||
.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model);
|
||||
|
||||
//Delete
|
||||
|
||||
Assert.IsType<ConfirmModel>(Assert
|
||||
.IsType<ViewResult>(paymentRequestController.RemovePaymentRequestPrompt(id).Result).Model);
|
||||
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(paymentRequestController.RemovePaymentRequest(id).Result);
|
||||
//Archive
|
||||
|
||||
Assert
|
||||
.IsType<NotFoundResult>(paymentRequestController.ViewPaymentRequest(id).Result);
|
||||
.IsType<RedirectToActionResult>(await paymentRequestController.TogglePaymentRequestArchival(id));
|
||||
Assert.True(Assert.IsType<ViewPaymentRequestViewModel>( Assert.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Archived);
|
||||
|
||||
Assert.Empty(Assert.IsType<ListPaymentRequestsViewModel>(Assert.IsType<ViewResult>(await paymentRequestController.GetPaymentRequests()).Model).Items);
|
||||
//unarchive
|
||||
Assert
|
||||
.IsType<RedirectToActionResult>(await paymentRequestController.TogglePaymentRequestArchival(id));
|
||||
|
||||
Assert.False(Assert.IsType<ViewPaymentRequestViewModel>( Assert.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Archived);
|
||||
|
||||
Assert.Single(Assert.IsType<ListPaymentRequestsViewModel>(Assert.IsType<ViewResult>(await paymentRequestController.GetPaymentRequests()).Model).Items);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,8 +19,10 @@ using System.Threading.Tasks;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Views.Manage;
|
||||
using BTCPayServer.Views.Stores;
|
||||
using BTCPayServer.Views.Wallets;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
@ -32,9 +34,9 @@ namespace BTCPayServer.Tests
|
||||
public IWebDriver Driver { get; set; }
|
||||
public ServerTester Server { get; set; }
|
||||
|
||||
public static SeleniumTester Create([CallerMemberNameAttribute] string scope = null)
|
||||
public static SeleniumTester Create([CallerMemberNameAttribute] string scope = null, bool newDb = false)
|
||||
{
|
||||
var server = ServerTester.Create(scope);
|
||||
var server = ServerTester.Create(scope, newDb);
|
||||
return new SeleniumTester()
|
||||
{
|
||||
Server = server
|
||||
@ -120,25 +122,26 @@ namespace BTCPayServer.Tests
|
||||
|
||||
return (usr, Driver.FindElement(By.Id("Id")).GetAttribute("value"));
|
||||
}
|
||||
|
||||
|
||||
public string GenerateWallet(string cryptoCode = "BTC", string seed = "", bool importkeys = false, bool privkeys = false)
|
||||
public Mnemonic GenerateWallet(string cryptoCode = "BTC", string seed = "", bool importkeys = false, bool privkeys = false, ScriptPubKeyType format = ScriptPubKeyType.Segwit)
|
||||
{
|
||||
Driver.FindElement(By.Id($"Modify{cryptoCode}")).ForceClick();
|
||||
Driver.FindElement(By.Id("import-from-btn")).ForceClick();
|
||||
Driver.FindElement(By.Id("nbxplorergeneratewalletbtn")).ForceClick();
|
||||
Thread.Sleep(200); // allow for modal to fade in
|
||||
Driver.WaitForElement(By.Id("ExistingMnemonic")).SendKeys(seed);
|
||||
SetCheckbox(Driver.FindElement(By.Id("SavePrivateKeys")), privkeys);
|
||||
SetCheckbox(Driver.FindElement(By.Id("ImportKeysToRPC")), importkeys);
|
||||
Driver.FindElement(By.Id("btn-generate")).ForceClick();
|
||||
SetCheckbox(Driver.WaitForElement(By.Id("SavePrivateKeys")), privkeys);
|
||||
SetCheckbox(Driver.WaitForElement(By.Id("ImportKeysToRPC")), importkeys);
|
||||
Driver.WaitForElement(By.Id("ScriptPubKeyType")).Click();
|
||||
Driver.WaitForElement(By.CssSelector($"#ScriptPubKeyType option[value={format}]")).Click();
|
||||
Logs.Tester.LogInformation("Trying to click btn-generate");
|
||||
Driver.WaitForElement(By.Id("btn-generate")).ForceClick();
|
||||
AssertHappyMessage();
|
||||
if (string.IsNullOrEmpty(seed))
|
||||
{
|
||||
seed = Driver.FindElements(By.ClassName("alert-success")).First().FindElement(By.TagName("code")).Text;
|
||||
}
|
||||
Driver.FindElement(By.Id("Confirm")).ForceClick();
|
||||
AssertHappyMessage();
|
||||
return seed;
|
||||
return new Mnemonic(seed);
|
||||
}
|
||||
|
||||
public void AddDerivationScheme(string cryptoCode = "BTC", string derivationScheme = "xpub661MyMwAqRbcGABgHMUXDzPzH1tU7eZaAaJQXhDXsSxsqyQzQeU6kznNfSuAyqAK9UaWSaZaMFdNiY5BCF4zBPAzSnwfUAwUhwttuAKwfRX-[legacy]")
|
||||
@ -254,13 +257,14 @@ namespace BTCPayServer.Tests
|
||||
|
||||
if (value != element.Selected)
|
||||
{
|
||||
Logs.Tester.LogInformation("SetCheckbox recursion, trying to click again");
|
||||
SetCheckbox(element, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCheckbox(SeleniumTester s, string inputName, bool value)
|
||||
public void SetCheckbox(SeleniumTester s, string checkboxId, bool value)
|
||||
{
|
||||
SetCheckbox(s.Driver.FindElement(By.Name(inputName)), value);
|
||||
SetCheckbox(s.Driver.WaitForElement(By.Id(checkboxId)), value);
|
||||
}
|
||||
|
||||
public void ScrollToElement(IWebElement element)
|
||||
@ -295,7 +299,7 @@ namespace BTCPayServer.Tests
|
||||
Driver.FindElement(By.Id("CreateNewInvoice")).Click();
|
||||
}
|
||||
|
||||
public string CreateInvoice(string store, decimal amount = 100, string currency = "USD", string refundEmail = "")
|
||||
public string CreateInvoice(string storeName, decimal amount = 100, string currency = "USD", string refundEmail = "")
|
||||
{
|
||||
GoToInvoices();
|
||||
Driver.FindElement(By.Id("CreateNewInvoice")).Click();
|
||||
@ -304,7 +308,7 @@ namespace BTCPayServer.Tests
|
||||
currencyEl.Clear();
|
||||
currencyEl.SendKeys(currency);
|
||||
Driver.FindElement(By.Id("BuyerEmail")).SendKeys(refundEmail);
|
||||
Driver.FindElement(By.Name("StoreId")).SendKeys(store + Keys.Enter);
|
||||
Driver.FindElement(By.Name("StoreId")).SendKeys(storeName + Keys.Enter);
|
||||
Driver.FindElement(By.Id("Create")).ForceClick();
|
||||
Assert.True(Driver.PageSource.Contains("just created!"), "Unable to create Invoice");
|
||||
var statusElement = Driver.FindElement(By.ClassName("alert-success"));
|
||||
@ -313,8 +317,38 @@ namespace BTCPayServer.Tests
|
||||
return id;
|
||||
}
|
||||
|
||||
public async Task FundStoreWallet(WalletId walletId, int coins = 1, decimal denomination = 1m)
|
||||
{
|
||||
GoToWallet(walletId, WalletsNavPages.Receive);
|
||||
Driver.FindElement(By.Id("generateButton")).Click();
|
||||
var addressStr = Driver.FindElement(By.Id("vue-address")).GetProperty("value");
|
||||
var address = BitcoinAddress.Create(addressStr, ((BTCPayNetwork)Server.NetworkProvider.GetNetwork(walletId.CryptoCode)).NBitcoinNetwork);
|
||||
for (int i = 0; i < coins; i++)
|
||||
{
|
||||
await Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(denomination));
|
||||
}
|
||||
}
|
||||
|
||||
public void PayInvoice(WalletId walletId, string invoiceId)
|
||||
{
|
||||
GoToInvoiceCheckout(invoiceId);
|
||||
var bip21 = Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
|
||||
.GetAttribute("href");
|
||||
Assert.Contains($"{PayjoinClient.BIP21EndpointKey}", bip21);
|
||||
|
||||
GoToWallet(walletId, WalletsNavPages.Send);
|
||||
Driver.FindElement(By.Id("bip21parse")).Click();
|
||||
Driver.SwitchTo().Alert().SendKeys(bip21);
|
||||
Driver.SwitchTo().Alert().Accept();
|
||||
Driver.ScrollTo(By.Id("SendMenu"));
|
||||
Driver.FindElement(By.Id("SendMenu")).ForceClick();
|
||||
Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
|
||||
Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private void CheckForJSErrors()
|
||||
{
|
||||
//wait for seleniun update: https://stackoverflow.com/questions/57520296/selenium-webdriver-3-141-0-driver-manage-logs-availablelogtypes-throwing-syste
|
||||
@ -338,6 +372,13 @@ namespace BTCPayServer.Tests
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void GoToWallet(WalletId walletId, WalletsNavPages navPages = WalletsNavPages.Send)
|
||||
{
|
||||
Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, $"wallets/{walletId}"));
|
||||
if (navPages != WalletsNavPages.Transactions)
|
||||
{
|
||||
Driver.FindElement(By.Id($"Wallet{navPages}")).Click();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,12 @@ using System.Threading.Tasks;
|
||||
using System.Text.RegularExpressions;
|
||||
using BTCPayServer.Models;
|
||||
using NBitcoin.Payment;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Views.Wallets;
|
||||
using Newtonsoft.Json;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -236,10 +242,26 @@ namespace BTCPayServer.Tests
|
||||
var storeUrl = s.Driver.Url;
|
||||
s.ClickOnAllSideMenus();
|
||||
s.GoToInvoices();
|
||||
s.CreateInvoice(store);
|
||||
var invoiceId = s.CreateInvoice(store);
|
||||
s.AssertHappyMessage();
|
||||
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
|
||||
var invoiceUrl = s.Driver.Url;
|
||||
|
||||
//let's test archiving an invoice
|
||||
Assert.DoesNotContain("Archived", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text);
|
||||
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
|
||||
s.AssertHappyMessage();
|
||||
Assert.Contains("Archived", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text);
|
||||
//check that it no longer appears in list
|
||||
s.GoToInvoices();
|
||||
Assert.DoesNotContain(invoiceId, s.Driver.PageSource);
|
||||
//ok, let's unarchive and see that it shows again
|
||||
s.Driver.Navigate().GoToUrl(invoiceUrl);
|
||||
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
|
||||
s.AssertHappyMessage();
|
||||
Assert.DoesNotContain("Archived", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text);
|
||||
s.GoToInvoices();
|
||||
Assert.Contains(invoiceId, s.Driver.PageSource);
|
||||
|
||||
// When logout we should not be able to access store and invoice details
|
||||
s.Driver.FindElement(By.Id("Logout")).Click();
|
||||
@ -358,11 +380,20 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("PointOfSale" + Keys.Enter);
|
||||
s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(store + Keys.Enter);
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
s.Driver.FindElement(By.Id("EnableShoppingCart")).Click();
|
||||
s.Driver.FindElement(By.Id("DefaultView")).SendKeys("Cart" + Keys.Enter);
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).ForceClick();
|
||||
s.Driver.FindElement(By.Id("ViewApp")).ForceClick();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
|
||||
var posBaseUrl = s.Driver.Url.Replace("/Cart", "");
|
||||
Assert.True(s.Driver.PageSource.Contains("Tea shop"), "Unable to create PoS");
|
||||
Assert.True(s.Driver.PageSource.Contains("Cart"), "PoS not showing correct default view");
|
||||
|
||||
s.Driver.Url = posBaseUrl + "/static";
|
||||
Assert.False(s.Driver.PageSource.Contains("Cart"), "Static PoS not showing correct view");
|
||||
|
||||
s.Driver.Url = posBaseUrl + "/cart";
|
||||
Assert.True(s.Driver.PageSource.Contains("Cart"), "Cart PoS not showing correct view");
|
||||
|
||||
s.Driver.Quit();
|
||||
}
|
||||
}
|
||||
@ -417,7 +448,68 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.Quit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanUseCoinSelection()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
var userId = s.RegisterNewUser(true);
|
||||
var storeId = s.CreateNewStore().storeId;
|
||||
s.GenerateWallet("BTC", "", false, true);
|
||||
var walletId = new WalletId(storeId, "BTC");
|
||||
s.GoToWallet(walletId, WalletsNavPages.Receive);
|
||||
s.Driver.FindElement(By.Id("generateButton")).Click();
|
||||
var addressStr = s.Driver.FindElement(By.Id("vue-address")).GetProperty("value");
|
||||
var address = BitcoinAddress.Create(addressStr, ((BTCPayNetwork)s.Server.NetworkProvider.GetNetwork("BTC")).NBitcoinNetwork);
|
||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
await s.Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(1.0m));
|
||||
}
|
||||
var targetTx = await s.Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(1.2m));
|
||||
var tx = await s.Server.ExplorerNode.GetRawTransactionAsync(targetTx);
|
||||
var spentOutpoint = new OutPoint(targetTx, tx.Outputs.FindIndex(txout => txout.Value == Money.Coins(1.2m)));
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var store = await s.Server.PayTester.StoreRepository.FindStore(storeId);
|
||||
var x = store.GetSupportedPaymentMethods(s.Server.NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.Single(settings => settings.PaymentId.CryptoCode == walletId.CryptoCode);
|
||||
Assert.Contains(
|
||||
await s.Server.PayTester.GetService<BTCPayWalletProvider>().GetWallet(walletId.CryptoCode)
|
||||
.GetUnspentCoins(x.AccountDerivation),
|
||||
coin => coin.OutPoint == spentOutpoint);
|
||||
});
|
||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||
s.GoToWallet(walletId, WalletsNavPages.Send);
|
||||
s.Driver.FindElement(By.Id("advancedSettings")).Click();
|
||||
s.Driver.FindElement(By.Id("toggleInputSelection")).Click();
|
||||
s.Driver.WaitForElement(By.Id(spentOutpoint.ToString()));
|
||||
Assert.Equal("true", s.Driver.FindElement(By.Name("InputSelection")).GetAttribute("value").ToLowerInvariant());
|
||||
var el = s.Driver.FindElement(By.Id(spentOutpoint.ToString()));
|
||||
s.Driver.FindElement(By.Id(spentOutpoint.ToString())).Click();
|
||||
var inputSelectionSelect = s.Driver.FindElement(By.Name("SelectedInputs"));
|
||||
Assert.Single(inputSelectionSelect.FindElements(By.CssSelector("[selected]")));
|
||||
|
||||
var bob = new Key().PubKey.Hash.GetAddress(Network.RegTest);
|
||||
SetTransactionOutput(s, 0, bob, 0.3m);
|
||||
s.Driver.FindElement(By.Id("SendMenu")).Click();
|
||||
s.Driver.FindElement(By.Id("spendWithNBxplorer")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
|
||||
var happyElement = s.AssertHappyMessage();
|
||||
var happyText = happyElement.Text;
|
||||
var txid = Regex.Match(happyText, @"\((.*)\)").Groups[1].Value;
|
||||
|
||||
tx = await s.Server.ExplorerNode.GetRawTransactionAsync(new uint256(txid));
|
||||
Assert.Single(tx.Inputs);
|
||||
Assert.Equal(spentOutpoint, tx.Inputs[0].PrevOut);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanManageWallet()
|
||||
{
|
||||
@ -476,7 +568,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.NotEqual( receiveAddr, s.Driver.FindElement(By.Id("vue-address")).GetAttribute("value"));
|
||||
|
||||
|
||||
var invoiceId = s.CreateInvoice(storeId.storeId);
|
||||
var invoiceId = s.CreateInvoice(storeId.storeName);
|
||||
var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
|
||||
var address = invoice.EntityToDTO().Addresses["BTC"];
|
||||
|
||||
@ -488,8 +580,8 @@ namespace BTCPayServer.Tests
|
||||
var mnemonic = s.GenerateWallet("BTC", "", true, true);
|
||||
|
||||
//lets import and save private keys
|
||||
var root = new Mnemonic(mnemonic).DeriveExtKey();
|
||||
invoiceId = s.CreateInvoice(storeId.storeId);
|
||||
var root = mnemonic.DeriveExtKey();
|
||||
invoiceId = s.CreateInvoice(storeId.storeName);
|
||||
invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice( invoiceId);
|
||||
address = invoice.EntityToDTO().Addresses["BTC"];
|
||||
result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
|
||||
@ -518,18 +610,18 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains(tx.ToString(), s.Driver.PageSource);
|
||||
|
||||
|
||||
void SignWith(string signingSource)
|
||||
void SignWith(Mnemonic signingSource)
|
||||
{
|
||||
// Send to bob
|
||||
s.Driver.FindElement(By.Id("WalletSend")).Click();
|
||||
var bob = new Key().PubKey.Hash.GetAddress(Network.RegTest);
|
||||
SetTransactionOutput(0, bob, 1);
|
||||
SetTransactionOutput(s, 0, bob, 1);
|
||||
s.Driver.ScrollTo(By.Id("SendMenu"));
|
||||
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=seed]")).Click();
|
||||
|
||||
// Input the seed
|
||||
s.Driver.FindElement(By.Id("SeedOrKey")).SendKeys(signingSource + Keys.Enter);
|
||||
s.Driver.FindElement(By.Id("SeedOrKey")).SendKeys(signingSource.ToString() + Keys.Enter);
|
||||
|
||||
// Broadcast
|
||||
Assert.Contains(bob.ToString(), s.Driver.PageSource);
|
||||
@ -537,19 +629,6 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
|
||||
Assert.Equal(walletTransactionLink, s.Driver.Url);
|
||||
}
|
||||
|
||||
void SetTransactionOutput(int index, BitcoinAddress dest, decimal amount, bool subtract = false)
|
||||
{
|
||||
s.Driver.FindElement(By.Id($"Outputs_{index}__DestinationAddress")).SendKeys(dest.ToString());
|
||||
var amountElement = s.Driver.FindElement(By.Id($"Outputs_{index}__Amount"));
|
||||
amountElement.Clear();
|
||||
amountElement.SendKeys(amount.ToString());
|
||||
var checkboxElement = s.Driver.FindElement(By.Id($"Outputs_{index}__SubtractFeesFromOutput"));
|
||||
if (checkboxElement.Selected != subtract)
|
||||
{
|
||||
checkboxElement.Click();
|
||||
}
|
||||
}
|
||||
|
||||
SignWith(mnemonic);
|
||||
|
||||
@ -558,7 +637,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("WalletSend")).Click();
|
||||
|
||||
var jack = new Key().PubKey.Hash.GetAddress(Network.RegTest);
|
||||
SetTransactionOutput(0, jack, 0.01m);
|
||||
SetTransactionOutput(s, 0, jack, 0.01m);
|
||||
s.Driver.ScrollTo(By.Id("SendMenu"));
|
||||
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
|
||||
|
||||
@ -587,6 +666,25 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(parsedBip21.Amount.ToString(false), s.Driver.FindElement(By.Id($"Outputs_0__Amount")).GetAttribute("value"));
|
||||
Assert.Equal(parsedBip21.Address.ToString(), s.Driver.FindElement(By.Id($"Outputs_0__DestinationAddress")).GetAttribute("value"));
|
||||
|
||||
|
||||
s.GoToWallet(new WalletId(storeId.storeId, "BTC"), WalletsNavPages.Settings);
|
||||
|
||||
s.Driver.FindElement(By.Id("SettingsMenu")).ForceClick();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=view-seed]")).Click();
|
||||
s.AssertHappyMessage();
|
||||
Assert.Equal(mnemonic.ToString(), s.Driver.FindElements(By.ClassName("alert-success")).First().FindElement(By.TagName("code")).Text);
|
||||
}
|
||||
}
|
||||
void SetTransactionOutput(SeleniumTester s, int index, BitcoinAddress dest, decimal amount, bool subtract = false)
|
||||
{
|
||||
s.Driver.FindElement(By.Id($"Outputs_{index}__DestinationAddress")).SendKeys(dest.ToString());
|
||||
var amountElement = s.Driver.FindElement(By.Id($"Outputs_{index}__Amount"));
|
||||
amountElement.Clear();
|
||||
amountElement.SendKeys(amount.ToString());
|
||||
var checkboxElement = s.Driver.FindElement(By.Id($"Outputs_{index}__SubtractFeesFromOutput"));
|
||||
if (checkboxElement.Selected != subtract)
|
||||
{
|
||||
checkboxElement.Click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,13 +29,13 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
public class ServerTester : IDisposable
|
||||
{
|
||||
public static ServerTester Create([CallerMemberNameAttribute]string scope = null)
|
||||
public static ServerTester Create([CallerMemberNameAttribute]string scope = null, bool newDb = false)
|
||||
{
|
||||
return new ServerTester(scope);
|
||||
return new ServerTester(scope, newDb);
|
||||
}
|
||||
|
||||
string _Directory;
|
||||
public ServerTester(string scope)
|
||||
public ServerTester(string scope, bool newDb)
|
||||
{
|
||||
_Directory = scope;
|
||||
if (Directory.Exists(_Directory))
|
||||
@ -56,9 +56,16 @@ namespace BTCPayServer.Tests
|
||||
// TODO: The fact that we use same conn string as development database can cause huge problems with tests
|
||||
// since in dev we already can have some users / stores registered, while on CI database is being initalized
|
||||
// for the first time and first registered user gets admin status by default
|
||||
SocksHTTPProxy = GetEnvironment("TESTS_SOCKSHTTP", "http://127.0.0.1:8118/"),
|
||||
Postgres = GetEnvironment("TESTS_POSTGRES", "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver"),
|
||||
MySQL = GetEnvironment("TESTS_MYSQL", "User ID=root;Host=127.0.0.1;Port=33036;Database=btcpayserver")
|
||||
};
|
||||
if (newDb)
|
||||
{
|
||||
var r = RandomUtils.GetUInt32();
|
||||
PayTester.Postgres = PayTester.Postgres.Replace("btcpayserver", $"btcpayserver{r}");
|
||||
PayTester.MySQL = PayTester.MySQL.Replace("btcpayserver", $"btcpayserver{r}");
|
||||
}
|
||||
PayTester.Port = int.Parse(GetEnvironment("TESTS_PORT", Utils.FreeTcpPort().ToString(CultureInfo.InvariantCulture)), CultureInfo.InvariantCulture);
|
||||
PayTester.HostName = GetEnvironment("TESTS_HOSTNAME", "127.0.0.1");
|
||||
PayTester.InContainer = bool.Parse(GetEnvironment("TESTS_INCONTAINER", "false"));
|
||||
@ -66,6 +73,7 @@ namespace BTCPayServer.Tests
|
||||
PayTester.SSHPassword = GetEnvironment("TESTS_SSHPASSWORD", "opD3i2282D");
|
||||
PayTester.SSHKeyFile = GetEnvironment("TESTS_SSHKEYFILE", "");
|
||||
PayTester.SSHConnection = GetEnvironment("TESTS_SSHCONNECTION", "root@127.0.0.1:21622");
|
||||
PayTester.SocksEndpoint = GetEnvironment("TESTS_SOCKSENDPOINT", "localhost:9050");
|
||||
}
|
||||
|
||||
public void ActivateLTC()
|
||||
@ -89,7 +97,7 @@ namespace BTCPayServer.Tests
|
||||
CustomerLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_CUSTOMERLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30992/"), btc);
|
||||
MerchantLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_MERCHANTLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30993/"), btc);
|
||||
MerchantCharge = new ChargeTester(this, "TEST_MERCHANTCHARGE", "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify", "merchant_lightningd", btc);
|
||||
MerchantLnd = new LndMockTester(this, "TEST_MERCHANTLND", "https://lnd:lnd@127.0.0.1:53280/", "merchant_lnd", btc);
|
||||
MerchantLnd = new LndMockTester(this, "TEST_MERCHANTLND", "https://lnd:lnd@127.0.0.1:35531/", "merchant_lnd", btc);
|
||||
PayTester.UseLightning = true;
|
||||
PayTester.IntegratedLightning = MerchantCharge.Client.Uri;
|
||||
}
|
||||
@ -111,6 +119,7 @@ namespace BTCPayServer.Tests
|
||||
public async Task EnsureChannelsSetup()
|
||||
{
|
||||
Logs.Tester.LogInformation("Connecting channels");
|
||||
BTCPayServer.Lightning.Tests.ConnectChannels.Logs = Logs.LogProvider.CreateLogger("Connect channels");
|
||||
await BTCPayServer.Lightning.Tests.ConnectChannels.ConnectAll(ExplorerNode, GetLightningSenderClients(), GetLightningDestClients()).ConfigureAwait(false);
|
||||
Logs.Tester.LogInformation("Channels connected");
|
||||
}
|
||||
@ -138,6 +147,19 @@ namespace BTCPayServer.Tests
|
||||
await CustomerLightningD.Pay(bolt11);
|
||||
}
|
||||
|
||||
public async Task<T> WaitForEvent<T>(Func<Task> action)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var sub = PayTester.GetService<EventAggregator>().Subscribe<T>(evt =>
|
||||
{
|
||||
tcs.TrySetResult(evt);
|
||||
});
|
||||
await action.Invoke();
|
||||
var result = await tcs.Task;
|
||||
sub.Dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
public ILightningClient CustomerLightningD { get; set; }
|
||||
|
||||
public ILightningClient MerchantLightningD { get; private set; }
|
||||
|
@ -8,8 +8,10 @@ using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Amazon.S3.Model;
|
||||
using Xunit;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using BTCPayServer.Payments;
|
||||
@ -20,44 +22,101 @@ using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using NBXplorer.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using NBitcoin.Payment;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class TestAccount
|
||||
{
|
||||
ServerTester parent;
|
||||
|
||||
public TestAccount(ServerTester parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
BitPay = new Bitpay(new Key(), parent.PayTester.ServerUri);
|
||||
}
|
||||
|
||||
public void GrantAccess()
|
||||
public void GrantAccess(bool isAdmin = false)
|
||||
{
|
||||
GrantAccessAsync().GetAwaiter().GetResult();
|
||||
GrantAccessAsync(isAdmin).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task MakeAdmin()
|
||||
public async Task MakeAdmin(bool isAdmin = true)
|
||||
{
|
||||
var userManager = parent.PayTester.GetService<UserManager<ApplicationUser>>();
|
||||
var u = await userManager.FindByIdAsync(UserId);
|
||||
await userManager.AddToRoleAsync(u, Roles.ServerAdmin);
|
||||
if (isAdmin)
|
||||
await userManager.AddToRoleAsync(u, Roles.ServerAdmin);
|
||||
else
|
||||
await userManager.RemoveFromRoleAsync(u, Roles.ServerAdmin);
|
||||
IsAdmin = true;
|
||||
}
|
||||
|
||||
public void Register()
|
||||
public Task<BTCPayServerClient> CreateClient()
|
||||
{
|
||||
RegisterAsync().GetAwaiter().GetResult();
|
||||
return Task.FromResult(new BTCPayServerClient(parent.PayTester.ServerUri, RegisterDetails.Email,
|
||||
RegisterDetails.Password));
|
||||
}
|
||||
public async Task GrantAccessAsync()
|
||||
|
||||
public async Task<BTCPayServerClient> CreateClient(params string[] permissions)
|
||||
{
|
||||
await RegisterAsync();
|
||||
var manageController = parent.PayTester.GetController<ManageController>(UserId, StoreId, IsAdmin);
|
||||
var x = Assert.IsType<RedirectToActionResult>(await manageController.AddApiKey(
|
||||
new ManageController.AddApiKeyViewModel()
|
||||
{
|
||||
PermissionValues = permissions.Select(s =>
|
||||
{
|
||||
Permission.TryParse(s, out var p);
|
||||
return p;
|
||||
}).GroupBy(permission => permission.Policy).Select(p =>
|
||||
{
|
||||
var stores = p.Where(permission => !string.IsNullOrEmpty(permission.Scope))
|
||||
.Select(permission => permission.Scope).ToList();
|
||||
return new ManageController.AddApiKeyViewModel.PermissionValueItem()
|
||||
{
|
||||
Permission = p.Key,
|
||||
Forbidden = false,
|
||||
StoreMode = stores.Any()? ManageController.AddApiKeyViewModel.ApiKeyStoreMode.Specific: ManageController.AddApiKeyViewModel.ApiKeyStoreMode.AllStores,
|
||||
SpecificStores = stores,
|
||||
Value = true
|
||||
};
|
||||
}).ToList()
|
||||
}));
|
||||
var statusMessage = manageController.TempData.GetStatusMessageModel();
|
||||
Assert.NotNull(statusMessage);
|
||||
var str = "<code class='alert-link'>";
|
||||
var apiKey = statusMessage.Html.Substring(statusMessage.Html.IndexOf(str) + str.Length);
|
||||
apiKey = apiKey.Substring(0, apiKey.IndexOf("</code>"));
|
||||
return new BTCPayServerClient(parent.PayTester.ServerUri, apiKey);
|
||||
}
|
||||
|
||||
public void Register(bool isAdmin = false)
|
||||
{
|
||||
RegisterAsync(isAdmin).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task GrantAccessAsync(bool isAdmin = false)
|
||||
{
|
||||
await RegisterAsync(isAdmin);
|
||||
await CreateStoreAsync();
|
||||
var store = this.GetController<StoresController>();
|
||||
var pairingCode = BitPay.RequestClientAuthorization("test", Facade.Merchant);
|
||||
Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString()));
|
||||
await store.Pair(pairingCode.ToString(), StoreId);
|
||||
}
|
||||
|
||||
public BTCPayServerClient CreateClientFromAPIKey(string apiKey)
|
||||
{
|
||||
return new BTCPayServerClient(parent.PayTester.ServerUri, apiKey);
|
||||
}
|
||||
|
||||
public void CreateStore()
|
||||
{
|
||||
CreateStoreAsync().GetAwaiter().GetResult();
|
||||
@ -70,6 +129,7 @@ namespace BTCPayServer.Tests
|
||||
store.NetworkFeeMode = mode;
|
||||
});
|
||||
}
|
||||
|
||||
public void ModifyStore(Action<StoreViewModel> modify)
|
||||
{
|
||||
var storeController = GetController<StoresController>();
|
||||
@ -77,6 +137,13 @@ namespace BTCPayServer.Tests
|
||||
modify(store);
|
||||
storeController.UpdateStore(store).GetAwaiter().GetResult();
|
||||
}
|
||||
public Task ModifyStoreAsync(Action<StoreViewModel> modify)
|
||||
{
|
||||
var storeController = GetController<StoresController>();
|
||||
StoreViewModel store = (StoreViewModel)((ViewResult)storeController.UpdateStore()).Model;
|
||||
modify(store);
|
||||
return storeController.UpdateStore(store);
|
||||
}
|
||||
|
||||
public T GetController<T>(bool setImplicitStore = true) where T : Controller
|
||||
{
|
||||
@ -86,45 +153,58 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public async Task CreateStoreAsync()
|
||||
{
|
||||
if (UserId is null)
|
||||
{
|
||||
await RegisterAsync();
|
||||
}
|
||||
var store = this.GetController<UserStoresController>();
|
||||
await store.CreateStore(new CreateStoreViewModel() { Name = "Test Store" });
|
||||
await store.CreateStore(new CreateStoreViewModel() {Name = "Test Store"});
|
||||
StoreId = store.CreatedStoreId;
|
||||
parent.Stores.Add(StoreId);
|
||||
}
|
||||
|
||||
public BTCPayNetwork SupportedNetwork { get; set; }
|
||||
|
||||
public WalletId RegisterDerivationScheme(string crytoCode, bool segwit = false, bool importKeysToNBX = false)
|
||||
public WalletId RegisterDerivationScheme(string crytoCode, ScriptPubKeyType segwit = ScriptPubKeyType.Legacy, bool importKeysToNBX = false)
|
||||
{
|
||||
return RegisterDerivationSchemeAsync(crytoCode, segwit, importKeysToNBX).GetAwaiter().GetResult();
|
||||
}
|
||||
public async Task<WalletId> RegisterDerivationSchemeAsync(string cryptoCode, bool segwit = false, bool importKeysToNBX = false)
|
||||
|
||||
public async Task<WalletId> RegisterDerivationSchemeAsync(string cryptoCode, ScriptPubKeyType segwit = ScriptPubKeyType.Legacy,
|
||||
bool importKeysToNBX = false)
|
||||
{
|
||||
if (StoreId is null)
|
||||
await CreateStoreAsync();
|
||||
SupportedNetwork = parent.NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var store = parent.PayTester.GetController<StoresController>(UserId, StoreId);
|
||||
GenerateWalletResponseV = await parent.ExplorerClient.GenerateWalletAsync(new GenerateWalletRequest()
|
||||
{
|
||||
ScriptPubKeyType = segwit ? ScriptPubKeyType.Segwit : ScriptPubKeyType.Legacy,
|
||||
SavePrivateKeys = importKeysToNBX
|
||||
ScriptPubKeyType = segwit,
|
||||
SavePrivateKeys = importKeysToNBX,
|
||||
});
|
||||
|
||||
await store.AddDerivationScheme(StoreId, new DerivationSchemeViewModel()
|
||||
{
|
||||
Enabled = true,
|
||||
CryptoCode = cryptoCode,
|
||||
Network = SupportedNetwork,
|
||||
RootFingerprint = GenerateWalletResponseV.AccountKeyPath.MasterFingerprint.ToString(),
|
||||
RootKeyPath = SupportedNetwork.GetRootKeyPath(),
|
||||
Source = "NBXplorer",
|
||||
AccountKey = GenerateWalletResponseV.AccountHDKey.Neuter().ToWif(),
|
||||
DerivationSchemeFormat = "BTCPay",
|
||||
KeyPath = GenerateWalletResponseV.AccountKeyPath.KeyPath.ToString(),
|
||||
DerivationScheme = DerivationScheme.ToString(),
|
||||
Confirmation = true
|
||||
}, cryptoCode);
|
||||
await store.AddDerivationScheme(StoreId,
|
||||
new DerivationSchemeViewModel()
|
||||
{
|
||||
Enabled = true,
|
||||
CryptoCode = cryptoCode,
|
||||
Network = SupportedNetwork,
|
||||
RootFingerprint = GenerateWalletResponseV.AccountKeyPath.MasterFingerprint.ToString(),
|
||||
RootKeyPath = SupportedNetwork.GetRootKeyPath(),
|
||||
Source = "NBXplorer",
|
||||
AccountKey = GenerateWalletResponseV.AccountHDKey.Neuter().ToWif(),
|
||||
DerivationSchemeFormat = "BTCPay",
|
||||
KeyPath = GenerateWalletResponseV.AccountKeyPath.KeyPath.ToString(),
|
||||
DerivationScheme = DerivationScheme.ToString(),
|
||||
Confirmation = true
|
||||
}, cryptoCode);
|
||||
return new WalletId(StoreId, cryptoCode);
|
||||
}
|
||||
|
||||
public Task EnablePayJoin()
|
||||
{
|
||||
return ModifyStoreAsync(s => s.PayJoinEnabled = true);
|
||||
}
|
||||
|
||||
public GenerateWalletResponse GenerateWalletResponseV { get; set; }
|
||||
|
||||
public DerivationStrategyBase DerivationScheme
|
||||
@ -135,7 +215,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RegisterAsync()
|
||||
private async Task RegisterAsync(bool isAdmin = false)
|
||||
{
|
||||
var account = parent.PayTester.GetController<AccountController>();
|
||||
RegisterDetails = new RegisterViewModel()
|
||||
@ -143,55 +223,210 @@ namespace BTCPayServer.Tests
|
||||
Email = Guid.NewGuid() + "@toto.com",
|
||||
ConfirmPassword = "Kitten0@",
|
||||
Password = "Kitten0@",
|
||||
IsAdmin = isAdmin
|
||||
};
|
||||
await account.Register(RegisterDetails);
|
||||
UserId = account.RegisteredUserId;
|
||||
IsAdmin = account.RegisteredAdmin;
|
||||
}
|
||||
|
||||
public RegisterViewModel RegisterDetails{ get; set; }
|
||||
public RegisterViewModel RegisterDetails { get; set; }
|
||||
|
||||
public Bitpay BitPay
|
||||
{
|
||||
get; set;
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string UserId
|
||||
{
|
||||
get; set;
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string StoreId
|
||||
{
|
||||
get; set;
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool IsAdmin { get; internal set; }
|
||||
|
||||
public void RegisterLightningNode(string cryptoCode, LightningConnectionType connectionType)
|
||||
public void RegisterLightningNode(string cryptoCode, LightningConnectionType connectionType, bool isMerchant = true)
|
||||
{
|
||||
RegisterLightningNodeAsync(cryptoCode, connectionType).GetAwaiter().GetResult();
|
||||
RegisterLightningNodeAsync(cryptoCode, connectionType, isMerchant).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType connectionType)
|
||||
public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType connectionType, bool isMerchant = true)
|
||||
{
|
||||
var storeController = this.GetController<StoresController>();
|
||||
|
||||
string connectionString = null;
|
||||
if (connectionType == LightningConnectionType.Charge)
|
||||
connectionString = "type=charge;server=" + parent.MerchantCharge.Client.Uri.AbsoluteUri;
|
||||
{
|
||||
if (isMerchant)
|
||||
connectionString = "type=charge;server=" + parent.MerchantCharge.Client.Uri.AbsoluteUri;
|
||||
else
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
else if (connectionType == LightningConnectionType.CLightning)
|
||||
connectionString = "type=clightning;server=" + ((CLightningClient)parent.MerchantLightningD).Address.AbsoluteUri;
|
||||
{
|
||||
if (isMerchant)
|
||||
connectionString = "type=clightning;server=" +
|
||||
((CLightningClient)parent.MerchantLightningD).Address.AbsoluteUri;
|
||||
else
|
||||
connectionString = "type=clightning;server=" +
|
||||
((CLightningClient)parent.CustomerLightningD).Address.AbsoluteUri;
|
||||
}
|
||||
else if (connectionType == LightningConnectionType.LndREST)
|
||||
connectionString = $"type=lnd-rest;server={parent.MerchantLnd.Swagger.BaseUrl};allowinsecure=true";
|
||||
{
|
||||
if (isMerchant)
|
||||
connectionString = $"type=lnd-rest;server={parent.MerchantLnd.Swagger.BaseUrl};allowinsecure=true";
|
||||
else
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
else
|
||||
throw new NotSupportedException(connectionType.ToString());
|
||||
|
||||
await storeController.AddLightningNode(StoreId, new LightningNodeViewModel()
|
||||
{
|
||||
ConnectionString = connectionString,
|
||||
SkipPortTest = true
|
||||
}, "save", "BTC");
|
||||
await storeController.AddLightningNode(StoreId,
|
||||
new LightningNodeViewModel() {ConnectionString = connectionString, SkipPortTest = true}, "save", "BTC");
|
||||
if (storeController.ModelState.ErrorCount != 0)
|
||||
Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
|
||||
}
|
||||
|
||||
public async Task<Coin> ReceiveUTXO(Money value, BTCPayNetwork network)
|
||||
{
|
||||
var cashCow = parent.ExplorerNode;
|
||||
var btcPayWallet = parent.PayTester.GetService<BTCPayWalletProvider>().GetWallet(network);
|
||||
var address = (await btcPayWallet.ReserveAddressAsync(this.DerivationScheme)).Address;
|
||||
await parent.WaitForEvent<NewOnChainTransactionEvent>(async () =>
|
||||
{
|
||||
await cashCow.SendToAddressAsync(address, value);
|
||||
});
|
||||
int i = 0;
|
||||
while (i <30)
|
||||
{
|
||||
var result = (await btcPayWallet.GetUnspentCoins(DerivationScheme))
|
||||
.FirstOrDefault(c => c.ScriptPubKey == address.ScriptPubKey)?.Coin;
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
await Task.Delay(1000);
|
||||
i++;
|
||||
}
|
||||
Assert.False(true);
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<BitcoinAddress> GetNewAddress(BTCPayNetwork network)
|
||||
{
|
||||
var cashCow = parent.ExplorerNode;
|
||||
var btcPayWallet = parent.PayTester.GetService<BTCPayWalletProvider>().GetWallet(network);
|
||||
var address = (await btcPayWallet.ReserveAddressAsync(this.DerivationScheme)).Address;
|
||||
return address;
|
||||
}
|
||||
|
||||
public async Task<PSBT> Sign(PSBT psbt)
|
||||
{
|
||||
var btcPayWallet = parent.PayTester.GetService<BTCPayWalletProvider>()
|
||||
.GetWallet(psbt.Network.NetworkSet.CryptoCode);
|
||||
var explorerClient = parent.PayTester.GetService<ExplorerClientProvider>()
|
||||
.GetExplorerClient(psbt.Network.NetworkSet.CryptoCode);
|
||||
psbt = (await explorerClient.UpdatePSBTAsync(new UpdatePSBTRequest()
|
||||
{
|
||||
DerivationScheme = DerivationScheme, PSBT = psbt
|
||||
})).PSBT;
|
||||
return psbt.SignAll(this.DerivationScheme, GenerateWalletResponseV.AccountHDKey,
|
||||
GenerateWalletResponseV.AccountKeyPath);
|
||||
}
|
||||
|
||||
public async Task<PSBT> SubmitPayjoin(Invoice invoice, PSBT psbt, string expectedError = null, bool senderError= false)
|
||||
{
|
||||
var endpoint = GetPayjoinEndpoint(invoice, psbt.Network);
|
||||
if (endpoint == null)
|
||||
{
|
||||
throw new InvalidOperationException("No payjoin endpoint for the invoice");
|
||||
}
|
||||
var pjClient = parent.PayTester.GetService<PayjoinClient>();
|
||||
var storeRepository = parent.PayTester.GetService<StoreRepository>();
|
||||
var store = await storeRepository.FindStore(StoreId);
|
||||
var settings = store.GetSupportedPaymentMethods(parent.NetworkProvider).OfType<DerivationSchemeSettings>()
|
||||
.First();
|
||||
Logs.Tester.LogInformation($"Proposing {psbt.GetGlobalTransaction().GetHash()}");
|
||||
if (expectedError is null && !senderError)
|
||||
{
|
||||
var proposed = await pjClient.RequestPayjoin(endpoint, settings, psbt, default);
|
||||
Logs.Tester.LogInformation($"Proposed payjoin is {proposed.GetGlobalTransaction().GetHash()}");
|
||||
Assert.NotNull(proposed);
|
||||
return proposed;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (senderError)
|
||||
{
|
||||
await Assert.ThrowsAsync<PayjoinSenderException>(async () => await pjClient.RequestPayjoin(endpoint, settings, psbt, default));
|
||||
}
|
||||
else
|
||||
{
|
||||
var ex = await Assert.ThrowsAsync<PayjoinReceiverException>(async () => await pjClient.RequestPayjoin(endpoint, settings, psbt, default));
|
||||
var split = expectedError.Split('|');
|
||||
Assert.Equal(split[0], ex.ErrorCode);
|
||||
if (split.Length > 1)
|
||||
Assert.Contains(split[1], ex.ReceiverMessage);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Transaction> SubmitPayjoin(Invoice invoice, Transaction transaction, BTCPayNetwork network,
|
||||
string expectedError = null)
|
||||
{
|
||||
var response =
|
||||
await SubmitPayjoinCore(transaction.ToHex(), invoice, network.NBitcoinNetwork, expectedError);
|
||||
if (response == null)
|
||||
return null;
|
||||
var signed = Transaction.Parse(await response.Content.ReadAsStringAsync(), network.NBitcoinNetwork);
|
||||
return signed;
|
||||
}
|
||||
|
||||
async Task<HttpResponseMessage> SubmitPayjoinCore(string content, Invoice invoice, Network network,
|
||||
string expectedError)
|
||||
{
|
||||
var endpoint = GetPayjoinEndpoint(invoice, network);
|
||||
var response = await parent.PayTester.HttpClient.PostAsync(endpoint,
|
||||
new StringContent(content, Encoding.UTF8, "text/plain"));
|
||||
if (expectedError != null)
|
||||
{
|
||||
var split = expectedError.Split('|');
|
||||
Assert.False(response.IsSuccessStatusCode);
|
||||
var error = JObject.Parse(await response.Content.ReadAsStringAsync());
|
||||
if (split.Length > 0)
|
||||
Assert.Equal(split[0], error["errorCode"].Value<string>());
|
||||
if (split.Length > 1)
|
||||
Assert.Contains(split[1], error["message"].Value<string>());
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var error = JObject.Parse(await response.Content.ReadAsStringAsync());
|
||||
Assert.True(false,
|
||||
$"Error: {error["errorCode"].Value<string>()}: {error["message"].Value<string>()}");
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public static Uri GetPayjoinEndpoint(Invoice invoice, Network network)
|
||||
{
|
||||
var parsedBip21 = new BitcoinUrlBuilder(
|
||||
invoice.CryptoInfo.First(c => c.CryptoCode == network.NetworkSet.CryptoCode).PaymentUrls.BIP21,
|
||||
network);
|
||||
return parsedBip21.UnknowParameters.TryGetValue($"{PayjoinClient.BIP21EndpointKey}", out var uri) ? new Uri(uri, UriKind.Absolute) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ using Xunit.Sdk;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -41,6 +42,12 @@ namespace BTCPayServer.Tests
|
||||
return Path.Combine(directory.FullName, "TestData", relativeFilePath);
|
||||
}
|
||||
|
||||
public static T AssertType<T>(this object obj)
|
||||
{
|
||||
Assert.IsType<T>(obj);
|
||||
return (T)obj;
|
||||
}
|
||||
|
||||
public static FormFile GetFormFile(string filename, string content)
|
||||
{
|
||||
File.WriteAllText(filename, content);
|
||||
|
File diff suppressed because it is too large
Load Diff
5
BTCPayServer.Tests/docker-bitcoin-generate.sh
Executable file
5
BTCPayServer.Tests/docker-bitcoin-generate.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
bitcoind_container_id="$(docker ps -q --filter label=com.docker.compose.project=btcpayservertests --filter label=com.docker.compose.service=bitcoind)"
|
||||
address=$(docker exec -ti $bitcoind_container_id bitcoin-cli -datadir="/data" getnewaddress)
|
||||
docker exec -ti $bitcoind_container_id bitcoin-cli -datadir="/data" generatetoaddress "$@" "$address"
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user