Compare commits
281 Commits
v1.2.4-wip
...
v1.3.0
Author | SHA1 | Date | |
---|---|---|---|
d6e3fb46ee | |||
efc05edca3 | |||
aa3d384f47 | |||
a5aa5cf059 | |||
ccd8859d7f | |||
a28399e31d | |||
61f63a9996 | |||
de93c5c9d6 | |||
94865815c7 | |||
33754933d5 | |||
560b6db480 | |||
4c71167535 | |||
707484709a | |||
274be7c1bc | |||
3f176a6b6b | |||
fc8a5ff95f | |||
25f84d000b | |||
31b7826dce | |||
2d4aa52fa5 | |||
eee8008bb2 | |||
a5ae509f9f | |||
8f117b5079 | |||
0b5d0349d4 | |||
8a0660cbd6 | |||
f7a0b91ec1 | |||
1ecd1c1e54 | |||
fccbbb6fb7 | |||
d1886b039e | |||
79c61f01c8 | |||
e50c9266b4 | |||
fd27bd94e2 | |||
05f99f3855 | |||
31ef763c05 | |||
c0abcbea24 | |||
a73383cd87 | |||
951bfeefb1 | |||
fc7125b8cd | |||
26bcdbc766 | |||
fb1fcbe0b9 | |||
2c45f803e4 | |||
fbdd2fc470 | |||
0558631982 | |||
63e1c5807e | |||
a3cc1f2ef0 | |||
5318684e5c | |||
b3b9651cd8 | |||
86e528e5df | |||
c46a69e1bd | |||
9b0d1a23dc | |||
db038723f4 | |||
a193e1cbf3 | |||
493f1b98c2 | |||
926b60df3d | |||
d8a162fb6e | |||
4cf3249e0b | |||
407f26b1dc | |||
0159588eed | |||
6f7be7eb09 | |||
09a53718bb | |||
5655f22397 | |||
791d0abb34 | |||
13d9930955 | |||
c4f40d68e9 | |||
9951370321 | |||
0119ad452b | |||
3d3016fdca | |||
262798d577 | |||
cf206e64a7 | |||
5ac4135a13 | |||
5176eaf4ba | |||
514417e888 | |||
f83e85dc36 | |||
9592a77cff | |||
33a893ba31 | |||
f89cdadde8 | |||
7d2aa28e1f | |||
9df4429fc2 | |||
843a2491ef | |||
d64fb15ac2 | |||
3671e7f18c | |||
899bf98f45 | |||
4230ba513f | |||
18f1b4d8c1 | |||
75776687bc | |||
b5ebd14589 | |||
67ba64b0a1 | |||
b26e8311c1 | |||
0033aab03e | |||
1037fe0b14 | |||
6c688b9684 | |||
601e17ed0f | |||
ad86c16bc9 | |||
791db983c7 | |||
d7a7382d00 | |||
e842a00402 | |||
78a8c8c1be | |||
a7c093a0eb | |||
86956c1e7b | |||
54539001f1 | |||
4321cbf41a | |||
8d9941bfd2 | |||
f9e38deee7 | |||
89fd044b00 | |||
039f88d14c | |||
d3f9eb38a9 | |||
768d97ac6c | |||
154078d46f | |||
d74c6a30c8 | |||
1407d5be8d | |||
721c06e157 | |||
a0265f18d9 | |||
8948475cad | |||
faed5349fb | |||
7bcaf956e7 | |||
31c2a80758 | |||
150e4b842c | |||
b970f64639 | |||
08bd13b2cd | |||
6e3d6125c2 | |||
143d5f69c1 | |||
156ddd24fa | |||
cf2c147f4f | |||
ce1903b2fa | |||
ccdfe5ac86 | |||
b0ef98dd63 | |||
5c8e62bd90 | |||
6674d76d6d | |||
dea747a9a9 | |||
a32ace1dcb | |||
4785f0a4dd | |||
8249d8a0f5 | |||
234bc30369 | |||
b7a081b9a4 | |||
88c925017d | |||
a3c2a9ac61 | |||
7cad6302b7 | |||
4b34090376 | |||
ac554a27b6 | |||
e1ea7200cf | |||
802fec6bf3 | |||
67aee9bdc6 | |||
975ad2f8ab | |||
b0814166f5 | |||
8f397865fd | |||
8ffa7525c5 | |||
6048a26511 | |||
1a8a7fc27f | |||
64901dfc22 | |||
6a28497d33 | |||
ed1ec2300d | |||
edfde494fa | |||
a3cc573e4d | |||
44fe70ae44 | |||
d05a9295b3 | |||
7aa4cd8104 | |||
4a6088b6b9 | |||
d949680d5a | |||
0cd7380af0 | |||
a3afcd2a6e | |||
bd6c7a8c3d | |||
920955657d | |||
d975ea1509 | |||
ba5c49a8df | |||
9f6c7180b2 | |||
ef70f4d547 | |||
8c061b1f07 | |||
0aed8fdb5b | |||
6125a99381 | |||
83b54b7be1 | |||
047222b3ee | |||
2fe1ab83f5 | |||
5372058c33 | |||
a08261a798 | |||
869985f005 | |||
97b5c473c1 | |||
337db3daab | |||
10da82607d | |||
7f49824783 | |||
5d8bc73063 | |||
82675384c4 | |||
3dc663f4d8 | |||
2b0fcdf95c | |||
d0120f1427 | |||
ac34109da3 | |||
d5c96eee32 | |||
7b252369e9 | |||
9d48358f2a | |||
2dcd7db797 | |||
c267cf0e9c | |||
403820cf14 | |||
6d667e2d78 | |||
258a19fdf5 | |||
1a593a1ced | |||
b8ac57a5b3 | |||
b93f3ff445 | |||
5984edb7f4 | |||
603bd2692e | |||
2e61bdf88f | |||
e6aa73197a | |||
b2f9353be1 | |||
5cba59932b | |||
809340e629 | |||
bb6a188883 | |||
24674d354c | |||
e93562b1db | |||
f7099cf6aa | |||
d88f012d82 | |||
f96767d3dc | |||
6666786b7a | |||
eccbe8e018 | |||
aac87539ae | |||
a8995d2bed | |||
7f40698bba | |||
c4f4c3138c | |||
3b9c7db481 | |||
649f650da6 | |||
4c83265de4 | |||
ad7b62fa3d | |||
650df97e50 | |||
6f75125cf5 | |||
26c05a8d5f | |||
fc4e47cec6 | |||
c39f1341aa | |||
a39d1e0886 | |||
435f51a777 | |||
de2c12c1df | |||
ae43af999b | |||
14a10eeef8 | |||
180be49824 | |||
5faa756f1c | |||
06db29dd43 | |||
6d317937c7 | |||
522c990278 | |||
e584ebe7de | |||
1422bd8540 | |||
fe18e71538 | |||
e5699f674b | |||
203db44b4e | |||
69c5f5b9e5 | |||
5a16dfced3 | |||
cb46ef9e6c | |||
3b810e9b8d | |||
663dcecb8e | |||
fbb5671f89 | |||
47d348359f | |||
ab2f460f35 | |||
c77e3a9396 | |||
014c43f79b | |||
21fb3813dd | |||
9a039a747a | |||
17578a4615 | |||
abb68d6f59 | |||
3c443344a5 | |||
2887017d12 | |||
f2286fb1be | |||
bdd11a14ff | |||
18f6e5af4d | |||
83b07e0caf | |||
840a5ac4b4 | |||
4923311168 | |||
748423dfe8 | |||
2f4e610900 | |||
d39ec97b9a | |||
0d08bd3ad1 | |||
3343738bb3 | |||
373b0b7850 | |||
aa24da72b9 | |||
aecc5f3c6e | |||
3d4ef48ceb | |||
9bb74a17d8 | |||
b654dfb237 | |||
1bca8c81a4 | |||
4d35fd4ab3 | |||
bf5ea23bc6 | |||
b4c1f695a8 | |||
de3d966835 | |||
bcdb1ab1d8 | |||
63944792b0 | |||
3895b133a3 | |||
4371b81ef3 | |||
cd93a5ab6b |
@ -31,10 +31,10 @@
|
||||
<None Include="icon.png" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.4" />
|
||||
<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.EntityFrameworkCore" Version="3.1.19" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.19" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.18" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.2.7" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BTCPayServer.Client\BTCPayServer.Client.csproj" />
|
||||
|
@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NBitcoin.Crypto;
|
||||
|
||||
namespace BTCPayServer.Security
|
||||
{
|
||||
@ -9,6 +10,8 @@ namespace BTCPayServer.Security
|
||||
{
|
||||
public ConsentSecurityPolicy(string name, string value)
|
||||
{
|
||||
if (value.Contains(';', StringComparison.OrdinalIgnoreCase))
|
||||
throw new FormatException();
|
||||
_Value = value;
|
||||
_Name = name;
|
||||
}
|
||||
@ -67,10 +70,48 @@ namespace BTCPayServer.Security
|
||||
}
|
||||
|
||||
readonly HashSet<ConsentSecurityPolicy> _Policies = new HashSet<ConsentSecurityPolicy>();
|
||||
|
||||
/// <summary>
|
||||
/// Allow a specific script as event handler
|
||||
/// </summary>
|
||||
/// <param name="script"></param>
|
||||
public void AllowUnsafeHashes(string script = null)
|
||||
{
|
||||
if (!allowUnsafeHashes)
|
||||
{
|
||||
Add("script-src", $"'unsafe-hashes'");
|
||||
allowUnsafeHashes = true;
|
||||
}
|
||||
if (script != null)
|
||||
{
|
||||
var sha = GetSha256(script);
|
||||
Add("script-src", $"'sha256-{sha}'");
|
||||
}
|
||||
}
|
||||
|
||||
bool allowUnsafeHashes = false;
|
||||
/// <summary>
|
||||
/// Allow the injection of script tag with the following script
|
||||
/// </summary>
|
||||
/// <param name="script"></param>
|
||||
public void AllowInline(string script)
|
||||
{
|
||||
if (script is null)
|
||||
throw new ArgumentNullException(nameof(script));
|
||||
var sha = GetSha256(script);
|
||||
Add("script-src", $"'sha256-{sha}'");
|
||||
}
|
||||
static string GetSha256(string script)
|
||||
{
|
||||
return Convert.ToBase64String(Hashes.SHA256(Encoding.UTF8.GetBytes(script.Replace("\r\n", "\n", StringComparison.Ordinal))));
|
||||
}
|
||||
|
||||
public void Add(string name, string value)
|
||||
{
|
||||
Add(new ConsentSecurityPolicy(name, value));
|
||||
}
|
||||
public void Add(ConsentSecurityPolicy policy)
|
||||
{
|
||||
if (_Policies.Any(p => p.Name == policy.Name && p.Value == policy.Name))
|
||||
return;
|
||||
_Policies.Add(policy);
|
||||
}
|
||||
|
||||
@ -87,34 +128,19 @@ namespace BTCPayServer.Security
|
||||
{
|
||||
value.Append(';');
|
||||
}
|
||||
List<string> values = new List<string>();
|
||||
HashSet<string> values = new HashSet<string>();
|
||||
List<string> valuesList = new List<string>();
|
||||
values.Add(group.Key);
|
||||
valuesList.Add(group.Key);
|
||||
foreach (var v in group)
|
||||
{
|
||||
values.Add(v.Value);
|
||||
if (values.Add(v.Value))
|
||||
valuesList.Add(v.Value);
|
||||
}
|
||||
foreach (var i in authorized)
|
||||
{
|
||||
values.Add(i);
|
||||
}
|
||||
value.Append(String.Join(" ", values.OfType<object>().ToArray()));
|
||||
value.Append(String.Join(" ", valuesList.OfType<object>().ToArray()));
|
||||
firstGroup = false;
|
||||
}
|
||||
return value.ToString();
|
||||
}
|
||||
|
||||
internal void Clear()
|
||||
{
|
||||
authorized.Clear();
|
||||
_Policies.Clear();
|
||||
}
|
||||
|
||||
readonly HashSet<string> authorized = new HashSet<string>();
|
||||
internal void AddAllAuthorized(string v)
|
||||
{
|
||||
authorized.Add(v);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Authorized => authorized;
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Version Condition=" '$(Version)' == '' ">1.4.0</Version>
|
||||
<Version Condition=" '$(Version)' == '' ">1.5.0</Version>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
@ -27,9 +27,9 @@
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NBitcoin" Version="6.0.8" />
|
||||
<PackageReference Include="NBitcoin" Version="6.0.15" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.2.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="icon.png" Pack="true" PackagePath="\" />
|
||||
|
@ -0,0 +1,59 @@
|
||||
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<LNURLPayPaymentMethodData>>
|
||||
GetStoreLNURLPayPaymentMethods(string storeId, bool? enabled = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var query = new Dictionary<string, object>();
|
||||
if (enabled != null)
|
||||
{
|
||||
query.Add(nameof(enabled), enabled);
|
||||
}
|
||||
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LNURLPay",
|
||||
query), token);
|
||||
return await HandleResponse<IEnumerable<LNURLPayPaymentMethodData>>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LNURLPayPaymentMethodData> GetStoreLNURLPayPaymentMethod(
|
||||
string storeId,
|
||||
string cryptoCode, CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LNURLPay/{cryptoCode}"), token);
|
||||
return await HandleResponse<LNURLPayPaymentMethodData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task RemoveStoreLNURLPayPaymentMethod(string storeId,
|
||||
string cryptoCode, CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LNURLPay/{cryptoCode}",
|
||||
method: HttpMethod.Delete), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LNURLPayPaymentMethodData> UpdateStoreLNURLPayPaymentMethod(
|
||||
string storeId,
|
||||
string cryptoCode, LNURLPayPaymentMethodData paymentMethod,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LNURLPay/{cryptoCode}",
|
||||
bodyPayload: paymentMethod, method: HttpMethod.Put), token);
|
||||
return await HandleResponse<LNURLPayPaymentMethodData>(response);
|
||||
}
|
||||
}
|
||||
}
|
@ -47,7 +47,7 @@ namespace BTCPayServer.Client
|
||||
|
||||
public virtual async Task<LightningNetworkPaymentMethodData> UpdateStoreLightningNetworkPaymentMethod(
|
||||
string storeId,
|
||||
string cryptoCode, LightningNetworkPaymentMethodData paymentMethod,
|
||||
string cryptoCode, UpdateLightningNetworkPaymentMethodRequest paymentMethod,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
@ -55,10 +55,5 @@ namespace BTCPayServer.Client
|
||||
bodyPayload: paymentMethod, method: HttpMethod.Put), token);
|
||||
return await HandleResponse<LightningNetworkPaymentMethodData>(response);
|
||||
}
|
||||
|
||||
public virtual Task<LightningNetworkPaymentMethodData>
|
||||
UpdateStoreLightningNetworkPaymentMethodToInternalNode(string storeId,
|
||||
string cryptoCode, CancellationToken token = default) => UpdateStoreLightningNetworkPaymentMethod(
|
||||
storeId, cryptoCode, new LightningNetworkPaymentMethodData(cryptoCode, "Internal Node", true), token);
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ namespace BTCPayServer.Client
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainPaymentMethodData> UpdateStoreOnChainPaymentMethod(string storeId,
|
||||
string cryptoCode, OnChainPaymentMethodData paymentMethod,
|
||||
string cryptoCode, UpdateOnChainPaymentMethodRequest paymentMethod,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
@ -56,7 +56,7 @@ namespace BTCPayServer.Client
|
||||
|
||||
public virtual async Task<OnChainPaymentMethodPreviewResultData>
|
||||
PreviewProposedStoreOnChainPaymentMethodAddresses(
|
||||
string storeId, string cryptoCode, OnChainPaymentMethodData paymentMethod, int offset = 0,
|
||||
string storeId, string cryptoCode, UpdateOnChainPaymentMethodRequest paymentMethod, int offset = 0,
|
||||
int amount = 10,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
|
@ -42,6 +42,8 @@ namespace BTCPayServer.Client
|
||||
public virtual async Task<WebhookDeliveryData> GetWebhookDelivery(string storeId, string webhookId, string deliveryId, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}"), token);
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
return null;
|
||||
return await HandleResponse<WebhookDeliveryData>(response);
|
||||
}
|
||||
public virtual async Task<string> RedeliverWebhook(string storeId, string webhookId, string deliveryId, CancellationToken token = default)
|
||||
|
@ -22,13 +22,17 @@ namespace BTCPayServer.JsonConverters
|
||||
switch (token.Type)
|
||||
{
|
||||
case JTokenType.Float:
|
||||
if (objectType == typeof(decimal) || objectType == typeof(decimal?))
|
||||
return token.Value<decimal>();
|
||||
if (objectType == typeof(double) || objectType == typeof(double?))
|
||||
return token.Value<double>();
|
||||
throw new JsonSerializationException("Unexpected object type: " + objectType);
|
||||
case JTokenType.Integer:
|
||||
case JTokenType.String:
|
||||
if (objectType == typeof(decimal) || objectType == typeof(decimal?) )
|
||||
return decimal.Parse(token.ToString(), CultureInfo.InvariantCulture);
|
||||
if (objectType == typeof(double) || objectType == typeof(double?))
|
||||
return double.Parse(token.ToString(), CultureInfo.InvariantCulture);
|
||||
|
||||
throw new JsonSerializationException("Unexpected object type: " + objectType);
|
||||
case JTokenType.Null when objectType == typeof(decimal?) || objectType == typeof(double?):
|
||||
return null;
|
||||
|
@ -1,9 +1,5 @@
|
||||
using System;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
|
@ -4,5 +4,6 @@
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public object Data { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ namespace BTCPayServer.Client.Models
|
||||
public SpeedPolicy? SpeedPolicy { get; set; }
|
||||
|
||||
public string[] PaymentMethods { get; set; }
|
||||
public string DefaultPaymentMethod { get; set; }
|
||||
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter.Minutes))]
|
||||
[JsonProperty("expirationMinutes")]
|
||||
@ -39,6 +40,7 @@ namespace BTCPayServer.Client.Models
|
||||
public string RedirectURL { get; set; }
|
||||
|
||||
public bool? RedirectAutomatically { get; set; }
|
||||
public bool? RequiresRefundEmail { get; set; } = null;
|
||||
public string DefaultLanguage { get; set; }
|
||||
}
|
||||
}
|
||||
@ -59,6 +61,8 @@ namespace BTCPayServer.Client.Models
|
||||
public DateTimeOffset ExpirationTime { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset CreatedTime { get; set; }
|
||||
[JsonProperty(ItemConverterType = typeof(StringEnumConverter))]
|
||||
public InvoiceStatus[] AvailableStatusesForManualMarking { get; set; }
|
||||
}
|
||||
public enum InvoiceStatus
|
||||
{
|
||||
|
14
BTCPayServer.Client/Models/LNURLPayPaymentMethodBaseData.cs
Normal file
14
BTCPayServer.Client/Models/LNURLPayPaymentMethodBaseData.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class LNURLPayPaymentMethodBaseData
|
||||
{
|
||||
public bool UseBech32Scheme { get; set; }
|
||||
public bool EnableForStandardInvoices { get; set; }
|
||||
public bool LUD12Enabled { get; set; }
|
||||
|
||||
public LNURLPayPaymentMethodBaseData()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
27
BTCPayServer.Client/Models/LNURLPayPaymentMethodData.cs
Normal file
27
BTCPayServer.Client/Models/LNURLPayPaymentMethodData.cs
Normal file
@ -0,0 +1,27 @@
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class LNURLPayPaymentMethodData: LNURLPayPaymentMethodBaseData
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the payment method is enabled
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Crypto code of the payment method
|
||||
/// </summary>
|
||||
public string CryptoCode { get; set; }
|
||||
|
||||
public LNURLPayPaymentMethodData()
|
||||
{
|
||||
}
|
||||
|
||||
public LNURLPayPaymentMethodData(string cryptoCode, bool enabled, bool useBech32Scheme, bool enableForStandardInvoices)
|
||||
{
|
||||
Enabled = enabled;
|
||||
CryptoCode = cryptoCode;
|
||||
UseBech32Scheme = useBech32Scheme;
|
||||
EnableForStandardInvoices = enableForStandardInvoices;
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
{
|
||||
|
||||
public string ConnectionString { get; set; }
|
||||
public bool DisableBOLT11PaymentOption { get; set; }
|
||||
public LightningNetworkPaymentMethodBaseData()
|
||||
{
|
||||
|
||||
|
@ -16,11 +16,15 @@ namespace BTCPayServer.Client.Models
|
||||
{
|
||||
}
|
||||
|
||||
public LightningNetworkPaymentMethodData(string cryptoCode, string connectionString, bool enabled)
|
||||
public LightningNetworkPaymentMethodData(string cryptoCode, string connectionString, bool enabled, string paymentMethod, bool disableBOLT11PaymentOption)
|
||||
{
|
||||
Enabled = enabled;
|
||||
CryptoCode = cryptoCode;
|
||||
ConnectionString = connectionString;
|
||||
PaymentMethod = paymentMethod;
|
||||
DisableBOLT11PaymentOption = disableBOLT11PaymentOption;
|
||||
}
|
||||
|
||||
public string PaymentMethod { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -2,30 +2,46 @@ using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class OnChainPaymentMethodData : OnChainPaymentMethodBaseData
|
||||
public class OnChainPaymentMethodDataPreview : OnChainPaymentMethodBaseData
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the payment method is enabled
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Crypto code of the payment method
|
||||
/// </summary>
|
||||
public string CryptoCode { get; set; }
|
||||
|
||||
public OnChainPaymentMethodData()
|
||||
public OnChainPaymentMethodDataPreview()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public OnChainPaymentMethodData(string cryptoCode, string derivationScheme, bool enabled, string label, RootedKeyPath accountKeyPath)
|
||||
public OnChainPaymentMethodDataPreview(string cryptoCode, string derivationScheme, string label, RootedKeyPath accountKeyPath)
|
||||
{
|
||||
Enabled = enabled;
|
||||
Label = label;
|
||||
AccountKeyPath = accountKeyPath;
|
||||
CryptoCode = cryptoCode;
|
||||
DerivationScheme = derivationScheme;
|
||||
}
|
||||
}
|
||||
|
||||
public class OnChainPaymentMethodData : OnChainPaymentMethodDataPreview
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the payment method is enabled
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public string PaymentMethod { get; set; }
|
||||
|
||||
public OnChainPaymentMethodData()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public OnChainPaymentMethodData(string cryptoCode, string derivationScheme, bool enabled, string label, RootedKeyPath accountKeyPath, string paymentMethod) :
|
||||
base(cryptoCode, derivationScheme, label, accountKeyPath)
|
||||
{
|
||||
Enabled = enabled;
|
||||
PaymentMethod = paymentMethod;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ namespace BTCPayServer.Client.Models
|
||||
}
|
||||
|
||||
public OnChainPaymentMethodDataWithSensitiveData(string cryptoCode, string derivationScheme, bool enabled,
|
||||
string label, RootedKeyPath accountKeyPath, Mnemonic mnemonic) : base(cryptoCode, derivationScheme, enabled,
|
||||
label, accountKeyPath)
|
||||
string label, RootedKeyPath accountKeyPath, Mnemonic mnemonic, string paymentMethod) : base(cryptoCode, derivationScheme, enabled,
|
||||
label, accountKeyPath, paymentMethod)
|
||||
{
|
||||
Mnemonic = mnemonic;
|
||||
}
|
||||
|
@ -29,8 +29,7 @@ namespace BTCPayServer.Client.Models
|
||||
public string LightningDescriptionTemplate { get; set; }
|
||||
public double PaymentTolerance { get; set; } = 0;
|
||||
public bool AnyoneCanCreateInvoice { get; set; }
|
||||
|
||||
|
||||
public string DefaultCurrency { get; set; }
|
||||
public bool RequiresRefundEmail { get; set; }
|
||||
public bool LightningAmountInSatoshi { get; set; }
|
||||
public bool LightningPrivateRouteHints { get; set; }
|
||||
|
@ -0,0 +1,20 @@
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class UpdateLightningNetworkPaymentMethodRequest: LightningNetworkPaymentMethodBaseData
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the payment method is enabled
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public UpdateLightningNetworkPaymentMethodRequest()
|
||||
{
|
||||
}
|
||||
|
||||
public UpdateLightningNetworkPaymentMethodRequest(string connectionString, bool enabled)
|
||||
{
|
||||
Enabled = enabled;
|
||||
ConnectionString = connectionString;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class UpdateOnChainPaymentMethodRequest : OnChainPaymentMethodBaseData
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the payment method is enabled
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public UpdateOnChainPaymentMethodRequest()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public UpdateOnChainPaymentMethodRequest(bool enabled, string derivationScheme, string label, RootedKeyPath accountKeyPath)
|
||||
{
|
||||
Enabled = enabled;
|
||||
Label = label;
|
||||
AccountKeyPath = accountKeyPath;
|
||||
DerivationScheme = derivationScheme;
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ namespace BTCPayServer.Client.Models
|
||||
InvoiceProcessing,
|
||||
InvoiceExpired,
|
||||
InvoiceSettled,
|
||||
InvoiceInvalid
|
||||
InvoiceInvalid,
|
||||
InvoicePaymentSettled,
|
||||
}
|
||||
}
|
||||
|
@ -10,72 +10,89 @@ namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public WebhookInvoiceEvent()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public WebhookInvoiceEvent(WebhookEventType evtType)
|
||||
{
|
||||
this.Type = evtType;
|
||||
}
|
||||
[JsonProperty(Order = 1)]
|
||||
public string StoreId { get; set; }
|
||||
[JsonProperty(Order = 2)]
|
||||
public string InvoiceId { get; set; }
|
||||
|
||||
[JsonProperty(Order = 1)] public string StoreId { get; set; }
|
||||
[JsonProperty(Order = 2)] public string InvoiceId { get; set; }
|
||||
}
|
||||
|
||||
public class WebhookInvoiceSettledEvent : WebhookInvoiceEvent
|
||||
{
|
||||
public WebhookInvoiceSettledEvent()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public WebhookInvoiceSettledEvent(WebhookEventType evtType) : base(evtType)
|
||||
{
|
||||
}
|
||||
|
||||
public bool ManuallyMarked { get; set; }
|
||||
}
|
||||
|
||||
public class WebhookInvoiceInvalidEvent : WebhookInvoiceEvent
|
||||
{
|
||||
public WebhookInvoiceInvalidEvent()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public WebhookInvoiceInvalidEvent(WebhookEventType evtType) : base(evtType)
|
||||
{
|
||||
}
|
||||
|
||||
public bool ManuallyMarked { get; set; }
|
||||
}
|
||||
|
||||
public class WebhookInvoiceProcessingEvent : WebhookInvoiceEvent
|
||||
{
|
||||
public WebhookInvoiceProcessingEvent()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public WebhookInvoiceProcessingEvent(WebhookEventType evtType) : base(evtType)
|
||||
{
|
||||
}
|
||||
|
||||
public bool OverPaid { get; set; }
|
||||
}
|
||||
|
||||
public class WebhookInvoiceReceivedPaymentEvent : WebhookInvoiceEvent
|
||||
{
|
||||
public WebhookInvoiceReceivedPaymentEvent()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public WebhookInvoiceReceivedPaymentEvent(WebhookEventType evtType) : base(evtType)
|
||||
{
|
||||
}
|
||||
|
||||
public bool AfterExpiration { get; set; }
|
||||
public string PaymentMethod { get; set; }
|
||||
public InvoicePaymentMethodDataModel.Payment Payment { get; set; }
|
||||
public bool OverPaid { get; set; }
|
||||
}
|
||||
|
||||
public class WebhookInvoicePaymentSettledEvent : WebhookInvoiceReceivedPaymentEvent
|
||||
{
|
||||
public WebhookInvoicePaymentSettledEvent()
|
||||
{
|
||||
}
|
||||
|
||||
public WebhookInvoicePaymentSettledEvent(WebhookEventType evtType) : base(evtType)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class WebhookInvoiceExpiredEvent : WebhookInvoiceEvent
|
||||
{
|
||||
public WebhookInvoiceExpiredEvent()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public WebhookInvoiceExpiredEvent(WebhookEventType evtType) : base(evtType)
|
||||
{
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ namespace BTCPayServer
|
||||
DisplayName = "Althash",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.htmlcoin.com/api/tx/{0}" : "https://explorer.htmlcoin.com/api/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "htmlcoin",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"HTML_X = HTML_USD",
|
||||
|
@ -15,7 +15,6 @@ namespace BTCPayServer
|
||||
? "https://chainz.cryptoid.info/agm/tx.dws?{0}"
|
||||
: "https://chainz.cryptoid.info/agm-test/tx.dws?{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "argoneum",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"AGM_X = AGM_BTC * BTC_X",
|
||||
|
@ -13,7 +13,6 @@ namespace BTCPayServer
|
||||
DisplayName = "BGold",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://btgexplorer.com/tx/{0}" : "https://testnet.btgexplorer.com/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "bitcoingold",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"BTG_X = BTG_BTC * BTC_X",
|
||||
|
@ -14,7 +14,6 @@ namespace BTCPayServer
|
||||
DisplayName = "BPlus",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://chainz.cryptoid.info/xbc/tx.dws?{0}" : "https://chainz.cryptoid.info/xbc/tx.dws?{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "bplus-fix-it",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"XBC_X = XBC_BTC * BTC_X",
|
||||
|
@ -14,7 +14,6 @@ namespace BTCPayServer
|
||||
DisplayName = "BitCore",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.bitcore.cc/tx/{0}" : "https://explorer.bitcore.cc/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "bitcore",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"BTX_X = BTX_BTC * BTC_X",
|
||||
|
@ -15,7 +15,6 @@ namespace BTCPayServer
|
||||
? "https://explorer.chaincoin.org/Explorer/Transaction/{0}"
|
||||
: "https://test.explorer.chaincoin.org/Explorer/Transaction/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "chaincoin",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"CHC_X = CHC_BTC * BTC_X",
|
||||
|
@ -16,7 +16,6 @@ namespace BTCPayServer
|
||||
? "https://insight.dash.org/insight/tx/{0}"
|
||||
: "https://testnet-insight.dashevo.org/insight/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "dash",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"DASH_X = DASH_BTC * BTC_X",
|
||||
|
@ -14,7 +14,6 @@ namespace BTCPayServer
|
||||
DisplayName = "Dogecoin",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://dogechain.info/tx/{0}" : "https://dogechain.info/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "dogecoin",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"DOGE_X = DOGE_BTC * BTC_X",
|
||||
|
@ -14,7 +14,6 @@ namespace BTCPayServer
|
||||
DisplayName = "Feathercoin",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.feathercoin.com/tx/{0}" : "https://explorer.feathercoin.com/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "feathercoin",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"FTC_X = FTC_BTC * BTC_X",
|
||||
|
@ -15,7 +15,6 @@ namespace BTCPayServer
|
||||
? "https://chainz.cryptoid.info/grs/tx.dws?{0}.htm"
|
||||
: "https://chainz.cryptoid.info/grs-test/tx.dws?{0}.htm",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "groestlcoin",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"GRS_X = GRS_BTC * BTC_X",
|
||||
|
@ -17,7 +17,6 @@ namespace BTCPayServer
|
||||
? "https://live.blockcypher.com/ltc/tx/{0}/"
|
||||
: "http://explorer.litecointools.com/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "litecoin",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"LTC_X = LTC_BTC * BTC_X",
|
||||
|
@ -14,7 +14,6 @@ namespace BTCPayServer
|
||||
DisplayName = "Monacoin",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://mona.insight.monaco-ex.org/insight/tx/{0}" : "https://testnet-mona.insight.monaco-ex.org/insight/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "monacoin",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"MONA_X = MONA_BTC * BTC_X",
|
||||
|
@ -14,7 +14,6 @@ namespace BTCPayServer
|
||||
DisplayName = "MonetaryUnit",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.monetaryunit.org/#/MUE/mainnet/tx/{0}" : "https://explorer.monetaryunit.org/#/MUE/mainnet/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "monetaryunit",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"MUE_X = MUE_BTC * BTC_X",
|
||||
|
@ -14,7 +14,6 @@ namespace BTCPayServer
|
||||
DisplayName = "Polis",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockbook.polispay.org/tx/{0}" : "https://blockbook.polispay.org/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "polis",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"POLIS_X = POLIS_BTC * BTC_X",
|
||||
|
@ -14,7 +14,6 @@ namespace BTCPayServer
|
||||
DisplayName = "Ufo",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://chainz.cryptoid.info/ufo/tx.dws?{0}" : "https://chainz.cryptoid.info/ufo/tx.dws?{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "ufo",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"UFO_X = UFO_BTC * BTC_X",
|
||||
|
@ -14,7 +14,6 @@ namespace BTCPayServer
|
||||
DisplayName = "Viacoin",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.viacoin.org/tx/{0}" : "https://explorer.viacoin.org/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "viacoin",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"VIA_X = VIA_BTC * BTC_X",
|
||||
|
@ -24,7 +24,6 @@ namespace BTCPayServer
|
||||
},
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "liquidnetwork",
|
||||
CryptoImagePath = "imlegacy/liquid.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
|
@ -23,7 +23,6 @@ namespace BTCPayServer
|
||||
DisplayName = "Liquid Tether",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "liquidnetwork",
|
||||
CryptoImagePath = "imlegacy/liquid-tether.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
@ -47,7 +46,6 @@ namespace BTCPayServer
|
||||
DisplayName = "Ethiopian Birr",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "liquidnetwork",
|
||||
CryptoImagePath = "imlegacy/etb.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
@ -70,7 +68,6 @@ namespace BTCPayServer
|
||||
DisplayName = "Liquid CAD",
|
||||
BlockExplorerLink = NetworkType == ChainName.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 == ChainName.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
|
@ -66,44 +66,9 @@ namespace BTCPayServer
|
||||
public virtual bool ReadonlyWallet { get; set; } = false;
|
||||
public virtual bool VaultSupported { get; set; } = false;
|
||||
public int MaxTrackedConfirmation { get; set; } = 6;
|
||||
public string UriScheme { get; set; }
|
||||
public bool SupportPayJoin { get; set; } = false;
|
||||
public bool SupportLightning { get; set; } = true;
|
||||
|
||||
public KeyPath GetRootKeyPath(DerivationType type)
|
||||
{
|
||||
KeyPath baseKey;
|
||||
if (!NBitcoinNetwork.Consensus.SupportSegwit)
|
||||
{
|
||||
baseKey = new KeyPath("44'");
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case DerivationType.Legacy:
|
||||
baseKey = new KeyPath("44'");
|
||||
break;
|
||||
case DerivationType.SegwitP2SH:
|
||||
baseKey = new KeyPath("49'");
|
||||
break;
|
||||
case DerivationType.Segwit:
|
||||
baseKey = new KeyPath("84'");
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
}
|
||||
}
|
||||
return baseKey
|
||||
.Derive(CoinType);
|
||||
}
|
||||
|
||||
public KeyPath GetRootKeyPath()
|
||||
{
|
||||
return new KeyPath(NBitcoinNetwork.Consensus.SupportSegwit ? "49'" : "44'")
|
||||
.Derive(CoinType);
|
||||
}
|
||||
|
||||
public override T ToObject<T>(string json)
|
||||
{
|
||||
return NBXplorerNetwork.Serializer.ToObject<T>(json);
|
||||
@ -124,7 +89,7 @@ namespace BTCPayServer
|
||||
|
||||
public virtual PaymentUrlBuilder GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
|
||||
{
|
||||
var builder = new PaymentUrlBuilder(UriScheme);
|
||||
var builder = new PaymentUrlBuilder(this.NBitcoinNetwork.UriScheme);
|
||||
builder.Host = cryptoInfoAddress;
|
||||
if (cryptoInfoDue != null && cryptoInfoDue != Money.Zero)
|
||||
{
|
||||
|
@ -17,7 +17,6 @@ namespace BTCPayServer
|
||||
NetworkType == Bitcoin.Instance.Signet.ChainName ? "https://explorer.bc-2.jp/tx/{0}"
|
||||
: "https://blockstream.info/testnet/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "bitcoin",
|
||||
CryptoImagePath = "imlegacy/bitcoin.svg",
|
||||
LightningImagePath = "imlegacy/bitcoin-lightning.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
|
@ -1,10 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="4.1.0" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="4.1.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Altcoins)' != 'true'">
|
||||
<Compile Remove="Altcoins\**\*.cs"></Compile>
|
||||
|
@ -3,11 +3,11 @@
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.4">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.19">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.19" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
|
||||
|
@ -24,7 +24,6 @@ namespace BTCPayServer.Data
|
||||
.HasOne(o => o.ApplicationUser)
|
||||
.WithMany(i => i.Fido2Credentials)
|
||||
.HasForeignKey(i => i.ApplicationUserId).OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
}
|
||||
|
||||
public ApplicationUser ApplicationUser { get; set; }
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using BTCPayServer.Client.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
@ -19,25 +20,24 @@ namespace BTCPayServer.Data
|
||||
[MaxLength(20)]
|
||||
[Required]
|
||||
public string PaymentMethodId { get; set; }
|
||||
public string Destination { get; set; }
|
||||
public byte[] Blob { get; set; }
|
||||
public byte[] Proof { get; set; }
|
||||
|
||||
#nullable enable
|
||||
public string? Destination { get; set; }
|
||||
#nullable restore
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
builder.Entity<PayoutData>()
|
||||
.HasOne(o => o.PullPaymentData)
|
||||
.WithMany(o => o.Payouts).OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<PayoutData>()
|
||||
.Property(o => o.State)
|
||||
.HasConversion<string>();
|
||||
builder.Entity<PayoutData>()
|
||||
.HasIndex(o => o.Destination)
|
||||
.IsUnique();
|
||||
builder.Entity<PayoutData>()
|
||||
.HasIndex(o => o.State);
|
||||
builder.Entity<PayoutData>()
|
||||
.HasIndex(x => new { DestinationId = x.Destination, x.State});
|
||||
}
|
||||
|
||||
// utility methods
|
||||
|
@ -0,0 +1,41 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20211021085011_RemovePayoutDestinationConstraint")]
|
||||
public partial class RemovePayoutDestinationConstraint : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Payouts_Destination",
|
||||
table: "Payouts");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Payouts_Destination_State",
|
||||
table: "Payouts",
|
||||
columns: new[] { "Destination", "State" });
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Payouts_Destination_State",
|
||||
table: "Payouts");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Payouts_Destination",
|
||||
table: "Payouts",
|
||||
column: "Destination",
|
||||
unique: true);
|
||||
}
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "3.1.4");
|
||||
.HasAnnotation("ProductVersion", "3.1.19");
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b =>
|
||||
{
|
||||
@ -527,13 +527,12 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Destination")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("PullPaymentDataId");
|
||||
|
||||
b.HasIndex("State");
|
||||
|
||||
b.HasIndex("Destination", "State");
|
||||
|
||||
b.ToTable("Payouts");
|
||||
});
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
<EmbeddedResource Include="Resources\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.4">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.19">
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -4,10 +4,10 @@
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.6.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.11.0" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
|
||||
<PackageReference Include="NBitcoin" Version="6.0.8" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="NBitcoin" Version="6.0.15" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.6.3" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
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 CryptoMarketExchangeRateProvider : IRateProvider
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
public CryptoMarketExchangeRateProvider(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
}
|
||||
|
||||
|
||||
readonly List<string> SupportedPairs = new List<string>()
|
||||
{
|
||||
"BTCARS",
|
||||
"BTCCLP",
|
||||
"BTCBRL"
|
||||
};
|
||||
|
||||
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _httpClient.GetAsync("https://api.exchange.cryptomkt.com/api/3/public/ticker/", cancellationToken);
|
||||
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
|
||||
|
||||
return ((jobj as JObject) ?? new JObject())
|
||||
.Properties()
|
||||
.Where(p => SupportedPairs.Contains(p.Name))
|
||||
.Select(p => new PairRate(CurrencyPair.Parse(p.Name), CreateBidAsk(p)))
|
||||
.ToArray();
|
||||
}
|
||||
private static BidAsk CreateBidAsk(JProperty p)
|
||||
{
|
||||
var bid = decimal.Parse(p.Value["bid"].Value<string>(), System.Globalization.NumberStyles.Any, CultureInfo.InvariantCulture);
|
||||
var ask = decimal.Parse(p.Value["ask"].Value<string>(), System.Globalization.NumberStyles.Any, CultureInfo.InvariantCulture);
|
||||
if (bid > ask)
|
||||
return null;
|
||||
return new BidAsk(bid, ask);
|
||||
}
|
||||
}
|
||||
}
|
42
BTCPayServer.Rating/Providers/RipioExchangeProvider.cs
Normal file
42
BTCPayServer.Rating/Providers/RipioExchangeProvider.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Rating;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
public class RipioExchangeProvider : IRateProvider
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
public RipioExchangeProvider(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
}
|
||||
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _httpClient.GetAsync("https://api.exchange.ripio.com/api/v1/rate/all/", cancellationToken);
|
||||
var jarray = (JArray)(await response.Content.ReadAsAsync<JArray>(cancellationToken));
|
||||
return jarray
|
||||
.Children<JObject>()
|
||||
.Select(jobj => ParsePair(jobj))
|
||||
.Where(p => p != null)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private PairRate ParsePair(JObject jobj)
|
||||
{
|
||||
var pair = CurrencyPair.Parse(jobj["pair"].Value<string>());
|
||||
var bid = decimal.Parse(jobj["bid"].Value<string>(), System.Globalization.NumberStyles.Any, CultureInfo.InvariantCulture);
|
||||
var ask = decimal.Parse(jobj["ask"].Value<string>(), System.Globalization.NumberStyles.Any, CultureInfo.InvariantCulture);
|
||||
if (bid > ask)
|
||||
return null;
|
||||
return new PairRate(pair, new BidAsk(bid, ask));
|
||||
}
|
||||
}
|
||||
}
|
@ -75,6 +75,8 @@ namespace BTCPayServer.Services.Rates
|
||||
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("ripio", "Ripio", "https://api.exchange.ripio.com/api/v1/rate/all/");
|
||||
yield return new AvailableRateProvider("cryptomarket", "CryptoMarket", "https://api.exchange.cryptomkt.com/api/3/public/ticker/");
|
||||
|
||||
yield return new AvailableRateProvider("polispay", "PolisPay", "https://obol.polispay.com/complex/btc/polis");
|
||||
|
||||
@ -99,6 +101,8 @@ 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("ripio", new RipioExchangeProvider(_httpClientFactory?.CreateClient("EXCHANGE_RIPIO")));
|
||||
Providers.Add("cryptomarket", new CryptoMarketExchangeRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_CRYPTOMARKET")));
|
||||
Providers.Add("bitflyer", new BitflyerRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITFLYER")));
|
||||
Providers.Add("polispay", new PolisRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_POLIS")));
|
||||
// Providers.Add("argoneum", new ArgoneumRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_ARGONEUM")));
|
||||
|
@ -26,6 +26,7 @@ using Newtonsoft.Json.Linq;
|
||||
using OpenQA.Selenium;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using WalletSettingsViewModel = BTCPayServer.Models.StoreViewModels.WalletSettingsViewModel;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -76,10 +77,10 @@ namespace BTCPayServer.Tests
|
||||
Assert.IsType<RedirectToActionResult>(response);
|
||||
|
||||
// Get enabled state from overview action
|
||||
StoreViewModel storeModel;
|
||||
response = await controller.UpdateStore();
|
||||
storeModel = (StoreViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
var lnNode = storeModel.LightningNodes.Find(node => node.CryptoCode == cryptoCode);
|
||||
PaymentMethodsViewModel paymentMethodsModel;
|
||||
response = controller.PaymentMethods();
|
||||
paymentMethodsModel = (PaymentMethodsViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
var lnNode = paymentMethodsModel.LightningNodes.Find(node => node.CryptoCode == cryptoCode);
|
||||
Assert.NotNull(lnNode);
|
||||
Assert.False(lnNode.Enabled);
|
||||
|
||||
@ -89,18 +90,18 @@ namespace BTCPayServer.Tests
|
||||
Assert.IsType<ViewResult>(response);
|
||||
|
||||
// Get enabled state from overview action
|
||||
response = await controller.UpdateStore();
|
||||
storeModel = (StoreViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
var derivationScheme = storeModel.DerivationSchemes.Find(scheme => scheme.Crypto == cryptoCode);
|
||||
response = controller.PaymentMethods();
|
||||
paymentMethodsModel = (PaymentMethodsViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
var derivationScheme = paymentMethodsModel.DerivationSchemes.Find(scheme => scheme.Crypto == cryptoCode);
|
||||
Assert.NotNull(derivationScheme);
|
||||
Assert.True(derivationScheme.Enabled);
|
||||
|
||||
// Disable wallet
|
||||
response = controller.SetWalletEnabled(storeId, cryptoCode, false).GetAwaiter().GetResult();
|
||||
Assert.IsType<RedirectToActionResult>(response);
|
||||
response = await controller.UpdateStore();
|
||||
storeModel = (StoreViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
derivationScheme = storeModel.DerivationSchemes.Find(scheme => scheme.Crypto == cryptoCode);
|
||||
response = controller.PaymentMethods();
|
||||
paymentMethodsModel = (PaymentMethodsViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
derivationScheme = paymentMethodsModel.DerivationSchemes.Find(scheme => scheme.Crypto == cryptoCode);
|
||||
Assert.NotNull(derivationScheme);
|
||||
Assert.False(derivationScheme.Enabled);
|
||||
|
||||
@ -138,9 +139,9 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(setupVm.Confirmation);
|
||||
response = await controller.UpdateWallet(setupVm);
|
||||
Assert.IsType<RedirectToActionResult>(response);
|
||||
response = await controller.ModifyWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode });
|
||||
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.Equal("CoboVault", setupVm.Source);
|
||||
response = await controller.WalletSettings(storeId, cryptoCode);
|
||||
var settingsVm = (WalletSettingsViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.Equal("CoboVault", settingsVm.Source);
|
||||
|
||||
// wasabi wallet file
|
||||
content = "{\r\n \"EncryptedSecret\": \"6PYWBQ1zsukowsnTNA57UUx791aBuJusm7E4egXUmF5WGw3tcdG3cmTL57\",\r\n \"ChainCode\": \"waSIVbn8HaoovoQg/0t8IS1+ZCxGsJRGFT21i06nWnc=\",\r\n \"MasterFingerprint\": \"7a7563b5\",\r\n \"ExtPubKey\": \"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\r\n \"PasswordVerified\": false,\r\n \"MinGapLimit\": 21,\r\n \"AccountKeyPath\": \"84'/0'/0'\",\r\n \"BlockchainState\": {\r\n \"Network\": \"RegTest\",\r\n \"Height\": \"0\"\r\n },\r\n \"HdPubKeys\": []\r\n}";
|
||||
@ -149,9 +150,9 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(setupVm.Confirmation);
|
||||
response = await controller.UpdateWallet(setupVm);
|
||||
Assert.IsType<RedirectToActionResult>(response);
|
||||
response = await controller.ModifyWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode });
|
||||
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.Equal("WasabiFile", setupVm.Source);
|
||||
response = await controller.WalletSettings(storeId, cryptoCode);
|
||||
settingsVm = (WalletSettingsViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.Equal("WasabiFile", settingsVm.Source);
|
||||
|
||||
// Can we upload coldcard settings? (Should fail, we are giving a mainnet file to a testnet network)
|
||||
content = "{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}";
|
||||
@ -166,9 +167,9 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(setupVm.Confirmation);
|
||||
response = await controller.UpdateWallet(setupVm);
|
||||
Assert.IsType<RedirectToActionResult>(response);
|
||||
response = await controller.ModifyWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode });
|
||||
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.Equal("ElectrumFile", setupVm.Source);
|
||||
response = await controller.WalletSettings(storeId, cryptoCode);
|
||||
settingsVm = (WalletSettingsViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.Equal("ElectrumFile", settingsVm.Source);
|
||||
|
||||
// Now let's check that no data has been lost in the process
|
||||
var store = tester.PayTester.StoreRepository.FindStore(storeId).GetAwaiter().GetResult();
|
||||
@ -612,7 +613,7 @@ namespace BTCPayServer.Tests
|
||||
user.RegisterDerivationScheme("LTC");
|
||||
var apps = user.GetController<AppsController>();
|
||||
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp().Result).Model);
|
||||
vm.Name = "test";
|
||||
vm.AppName = "test";
|
||||
vm.SelectedAppType = AppType.PointOfSale.ToString();
|
||||
Assert.IsType<RedirectToActionResult>(apps.CreateApp(vm).Result);
|
||||
var appId = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps().Result).Model)
|
||||
@ -820,6 +821,51 @@ normal:
|
||||
normalInvoice.CryptoInfo,
|
||||
s => PaymentTypes.BTCLike.ToString() == s.PaymentType && new[] { "BTC", "LTC" }.Contains(
|
||||
s.CryptoCode));
|
||||
|
||||
//test topup option
|
||||
vmpos.Template = @"
|
||||
a:
|
||||
price: 1000.0
|
||||
title: good apple
|
||||
|
||||
b:
|
||||
price: 10.0
|
||||
custom: false
|
||||
c:
|
||||
price: 1.02
|
||||
custom: true
|
||||
d:
|
||||
price: 1.02
|
||||
price_type: fixed
|
||||
e:
|
||||
price: 1.02
|
||||
price_type: minimum
|
||||
f:
|
||||
price_type: topup
|
||||
g:
|
||||
custom: topup
|
||||
";
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(appId, vmpos).Result);
|
||||
vmpos = Assert.IsType<UpdatePointOfSaleViewModel>(Assert
|
||||
.IsType<ViewResult>(await apps.UpdatePointOfSale(appId)).Model);
|
||||
Assert.DoesNotContain("custom", vmpos.Template);
|
||||
var items = appService.Parse(vmpos.Template, vmpos.Currency);
|
||||
Assert.Contains(items, item => item.Id == "a" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "b" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "c" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Minimum);
|
||||
Assert.Contains(items, item => item.Id == "d" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "e" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Minimum);
|
||||
Assert.Contains(items, item => item.Id == "f" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup);
|
||||
Assert.Contains(items, item => item.Id == "g" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup);
|
||||
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(appId, PosViewType.Static, null, null, null, null, null, "g").Result);
|
||||
invoices = user.BitPay.GetInvoices();
|
||||
var topupInvoice = invoices.Single(invoice => invoice.ItemCode == "g");
|
||||
Assert.Equal(0, topupInvoice.Price);
|
||||
Assert.Equal("new", topupInvoice.Status);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,11 +125,11 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
|
||||
//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);
|
||||
var etbBip21 = new BitcoinUrlBuilder(invoice.CryptoInfo.Single(info => info.CryptoCode == "ETB").PaymentUrls.BIP21, 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);
|
||||
var lbtcBip21 = new BitcoinUrlBuilder(invoice.CryptoInfo.Single(info => info.CryptoCode == "LBTC").PaymentUrls.BIP21, lbtc.NBitcoinNetwork);
|
||||
//precision = 8, 0.1 = 0.1
|
||||
Assert.Equal(0.1m, lbtcBip21.Amount.ToDecimal(MoneyUnit.BTC));
|
||||
}
|
||||
|
@ -124,8 +124,8 @@ namespace BTCPayServer.Tests
|
||||
//redirect
|
||||
//appidentifier
|
||||
var appidentifier = "testapp";
|
||||
var callbackUrl = tester.PayTester.ServerUri + "postredirect-callback-test";
|
||||
var authUrl = BTCPayServerClient.GenerateAuthorizeUri(tester.PayTester.ServerUri,
|
||||
var callbackUrl = s.ServerUri + "postredirect-callback-test";
|
||||
var authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri,
|
||||
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, applicationDetails: (appidentifier, new Uri(callbackUrl))).ToString();
|
||||
s.Driver.Navigate().GoToUrl(authUrl);
|
||||
Assert.Contains(appidentifier, s.Driver.PageSource);
|
||||
@ -143,7 +143,7 @@ namespace BTCPayServer.Tests
|
||||
await TestApiAgainstAccessToken(accessToken, tester, user,
|
||||
(await apiKeyRepo.GetKey(accessToken)).GetBlob().Permissions);
|
||||
|
||||
authUrl = BTCPayServerClient.GenerateAuthorizeUri(tester.PayTester.ServerUri,
|
||||
authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri,
|
||||
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, false, true, applicationDetails: (null, new Uri(callbackUrl))).ToString();
|
||||
|
||||
s.Driver.Navigate().GoToUrl(authUrl);
|
||||
@ -164,7 +164,7 @@ namespace BTCPayServer.Tests
|
||||
(await apiKeyRepo.GetKey(accessToken)).GetBlob().Permissions);
|
||||
|
||||
//let's test the app identifier system
|
||||
authUrl = BTCPayServerClient.GenerateAuthorizeUri(tester.PayTester.ServerUri,
|
||||
authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri,
|
||||
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, false, true, (appidentifier, new Uri(callbackUrl))).ToString();
|
||||
|
||||
//if it's the same, go to the confirm page
|
||||
@ -173,7 +173,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(callbackUrl, s.Driver.Url);
|
||||
|
||||
//same app but different redirect = nono
|
||||
authUrl = BTCPayServerClient.GenerateAuthorizeUri(tester.PayTester.ServerUri,
|
||||
authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri,
|
||||
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, false, true, (appidentifier, new Uri("https://international.local/callback"))).ToString();
|
||||
|
||||
s.Driver.Navigate().GoToUrl(authUrl);
|
||||
|
@ -19,11 +19,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.13" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
|
||||
<PackageReference Include="Selenium.Support" Version="3.141.0" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="90.0.4430.2400" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="94.0.4606.6100" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
@ -85,7 +85,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public HashSet<string> Chains { get; set; } = new HashSet<string>() { "BTC" };
|
||||
public bool UseLightning { get; set; }
|
||||
public bool AllowAdminRegistration { get; set; } = true;
|
||||
public bool CheatMode { get; set; } = true;
|
||||
public bool DisableRegistration { get; set; } = false;
|
||||
public async Task StartAsync()
|
||||
{
|
||||
@ -128,13 +128,13 @@ namespace BTCPayServer.Tests
|
||||
config.AppendLine($"lbtc.explorer.url={LBTCNBXplorerUri.AbsoluteUri}");
|
||||
config.AppendLine($"lbtc.explorer.cookiefile=0");
|
||||
}
|
||||
if (AllowAdminRegistration)
|
||||
config.AppendLine("allow-admin-registration=1");
|
||||
if (CheatMode)
|
||||
config.AppendLine("cheatmode=1");
|
||||
|
||||
config.AppendLine($"torrcfile={TestUtils.GetTestDataFullPath("Tor/torrc")}");
|
||||
config.AppendLine($"socksendpoint={SocksEndpoint}");
|
||||
config.AppendLine($"debuglog=debug.log");
|
||||
|
||||
config.AppendLine($"nocsp={NoCSP.ToString().ToLowerInvariant()}");
|
||||
|
||||
if (!string.IsNullOrEmpty(SSHPassword) && string.IsNullOrEmpty(SSHKeyFile))
|
||||
config.AppendLine($"sshpassword={SSHPassword}");
|
||||
@ -198,6 +198,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
coinAverageMock = new MockRateProvider();
|
||||
coinAverageMock.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_USD"), new BidAsk(5000m)));
|
||||
coinAverageMock.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_EUR"), new BidAsk(4000m)));
|
||||
coinAverageMock.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_CAD"), new BidAsk(4500m)));
|
||||
coinAverageMock.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_LTC"), new BidAsk(162m)));
|
||||
coinAverageMock.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("LTC_USD"), new BidAsk(500m)));
|
||||
@ -283,6 +284,8 @@ namespace BTCPayServer.Tests
|
||||
public string SSHPassword { get; internal set; }
|
||||
public string SSHKeyFile { get; internal set; }
|
||||
public string SSHConnection { get; set; }
|
||||
public bool NoCSP { get; set; }
|
||||
|
||||
public T GetController<T>(string userId = null, string storeId = null, bool isAdmin = false) where T : Controller
|
||||
{
|
||||
var context = new DefaultHttpContext();
|
||||
|
@ -1,13 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Views.Stores;
|
||||
using NBitcoin;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
@ -34,7 +31,7 @@ namespace BTCPayServer.Tests
|
||||
s.RegisterNewUser();
|
||||
var store = s.CreateNewStore();
|
||||
s.AddDerivationScheme("BTC");
|
||||
s.GoToStore(store.storeId, StoreNavPages.Checkout);
|
||||
s.GoToStore(store.storeId, StoreNavPages.CheckoutAppearance);
|
||||
s.Driver.FindElement(By.Id("RequiresRefundEmail")).Click();
|
||||
s.Driver.FindElement(By.Name("command")).Click();
|
||||
|
||||
@ -73,6 +70,68 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanHandleRefundEmailForm2()
|
||||
{
|
||||
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
// Prepare user account and store
|
||||
await s.StartAsync();
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser();
|
||||
var store = s.CreateNewStore();
|
||||
s.AddDerivationScheme("BTC");
|
||||
|
||||
// Now create an invoice that requires a refund email
|
||||
var invoice = s.CreateInvoice(store.storeName, 100, "USD", "", null, true);
|
||||
s.GoToInvoiceCheckout(invoice);
|
||||
|
||||
var emailInput = s.Driver.FindElement(By.Id("emailAddressFormInput"));
|
||||
Assert.True(emailInput.Displayed);
|
||||
|
||||
emailInput.SendKeys("a@g.com");
|
||||
|
||||
var actionButton = s.Driver.FindElement(By.Id("emailAddressForm")).FindElement(By.CssSelector("button.action-button"));
|
||||
actionButton.Click();
|
||||
try // Sometimes the click only take the focus, without actually really clicking on it...
|
||||
{
|
||||
actionButton.Click();
|
||||
}
|
||||
catch { }
|
||||
|
||||
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
|
||||
s.Driver.Navigate().Refresh();
|
||||
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
|
||||
|
||||
s.GoToHome();
|
||||
|
||||
// Now create an invoice that doesn't require a refund email
|
||||
s.CreateInvoice(store.storeName, 100, "USD", "", null, false);
|
||||
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
s.Driver.Navigate().Back();
|
||||
s.Driver.FindElement(By.ClassName("invoice-checkout-link")).Click();
|
||||
Assert.NotEmpty(s.Driver.FindElements(By.Id("checkoutCtrl")));
|
||||
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
|
||||
s.Driver.Navigate().Refresh();
|
||||
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
|
||||
|
||||
s.GoToHome();
|
||||
|
||||
// Now create an invoice that requires refund email but already has one set, email input shouldn't show up
|
||||
s.CreateInvoice(store.storeName, 100, "USD", "a@g.com", null, true);
|
||||
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
s.Driver.Navigate().Back();
|
||||
s.Driver.FindElement(By.ClassName("invoice-checkout-link")).Click();
|
||||
Assert.NotEmpty(s.Driver.FindElements(By.Id("checkoutCtrl")));
|
||||
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
|
||||
s.Driver.Navigate().Refresh();
|
||||
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanUseLanguageDropdown()
|
||||
{
|
||||
@ -105,7 +164,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanUseLightningSatsFeature()
|
||||
public async Task CanSetDefaultPaymentMethod()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
@ -115,15 +174,37 @@ namespace BTCPayServer.Tests
|
||||
s.RegisterNewUser(true);
|
||||
var store = s.CreateNewStore();
|
||||
s.AddLightningNode();
|
||||
s.GoToStore(store.storeId, StoreNavPages.Checkout);
|
||||
s.Driver.SetCheckbox(By.Id("LightningAmountInSatoshi"), true);
|
||||
var command = s.Driver.FindElement(By.Name("command"));
|
||||
s.AddDerivationScheme("BTC");
|
||||
|
||||
command.Click();
|
||||
var invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com");
|
||||
var invoiceId = s.CreateInvoice(store.storeName, defaultPaymentMethod: "BTC_LightningLike");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
|
||||
Assert.Equal("Bitcoin (Lightning) (BTC)", s.Driver.FindElement(By.ClassName("payment__currencies")).Text);
|
||||
s.Driver.Quit();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanUseLightningSatsFeature()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
s.Server.ActivateLightning();
|
||||
await s.StartAsync();
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser(true);
|
||||
(string storeName, string storeId) = s.CreateNewStore();
|
||||
s.AddLightningNode();
|
||||
s.GoToStore(storeId);
|
||||
s.Driver.FindElement(By.Id("Modify-LightningBTC")).Click();
|
||||
s.Driver.SetCheckbox(By.Id("LightningAmountInSatoshi"), true);
|
||||
s.Driver.FindElement(By.Id("save")).Click();
|
||||
Assert.Contains("BTC Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
var invoiceId = s.CreateInvoice(storeName, 10, "USD", "a@g.com");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
Assert.Contains("Sats", s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Text);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,7 +222,7 @@ namespace BTCPayServer.Tests
|
||||
var invoiceId = s.CreateInvoice(store.storeId, 0.001m, "BTC", "a@x.com");
|
||||
var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
|
||||
s.Driver.Navigate()
|
||||
.GoToUrl(new Uri(s.Server.PayTester.ServerUri, $"tests/index.html?invoice={invoiceId}"));
|
||||
.GoToUrl(new Uri(s.ServerUri, $"tests/index.html?invoice={invoiceId}"));
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
Assert.True(s.Driver.FindElement(By.Name("btcpay")).Displayed);
|
||||
@ -162,7 +243,7 @@ namespace BTCPayServer.Tests
|
||||
closebutton.Click();
|
||||
s.Driver.AssertElementNotFound(By.Name("btcpay"));
|
||||
Assert.Equal(s.Driver.Url,
|
||||
new Uri(s.Server.PayTester.ServerUri, $"tests/index.html?invoice={invoiceId}").ToString());
|
||||
new Uri(s.ServerUri, $"tests/index.html?invoice={invoiceId}").ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,8 +38,8 @@ namespace BTCPayServer.Tests
|
||||
var apps2 = user2.GetController<AppsController>();
|
||||
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp().Result).Model);
|
||||
Assert.NotNull(vm.SelectedAppType);
|
||||
Assert.Null(vm.Name);
|
||||
vm.Name = "test";
|
||||
Assert.Null(vm.AppName);
|
||||
vm.AppName = "test";
|
||||
vm.SelectedAppType = AppType.Crowdfund.ToString();
|
||||
var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.CreateApp(vm).Result);
|
||||
Assert.Equal(nameof(apps.UpdateCrowdfund), redirectToAction.ActionName);
|
||||
@ -75,7 +75,7 @@ namespace BTCPayServer.Tests
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
var apps = user.GetController<AppsController>();
|
||||
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp().Result).Model);
|
||||
vm.Name = "test";
|
||||
vm.AppName = "test";
|
||||
vm.SelectedAppType = AppType.Crowdfund.ToString();
|
||||
Assert.IsType<RedirectToActionResult>(apps.CreateApp(vm).Result);
|
||||
var appId = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps().Result).Model)
|
||||
@ -163,12 +163,12 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
await user.GrantAccessAsync();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
await user.ModifyStore(s => s.NetworkFeeMode = NetworkFeeMode.Never);
|
||||
await user.SetNetworkFeeMode(NetworkFeeMode.Never);
|
||||
var apps = user.GetController<AppsController>();
|
||||
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp().Result).Model);
|
||||
vm.Name = "test";
|
||||
vm.AppName = "test";
|
||||
vm.SelectedAppType = AppType.Crowdfund.ToString();
|
||||
Assert.IsType<RedirectToActionResult>(apps.CreateApp(vm).Result);
|
||||
var appId = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps().Result).Model)
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1.202 AS builder
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1.413 AS builder
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends chromium-driver \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
@ -48,6 +48,22 @@ namespace BTCPayServer.Tests
|
||||
return Assert.IsType<T>(vr.Model);
|
||||
}
|
||||
|
||||
// Sometimes, selenium is flaky...
|
||||
public static IWebElement FindElementUntilNotStaled(this IWebDriver driver, By by, Action<IWebElement> act)
|
||||
{
|
||||
retry:
|
||||
try
|
||||
{
|
||||
var el = driver.FindElement(by);
|
||||
act(el);
|
||||
return el;
|
||||
}
|
||||
catch (StaleElementReferenceException)
|
||||
{
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
|
||||
public static void AssertElementNotFound(this IWebDriver driver, By by)
|
||||
{
|
||||
DateTimeOffset now = DateTimeOffset.Now;
|
||||
@ -115,7 +131,14 @@ namespace BTCPayServer.Tests
|
||||
var element = driver.FindElement(selector);
|
||||
if ((value && !element.Selected) || (!value && element.Selected))
|
||||
{
|
||||
driver.WaitForAndClick(selector);
|
||||
try
|
||||
{
|
||||
driver.WaitForAndClick(selector);
|
||||
}
|
||||
catch (ElementClickInterceptedException)
|
||||
{
|
||||
element.SendKeys(" ");
|
||||
}
|
||||
}
|
||||
|
||||
if (value != element.Selected)
|
||||
|
@ -440,9 +440,11 @@ namespace BTCPayServer.Tests
|
||||
Assert.Null(payout.PaymentMethodAmount);
|
||||
|
||||
Logs.Tester.LogInformation("Can't overdraft");
|
||||
|
||||
var destination2 = (await tester.ExplorerNode.GetNewAddressAsync()).ToString();
|
||||
await this.AssertAPIError("overdraft", async () => await unauthenticated.CreatePayout(pps[0].Id, new CreatePayoutRequest()
|
||||
{
|
||||
Destination = destination,
|
||||
Destination = destination2,
|
||||
Amount = 0.00001m,
|
||||
PaymentMethod = "BTC"
|
||||
}));
|
||||
@ -450,7 +452,7 @@ namespace BTCPayServer.Tests
|
||||
Logs.Tester.LogInformation("Can't create too low payout");
|
||||
await this.AssertAPIError("amount-too-low", async () => await unauthenticated.CreatePayout(pps[0].Id, new CreatePayoutRequest()
|
||||
{
|
||||
Destination = destination,
|
||||
Destination = destination2,
|
||||
PaymentMethod = "BTC"
|
||||
}));
|
||||
|
||||
@ -781,9 +783,10 @@ namespace BTCPayServer.Tests
|
||||
var newDeliveryId = await clientProfile.RedeliverWebhook(user.StoreId, hook.Id, delivery.Id);
|
||||
req = await fakeServer.GetNextRequest();
|
||||
req.Response.StatusCode = 404;
|
||||
fakeServer.Done();
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
// Releasing semaphore several times may help making this test less flaky
|
||||
fakeServer.Done();
|
||||
var newDelivery = await clientProfile.GetWebhookDelivery(user.StoreId, hook.Id, newDeliveryId);
|
||||
Assert.NotNull(newDelivery);
|
||||
Assert.Equal(404, newDelivery.HttpCode);
|
||||
@ -1023,6 +1026,38 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanOverpayInvoice()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
var client = await user.CreateClient();
|
||||
var invoice = await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest() { Amount = 5000.0m, Currency = "USD" });
|
||||
var methods = await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id);
|
||||
var method = methods.First();
|
||||
var amount = method.Amount;
|
||||
Assert.Equal(amount, method.Due);
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var btc = tester.NetworkProvider.BTC.NBitcoinNetwork;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(method.Destination, btc), Money.Coins(method.Due) + Money.Coins(1.0m));
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
invoice = await client.GetInvoice(user.StoreId, invoice.Id);
|
||||
Assert.True(invoice.Status == InvoiceStatus.Processing);
|
||||
methods = await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id);
|
||||
method = methods.First();
|
||||
Assert.Equal(amount, method.Amount);
|
||||
Assert.Equal(-1.0m, method.Due);
|
||||
Assert.Equal(amount + 1.0m, method.TotalPaid);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task InvoiceTests()
|
||||
@ -1040,7 +1075,7 @@ namespace BTCPayServer.Tests
|
||||
//create
|
||||
|
||||
//validation errors
|
||||
await AssertValidationError(new[] { nameof(CreateInvoiceRequest.Currency), nameof(CreateInvoiceRequest.Amount), $"{nameof(CreateInvoiceRequest.Checkout)}.{nameof(CreateInvoiceRequest.Checkout.PaymentTolerance)}", $"{nameof(CreateInvoiceRequest.Checkout)}.{nameof(CreateInvoiceRequest.Checkout.PaymentMethods)}[0]" }, async () =>
|
||||
await AssertValidationError(new[] { nameof(CreateInvoiceRequest.Amount), $"{nameof(CreateInvoiceRequest.Checkout)}.{nameof(CreateInvoiceRequest.Checkout.PaymentTolerance)}", $"{nameof(CreateInvoiceRequest.Checkout)}.{nameof(CreateInvoiceRequest.Checkout.PaymentMethods)}[0]" }, async () =>
|
||||
{
|
||||
await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest() { Amount = -1, Checkout = new CreateInvoiceRequest.CheckoutOptions() { PaymentTolerance = -2, PaymentMethods = new[] { "jasaas_sdsad" } } });
|
||||
});
|
||||
@ -1059,11 +1094,13 @@ namespace BTCPayServer.Tests
|
||||
Metadata = JObject.Parse("{\"itemCode\": \"testitem\", \"orderId\": \"testOrder\"}"),
|
||||
Checkout = new CreateInvoiceRequest.CheckoutOptions()
|
||||
{
|
||||
RedirectAutomatically = true
|
||||
RedirectAutomatically = true,
|
||||
RequiresRefundEmail = true
|
||||
},
|
||||
AdditionalSearchTerms = new string[] { "Banana" }
|
||||
});
|
||||
Assert.True(newInvoice.Checkout.RedirectAutomatically);
|
||||
Assert.True(newInvoice.Checkout.RequiresRefundEmail);
|
||||
Assert.Equal(user.StoreId, newInvoice.StoreId);
|
||||
//list
|
||||
var invoices = await viewOnly.GetInvoices(user.StoreId);
|
||||
@ -1155,10 +1192,16 @@ namespace BTCPayServer.Tests
|
||||
//update
|
||||
newInvoice = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest() { Currency = "USD", Amount = 1 });
|
||||
Assert.Contains(InvoiceStatus.Settled, newInvoice.AvailableStatusesForManualMarking);
|
||||
Assert.Contains(InvoiceStatus.Invalid, newInvoice.AvailableStatusesForManualMarking);
|
||||
await client.MarkInvoiceStatus(user.StoreId, newInvoice.Id, new MarkInvoiceStatusRequest()
|
||||
{
|
||||
Status = InvoiceStatus.Settled
|
||||
});
|
||||
newInvoice = await client.GetInvoice(user.StoreId, newInvoice.Id);
|
||||
|
||||
Assert.DoesNotContain(InvoiceStatus.Settled, newInvoice.AvailableStatusesForManualMarking);
|
||||
Assert.Contains(InvoiceStatus.Invalid, newInvoice.AvailableStatusesForManualMarking);
|
||||
newInvoice = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest() { Currency = "USD", Amount = 1 });
|
||||
await client.MarkInvoiceStatus(user.StoreId, newInvoice.Id, new MarkInvoiceStatusRequest()
|
||||
@ -1166,6 +1209,10 @@ namespace BTCPayServer.Tests
|
||||
Status = InvoiceStatus.Invalid
|
||||
});
|
||||
|
||||
newInvoice = await client.GetInvoice(user.StoreId, newInvoice.Id);
|
||||
|
||||
Assert.Contains(InvoiceStatus.Settled, newInvoice.AvailableStatusesForManualMarking);
|
||||
Assert.DoesNotContain(InvoiceStatus.Invalid, newInvoice.AvailableStatusesForManualMarking);
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnly.UpdateInvoice(user.StoreId, invoice.Id,
|
||||
@ -1289,6 +1336,66 @@ namespace BTCPayServer.Tests
|
||||
paymentMethods = await client.GetInvoicePaymentMethods(store.Id, invoice.Id);
|
||||
Assert.Single(paymentMethods);
|
||||
Assert.True(paymentMethods.First().Activated);
|
||||
|
||||
var invoiceWithdefaultPaymentMethodLN = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest()
|
||||
{
|
||||
Currency = "USD",
|
||||
Amount = 100,
|
||||
Checkout = new CreateInvoiceRequest.CheckoutOptions()
|
||||
{
|
||||
PaymentMethods = new[] { "BTC", "BTC-LightningNetwork" },
|
||||
DefaultPaymentMethod = "BTC_LightningLike"
|
||||
}
|
||||
});
|
||||
Assert.Equal("BTC_LightningLike", invoiceWithdefaultPaymentMethodLN.Checkout.DefaultPaymentMethod);
|
||||
|
||||
var invoiceWithdefaultPaymentMethodOnChain = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest()
|
||||
{
|
||||
Currency = "USD",
|
||||
Amount = 100,
|
||||
Checkout = new CreateInvoiceRequest.CheckoutOptions()
|
||||
{
|
||||
PaymentMethods = new[] { "BTC", "BTC-LightningNetwork" },
|
||||
DefaultPaymentMethod = "BTC"
|
||||
}
|
||||
});
|
||||
Assert.Equal("BTC", invoiceWithdefaultPaymentMethodOnChain.Checkout.DefaultPaymentMethod);
|
||||
|
||||
store = await client.GetStore(user.StoreId);
|
||||
store.LazyPaymentMethods = false;
|
||||
store = await client.UpdateStore(store.Id,
|
||||
JObject.FromObject(store).ToObject<UpdateStoreRequest>());
|
||||
|
||||
|
||||
//let's see the overdue amount
|
||||
invoice = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest()
|
||||
{
|
||||
Currency = "BTC",
|
||||
Amount = 0.0001m,
|
||||
Checkout = new CreateInvoiceRequest.CheckoutOptions()
|
||||
{
|
||||
PaymentMethods = new[] { "BTC" },
|
||||
DefaultPaymentMethod = "BTC"
|
||||
}
|
||||
});
|
||||
var pm = Assert.Single(await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id));
|
||||
Assert.Equal(0.0001m, pm.Due);
|
||||
|
||||
await tester.WaitForEvent<NewOnChainTransactionEvent>(async () =>
|
||||
{
|
||||
await tester.ExplorerNode.SendToAddressAsync(
|
||||
BitcoinAddress.Create(pm.Destination, tester.ExplorerClient.Network.NBitcoinNetwork),
|
||||
new Money(0.0002m, MoneyUnit.BTC));
|
||||
});
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var pm = Assert.Single(await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id));
|
||||
Assert.Single(pm.Payments);
|
||||
Assert.Equal(-0.0001m, pm.Due);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1440,7 +1547,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Empty(await client.GetStoreOnChainPaymentMethods(store.Id));
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.UpdateStoreOnChainPaymentMethod(store.Id, "BTC", new OnChainPaymentMethodData() { });
|
||||
await viewOnlyClient.UpdateStoreOnChainPaymentMethod(store.Id, "BTC", new UpdateOnChainPaymentMethodRequest() { });
|
||||
});
|
||||
|
||||
var xpriv = new Mnemonic("all all all all all all all all all all all all").DeriveExtKey()
|
||||
@ -1453,15 +1560,15 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
|
||||
Assert.Equal(firstAddress, (await viewOnlyClient.PreviewProposedStoreOnChainPaymentMethodAddresses(store.Id, "BTC",
|
||||
new OnChainPaymentMethodData() { Enabled = true, DerivationScheme = xpub })).Addresses.First().Address);
|
||||
new UpdateOnChainPaymentMethodRequest() { Enabled = true, DerivationScheme = xpub })).Addresses.First().Address);
|
||||
|
||||
var method = await client.UpdateStoreOnChainPaymentMethod(store.Id, "BTC",
|
||||
new OnChainPaymentMethodData() { Enabled = true, DerivationScheme = xpub });
|
||||
new UpdateOnChainPaymentMethodRequest() { Enabled = true, DerivationScheme = xpub });
|
||||
|
||||
Assert.Equal(xpub, method.DerivationScheme);
|
||||
|
||||
method = await client.UpdateStoreOnChainPaymentMethod(store.Id, "BTC",
|
||||
new OnChainPaymentMethodData() { Enabled = true, DerivationScheme = xpub, Label = "lol", AccountKeyPath = RootedKeyPath.Parse("01020304/1/2/3") });
|
||||
new UpdateOnChainPaymentMethodRequest() { Enabled = true, DerivationScheme = xpub, Label = "lol", AccountKeyPath = RootedKeyPath.Parse("01020304/1/2/3") });
|
||||
|
||||
method = await client.GetStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
|
||||
@ -1557,7 +1664,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Empty(await adminClient.GetStoreLightningNetworkPaymentMethods(store.Id));
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC", new LightningNetworkPaymentMethodData() { });
|
||||
await viewOnlyClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC", new UpdateLightningNetworkPaymentMethodRequest() { });
|
||||
});
|
||||
await AssertHttpError(404, async () =>
|
||||
{
|
||||
@ -1592,37 +1699,33 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
var ex = await AssertValidationError(new[] { "ConnectionString" }, async () =>
|
||||
{
|
||||
await adminClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC", new LightningNetworkPaymentMethodData()
|
||||
await adminClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
|
||||
{
|
||||
ConnectionString = forbidden,
|
||||
CryptoCode = "BTC",
|
||||
Enabled = true
|
||||
});
|
||||
});
|
||||
Assert.Contains("btcpay.server.canmodifyserversettings", ex.Message);
|
||||
// However, the other client should work because he has `btcpay.server.canmodifyserversettings`
|
||||
await admin2Client.UpdateStoreLightningNetworkPaymentMethod(admin2.StoreId, "BTC", new LightningNetworkPaymentMethodData()
|
||||
await admin2Client.UpdateStoreLightningNetworkPaymentMethod(admin2.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
|
||||
{
|
||||
ConnectionString = forbidden,
|
||||
CryptoCode = "BTC",
|
||||
Enabled = true
|
||||
});
|
||||
}
|
||||
// Allowed ip should be ok
|
||||
await adminClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC", new LightningNetworkPaymentMethodData()
|
||||
await adminClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
|
||||
{
|
||||
ConnectionString = "type=clightning;server=tcp://8.8.8.8",
|
||||
CryptoCode = "BTC",
|
||||
Enabled = true
|
||||
});
|
||||
// If we strip the admin's right, he should not be able to set unsafe anymore, even if the API key is still valid
|
||||
await admin2.MakeAdmin(false);
|
||||
await AssertValidationError(new[] { "ConnectionString" }, async () =>
|
||||
{
|
||||
await admin2Client.UpdateStoreLightningNetworkPaymentMethod(admin2.StoreId, "BTC", new LightningNetworkPaymentMethodData()
|
||||
await admin2Client.UpdateStoreLightningNetworkPaymentMethod(admin2.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
|
||||
{
|
||||
ConnectionString = "type=clightning;server=tcp://127.0.0.1",
|
||||
CryptoCode = "BTC",
|
||||
Enabled = true
|
||||
});
|
||||
});
|
||||
@ -1640,14 +1743,22 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
await Assert.ThrowsAsync<GreenFieldValidationException>(async () =>
|
||||
{
|
||||
await nonAdminUserClient.UpdateStoreLightningNetworkPaymentMethod(nonAdminUser.StoreId, "BTC", method);
|
||||
await nonAdminUserClient.UpdateStoreLightningNetworkPaymentMethod(nonAdminUser.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
|
||||
{
|
||||
Enabled = method.Enabled,
|
||||
ConnectionString = method.ConnectionString
|
||||
});
|
||||
});
|
||||
|
||||
settings = await tester.PayTester.GetService<SettingsRepository>().GetSettingAsync<PoliciesSettings>();
|
||||
settings.AllowLightningInternalNodeForAll = true;
|
||||
await tester.PayTester.GetService<SettingsRepository>().UpdateSetting(settings);
|
||||
|
||||
await nonAdminUserClient.UpdateStoreLightningNetworkPaymentMethod(nonAdminUser.StoreId, "BTC", method);
|
||||
await nonAdminUserClient.UpdateStoreLightningNetworkPaymentMethod(nonAdminUser.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
|
||||
{
|
||||
Enabled = method.Enabled,
|
||||
ConnectionString = method.ConnectionString
|
||||
});
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
@ -1870,10 +1981,13 @@ namespace BTCPayServer.Tests
|
||||
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Theory(Timeout = TestTimeout)]
|
||||
[InlineData("DE-de")]
|
||||
[InlineData("")]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void NumericJsonConverterTests()
|
||||
public void NumericJsonConverterTests(string culture)
|
||||
{
|
||||
System.Globalization.CultureInfo.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo(culture);
|
||||
JsonReader Get(string val)
|
||||
{
|
||||
return new JsonTextReader(new StringReader(val));
|
||||
@ -1923,16 +2037,17 @@ namespace BTCPayServer.Tests
|
||||
var admin = tester.NewAccount();
|
||||
await admin.GrantAccessAsync(true);
|
||||
var adminClient = await admin.CreateClient(Policies.Unrestricted);
|
||||
var viewerOnlyClient = await admin.CreateClient(Policies.CanViewStoreSettings);
|
||||
var store = await adminClient.GetStore(admin.StoreId);
|
||||
|
||||
Assert.Empty(await adminClient.GetStorePaymentMethods(store.Id));
|
||||
|
||||
await adminClient.UpdateStoreLightningNetworkPaymentMethodToInternalNode(admin.StoreId, "BTC");
|
||||
await adminClient.UpdateStoreLightningNetworkPaymentMethod(admin.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest("Internal Node", true));
|
||||
|
||||
void VerifyLightning(Dictionary<string, GenericPaymentMethodData> dictionary)
|
||||
{
|
||||
Assert.True(dictionary.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToStringNormalized(), out var item));
|
||||
var lightningNetworkPaymentMethodBaseData =Assert.IsType<JObject>(item.Data).ToObject<LightningNetworkPaymentMethodBaseData>();
|
||||
var lightningNetworkPaymentMethodBaseData = Assert.IsType<JObject>(item.Data).ToObject<LightningNetworkPaymentMethodBaseData>();
|
||||
Assert.Equal("Internal Node", lightningNetworkPaymentMethodBaseData.ConnectionString);
|
||||
}
|
||||
|
||||
@ -1942,12 +2057,12 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var randK = new Mnemonic(Wordlist.English, WordCount.Twelve).DeriveExtKey().Neuter().ToString(Network.RegTest);
|
||||
await adminClient.UpdateStoreOnChainPaymentMethod(admin.StoreId, "BTC",
|
||||
new OnChainPaymentMethodData("BTC", randK, true, "testing", null));
|
||||
new UpdateOnChainPaymentMethodRequest(true, randK, "testing", null));
|
||||
|
||||
void VerifyOnChain(Dictionary<string, GenericPaymentMethodData> dictionary)
|
||||
{
|
||||
Assert.True(dictionary.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.BTCLike).ToStringNormalized(), out var item));
|
||||
var paymentMethodBaseData =Assert.IsType<JObject>(item.Data).ToObject<OnChainPaymentMethodBaseData>();
|
||||
var paymentMethodBaseData = Assert.IsType<JObject>(item.Data).ToObject<OnChainPaymentMethodBaseData>();
|
||||
Assert.Equal(randK, paymentMethodBaseData.DerivationScheme);
|
||||
}
|
||||
|
||||
@ -1956,8 +2071,30 @@ namespace BTCPayServer.Tests
|
||||
VerifyLightning(methods);
|
||||
VerifyOnChain(methods);
|
||||
|
||||
|
||||
methods = await viewerOnlyClient.GetStorePaymentMethods(store.Id);
|
||||
|
||||
VerifyLightning(methods);
|
||||
|
||||
|
||||
|
||||
await adminClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC",
|
||||
new UpdateLightningNetworkPaymentMethodRequest(
|
||||
tester.GetLightningConnectionString(LightningConnectionType.CLightning, true), true));
|
||||
methods = await viewerOnlyClient.GetStorePaymentMethods(store.Id);
|
||||
|
||||
Assert.True(methods.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToStringNormalized(), out var item));
|
||||
var lightningNetworkPaymentMethodBaseData =Assert.IsType<JObject>(item.Data).ToObject<LightningNetworkPaymentMethodBaseData>();
|
||||
Assert.Equal("*NEED CanModifyStoreSettings PERMISSION TO VIEW*", lightningNetworkPaymentMethodBaseData.ConnectionString);
|
||||
|
||||
|
||||
methods = await adminClient.GetStorePaymentMethods(store.Id);
|
||||
|
||||
Assert.True(methods.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToStringNormalized(), out item));
|
||||
lightningNetworkPaymentMethodBaseData =Assert.IsType<JObject>(item.Data).ToObject<LightningNetworkPaymentMethodBaseData>();
|
||||
Assert.NotEqual("*NEED CanModifyStoreSettings PERMISSION TO VIEW*", lightningNetworkPaymentMethodBaseData.ConnectionString);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
71
BTCPayServer.Tests/POSTests.cs
Normal file
71
BTCPayServer.Tests/POSTests.cs
Normal file
@ -0,0 +1,71 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using static BTCPayServer.Tests.UnitTest1;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class POSTests
|
||||
{
|
||||
public POSTests(ITestOutputHelper helper)
|
||||
{
|
||||
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
|
||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
public async Task CanUsePoSApp1()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
var apps = user.GetController<AppsController>();
|
||||
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp().Result).Model);
|
||||
vm.AppName = "test";
|
||||
vm.SelectedAppType = AppType.PointOfSale.ToString();
|
||||
Assert.IsType<RedirectToActionResult>(apps.CreateApp(vm).Result);
|
||||
var appId = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps().Result).Model)
|
||||
.Apps[0].Id;
|
||||
var vmpos = Assert.IsType<UpdatePointOfSaleViewModel>(Assert
|
||||
.IsType<ViewResult>(apps.UpdatePointOfSale(appId).Result).Model);
|
||||
vmpos.Template = @"
|
||||
apple:
|
||||
price: 5.0
|
||||
title: good apple
|
||||
disabled: true
|
||||
orange:
|
||||
price: 10.0
|
||||
donation:
|
||||
price: 1.02
|
||||
custom: true
|
||||
";
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(appId, vmpos).Result);
|
||||
vmpos = Assert.IsType<UpdatePointOfSaleViewModel>(Assert
|
||||
.IsType<ViewResult>(apps.UpdatePointOfSale(appId).Result).Model);
|
||||
var publicApps = user.GetController<AppsPublicController>();
|
||||
var vmview =
|
||||
Assert.IsType<ViewPointOfSaleViewModel>(Assert
|
||||
.IsType<ViewResult>(publicApps.ViewPointOfSale(appId, PosViewType.Cart).Result).Model);
|
||||
|
||||
// apple shouldn't be available since we it's set to "disabled: true" above
|
||||
Assert.Equal(2, vmview.Items.Length);
|
||||
Assert.Equal("orange", vmview.Items[0].Title);
|
||||
Assert.Equal("donation", vmview.Items[1].Title);
|
||||
// orange is available
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(appId, PosViewType.Cart, 0, null, null, null, null, "orange").Result);
|
||||
// apple is not found
|
||||
Assert.IsType<NotFoundResult>(publicApps
|
||||
.ViewPointOfSale(appId, PosViewType.Cart, 0, null, null, null, null, "apple").Result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -73,26 +73,26 @@ namespace BTCPayServer.Tests
|
||||
var filePSBT = (FileContentResult)(await walletController.WalletPSBT(walletId, vmPSBT, "save-psbt"));
|
||||
PSBT.Load(filePSBT.FileContents, user.SupportedNetwork.NBitcoinNetwork);
|
||||
|
||||
var vmPSBT2 = await walletController.WalletPSBTReady(walletId, new WalletPSBTReadyViewModel()
|
||||
var vmPSBT2 = await walletController.WalletPSBT(walletId, new WalletPSBTViewModel
|
||||
{
|
||||
SigningContext = new SigningContextModel()
|
||||
SigningContext = new SigningContextModel
|
||||
{
|
||||
PSBT = AssertRedirectedPSBT(await walletController.WalletPSBT(walletId, vmPSBT, "broadcast"), nameof(walletController.WalletPSBTReady))
|
||||
}
|
||||
}).AssertViewModelAsync<WalletPSBTReadyViewModel>();
|
||||
}).AssertViewModelAsync<WalletPSBTViewModel>();
|
||||
Assert.NotEmpty(vmPSBT2.Inputs.Where(i => i.Error != null));
|
||||
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
|
||||
var psbtReady = await walletController.WalletPSBT(walletId, new WalletPSBTViewModel
|
||||
{
|
||||
SigningContext = new SigningContextModel
|
||||
{
|
||||
PSBT = AssertRedirectedPSBT(await walletController.WalletPSBT(walletId, vmPSBT, "broadcast"), nameof(walletController.WalletPSBTReady))
|
||||
}
|
||||
}).AssertViewModelAsync<WalletPSBTReadyViewModel>();
|
||||
}).AssertViewModelAsync<WalletPSBTViewModel>();
|
||||
Assert.Equal(2 + 1, psbtReady.Destinations.Count); // The fee is a destination
|
||||
Assert.Contains(psbtReady.Destinations, d => d.Destination == sendDestination && !d.Positive);
|
||||
Assert.Contains(psbtReady.Destinations, d => d.Positive);
|
||||
@ -117,10 +117,10 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(signedPSBT2.TryFinalize(out _));
|
||||
Assert.Equal(signedPSBT, signedPSBT2);
|
||||
|
||||
var ready = (await walletController.WalletPSBTReady(walletId, new WalletPSBTReadyViewModel
|
||||
var ready = (await walletController.WalletPSBT(walletId, new WalletPSBTViewModel
|
||||
{
|
||||
SigningContext = new SigningContextModel(signedPSBT)
|
||||
})).AssertViewModel<WalletPSBTReadyViewModel>();
|
||||
})).AssertViewModel<WalletPSBTViewModel>();
|
||||
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);
|
||||
@ -131,7 +131,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.False(string.IsNullOrEmpty(Assert.IsType<WalletPSBTViewModel>(
|
||||
Assert.IsType<ViewResult>(
|
||||
await walletController.WalletPSBT(walletId,
|
||||
new WalletPSBTViewModel()
|
||||
new WalletPSBTViewModel
|
||||
{
|
||||
UploadedPSBTFile = TestUtils.GetFormFile("base64", signedPSBT.ToBase64())
|
||||
})).Model).PSBT));
|
||||
|
@ -20,6 +20,7 @@ using BTCPayServer.Views.Wallets;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using NBitcoin;
|
||||
using BTCPayServer.BIP78.Sender;
|
||||
using BTCPayServer.Views.Stores;
|
||||
using NBitcoin.Payment;
|
||||
using NBitpayClient;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
@ -200,7 +201,7 @@ namespace BTCPayServer.Tests
|
||||
var receiverUser = tester.NewAccount();
|
||||
receiverUser.GrantAccess(true);
|
||||
receiverUser.RegisterDerivationScheme("BTC", receiverAddressType, true);
|
||||
await receiverUser.EnablePayJoin();
|
||||
await receiverUser.ModifyWalletSettings(p => p.PayJoinEnabled = true);
|
||||
var receiverCoin = await receiverUser.ReceiveUTXO(Money.Satoshis(810), network);
|
||||
|
||||
string errorCode = receiverAddressType == senderAddressType ? null : "unavailable|any UTXO available";
|
||||
@ -255,8 +256,8 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("bip21parse")).Click();
|
||||
s.Driver.SwitchTo().Alert().SendKeys(bip21);
|
||||
s.Driver.SwitchTo().Alert().Accept();
|
||||
s.Driver.FindElement(By.Id("Outputs_0__Amount")).Clear();
|
||||
s.Driver.FindElement(By.Id("Outputs_0__Amount")).SendKeys("0.023");
|
||||
s.Driver.FindElementUntilNotStaled(By.Id("Outputs_0__Amount"), we => we.Clear());
|
||||
s.Driver.FindElementUntilNotStaled(By.Id("Outputs_0__Amount"), we => we.SendKeys("0.023"));
|
||||
|
||||
s.Driver.FindElement(By.Id("SignTransaction")).Click();
|
||||
|
||||
@ -289,9 +290,10 @@ namespace BTCPayServer.Tests
|
||||
|
||||
foreach (var format in new []{ScriptPubKeyType.Segwit, ScriptPubKeyType.SegwitP2SH})
|
||||
{
|
||||
var cryptoCode = "BTC";
|
||||
var receiver = s.CreateNewStore();
|
||||
var receiverSeed = s.GenerateWallet("BTC", "", true, true, format);
|
||||
var receiverWalletId = new WalletId(receiver.storeId, "BTC");
|
||||
var receiverSeed = s.GenerateWallet(cryptoCode, "", true, true, format);
|
||||
var receiverWalletId = new WalletId(receiver.storeId, cryptoCode);
|
||||
|
||||
//payjoin is enabled by default.
|
||||
var invoiceId = s.CreateInvoice(receiver.storeName);
|
||||
@ -302,11 +304,12 @@ namespace BTCPayServer.Tests
|
||||
|
||||
s.GoToHome();
|
||||
s.GoToStore(receiver.storeId);
|
||||
s.Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click();
|
||||
Assert.True(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected);
|
||||
|
||||
var sender = s.CreateNewStore();
|
||||
var senderSeed = s.GenerateWallet("BTC", "", true, true, format);
|
||||
var senderWalletId = new WalletId(sender.storeId, "BTC");
|
||||
var senderSeed = s.GenerateWallet(cryptoCode, "", true, true, format);
|
||||
var senderWalletId = new WalletId(sender.storeId, cryptoCode);
|
||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||
await s.FundStoreWallet(senderWalletId);
|
||||
|
||||
@ -570,9 +573,9 @@ namespace BTCPayServer.Tests
|
||||
address = (await nbx.GetUnusedAsync(bob.DerivationScheme, DerivationFeature.Deposit)).Address;
|
||||
tester.ExplorerNode.SendToAddress(address, Money.Coins(1.1m));
|
||||
await notifications.NextEventAsync();
|
||||
await bob.ModifyStore(s => s.PayJoinEnabled = true);
|
||||
await bob.ModifyWalletSettings(p => p.PayJoinEnabled = true);
|
||||
var invoice = bob.BitPay.CreateInvoice(
|
||||
new Invoice() { Price = 0.1m, Currency = "BTC", FullNotifications = true });
|
||||
new Invoice { Price = 0.1m, Currency = "BTC", FullNotifications = true });
|
||||
var invoiceBIP21 = new BitcoinUrlBuilder(invoice.CryptoInfo.First().PaymentUrls.BIP21,
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork);
|
||||
|
||||
@ -659,7 +662,7 @@ namespace BTCPayServer.Tests
|
||||
var receiverUser = tester.NewAccount();
|
||||
receiverUser.GrantAccess(true);
|
||||
receiverUser.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit, true);
|
||||
await receiverUser.EnablePayJoin();
|
||||
await receiverUser.ModifyWalletSettings(p => p.PayJoinEnabled = true);
|
||||
var receiverCoin = await receiverUser.ReceiveUTXO(Money.Satoshis(810), network);
|
||||
string lastInvoiceId = null;
|
||||
|
||||
@ -856,7 +859,7 @@ retry:
|
||||
receiverUser.GrantAccess(true);
|
||||
receiverUser.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit, true);
|
||||
|
||||
await receiverUser.EnablePayJoin();
|
||||
await receiverUser.ModifyWalletSettings(p => p.PayJoinEnabled = true);
|
||||
// payjoin is enabled, with a segwit wallet, and the keys are available in nbxplorer
|
||||
invoice = receiverUser.BitPay.CreateInvoice(
|
||||
new Invoice() { Price = 0.02m, Currency = "BTC", FullNotifications = true });
|
||||
|
@ -189,10 +189,20 @@ namespace BTCPayServer.Tests
|
||||
var response = Assert
|
||||
.IsType<RedirectToActionResult>(paymentRequestController.EditPaymentRequest(null, request).Result)
|
||||
.RouteValues.First();
|
||||
var invoiceId = response.Value.ToString();
|
||||
await paymentRequestController.PayPaymentRequest(invoiceId, false);
|
||||
Assert.IsType<BadRequestObjectResult>(await
|
||||
paymentRequestController.CancelUnpaidPendingInvoice(invoiceId, false));
|
||||
|
||||
request.AllowCustomPaymentAmounts = true;
|
||||
|
||||
response = Assert
|
||||
.IsType<RedirectToActionResult>(paymentRequestController.EditPaymentRequest(null, request).Result)
|
||||
.RouteValues.First();
|
||||
|
||||
var paymentRequestId = response.Value.ToString();
|
||||
|
||||
var invoiceId = Assert
|
||||
invoiceId = Assert
|
||||
.IsType<OkObjectResult>(await paymentRequestController.PayPaymentRequest(paymentRequestId, false))
|
||||
.Value
|
||||
.ToString();
|
||||
@ -224,12 +234,12 @@ namespace BTCPayServer.Tests
|
||||
|
||||
invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
|
||||
|
||||
//a hack to generate invoices for the payment request is to manually create an invocie with an order id that matches:
|
||||
//a hack to generate invoices for the payment request is to manually create an invoice with an order id that matches:
|
||||
user.BitPay.CreateInvoice(new Invoice(1, "USD")
|
||||
{
|
||||
OrderId = PaymentRequestRepository.GetOrderIdForPaymentRequest(paymentRequestId)
|
||||
});
|
||||
//shouldnt crash
|
||||
//shouldn't crash
|
||||
await paymentRequestController.ViewPaymentRequest(paymentRequestId);
|
||||
await paymentRequestController.CancelUnpaidPendingInvoice(paymentRequestId);
|
||||
}
|
||||
|
@ -3,11 +3,11 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Views.Manage;
|
||||
using BTCPayServer.Views.Server;
|
||||
@ -18,8 +18,8 @@ using NBitcoin;
|
||||
using BTCPayServer.BIP78.Sender;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Chrome;
|
||||
using OpenQA.Selenium.Support.Extensions;
|
||||
using Xunit;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -38,6 +38,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public async Task StartAsync()
|
||||
{
|
||||
Server.PayTester.NoCSP = true;
|
||||
await Server.StartAsync();
|
||||
|
||||
var windowSize = (Width: 1200, Height: 1000);
|
||||
@ -52,11 +53,6 @@ namespace BTCPayServer.Tests
|
||||
var chromeDriverPath = config["ChromeDriverDirectory"] ?? (Server.PayTester.InContainer ? "/usr/bin" : Directory.GetCurrentDirectory());
|
||||
|
||||
var options = new ChromeOptions();
|
||||
if (Server.PayTester.InContainer)
|
||||
{
|
||||
// this must be first option https://stackoverflow.com/questions/53073411/selenium-webdriverexceptionchrome-failed-to-start-crashed-as-google-chrome-is#comment102570662_53073789
|
||||
options.AddArgument("no-sandbox");
|
||||
}
|
||||
if (!runInBrowser)
|
||||
{
|
||||
options.AddArguments("headless");
|
||||
@ -64,28 +60,48 @@ namespace BTCPayServer.Tests
|
||||
options.AddArguments($"window-size={windowSize.Width}x{windowSize.Height}");
|
||||
options.AddArgument("shm-size=2g");
|
||||
options.AddArgument("start-maximized");
|
||||
|
||||
var cds = ChromeDriverService.CreateDefaultService(chromeDriverPath);
|
||||
cds.EnableVerboseLogging = true;
|
||||
cds.Port = Utils.FreeTcpPort();
|
||||
cds.HostName = "127.0.0.1";
|
||||
cds.Start();
|
||||
Driver = new ChromeDriver(cds, options,
|
||||
// A bit less than test timeout
|
||||
TimeSpan.FromSeconds(50));
|
||||
if (Server.PayTester.InContainer)
|
||||
{
|
||||
Driver = new OpenQA.Selenium.Remote.RemoteWebDriver(new Uri("http://selenium:4444/wd/hub"), new RemoteSessionSettings(options));
|
||||
var containerIp = File.ReadAllText("/etc/hosts").Split('\n', StringSplitOptions.RemoveEmptyEntries).Last()
|
||||
.Split('\t', StringSplitOptions.RemoveEmptyEntries)[0].Trim();
|
||||
Logs.Tester.LogInformation($"Selenium: Container's IP {containerIp}");
|
||||
ServerUri = new Uri(Server.PayTester.ServerUri.AbsoluteUri.Replace($"http://{Server.PayTester.HostName}", $"http://{containerIp}", StringComparison.OrdinalIgnoreCase), UriKind.Absolute);
|
||||
}
|
||||
else
|
||||
{
|
||||
var cds = ChromeDriverService.CreateDefaultService(chromeDriverPath);
|
||||
cds.EnableVerboseLogging = true;
|
||||
cds.Port = Utils.FreeTcpPort();
|
||||
cds.HostName = "127.0.0.1";
|
||||
cds.Start();
|
||||
Driver = new ChromeDriver(cds, options,
|
||||
// A bit less than test timeout
|
||||
TimeSpan.FromSeconds(50));
|
||||
ServerUri = Server.PayTester.ServerUri;
|
||||
}
|
||||
Driver.Manage().Window.Maximize();
|
||||
|
||||
Logs.Tester.LogInformation($"Selenium: Using {Driver.GetType()}");
|
||||
Logs.Tester.LogInformation($"Selenium: Browsing to {Server.PayTester.ServerUri}");
|
||||
Logs.Tester.LogInformation($"Selenium: Browsing to {ServerUri}");
|
||||
Logs.Tester.LogInformation($"Selenium: Resolution {Driver.Manage().Window.Size}");
|
||||
GoToRegister();
|
||||
Driver.AssertNoError();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Use this ServerUri when trying to browse with selenium
|
||||
/// Because for some reason, the selenium container can't resolve the tests container domain name
|
||||
/// </summary>
|
||||
public Uri ServerUri;
|
||||
internal IWebElement FindAlertMessage(StatusMessageModel.StatusSeverity severity = StatusMessageModel.StatusSeverity.Success)
|
||||
{
|
||||
var className = $"alert-{StatusMessageModel.ToString(severity)}";
|
||||
var el = Driver.FindElement(By.ClassName(className)) ?? Driver.WaitForElement(By.ClassName(className));
|
||||
return FindAlertMessage(new[] {severity});
|
||||
}
|
||||
internal IWebElement FindAlertMessage(params StatusMessageModel.StatusSeverity[] severity)
|
||||
{
|
||||
var className = string.Join(", ", severity.Select(statusSeverity => $".alert-{StatusMessageModel.ToString(statusSeverity)}"));
|
||||
var el = Driver.FindElement(By.CssSelector(className)) ?? Driver.WaitForElement(By.CssSelector(className));
|
||||
if (el is null)
|
||||
throw new NoSuchElementException($"Unable to find {className}");
|
||||
return el;
|
||||
@ -93,7 +109,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public string Link(string relativeLink)
|
||||
{
|
||||
return Server.PayTester.ServerUri.AbsoluteUri.WithoutEndingSlash() + relativeLink.WithStartingSlash();
|
||||
return ServerUri.AbsoluteUri.WithoutEndingSlash() + relativeLink.WithStartingSlash();
|
||||
}
|
||||
|
||||
public void GoToRegister()
|
||||
@ -115,15 +131,19 @@ namespace BTCPayServer.Tests
|
||||
return usr;
|
||||
}
|
||||
|
||||
public (string storeName, string storeId) CreateNewStore()
|
||||
public (string storeName, string storeId) CreateNewStore(bool keepId = true)
|
||||
{
|
||||
Driver.WaitForElement(By.Id("Stores")).Click();
|
||||
Driver.WaitForElement(By.Id("CreateStore")).Click();
|
||||
var name = "Store" + RandomUtils.GetUInt64();
|
||||
Driver.WaitForElement(By.Id("Name")).SendKeys(name);
|
||||
Driver.WaitForElement(By.Id("Create")).Click();
|
||||
StoreId = Driver.WaitForElement(By.Id("Id")).GetAttribute("value");
|
||||
return (name, StoreId);
|
||||
Driver.FindElement(By.Id($"Nav-{StoreNavPages.GeneralSettings.ToString()}")).Click();
|
||||
var storeId = Driver.WaitForElement(By.Id("Id")).GetAttribute("value");
|
||||
Driver.FindElement(By.Id($"Nav-{StoreNavPages.PaymentMethods.ToString()}")).Click();
|
||||
if (keepId)
|
||||
StoreId = storeId;
|
||||
return (name, storeId);
|
||||
}
|
||||
|
||||
public Mnemonic GenerateWallet(string cryptoCode = "BTC", string seed = "", bool importkeys = false, bool privkeys = false, ScriptPubKeyType format = ScriptPubKeyType.Segwit)
|
||||
@ -135,7 +155,8 @@ namespace BTCPayServer.Tests
|
||||
if (Driver.PageSource.Contains("id=\"ChangeWalletLink\""))
|
||||
{
|
||||
Driver.FindElement(By.Id("ChangeWalletLink")).Click();
|
||||
Driver.FindElement(By.Id("continue")).Click();
|
||||
Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("REPLACE");
|
||||
Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
}
|
||||
|
||||
if (isImport)
|
||||
@ -195,10 +216,15 @@ namespace BTCPayServer.Tests
|
||||
FindAlertMessage();
|
||||
}
|
||||
|
||||
public void AddLightningNode(string cryptoCode = "BTC", LightningConnectionType? connectionType = null)
|
||||
public void AddLightningNode(string cryptoCode = "BTC", LightningConnectionType? connectionType = null, bool test = true)
|
||||
{
|
||||
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
|
||||
|
||||
if (Driver.PageSource.Contains("id=\"SetupLightningNodeLink\""))
|
||||
{
|
||||
Driver.FindElement(By.Id($"SetupLightningNodeLink")).Click();
|
||||
}
|
||||
|
||||
var connectionString = connectionType switch
|
||||
{
|
||||
LightningConnectionType.Charge =>
|
||||
@ -218,10 +244,13 @@ namespace BTCPayServer.Tests
|
||||
else
|
||||
{
|
||||
Driver.FindElement(By.CssSelector("label[for=\"LightningNodeType-Custom\"]")).Click();
|
||||
Driver.FindElement(By.Id("ConnectionString")).Clear();
|
||||
Driver.FindElement(By.Id("ConnectionString")).SendKeys(connectionString);
|
||||
|
||||
Driver.FindElement(By.Id("test")).Click();
|
||||
Assert.Contains("Connection to the Lightning node successful.", FindAlertMessage().Text);
|
||||
if (test)
|
||||
{
|
||||
Driver.FindElement(By.Id("test")).Click();
|
||||
Assert.Contains("Connection to the Lightning node successful.", FindAlertMessage().Text);
|
||||
}
|
||||
}
|
||||
|
||||
Driver.FindElement(By.Id("save")).Click();
|
||||
@ -239,7 +268,6 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
var links = Driver.FindElements(By.CssSelector(".nav .nav-link")).Select(c => c.GetAttribute("href")).ToList();
|
||||
Driver.AssertNoError();
|
||||
Assert.NotEmpty(links);
|
||||
foreach (var l in links)
|
||||
{
|
||||
Logs.Tester.LogInformation($"Checking no error on {l}");
|
||||
@ -274,7 +302,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public void GoToHome()
|
||||
{
|
||||
Driver.Navigate().GoToUrl(Server.PayTester.ServerUri);
|
||||
Driver.Navigate().GoToUrl(ServerUri);
|
||||
}
|
||||
|
||||
public void Logout()
|
||||
@ -288,20 +316,24 @@ namespace BTCPayServer.Tests
|
||||
Driver.FindElement(By.Id("Password")).SendKeys(password);
|
||||
Driver.FindElement(By.Id("LoginButton")).Click();
|
||||
}
|
||||
|
||||
public void GoToApps()
|
||||
{
|
||||
Driver.FindElement(By.Id("Apps")).Click();
|
||||
}
|
||||
public void GoToStores()
|
||||
{
|
||||
Driver.FindElement(By.Id("Stores")).Click();
|
||||
}
|
||||
|
||||
public void GoToStore(string storeId, StoreNavPages storeNavPage = StoreNavPages.Index)
|
||||
public void GoToStore(string storeId, StoreNavPages storeNavPage = StoreNavPages.PaymentMethods)
|
||||
{
|
||||
Driver.FindElement(By.Id("Stores")).Click();
|
||||
GoToHome();
|
||||
Driver.WaitForAndClick(By.Id("Stores"));
|
||||
Driver.FindElement(By.Id($"update-store-{storeId}")).Click();
|
||||
|
||||
if (storeNavPage != StoreNavPages.Index)
|
||||
if (storeNavPage != StoreNavPages.PaymentMethods)
|
||||
{
|
||||
Driver.FindElement(By.Id(storeNavPage.ToString())).Click();
|
||||
Driver.FindElement(By.Id($"Nav-{storeNavPage.ToString()}")).Click();
|
||||
}
|
||||
}
|
||||
|
||||
@ -328,10 +360,18 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public void GoToLogin()
|
||||
{
|
||||
Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, "/login"));
|
||||
Driver.Navigate().GoToUrl(new Uri(ServerUri, "/login"));
|
||||
}
|
||||
|
||||
public string CreateInvoice(string storeName, decimal? amount = 100, string currency = "USD", string refundEmail = "")
|
||||
public string CreateInvoice(
|
||||
string storeName,
|
||||
decimal? amount = 100,
|
||||
string currency = "USD",
|
||||
string refundEmail = "",
|
||||
string defaultPaymentMethod = null,
|
||||
bool? requiresRefundEmail = null,
|
||||
StatusMessageModel.StatusSeverity expectedSeverity = StatusMessageModel.StatusSeverity.Success
|
||||
)
|
||||
{
|
||||
GoToInvoices();
|
||||
Driver.FindElement(By.Id("CreateNewInvoice")).Click();
|
||||
@ -342,11 +382,14 @@ namespace BTCPayServer.Tests
|
||||
currencyEl.SendKeys(currency);
|
||||
Driver.FindElement(By.Id("BuyerEmail")).SendKeys(refundEmail);
|
||||
Driver.FindElement(By.Name("StoreId")).SendKeys(storeName);
|
||||
if (defaultPaymentMethod is string)
|
||||
new SelectElement(Driver.FindElement(By.Name("DefaultPaymentMethod"))).SelectByValue(defaultPaymentMethod);
|
||||
if (requiresRefundEmail is bool)
|
||||
new SelectElement(Driver.FindElement(By.Name("RequiresRefundEmail"))).SelectByValue(requiresRefundEmail == true ? "1" : "2");
|
||||
Driver.FindElement(By.Id("Create")).Click();
|
||||
|
||||
var statusElement = FindAlertMessage();
|
||||
var id = statusElement.Text.Split(" ")[1];
|
||||
return id;
|
||||
var statusElement = FindAlertMessage(expectedSeverity);
|
||||
return expectedSeverity == StatusMessageModel.StatusSeverity.Success ? statusElement.Text.Split(" ")[1] : null;
|
||||
}
|
||||
|
||||
public async Task FundStoreWallet(WalletId walletId = null, int coins = 1, decimal denomination = 1m)
|
||||
@ -404,7 +447,7 @@ namespace BTCPayServer.Tests
|
||||
public void GoToWallet(WalletId walletId = null, WalletsNavPages navPages = WalletsNavPages.Send)
|
||||
{
|
||||
walletId ??= WalletId;
|
||||
Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, $"wallets/{walletId}"));
|
||||
Driver.Navigate().GoToUrl(new Uri(ServerUri, $"wallets/{walletId}"));
|
||||
if (navPages != WalletsNavPages.Transactions)
|
||||
{
|
||||
Driver.FindElement(By.Id($"Wallet{navPages}")).Click();
|
||||
@ -413,7 +456,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public void GoToUrl(string relativeUrl)
|
||||
{
|
||||
Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, relativeUrl));
|
||||
Driver.Navigate().GoToUrl(new Uri(ServerUri, relativeUrl));
|
||||
}
|
||||
|
||||
public void GoToServer(ServerNavPages navPages = ServerNavPages.Index)
|
||||
|
@ -2,19 +2,30 @@ using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Security;
|
||||
using System.Security.Authentication;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.Charge;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Lightning.LND;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Views.Manage;
|
||||
using BTCPayServer.Views.Server;
|
||||
using BTCPayServer.Views.Stores;
|
||||
using BTCPayServer.Views.Wallets;
|
||||
using LNURL;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
@ -27,6 +38,7 @@ using OpenQA.Selenium.Support.UI;
|
||||
using Renci.SshNet.Security.Cryptography;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using CreateInvoiceRequest = BTCPayServer.Lightning.Charge.CreateInvoiceRequest;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -85,7 +97,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(passEl.Displayed);
|
||||
Assert.Contains(passEl.Text, "hellorockstar", StringComparison.OrdinalIgnoreCase);
|
||||
s.Driver.FindElement(By.Id("delete")).Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
|
||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
s.FindAlertMessage();
|
||||
seedEl = s.Driver.FindElement(By.Id("Seed"));
|
||||
Assert.Contains("Seed removed", seedEl.Text, StringComparison.OrdinalIgnoreCase);
|
||||
@ -205,6 +218,15 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// We should be logged in now
|
||||
s.Driver.FindElement(By.Id("mainNav"));
|
||||
|
||||
//let's test delete user quickly while we're at it
|
||||
s.GoToProfile();
|
||||
s.Driver.FindElement(By.Id("danger-zone-expander")).Click();
|
||||
s.Driver.FindElement(By.Id("delete-user")).Click();
|
||||
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
|
||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
|
||||
Assert.Contains("/login", s.Driver.Url);
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,13 +243,15 @@ namespace BTCPayServer.Tests
|
||||
s.RegisterNewUser(isAdmin: true);
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services"));
|
||||
Assert.Contains("server/services/ssh", s.Driver.PageSource);
|
||||
using (var client = await s.Server.PayTester.GetService<Configuration.BTCPayServerOptions>().SSHSettings.ConnectAsync())
|
||||
using (var client = await s.Server.PayTester.GetService<Configuration.BTCPayServerOptions>().SSHSettings
|
||||
.ConnectAsync())
|
||||
{
|
||||
var result = await client.RunBash("echo hello");
|
||||
Assert.Equal(string.Empty, result.Error);
|
||||
Assert.Equal("hello\n", result.Output);
|
||||
Assert.Equal(0, result.ExitStatus);
|
||||
}
|
||||
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/ssh"));
|
||||
s.Driver.AssertNoError();
|
||||
s.Driver.FindElement(By.Id("SSHKeyFileContent")).Clear();
|
||||
@ -239,7 +263,8 @@ namespace BTCPayServer.Tests
|
||||
// Browser replace \n to \r\n, so it is hard to compare exactly what we want
|
||||
Assert.Contains("tes't", text);
|
||||
Assert.Contains("test2", text);
|
||||
Assert.True(s.Driver.PageSource.Contains("authorized_keys has been updated", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.True(s.Driver.PageSource.Contains("authorized_keys has been updated",
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
s.Driver.FindElement(By.Id("SSHKeyFileContent")).Clear();
|
||||
s.Driver.FindElement(By.Id("submit")).Click();
|
||||
@ -249,12 +274,14 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// Let's try to disable it now
|
||||
s.Driver.FindElement(By.Id("disable")).Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DISABLE");
|
||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/ssh"));
|
||||
Assert.True(s.Driver.PageSource.Contains("404 - Page not found", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
policies = await settings.GetSettingAsync<PoliciesSettings>();
|
||||
Assert.True(policies.DisableSSHService);
|
||||
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/ssh"));
|
||||
Assert.True(s.Driver.PageSource.Contains("404 - Page not found", StringComparison.OrdinalIgnoreCase));
|
||||
policies.DisableSSHService = false;
|
||||
await settings.UpdateSetting(policies);
|
||||
}
|
||||
@ -273,6 +300,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.CssSelector("button[value=\"ResetPassword\"]")).Submit();
|
||||
s.FindAlertMessage();
|
||||
}
|
||||
|
||||
CanSetupEmailCore(s);
|
||||
s.CreateNewStore();
|
||||
s.GoToUrl($"stores/{s.StoreId}/emails");
|
||||
@ -296,8 +324,9 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
// Cleanup old test run
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/dynamic-dns/pouet.hello.com/delete"));
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
}
|
||||
|
||||
s.Driver.FindElement(By.Id("AddDynamicDNS")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
// We will just cheat for test purposes by only querying the server
|
||||
@ -323,7 +352,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/dynamic-dns"));
|
||||
Assert.Contains("/server/services/dynamic-dns/pouet.hello.com/delete", s.Driver.PageSource);
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/dynamic-dns/pouet.hello.com/delete"));
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
|
||||
Assert.DoesNotContain("/server/services/dynamic-dns/pouet.hello.com/delete", s.Driver.PageSource);
|
||||
@ -353,13 +382,15 @@ namespace BTCPayServer.Tests
|
||||
s.GoToStore(storeId);
|
||||
Assert.Contains(storeName, s.Driver.PageSource);
|
||||
Assert.True(s.Driver.PageSource.Contains(onchainHint), "Wallet hint should be present at this point");
|
||||
Assert.True(s.Driver.PageSource.Contains(offchainHint), "Lightning hint should be present at this point");
|
||||
Assert.True(s.Driver.PageSource.Contains(offchainHint),
|
||||
"Lightning hint should be present at this point");
|
||||
|
||||
// setup onchain wallet
|
||||
s.GoToStore(storeId);
|
||||
s.AddDerivationScheme();
|
||||
s.Driver.AssertNoError();
|
||||
Assert.False(s.Driver.PageSource.Contains(onchainHint), "Wallet hint not dismissed on derivation scheme add");
|
||||
Assert.False(s.Driver.PageSource.Contains(onchainHint),
|
||||
"Wallet hint not dismissed on derivation scheme add");
|
||||
|
||||
// setup offchain wallet
|
||||
s.GoToStore(storeId);
|
||||
@ -367,7 +398,8 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.AssertNoError();
|
||||
var successAlert = s.FindAlertMessage();
|
||||
Assert.Contains("BTC Lightning node updated.", successAlert.Text);
|
||||
Assert.False(s.Driver.PageSource.Contains(offchainHint), "Lightning hint should be dismissed at this point");
|
||||
Assert.False(s.Driver.PageSource.Contains(offchainHint),
|
||||
"Lightning hint should be dismissed at this point");
|
||||
|
||||
var storeUrl = s.Driver.Url;
|
||||
s.ClickOnAllSideMenus();
|
||||
@ -427,8 +459,9 @@ namespace BTCPayServer.Tests
|
||||
s.Logout();
|
||||
s.LogIn(alice);
|
||||
s.Driver.FindElement(By.Id("Stores")).Click();
|
||||
s.Driver.FindElement(By.LinkText("Remove")).Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.Driver.FindElement(By.LinkText("Delete")).Click();
|
||||
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
|
||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
s.Driver.FindElement(By.Id("Stores")).Click();
|
||||
s.Driver.Navigate().GoToUrl(storeUrl);
|
||||
Assert.Contains("ReturnUrl", s.Driver.Url);
|
||||
@ -448,7 +481,7 @@ namespace BTCPayServer.Tests
|
||||
s.CreateNewStore();
|
||||
s.AddDerivationScheme();
|
||||
|
||||
s.Driver.FindElement(By.Id("Tokens")).Click();
|
||||
s.Driver.FindElement(By.Id("Nav-Tokens")).Click();
|
||||
s.Driver.FindElement(By.Id("CreateNewToken")).Click();
|
||||
s.Driver.FindElement(By.Id("RequestPairing")).Click();
|
||||
var pairingCode = AssertUrlHasPairingCode(s);
|
||||
@ -457,27 +490,21 @@ namespace BTCPayServer.Tests
|
||||
s.FindAlertMessage();
|
||||
Assert.Contains(pairingCode, s.Driver.PageSource);
|
||||
|
||||
var client = new NBitpayClient.Bitpay(new Key(), s.Server.PayTester.ServerUri);
|
||||
var client = new NBitpayClient.Bitpay(new Key(), s.ServerUri);
|
||||
await client.AuthorizeClient(new NBitpayClient.PairingCode(pairingCode));
|
||||
await client.CreateInvoiceAsync(new NBitpayClient.Invoice()
|
||||
{
|
||||
Price = 0.000000012m,
|
||||
Currency = "USD",
|
||||
FullNotifications = true
|
||||
}, NBitpayClient.Facade.Merchant);
|
||||
await client.CreateInvoiceAsync(
|
||||
new NBitpayClient.Invoice() { Price = 0.000000012m, Currency = "USD", FullNotifications = true },
|
||||
NBitpayClient.Facade.Merchant);
|
||||
|
||||
client = new NBitpayClient.Bitpay(new Key(), s.Server.PayTester.ServerUri);
|
||||
client = new NBitpayClient.Bitpay(new Key(), s.ServerUri);
|
||||
|
||||
var code = await client.RequestClientAuthorizationAsync("hehe", NBitpayClient.Facade.Merchant);
|
||||
s.Driver.Navigate().GoToUrl(code.CreateLink(s.Server.PayTester.ServerUri));
|
||||
s.Driver.Navigate().GoToUrl(code.CreateLink(s.ServerUri));
|
||||
s.Driver.FindElement(By.Id("ApprovePairing")).Click();
|
||||
|
||||
await client.CreateInvoiceAsync(new NBitpayClient.Invoice()
|
||||
{
|
||||
Price = 0.000000012m,
|
||||
Currency = "USD",
|
||||
FullNotifications = true
|
||||
}, NBitpayClient.Facade.Merchant);
|
||||
await client.CreateInvoiceAsync(
|
||||
new NBitpayClient.Invoice() { Price = 0.000000012m, Currency = "USD", FullNotifications = true },
|
||||
NBitpayClient.Facade.Merchant);
|
||||
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/api-tokens"));
|
||||
s.Driver.FindElement(By.Id("RequestPairing")).Click();
|
||||
@ -497,7 +524,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
s.Driver.FindElement(By.Id("Apps")).Click();
|
||||
s.Driver.FindElement(By.Id("CreateNewApp")).Click();
|
||||
s.Driver.FindElement(By.Name("Name")).SendKeys("PoS" + Guid.NewGuid());
|
||||
s.Driver.FindElement(By.Name("AppName")).SendKeys("PoS" + Guid.NewGuid());
|
||||
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("Point of Sale");
|
||||
s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(storeName);
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
@ -538,7 +565,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
s.Driver.FindElement(By.Id("Apps")).Click();
|
||||
s.Driver.FindElement(By.Id("CreateNewApp")).Click();
|
||||
s.Driver.FindElement(By.Name("Name")).SendKeys("CF" + Guid.NewGuid());
|
||||
s.Driver.FindElement(By.Name("AppName")).SendKeys("CF" + Guid.NewGuid());
|
||||
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("Crowdfund");
|
||||
s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(storeName);
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
@ -548,7 +575,8 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("TargetAmount")).SendKeys("700");
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).Click();
|
||||
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
||||
Assert.Equal("Currently Active!", s.Driver.FindElement(By.CssSelector(".h6.text-muted")).Text);
|
||||
Assert.Equal("currently active!",
|
||||
s.Driver.FindElement(By.CssSelector("[data-test='time-state']")).Text);
|
||||
}
|
||||
}
|
||||
|
||||
@ -571,22 +599,26 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Name("ViewAppButton")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
Assert.Equal("Amount due", s.Driver.FindElement(By.CssSelector("[data-test='amount-due-title']")).Text);
|
||||
Assert.Equal("Pay Invoice", s.Driver.FindElement(By.CssSelector("[data-test='pay-button']")).Text.Trim());
|
||||
|
||||
Assert.Equal("Pay Invoice",
|
||||
s.Driver.FindElement(By.CssSelector("[data-test='pay-button']")).Text.Trim());
|
||||
|
||||
// expire
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
s.Driver.ExecuteJavaScript("document.getElementById('ExpiryDate').value = '2021-01-21T21:00:00.000Z'");
|
||||
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
Assert.Equal("Expired", s.Driver.FindElement(By.CssSelector("[data-test='status']")).Text);
|
||||
|
||||
s.Driver.Navigate().Refresh();
|
||||
Assert.Equal("Expired", s.Driver.WaitForElement(By.CssSelector("[data-test='status']")).Text);
|
||||
|
||||
// unexpire
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
s.Driver.FindElement(By.Id("ClearExpiryDate")).Click();
|
||||
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
s.Driver.Navigate().Refresh();
|
||||
s.Driver.AssertElementNotFound(By.CssSelector("[data-test='status']"));
|
||||
Assert.Equal("Pay Invoice", s.Driver.FindElement(By.CssSelector("[data-test='pay-button']")).Text.Trim());
|
||||
Assert.Equal("Pay Invoice",
|
||||
s.Driver.FindElement(By.CssSelector("[data-test='pay-button']")).Text.Trim());
|
||||
}
|
||||
}
|
||||
|
||||
@ -603,15 +635,18 @@ namespace BTCPayServer.Tests
|
||||
s.GoToWallet(walletId, WalletsNavPages.Receive);
|
||||
s.Driver.FindElement(By.Id("generateButton")).Click();
|
||||
var addressStr = s.Driver.FindElement(By.Id("address")).GetProperty("value");
|
||||
var address = BitcoinAddress.Create(addressStr, ((BTCPayNetwork)s.Server.NetworkProvider.GetNetwork("BTC")).NBitcoinNetwork);
|
||||
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)));
|
||||
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);
|
||||
@ -626,10 +661,10 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||
s.GoToWallet(walletId);
|
||||
s.Driver.ToggleCollapse("AdvancedSettings");
|
||||
s.Driver.WaitForAndClick(By.Id("toggleInputSelection"));
|
||||
s.Driver.FindElement(By.Id(spentOutpoint.ToString()));
|
||||
Assert.Equal("true", s.Driver.FindElement(By.Name("InputSelection")).GetAttribute("value").ToLowerInvariant());
|
||||
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"));
|
||||
@ -674,7 +709,8 @@ namespace BTCPayServer.Tests
|
||||
var deletes = s.Driver.FindElements(By.LinkText("Delete"));
|
||||
Assert.Equal(2, deletes.Count);
|
||||
deletes[0].Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
|
||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
deletes = s.Driver.FindElements(By.LinkText("Delete"));
|
||||
Assert.Single(deletes);
|
||||
s.FindAlertMessage();
|
||||
@ -698,6 +734,7 @@ namespace BTCPayServer.Tests
|
||||
// Fix as needed.
|
||||
Assert.Contains($"value=\"{value}\"", s.Driver.PageSource);
|
||||
}
|
||||
|
||||
// This one should be checked
|
||||
Assert.Contains($"value=\"InvoiceProcessing\" checked", s.Driver.PageSource);
|
||||
Assert.Contains($"value=\"InvoiceCreated\" checked", s.Driver.PageSource);
|
||||
@ -716,7 +753,8 @@ namespace BTCPayServer.Tests
|
||||
var headers = request.Request.Headers;
|
||||
var actualSig = headers["BTCPay-Sig"].First();
|
||||
var bytes = await request.Request.Body.ReadBytesAsync((int)headers.ContentLength.Value);
|
||||
var expectedSig = $"sha256={Encoders.Hex.EncodeData(new HMACSHA256(Encoding.UTF8.GetBytes("HelloWorld")).ComputeHash(bytes))}";
|
||||
var expectedSig =
|
||||
$"sha256={Encoders.Hex.EncodeData(new HMACSHA256(Encoding.UTF8.GetBytes("HelloWorld")).ComputeHash(bytes))}";
|
||||
Assert.Equal(expectedSig, actualSig);
|
||||
request.Response.StatusCode = 200;
|
||||
server.Done();
|
||||
@ -757,15 +795,34 @@ namespace BTCPayServer.Tests
|
||||
server.Done();
|
||||
|
||||
Logs.Tester.LogInformation("Let's see if we can delete store with some webhooks inside");
|
||||
s.GoToStore(storeId);
|
||||
|
||||
s.Driver.ToggleCollapse("danger-zone");
|
||||
s.GoToStore(storeId, StoreNavPages.GeneralSettings);
|
||||
s.Driver.FindElement(By.Id("delete-store")).Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.Driver.WaitForElement(By.Id("ConfirmContinue")).Click();
|
||||
s.FindAlertMessage();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanImportMnemonic()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser(true);
|
||||
foreach (var isHotwallet in new[] { false, true })
|
||||
{
|
||||
var (storeName, storeId) = s.CreateNewStore();
|
||||
s.GenerateWallet(privkeys: isHotwallet,
|
||||
seed: "melody lizard phrase voice unique car opinion merge degree evil swift cargo");
|
||||
s.GoToWallet(s.WalletId, WalletsNavPages.Settings);
|
||||
if (isHotwallet)
|
||||
Assert.Contains("View seed", s.Driver.PageSource);
|
||||
else
|
||||
Assert.DoesNotContain("View seed", s.Driver.PageSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanManageWallet()
|
||||
{
|
||||
@ -804,7 +861,8 @@ namespace BTCPayServer.Tests
|
||||
var sess = await s.Server.ExplorerClient.CreateWebsocketNotificationSessionAsync();
|
||||
await sess.ListenAllTrackedSourceAsync();
|
||||
var nextEvent = sess.NextEventAsync();
|
||||
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(receiveAddr, Network.RegTest), Money.Parse("0.1"));
|
||||
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(receiveAddr, Network.RegTest),
|
||||
Money.Parse("0.1"));
|
||||
await nextEvent;
|
||||
await Task.Delay(200);
|
||||
s.Driver.Navigate().Refresh();
|
||||
@ -827,7 +885,8 @@ namespace BTCPayServer.Tests
|
||||
var address = invoice.EntityToDTO().Addresses["BTC"];
|
||||
|
||||
//wallet should have been imported to bitcoin core wallet in watch only mode.
|
||||
var result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
|
||||
var result =
|
||||
await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
|
||||
Assert.True(result.IsWatchOnly);
|
||||
s.GoToStore(storeId);
|
||||
var mnemonic = s.GenerateWallet("BTC", "", true, true);
|
||||
@ -837,21 +896,25 @@ namespace BTCPayServer.Tests
|
||||
invoiceId = s.CreateInvoice(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));
|
||||
result = await s.Server.ExplorerNode.GetAddressInfoAsync(
|
||||
BitcoinAddress.Create(address, Network.RegTest));
|
||||
//spendable from bitcoin core wallet!
|
||||
Assert.False(result.IsWatchOnly);
|
||||
var tx = s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create(address, Network.RegTest), Money.Coins(3.0m));
|
||||
var tx = s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create(address, Network.RegTest),
|
||||
Money.Coins(3.0m));
|
||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||
|
||||
s.Driver.FindElement(By.Id("Wallets")).Click();
|
||||
s.Driver.FindElement(By.LinkText("Manage")).Click();
|
||||
|
||||
s.ClickOnAllSideMenus();
|
||||
|
||||
|
||||
// Make sure wallet info is correct
|
||||
s.Driver.FindElement(By.Id("WalletSettings")).Click();
|
||||
Assert.Contains(mnemonic.DeriveExtKey().GetPublicKey().GetHDFingerPrint().ToString(), s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value"));
|
||||
Assert.Contains("m/84'/1'/0'", s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).GetAttribute("value"));
|
||||
Assert.Contains(mnemonic.DeriveExtKey().GetPublicKey().GetHDFingerPrint().ToString(),
|
||||
s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value"));
|
||||
Assert.Contains("m/84'/1'/0'",
|
||||
s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).GetAttribute("value"));
|
||||
|
||||
// Make sure we can rescan, because we are admin!
|
||||
s.Driver.FindElement(By.Id("WalletRescan")).Click();
|
||||
@ -865,7 +928,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var walletTransactionLink = s.Driver.Url;
|
||||
Assert.Contains(tx.ToString(), s.Driver.PageSource);
|
||||
|
||||
|
||||
// Send to bob
|
||||
s.Driver.FindElement(By.Id("WalletSend")).Click();
|
||||
var bob = new Key().PubKey.Hash.GetAddress(Network.RegTest);
|
||||
@ -888,12 +951,6 @@ namespace BTCPayServer.Tests
|
||||
|
||||
Assert.Contains(jack.ToString(), s.Driver.PageSource);
|
||||
Assert.Contains("0.01000000", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.CssSelector("button[value=analyze-psbt]")).Click();
|
||||
Assert.EndsWith("psbt", s.Driver.Url);
|
||||
|
||||
s.Driver.FindElement(By.Id("OtherActionsDropdownToggle")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
|
||||
|
||||
Assert.EndsWith("psbt/ready", s.Driver.Url);
|
||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
|
||||
Assert.Equal(walletTransactionLink, s.Driver.Url);
|
||||
@ -909,8 +966,10 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.SwitchTo().Alert().SendKeys(bip21);
|
||||
s.Driver.SwitchTo().Alert().Accept();
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Info);
|
||||
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"));
|
||||
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, "BTC"), WalletsNavPages.Settings);
|
||||
var walletUrl = s.Driver.Url;
|
||||
@ -918,9 +977,11 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.CssSelector("button[value=view-seed]")).Click();
|
||||
|
||||
// Seed backup page
|
||||
var recoveryPhrase = s.Driver.FindElements(By.Id("RecoveryPhrase")).First().GetAttribute("data-mnemonic");
|
||||
var recoveryPhrase = s.Driver.FindElements(By.Id("RecoveryPhrase")).First()
|
||||
.GetAttribute("data-mnemonic");
|
||||
Assert.Equal(mnemonic.ToString(), recoveryPhrase);
|
||||
Assert.Contains("The recovery phrase will also be stored on the server as a hot wallet.", s.Driver.PageSource);
|
||||
Assert.Contains("The recovery phrase will also be stored on the server as a hot wallet.",
|
||||
s.Driver.PageSource);
|
||||
|
||||
// No confirmation, just a link to return to the wallet
|
||||
Assert.Empty(s.Driver.FindElements(By.Id("confirm")));
|
||||
@ -937,20 +998,25 @@ namespace BTCPayServer.Tests
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser(true);
|
||||
var (_, storeId) = s.CreateNewStore();
|
||||
var mnemonic = s.GenerateWallet("BTC", "click chunk owner kingdom faint steak safe evidence bicycle repeat bulb wheel");
|
||||
|
||||
var mnemonic = s.GenerateWallet("BTC",
|
||||
"click chunk owner kingdom faint steak safe evidence bicycle repeat bulb wheel");
|
||||
|
||||
// Make sure wallet info is correct
|
||||
s.GoToWallet(new WalletId(storeId, "BTC"), WalletsNavPages.Settings);
|
||||
Assert.Contains(mnemonic.DeriveExtKey().GetPublicKey().GetHDFingerPrint().ToString(), s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value"));
|
||||
Assert.Contains( "m/84'/1'/0'", s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).GetAttribute("value"));
|
||||
Assert.Contains(mnemonic.DeriveExtKey().GetPublicKey().GetHDFingerPrint().ToString(),
|
||||
s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value"));
|
||||
Assert.Contains("m/84'/1'/0'",
|
||||
s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).GetAttribute("value"));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanUsePullPaymentsViaUI()
|
||||
{
|
||||
using var s = SeleniumTester.Create();
|
||||
s.Server.ActivateLightning();
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser(true);
|
||||
s.CreateNewStore();
|
||||
@ -958,15 +1024,16 @@ namespace BTCPayServer.Tests
|
||||
|
||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||
await s.FundStoreWallet(denomination: 50.0m);
|
||||
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
|
||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0");;
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0");
|
||||
;
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
|
||||
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
|
||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("PP2");
|
||||
@ -998,16 +1065,16 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var viewPullPaymentUrl = s.Driver.Url;
|
||||
// This one should have nothing
|
||||
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
var payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
||||
Assert.Equal(2, payouts.Count);
|
||||
payouts[1].Click();
|
||||
Assert.Empty(s.Driver.FindElements(By.ClassName("payout")));
|
||||
// PP2 should have payouts
|
||||
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
||||
payouts[0].Click();
|
||||
|
||||
|
||||
Assert.NotEmpty(s.Driver.FindElements(By.ClassName("payout")));
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click();
|
||||
@ -1024,14 +1091,14 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
Assert.Equal("payout", s.Driver.FindElement(By.ClassName("transactionLabel")).Text);
|
||||
|
||||
s.GoToWallet(navPages: WalletsNavPages.Payouts);
|
||||
s.GoToStore(s.StoreId, StoreNavPages.Payouts);
|
||||
var x = s.Driver.PageSource;
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.InProgress}-view")).Click();
|
||||
ReadOnlyCollection<IWebElement> txs;
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
|
||||
|
||||
txs = s.Driver.FindElements(By.ClassName("transaction-link"));
|
||||
Assert.Equal(2, txs.Count);
|
||||
});
|
||||
@ -1062,12 +1129,11 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("NotificationsDropdownToggle")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("#notificationsForm button")).Click();
|
||||
|
||||
|
||||
var newStore = s.CreateNewStore();
|
||||
s.GenerateWallet("BTC", "", true, true);
|
||||
var newWalletId = new WalletId(newStore.storeId, "BTC");
|
||||
s.GoToWallet(newWalletId, WalletsNavPages.PullPayments);
|
||||
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
|
||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("External Test");
|
||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||
@ -1076,34 +1142,439 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC");
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
|
||||
|
||||
address = await s.Server.ExplorerNode.GetNewAddressAsync();
|
||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys(Keys.Enter);
|
||||
s.FindAlertMessage();
|
||||
|
||||
Assert.Contains(PayoutState.AwaitingApproval.GetStateString(), s.Driver.PageSource);
|
||||
s.GoToWallet(newWalletId, WalletsNavPages.Payouts);
|
||||
s.GoToStore(s.StoreId, StoreNavPages.Payouts);
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-view")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve")).Click();
|
||||
s.FindAlertMessage();
|
||||
var tx =await s.Server.ExplorerNode.SendToAddressAsync(address, Money.FromUnit(0.001m, MoneyUnit.BTC));
|
||||
var tx = await s.Server.ExplorerNode.SendToAddressAsync(address, Money.FromUnit(0.001m, MoneyUnit.BTC));
|
||||
|
||||
s.GoToStore(s.StoreId, StoreNavPages.Payouts);
|
||||
|
||||
s.GoToWallet(newWalletId, WalletsNavPages.Payouts);
|
||||
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-view")).Click();
|
||||
Assert.Contains(PayoutState.AwaitingPayment.GetStateString(), s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-selectAllCheckbox")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-actions")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-mark-paid")).Click();
|
||||
s.FindAlertMessage();
|
||||
|
||||
s.Driver.FindElement(By.Id("InProgress-view")).Click();
|
||||
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.InProgress}-view")).Click();
|
||||
Assert.Contains(tx.ToString(), s.Driver.PageSource);
|
||||
|
||||
//lightning tests
|
||||
newStore = s.CreateNewStore();
|
||||
s.AddLightningNode("BTC");
|
||||
//Currently an onchain wallet is required to use the Lightning payouts feature..
|
||||
s.GenerateWallet("BTC", "", true, true);
|
||||
|
||||
s.GoToStore(newStore.storeId, StoreNavPages.PullPayments);
|
||||
|
||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||
|
||||
var paymentMethodOptions = s.Driver.FindElements(By.CssSelector("#PaymentMethods option"));
|
||||
Assert.Equal(2, paymentMethodOptions.Count);
|
||||
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("Lightning Test");
|
||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("0.00001");
|
||||
s.Driver.FindElement(By.Id("Currency")).Clear();
|
||||
s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC");
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
|
||||
var bolt = (await s.Server.MerchantLnd.Client.CreateInvoice(
|
||||
LightMoney.FromUnit(0.00001m, LightMoneyUnit.BTC),
|
||||
$"LN payout test {DateTime.Now.Ticks}",
|
||||
TimeSpan.FromHours(1), CancellationToken.None)).BOLT11;
|
||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(bolt);
|
||||
s.Driver.FindElement(By.Id("SelectedPaymentMethod")).Click();
|
||||
s.Driver.FindElement(By.CssSelector(
|
||||
$"#SelectedPaymentMethod option[value={new PaymentMethodId("BTC", PaymentTypes.LightningLike)}]"))
|
||||
.Click();
|
||||
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys(Keys.Enter);
|
||||
//we do not allow short-life bolts.
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Error);
|
||||
|
||||
bolt = (await s.Server.MerchantLnd.Client.CreateInvoice(
|
||||
LightMoney.FromUnit(0.00001m, LightMoneyUnit.BTC),
|
||||
$"LN payout test {DateTime.Now.Ticks}",
|
||||
TimeSpan.FromDays(31), CancellationToken.None)).BOLT11;
|
||||
s.Driver.FindElement(By.Id("Destination")).Clear();
|
||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(bolt);
|
||||
s.Driver.FindElement(By.Id("SelectedPaymentMethod")).Click();
|
||||
s.Driver.FindElement(By.CssSelector(
|
||||
$"#SelectedPaymentMethod option[value={new PaymentMethodId("BTC", PaymentTypes.LightningLike)}]"))
|
||||
.Click();
|
||||
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys(Keys.Enter);
|
||||
s.FindAlertMessage();
|
||||
|
||||
Assert.Contains(PayoutState.AwaitingApproval.GetStateString(), s.Driver.PageSource);
|
||||
|
||||
s.GoToStore(newStore.storeId, StoreNavPages.Payouts);
|
||||
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike)}-view")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-view")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click();
|
||||
Assert.Contains(bolt, s.Driver.PageSource);
|
||||
Assert.Contains("0.00001 BTC", s.Driver.PageSource);
|
||||
|
||||
s.Driver.FindElement(By.CssSelector("#pay-invoices-form")).Submit();
|
||||
//lightning config in tests is very unstable so we can just go ahead and handle it as both
|
||||
s.FindAlertMessage(new[]
|
||||
{
|
||||
StatusMessageModel.StatusSeverity.Error, StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
s.GoToStore(newStore.storeId, StoreNavPages.Payouts);
|
||||
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike)}-view")).Click();
|
||||
|
||||
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.Completed}-view")).Click();
|
||||
if (!s.Driver.PageSource.Contains(bolt))
|
||||
{
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-view")).Click();
|
||||
Assert.Contains(bolt, s.Driver.PageSource);
|
||||
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-selectAllCheckbox")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-actions")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-mark-paid")).Click();
|
||||
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike)}-view")).Click();
|
||||
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.Completed}-view")).Click();
|
||||
Assert.Contains(bolt, s.Driver.PageSource);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanUsePOSPrint()
|
||||
{
|
||||
using var s = SeleniumTester.Create();
|
||||
s.Server.ActivateLightning();
|
||||
await s.StartAsync();
|
||||
|
||||
await s.Server.EnsureChannelsSetup();
|
||||
|
||||
s.RegisterNewUser(true);
|
||||
var store = s.CreateNewStore();
|
||||
var network = s.Server.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork;
|
||||
s.GoToStore(store.storeId);
|
||||
s.AddLightningNode("BTC", LightningConnectionType.CLightning, false);
|
||||
s.Driver.FindElement(By.Id($"Modify-LightningBTC")).Click();
|
||||
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
|
||||
s.GoToApps();
|
||||
s.Driver.FindElement(By.Id("CreateNewApp")).Click();
|
||||
s.Driver.FindElement(By.Id("SelectedAppType")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("option[value='PointOfSale']")).Click();
|
||||
s.Driver.FindElement(By.Id("AppName")).SendKeys(Guid.NewGuid().ToString());
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
s.Driver.FindElement(By.Id("DefaultView")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("option[value='3']")).Click();
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).Click();
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
|
||||
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
||||
var btns = s.Driver.FindElements(By.ClassName("lnurl"));
|
||||
foreach (IWebElement webElement in btns)
|
||||
{
|
||||
var choice = webElement.GetAttribute("data-choice");
|
||||
var lnurl = webElement.GetAttribute("href");
|
||||
var parsed = LNURL.LNURL.Parse(lnurl, out _);
|
||||
Assert.EndsWith(choice, parsed.ToString());
|
||||
Assert.IsType<LNURLPayRequest>(await LNURL.LNURL.FetchInformation(parsed, new HttpClient()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanUseLNURL()
|
||||
{
|
||||
using var s = SeleniumTester.Create();
|
||||
s.Server.ActivateLightning();
|
||||
await s.StartAsync();
|
||||
await s.Server.EnsureChannelsSetup();
|
||||
var cryptoCode = "BTC";
|
||||
await Lightning.Tests.ConnectChannels.ConnectAll(s.Server.ExplorerNode,
|
||||
new[] { s.Server.MerchantLightningD },
|
||||
new[] { s.Server.MerchantLnd.Client });
|
||||
s.RegisterNewUser(true);
|
||||
var store = s.CreateNewStore();
|
||||
var network = s.Server.NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode).NBitcoinNetwork;
|
||||
s.GoToStore(store.storeId);
|
||||
s.AddLightningNode(cryptoCode, LightningConnectionType.CLightning);
|
||||
s.Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
|
||||
// LNURL is false by default
|
||||
Assert.False(s.Driver.FindElement(By.Id("LNURLEnabled")).Selected);
|
||||
// LNURL settings are not expanded when LNURL is disabled
|
||||
Assert.DoesNotContain("show", s.Driver.FindElement(By.Id("LNURLSettings")).GetAttribute("class"));
|
||||
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
|
||||
s.Driver.WaitForAndClick(By.Id("save"));
|
||||
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
// Topup Invoice test
|
||||
var i = s.CreateInvoice(store.storeName, null, cryptoCode);
|
||||
s.GoToInvoiceCheckout(i);
|
||||
s.Driver.FindElement(By.Id("copy-tab")).Click();
|
||||
var lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
|
||||
var parsed = LNURL.LNURL.Parse(lnurl, out var tag);
|
||||
var fetchedReuqest =
|
||||
Assert.IsType<LNURL.LNURLPayRequest>(await LNURL.LNURL.FetchInformation(parsed, new HttpClient()));
|
||||
Assert.Equal(1m, fetchedReuqest.MinSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
||||
Assert.NotEqual(1m, fetchedReuqest.MaxSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
||||
var lnurlResponse = await fetchedReuqest.SendRequest(new LightMoney(0.000001m, LightMoneyUnit.BTC),
|
||||
network, new HttpClient());
|
||||
|
||||
Assert.Equal(new LightMoney(0.000001m, LightMoneyUnit.BTC),
|
||||
lnurlResponse.GetPaymentRequest(network).MinimumAmount);
|
||||
|
||||
var lnurlResponse2 = await fetchedReuqest.SendRequest(new LightMoney(0.000002m, LightMoneyUnit.BTC),
|
||||
network, new HttpClient());
|
||||
Assert.Equal(new LightMoney(0.000002m, LightMoneyUnit.BTC), lnurlResponse2.GetPaymentRequest(network).MinimumAmount);
|
||||
await Assert.ThrowsAnyAsync<LightningRPCException>(async () =>
|
||||
{
|
||||
// Initial bolt was cancelled
|
||||
await s.Server.CustomerLightningD.Pay(lnurlResponse.Pr);
|
||||
});
|
||||
|
||||
await s.Server.CustomerLightningD.Pay(lnurlResponse2.Pr);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var inv = await s.Server.PayTester.InvoiceRepository.GetInvoice(i);
|
||||
Assert.Equal(InvoiceStatusLegacy.Complete, inv.Status);
|
||||
});
|
||||
|
||||
// Standard invoice test
|
||||
s.GoToHome();
|
||||
i = s.CreateInvoice(store.storeName, 0.0000001m, cryptoCode);
|
||||
s.GoToInvoiceCheckout(i);
|
||||
s.Driver.FindElement(By.ClassName("payment__currencies")).Click();
|
||||
// BOLT11 is also available for standard invoices
|
||||
Assert.Equal(2, s.Driver.FindElements(By.CssSelector(".vex.vex-theme-btcpay .vex-content .vexmenu li.vexmenuitem")).Count);
|
||||
s.Driver.FindElement(By.CssSelector(".vex.vex-theme-btcpay .vex-content .vexmenu li.vexmenuitem")).Click();
|
||||
s.Driver.FindElement(By.Id("copy-tab")).Click();
|
||||
lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
|
||||
parsed = LNURL.LNURL.Parse(lnurl, out tag);
|
||||
fetchedReuqest = Assert.IsType<LNURLPayRequest>(await LNURL.LNURL.FetchInformation(parsed, new HttpClient()));
|
||||
Assert.Equal(0.0000001m, fetchedReuqest.MaxSendable.ToDecimal(LightMoneyUnit.BTC));
|
||||
Assert.Equal(0.0000001m, fetchedReuqest.MinSendable.ToDecimal(LightMoneyUnit.BTC));
|
||||
|
||||
await Assert.ThrowsAsync<HttpRequestException>(async () =>
|
||||
{
|
||||
await fetchedReuqest.SendRequest(new LightMoney(0.0000002m, LightMoneyUnit.BTC),
|
||||
network, new HttpClient());
|
||||
});
|
||||
await Assert.ThrowsAsync<HttpRequestException>(async () =>
|
||||
{
|
||||
await fetchedReuqest.SendRequest(new LightMoney(0.00000005m, LightMoneyUnit.BTC),
|
||||
network, new HttpClient());
|
||||
});
|
||||
|
||||
lnurlResponse = await fetchedReuqest.SendRequest(new LightMoney(0.0000001m, LightMoneyUnit.BTC),
|
||||
network, new HttpClient());
|
||||
lnurlResponse2 = await fetchedReuqest.SendRequest(new LightMoney(0.0000001m, LightMoneyUnit.BTC),
|
||||
network, new HttpClient());
|
||||
//invoice amounts do no change so the paymnet request is not regenerated
|
||||
Assert.Equal(lnurlResponse.Pr, lnurlResponse2.Pr);
|
||||
await s.Server.CustomerLightningD.Pay(lnurlResponse.Pr);
|
||||
Assert.Equal(new LightMoney(0.0000001m, LightMoneyUnit.BTC),
|
||||
lnurlResponse2.GetPaymentRequest(network).MinimumAmount);
|
||||
|
||||
|
||||
|
||||
s.GoToStore(s.StoreId);
|
||||
s.Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
|
||||
// LNURL is enabled and settings are expanded
|
||||
Assert.True(s.Driver.FindElement(By.Id("LNURLEnabled")).Selected);
|
||||
Assert.Contains("show", s.Driver.FindElement(By.Id("LNURLSettings")).GetAttribute("class"));
|
||||
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), false);
|
||||
s.Driver.FindElement(By.Id("save")).Click();
|
||||
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
i = s.CreateInvoice(store.storeName, 0.000001m, cryptoCode);
|
||||
s.GoToInvoiceCheckout(i);
|
||||
s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
|
||||
|
||||
s.GoToHome();
|
||||
i = s.CreateInvoice(store.storeName, null, cryptoCode);
|
||||
s.GoToInvoiceCheckout(i);
|
||||
s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
|
||||
|
||||
s.GoToStore(s.StoreId);
|
||||
s.Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
|
||||
s.Driver.SetCheckbox(By.Id("LNURLBech32Mode"), false);
|
||||
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), false);
|
||||
s.Driver.SetCheckbox(By.Id("DisableBolt11PaymentMethod"), true);
|
||||
s.Driver.FindElement(By.Id("save")).Click();
|
||||
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
// Ensure the toggles are set correctly
|
||||
s.Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
|
||||
|
||||
//TODO: DisableBolt11PaymentMethod is actually disabled because LNURLStandardInvoiceEnabled is disabled
|
||||
// checkboxes is not good choice here, in next release we should have multi choice instead
|
||||
Assert.False(s.Driver.FindElement(By.Id("DisableBolt11PaymentMethod")).Selected);
|
||||
Assert.False(s.Driver.FindElement(By.Id("LNURLStandardInvoiceEnabled")).Selected);
|
||||
Assert.False(s.Driver.FindElement(By.Id("LNURLBech32Mode")).Selected);
|
||||
s.CreateInvoice(store.storeName, 0.0000001m, cryptoCode,"",null, expectedSeverity: StatusMessageModel.StatusSeverity.Error);
|
||||
|
||||
i = s.CreateInvoice(store.storeName, null, cryptoCode);
|
||||
s.GoToInvoiceCheckout(i);
|
||||
s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
|
||||
s.Driver.FindElement(By.Id("copy-tab")).Click();
|
||||
lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
|
||||
Assert.StartsWith("lnurlp", lnurl);
|
||||
LNURL.LNURL.Parse(lnurl, out tag);
|
||||
|
||||
s.GoToHome();
|
||||
var newStore = s.CreateNewStore(false);
|
||||
s.AddLightningNode(cryptoCode, LightningConnectionType.LndREST, false);
|
||||
s.Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
|
||||
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
|
||||
s.Driver.SetCheckbox(By.Id("DisableBolt11PaymentMethod"), true);
|
||||
s.Driver.FindElement(By.Id("save")).Click();
|
||||
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||
var invForPP = s.CreateInvoice(newStore.storeName, 0.0000001m, cryptoCode);
|
||||
s.GoToInvoiceCheckout(invForPP);
|
||||
s.Driver.FindElement(By.Id("copy-tab")).Click();
|
||||
lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
|
||||
parsed = LNURL.LNURL.Parse(lnurl, out tag);
|
||||
|
||||
// Check that pull payment has lightning option
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||
Assert.Equal(new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike),PaymentMethodId.Parse(Assert.Single(s.Driver.FindElement(By.Id("PaymentMethods")).FindElements(By.TagName("option"))).GetAttribute("value")));
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
|
||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("0.0000001");
|
||||
;
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(lnurl);
|
||||
|
||||
var pullPaymentId = s.Driver.Url.Split('/').Last();
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("0.0000001" + Keys.Enter);
|
||||
s.FindAlertMessage();
|
||||
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
var payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
||||
payouts[0].Click();
|
||||
s.Driver.FindElement(By.Id("BTC_LightningLike-view")).Click();
|
||||
Assert.NotEmpty(s.Driver.FindElements(By.ClassName("payout")));
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click();
|
||||
|
||||
Assert.Contains(lnurl, s.Driver.PageSource);
|
||||
|
||||
s.Driver.FindElement(By.Id("pay-invoices-form")).Submit();
|
||||
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var inv = await s.Server.PayTester.InvoiceRepository.GetInvoice(invForPP);
|
||||
Assert.Equal(InvoiceStatusLegacy.Complete, inv.Status);
|
||||
|
||||
await using var ctx = s.Server.PayTester.GetService<ApplicationDbContextFactory>().CreateContext();
|
||||
var payoutsData = await ctx.Payouts.Where(p => p.PullPaymentDataId == pullPaymentId).ToListAsync();
|
||||
Assert.True(payoutsData.All(p => p.State == PayoutState.Completed));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanUseLNAddress()
|
||||
{
|
||||
using var s = SeleniumTester.Create();
|
||||
s.Server.ActivateLightning();
|
||||
await s.StartAsync();
|
||||
|
||||
await s.Server.EnsureChannelsSetup();
|
||||
s.RegisterNewUser(true);
|
||||
//ln address tests
|
||||
var store = s.CreateNewStore();
|
||||
s.GoToStore(s.StoreId, StoreNavPages.Integrations);
|
||||
//ensure ln address is not available as lnurl is not configured
|
||||
s.Driver.FindElement(By.Id("lightning-address-option"))
|
||||
.FindElement(By.ClassName("btcpay-status--disabled"));
|
||||
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PaymentMethods);
|
||||
s.AddLightningNode("BTC", LightningConnectionType.LndREST, false);
|
||||
|
||||
s.Driver.FindElement(By.Id($"Modify-LightningBTC")).Click();
|
||||
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
|
||||
s.Driver.WaitForAndClick(By.Id("save"));
|
||||
Assert.Contains($"BTC Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
s.GoToStore(s.StoreId, StoreNavPages.Integrations);
|
||||
s.Driver.FindElement(By.Id("lightning-address-option"))
|
||||
.FindElement(By.Id("lightning-address-setup-link")).Click();
|
||||
|
||||
s.Driver.ToggleCollapse("AddAddress");
|
||||
var lnaddress1 = Guid.NewGuid().ToString();
|
||||
s.Driver.FindElement(By.Id("Add_Username")).SendKeys(lnaddress1);
|
||||
s.Driver.FindElement(By.CssSelector("button[value='add']")).Click();
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
|
||||
s.Driver.ToggleCollapse("AddAddress");
|
||||
s.Driver.FindElement(By.Id("Add_Username")).SendKeys(lnaddress1);
|
||||
s.Driver.FindElement(By.CssSelector("button[value='add']")).Click();
|
||||
s.Driver.FindElement(By.ClassName("text-danger"));
|
||||
|
||||
s.Driver.FindElement(By.Id("Add_Username")).Clear();
|
||||
var lnaddress2 = "EUR" + Guid.NewGuid().ToString();
|
||||
s.Driver.FindElement(By.Id("Add_Username")).SendKeys(lnaddress2);
|
||||
|
||||
s.Driver.ToggleCollapse("AdvancedSettings");
|
||||
s.Driver.FindElement(By.Id("Add_CurrencyCode")).SendKeys("EUR");
|
||||
s.Driver.FindElement(By.Id("Add_Min")).SendKeys("2");
|
||||
s.Driver.FindElement(By.Id("Add_Max")).SendKeys("10");
|
||||
s.Driver.FindElement(By.CssSelector("button[value='add']")).Click();
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
|
||||
var addresses = s.Driver.FindElements(By.ClassName("lightning-address-value"));
|
||||
Assert.Equal(2, addresses.Count);
|
||||
|
||||
foreach (IWebElement webElement in addresses)
|
||||
{
|
||||
var value = webElement.GetAttribute("value");
|
||||
//cannot test this directly as https is not supported on our e2e tests
|
||||
// var request = await LNURL.LNURL.FetchPayRequestViaInternetIdentifier(value, new HttpClient());
|
||||
|
||||
var lnurl = new Uri(LNURL.LNURL.ExtractUriFromInternetIdentifier(value).ToString()
|
||||
.Replace("https", "http"));
|
||||
var request =(LNURL.LNURLPayRequest) await LNURL.LNURL.FetchInformation(lnurl, new HttpClient());
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case { } v when v.StartsWith(lnaddress2):
|
||||
Assert.Equal(2, request.MinSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
||||
Assert.Equal(10, request.MaxSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
||||
break;
|
||||
|
||||
case { } v when v.StartsWith(lnaddress1):
|
||||
Assert.Equal(1, request.MinSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
||||
Assert.Equal(6.12m, request.MaxSendable.ToDecimal(LightMoneyUnit.BTC));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void CanBrowseContent(SeleniumTester s)
|
||||
{
|
||||
s.Driver.FindElement(By.ClassName("delivery-content")).Click();
|
||||
|
@ -109,7 +109,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await RegisterAsync(isAdmin);
|
||||
await CreateStoreAsync();
|
||||
var store = this.GetController<StoresController>();
|
||||
var store = GetController<StoresController>();
|
||||
var pairingCode = BitPay.RequestClientAuthorization("test", Facade.Merchant);
|
||||
Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString()));
|
||||
await store.Pair(pairingCode.ToString(), StoreId);
|
||||
@ -127,19 +127,28 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public async Task SetNetworkFeeMode(NetworkFeeMode mode)
|
||||
{
|
||||
await ModifyStore(store =>
|
||||
await ModifyPayment(payment =>
|
||||
{
|
||||
store.NetworkFeeMode = mode;
|
||||
payment.NetworkFeeMode = mode;
|
||||
});
|
||||
}
|
||||
|
||||
public async Task ModifyStore(Action<StoreViewModel> modify)
|
||||
public async Task ModifyPayment(Action<PaymentMethodsViewModel> modify)
|
||||
{
|
||||
var storeController = GetController<StoresController>();
|
||||
var response = await storeController.UpdateStore();
|
||||
StoreViewModel store = (StoreViewModel)((ViewResult)response).Model;
|
||||
modify(store);
|
||||
storeController.UpdateStore(store).GetAwaiter().GetResult();
|
||||
var response = storeController.PaymentMethods();
|
||||
PaymentMethodsViewModel paymentMethods = (PaymentMethodsViewModel)((ViewResult)response).Model;
|
||||
modify(paymentMethods);
|
||||
await storeController.PaymentMethods(paymentMethods);
|
||||
}
|
||||
|
||||
public async Task ModifyWalletSettings(Action<WalletSettingsViewModel> modify)
|
||||
{
|
||||
var storeController = GetController<StoresController>();
|
||||
var response = await storeController.WalletSettings(StoreId, "BTC");
|
||||
WalletSettingsViewModel walletSettings = (WalletSettingsViewModel)((ViewResult)response).Model;
|
||||
modify(walletSettings);
|
||||
storeController.UpdateWalletSettings(walletSettings).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public T GetController<T>(bool setImplicitStore = true) where T : Controller
|
||||
@ -154,8 +163,8 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await RegisterAsync();
|
||||
}
|
||||
var store = this.GetController<UserStoresController>();
|
||||
await store.CreateStore(new CreateStoreViewModel() { Name = "Test Store" });
|
||||
var store = GetController<UserStoresController>();
|
||||
await store.CreateStore(new CreateStoreViewModel { Name = "Test Store" });
|
||||
StoreId = store.CreatedStoreId;
|
||||
parent.Stores.Add(StoreId);
|
||||
}
|
||||
@ -188,19 +197,11 @@ namespace BTCPayServer.Tests
|
||||
return new WalletId(StoreId, cryptoCode);
|
||||
}
|
||||
|
||||
public Task EnablePayJoin()
|
||||
{
|
||||
return ModifyStore(s => s.PayJoinEnabled = true);
|
||||
}
|
||||
|
||||
public GenerateWalletResponse GenerateWalletResponseV { get; set; }
|
||||
|
||||
public DerivationStrategyBase DerivationScheme
|
||||
{
|
||||
get
|
||||
{
|
||||
return GenerateWalletResponseV.DerivationScheme;
|
||||
}
|
||||
get => GenerateWalletResponseV.DerivationScheme;
|
||||
}
|
||||
|
||||
private async Task RegisterAsync(bool isAdmin = false)
|
||||
@ -255,8 +256,9 @@ namespace BTCPayServer.Tests
|
||||
var connectionString = parent.GetLightningConnectionString(connectionType, isMerchant);
|
||||
var nodeType = connectionString == LightningSupportedPaymentMethod.InternalNode ? LightningNodeType.Internal : LightningNodeType.Custom;
|
||||
|
||||
var vm = new LightningNodeViewModel { ConnectionString = connectionString, LightningNodeType = nodeType, SkipPortTest = true };
|
||||
await storeController.SetupLightningNode(storeId ?? StoreId,
|
||||
new LightningNodeViewModel { ConnectionString = connectionString, LightningNodeType = nodeType, SkipPortTest = true }, "save", cryptoCode);
|
||||
vm, "save", cryptoCode);
|
||||
if (storeController.ModelState.ErrorCount != 0)
|
||||
Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
|
||||
}
|
||||
|
@ -24,10 +24,12 @@ using BTCPayServer.Fido2.Models;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.AccountViewModels;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Models.ManageViewModels;
|
||||
using BTCPayServer.Models.ServerViewModels;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
@ -49,8 +51,6 @@ using ExchangeSharp;
|
||||
using Fido2NetLib;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Configuration.Memory;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
@ -362,15 +362,15 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
"https://www.btse.com", // not allowing to be hit from circleci
|
||||
"https://www.bitpay.com", // not allowing to be hit from circleci
|
||||
"https://support.bitpay.com",
|
||||
"https://www.pnxbet.com" //has geo blocking
|
||||
};
|
||||
|
||||
foreach (var match in regex.Matches(text).OfType<Match>())
|
||||
{
|
||||
var url = match.Groups[1].Value;
|
||||
if (urlBlacklist.Any(a => a.StartsWith(url.ToLowerInvariant())))
|
||||
if (urlBlacklist.Any(a => url.StartsWith(a.ToLowerInvariant())))
|
||||
continue;
|
||||
|
||||
checkLinks.Add(AssertLinkNotDead(httpClient, url, file));
|
||||
}
|
||||
|
||||
@ -817,11 +817,11 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// Set tolerance to 50%
|
||||
var stores = user.GetController<StoresController>();
|
||||
var response = await stores.UpdateStore();
|
||||
var vm = Assert.IsType<StoreViewModel>(Assert.IsType<ViewResult>(response).Model);
|
||||
var response = stores.PaymentMethods();
|
||||
var vm = Assert.IsType<PaymentMethodsViewModel>(Assert.IsType<ViewResult>(response).Model);
|
||||
Assert.Equal(0.0, vm.PaymentTolerance);
|
||||
vm.PaymentTolerance = 50.0;
|
||||
Assert.IsType<RedirectToActionResult>(stores.UpdateStore(vm).Result);
|
||||
Assert.IsType<RedirectToActionResult>(stores.PaymentMethods(vm).Result);
|
||||
|
||||
var invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
@ -995,8 +995,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(4, tor.Services.Length);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
@ -1011,7 +1010,7 @@ namespace BTCPayServer.Tests
|
||||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
await user.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning);
|
||||
await user.SetNetworkFeeMode(NetworkFeeMode.Never);
|
||||
await user.ModifyStore(model => model.SpeedPolicy = SpeedPolicy.HighSpeed);
|
||||
await user.ModifyWalletSettings(p => p.SpeedPolicy = SpeedPolicy.HighSpeed);
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.0001m, "BTC"));
|
||||
await tester.WaitForEvent<InvoiceNewPaymentDetailsEvent>(async () =>
|
||||
{
|
||||
@ -1041,14 +1040,22 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains(fetchedInvoice.Status, new[] { InvoiceStatusLegacy.Complete, InvoiceStatusLegacy.Confirmed });
|
||||
Assert.Equal(InvoiceExceptionStatus.None, fetchedInvoice.ExceptionStatus);
|
||||
|
||||
Logs.Tester.LogInformation($"Paying invoice {invoice.Id} original full amount bolt11 invoice ");
|
||||
evt = await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
|
||||
//BTCPay will attempt to cancel previous bolt11 invoices so that there are less weird edge case scenarios
|
||||
Logs.Tester.LogInformation($"Attempting to pay invoice {invoice.Id} original full amount bolt11 invoice ");
|
||||
await Assert.ThrowsAsync<LightningRPCException>(async () =>
|
||||
{
|
||||
await tester.SendLightningPaymentAsync(invoice);
|
||||
}, evt => evt.InvoiceId == invoice.Id);
|
||||
Assert.Equal(evt.InvoiceId, invoice.Id);
|
||||
fetchedInvoice = await tester.PayTester.InvoiceRepository.GetInvoice(evt.InvoiceId);
|
||||
Assert.Equal(3, fetchedInvoice.Payments.Count);
|
||||
});
|
||||
|
||||
//NOTE: Eclair does not support cancelling invoice so the below test case would make sense for it
|
||||
// Logs.Tester.LogInformation($"Paying invoice {invoice.Id} original full amount bolt11 invoice ");
|
||||
// evt = await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
|
||||
// {
|
||||
// await tester.SendLightningPaymentAsync(invoice);
|
||||
// }, evt => evt.InvoiceId == invoice.Id);
|
||||
// Assert.Equal(evt.InvoiceId, invoice.Id);
|
||||
// fetchedInvoice = await tester.PayTester.InvoiceRepository.GetInvoice(evt.InvoiceId);
|
||||
// Assert.Equal(3, fetchedInvoice.Payments.Count);
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
@ -1064,7 +1071,7 @@ namespace BTCPayServer.Tests
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess(true);
|
||||
var storeController = user.GetController<StoresController>();
|
||||
var storeResponse = await storeController.UpdateStore();
|
||||
var storeResponse = storeController.PaymentMethods();
|
||||
Assert.IsType<ViewResult>(storeResponse);
|
||||
Assert.IsType<ViewResult>(await storeController.SetupLightningNode(user.StoreId, "BTC"));
|
||||
|
||||
@ -1088,9 +1095,9 @@ namespace BTCPayServer.Tests
|
||||
new LightningNodeViewModel { ConnectionString = tester.MerchantCharge.Client.Uri.AbsoluteUri },
|
||||
"save", "BTC").GetAwaiter().GetResult());
|
||||
|
||||
storeResponse = await storeController.UpdateStore();
|
||||
storeResponse = storeController.PaymentMethods();
|
||||
var storeVm =
|
||||
Assert.IsType<StoreViewModel>(Assert
|
||||
Assert.IsType<PaymentMethodsViewModel>(Assert
|
||||
.IsType<ViewResult>(storeResponse).Model);
|
||||
Assert.Single(storeVm.LightningNodes.Where(l => !string.IsNullOrEmpty(l.Address)));
|
||||
}
|
||||
@ -1204,7 +1211,7 @@ namespace BTCPayServer.Tests
|
||||
var acc = tester.NewAccount();
|
||||
acc.GrantAccess();
|
||||
acc.RegisterDerivationScheme("BTC");
|
||||
await acc.ModifyStore(s => s.SpeedPolicy = SpeedPolicy.LowSpeed);
|
||||
await acc.ModifyWalletSettings(p => p.SpeedPolicy = SpeedPolicy.LowSpeed);
|
||||
var invoice = acc.BitPay.CreateInvoice(new Invoice
|
||||
{
|
||||
Price = 5.0m,
|
||||
@ -1619,7 +1626,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
await user.GrantAccessAsync();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
await user.SetNetworkFeeMode(NetworkFeeMode.Always);
|
||||
var invoice =
|
||||
@ -1723,10 +1730,9 @@ namespace BTCPayServer.Tests
|
||||
Assert.False(i.GetPayments(false).First().Accounted);
|
||||
});
|
||||
|
||||
Logs.Tester.LogInformation(
|
||||
$"Let's test if we can RBF a normal payment without adding fees to the invoice");
|
||||
Logs.Tester.LogInformation("Let's test if we can RBF a normal payment without adding fees to the invoice");
|
||||
await user.SetNetworkFeeMode(NetworkFeeMode.MultiplePaymentsOnly);
|
||||
invoice = user.BitPay.CreateInvoice(new Invoice() { Price = 5000.0m, Currency = "USD" }, Facade.Merchant);
|
||||
invoice = user.BitPay.CreateInvoice(new Invoice { Price = 5000.0m, Currency = "USD" }, Facade.Merchant);
|
||||
payment1 = invoice.BtcDue;
|
||||
tx1 = new uint256(tester.ExplorerNode.SendCommand("sendtoaddress", new object[]
|
||||
{
|
||||
@ -2031,7 +2037,7 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
Assert.Equal(404, (int)response.StatusCode);
|
||||
|
||||
await user.ModifyStore(s => s.AnyoneCanCreateInvoice = true);
|
||||
await user.ModifyPayment(p => p.AnyoneCanCreateInvoice = true);
|
||||
|
||||
Logs.Tester.LogInformation("Bad store with anyone can create invoice = 403");
|
||||
response = await tester.PayTester.HttpClient.SendAsync(
|
||||
@ -2117,7 +2123,7 @@ namespace BTCPayServer.Tests
|
||||
rng = new Random(seed);
|
||||
Logs.Tester.LogInformation("Seed: " + seed);
|
||||
foreach (var networkFeeMode in Enum.GetValues(typeof(NetworkFeeMode)).Cast<NetworkFeeMode>())
|
||||
{
|
||||
{
|
||||
await user.SetNetworkFeeMode(networkFeeMode);
|
||||
await AssertTopUpBtcPrice(tester, user, Money.Coins(1.0m), 5000.0m, networkFeeMode);
|
||||
await AssertTopUpBtcPrice(tester, user, Money.Coins(1.23456789m), 5000.0m * 1.23456789m, networkFeeMode);
|
||||
@ -2265,6 +2271,15 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void SetOrderIdMetadataDoesntConvertInOctal()
|
||||
{
|
||||
var m = new InvoiceMetadata();
|
||||
m.OrderId = "000000161";
|
||||
Assert.Equal("000000161", m.OrderId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void CanParseCurrencyValue()
|
||||
@ -2288,28 +2303,77 @@ namespace BTCPayServer.Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanSetPaymentMethodLimits()
|
||||
public async Task CanUseDefaultCurrency()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
await user.GrantAccessAsync(true);
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
var vm = Assert.IsType<CheckoutExperienceViewModel>(Assert
|
||||
.IsType<ViewResult>(user.GetController<StoresController>().CheckoutExperience()).Model);
|
||||
Assert.Single(vm.PaymentMethodCriteria);
|
||||
var criteria = vm.PaymentMethodCriteria.First();
|
||||
await user.ModifyPayment(s =>
|
||||
{
|
||||
Assert.Equal("USD", s.DefaultCurrency);
|
||||
s.DefaultCurrency = "EUR";
|
||||
});
|
||||
var client = await user.CreateClient();
|
||||
|
||||
// with greenfield
|
||||
var invoice = await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest());
|
||||
Assert.Equal("EUR", invoice.Currency);
|
||||
Assert.Equal(InvoiceType.TopUp, invoice.Type);
|
||||
|
||||
// with bitpay api
|
||||
var invoice2 = await user.BitPay.CreateInvoiceAsync(new Invoice());
|
||||
Assert.Equal("EUR", invoice2.Currency);
|
||||
|
||||
// via UI
|
||||
var controller = user.GetController<InvoiceController>();
|
||||
var model = await controller.CreateInvoice();
|
||||
(await controller.CreateInvoice(new CreateInvoiceModel(), default)).AssertType<RedirectToActionResult>();
|
||||
invoice = await client.GetInvoice(user.StoreId, controller.CreatedInvoiceId);
|
||||
Assert.Equal("EUR", invoice.Currency);
|
||||
Assert.Equal(InvoiceType.TopUp, invoice.Type);
|
||||
|
||||
// Check that the SendWallet use the default currency
|
||||
var walletController = user.GetController<WalletsController>();
|
||||
var walletSend = await walletController.WalletSend(new WalletId(user.StoreId, "BTC")).AssertViewModelAsync<WalletSendModel>();
|
||||
Assert.Equal("EUR", walletSend.Fiat);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanSetPaymentMethodLimits()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.ActivateLightning();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess(true);
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
await user.RegisterLightningNodeAsync("BTC");
|
||||
|
||||
|
||||
var lnMethod = new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToString();
|
||||
var btcMethod = new PaymentMethodId("BTC", PaymentTypes.BTCLike).ToString();
|
||||
|
||||
// We allow BTC and LN, but not BTC under 5 USD, so only LN should be in the invoice
|
||||
var vm = Assert.IsType<CheckoutAppearanceViewModel>(Assert
|
||||
.IsType<ViewResult>(user.GetController<StoresController>().CheckoutAppearance()).Model);
|
||||
Assert.Equal(2, vm.PaymentMethodCriteria.Count);
|
||||
var criteria = Assert.Single(vm.PaymentMethodCriteria.Where(m => m.PaymentMethod == btcMethod.ToString()));
|
||||
Assert.Equal(new PaymentMethodId("BTC", BitcoinPaymentType.Instance).ToString(), criteria.PaymentMethod);
|
||||
criteria.Value = "5 USD";
|
||||
criteria.Type = PaymentMethodCriteriaViewModel.CriteriaType.GreaterThan;
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<StoresController>().CheckoutExperience(vm)
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<StoresController>().CheckoutAppearance(vm)
|
||||
.Result);
|
||||
|
||||
var invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
new Invoice
|
||||
{
|
||||
Price = 5.5m,
|
||||
Price = 4.5m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
@ -2318,7 +2382,41 @@ namespace BTCPayServer.Tests
|
||||
}, Facade.Merchant);
|
||||
|
||||
Assert.Single(invoice.CryptoInfo);
|
||||
Assert.Equal(PaymentTypes.BTCLike.ToString(), invoice.CryptoInfo[0].PaymentType);
|
||||
Assert.Equal(PaymentTypes.LightningLike.ToString(), invoice.CryptoInfo[0].PaymentType);
|
||||
|
||||
// Let's replicate https://github.com/btcpayserver/btcpayserver/issues/2963
|
||||
// We allow BTC for more than 5 USD, and LN for less than 150. The default is LN, so the default
|
||||
// payment method should be LN.
|
||||
vm = Assert.IsType<CheckoutAppearanceViewModel>(Assert
|
||||
.IsType<ViewResult>(user.GetController<StoresController>().CheckoutAppearance()).Model);
|
||||
vm.DefaultPaymentMethod = lnMethod;
|
||||
criteria = vm.PaymentMethodCriteria.First();
|
||||
criteria.Value = "150 USD";
|
||||
criteria.Type = PaymentMethodCriteriaViewModel.CriteriaType.LessThan;
|
||||
criteria = vm.PaymentMethodCriteria.Skip(1).First();
|
||||
criteria.Value = "5 USD";
|
||||
criteria.Type = PaymentMethodCriteriaViewModel.CriteriaType.GreaterThan;
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<StoresController>().CheckoutAppearance(vm)
|
||||
.Result);
|
||||
invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
{
|
||||
Price = 50m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
var checkout = (await user.GetController<InvoiceController>().Checkout(invoice.Id)).AssertViewModel<PaymentModel>();
|
||||
Assert.Equal(lnMethod, checkout.PaymentMethodId);
|
||||
|
||||
// If we change store's default, it should change the checkout's default
|
||||
vm.DefaultPaymentMethod = btcMethod;
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<StoresController>().CheckoutAppearance(vm)
|
||||
.Result);
|
||||
checkout = (await user.GetController<InvoiceController>().Checkout(invoice.Id)).AssertViewModel<PaymentModel>();
|
||||
Assert.Equal(btcMethod, checkout.PaymentMethodId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2332,12 +2430,13 @@ namespace BTCPayServer.Tests
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess(true);
|
||||
user.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit);
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.CLightning);
|
||||
var cryptoCode = "BTC";
|
||||
await user.GrantAccessAsync(true);
|
||||
user.RegisterDerivationScheme(cryptoCode, ScriptPubKeyType.Segwit);
|
||||
user.RegisterLightningNode(cryptoCode, LightningConnectionType.CLightning);
|
||||
|
||||
var invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
new Invoice
|
||||
{
|
||||
Price = 5.5m,
|
||||
Currency = "USD",
|
||||
@ -2355,12 +2454,12 @@ namespace BTCPayServer.Tests
|
||||
Assert.DoesNotContain("&lightning=", paymentMethodFirst.InvoiceBitcoinUrlQR);
|
||||
|
||||
// enable unified QR code in settings
|
||||
var vm = Assert.IsType<CheckoutExperienceViewModel>(Assert
|
||||
.IsType<ViewResult>(user.GetController<StoresController>().CheckoutExperience()).Model
|
||||
var vm = Assert.IsType<LightningSettingsViewModel>(Assert
|
||||
.IsType<ViewResult>(await user.GetController<StoresController>().LightningSettings(user.StoreId, cryptoCode)).Model
|
||||
);
|
||||
vm.OnChainWithLnInvoiceFallback = true;
|
||||
Assert.IsType<RedirectToActionResult>(
|
||||
user.GetController<StoresController>().CheckoutExperience(vm).Result
|
||||
user.GetController<StoresController>().LightningSettings(vm).Result
|
||||
);
|
||||
|
||||
// validate that QR code now has both onchain and offchain payment urls
|
||||
@ -2377,7 +2476,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.True($"bitcoin:{paymentMethodSecond.BtcAddress.ToUpperInvariant()}" == split);
|
||||
|
||||
// Fallback lightning invoice should be uppercase inside the QR code.
|
||||
var lightningFallback = paymentMethodSecond.InvoiceBitcoinUrlQR.Split(new string[] { "&lightning=" }, StringSplitOptions.None)[1];
|
||||
var lightningFallback = paymentMethodSecond.InvoiceBitcoinUrlQR.Split(new [] { "&lightning=" }, StringSplitOptions.None)[1];
|
||||
Assert.True(lightningFallback.ToUpperInvariant() == lightningFallback);
|
||||
}
|
||||
}
|
||||
@ -2393,31 +2492,54 @@ namespace BTCPayServer.Tests
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
var cryptoCode = "BTC";
|
||||
user.GrantAccess(true);
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.Charge);
|
||||
var vm = Assert.IsType<CheckoutExperienceViewModel>(Assert
|
||||
.IsType<ViewResult>(user.GetController<StoresController>().CheckoutExperience()).Model);
|
||||
Assert.Single(vm.PaymentMethodCriteria);
|
||||
var criteria = vm.PaymentMethodCriteria.First();
|
||||
Assert.Equal(new PaymentMethodId("BTC", LightningPaymentType.Instance).ToString(), criteria.PaymentMethod);
|
||||
user.RegisterLightningNode(cryptoCode, LightningConnectionType.Charge);
|
||||
var vm = user.GetController<StoresController>().CheckoutAppearance().AssertViewModel<CheckoutAppearanceViewModel>();
|
||||
var criteria = Assert.Single(vm.PaymentMethodCriteria);
|
||||
Assert.Equal(new PaymentMethodId(cryptoCode, LightningPaymentType.Instance).ToString(), criteria.PaymentMethod);
|
||||
criteria.Value = "2 USD";
|
||||
criteria.Type = PaymentMethodCriteriaViewModel.CriteriaType.LessThan;
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<StoresController>().CheckoutExperience(vm)
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<StoresController>().CheckoutAppearance(vm)
|
||||
.Result);
|
||||
|
||||
var invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
new Invoice
|
||||
{
|
||||
Price = 1.5m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
Currency = "USD"
|
||||
}, Facade.Merchant);
|
||||
|
||||
Assert.Single(invoice.CryptoInfo);
|
||||
Assert.Equal(PaymentTypes.LightningLike.ToString(), invoice.CryptoInfo[0].PaymentType);
|
||||
|
||||
// Activating LNUrl, we should still have only 1 payment criteria that can be set.
|
||||
user.RegisterLightningNode(cryptoCode, LightningConnectionType.Charge);
|
||||
var lnSettingsVm = await user.GetController<StoresController>().LightningSettings(user.StoreId, cryptoCode).AssertViewModelAsync<LightningSettingsViewModel>();
|
||||
lnSettingsVm.LNURLEnabled = true;
|
||||
lnSettingsVm.LNURLStandardInvoiceEnabled = true;
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<StoresController>().LightningSettings(lnSettingsVm).Result);
|
||||
vm = user.GetController<StoresController>().CheckoutAppearance().AssertViewModel<CheckoutAppearanceViewModel>();
|
||||
criteria = Assert.Single(vm.PaymentMethodCriteria);
|
||||
Assert.Equal(new PaymentMethodId(cryptoCode, LightningPaymentType.Instance).ToString(), criteria.PaymentMethod);
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<StoresController>().CheckoutAppearance(vm).Result);
|
||||
|
||||
// However, creating an invoice should show LNURL
|
||||
invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice
|
||||
{
|
||||
Price = 1.5m,
|
||||
Currency = "USD"
|
||||
}, Facade.Merchant);
|
||||
Assert.Equal(2, invoice.CryptoInfo.Length);
|
||||
|
||||
// Make sure this throw: Since BOLT11 and LN Url share the same criteria, there should be no payment method available
|
||||
Assert.Throws<BitPayException>(() => user.BitPay.CreateInvoice(
|
||||
new Invoice
|
||||
{
|
||||
Price = 2.5m,
|
||||
Currency = "USD"
|
||||
}, Facade.Merchant));
|
||||
}
|
||||
}
|
||||
|
||||
@ -2610,11 +2732,11 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
await user.GrantAccessAsync();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
await user.SetNetworkFeeMode(NetworkFeeMode.Always);
|
||||
var invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
new Invoice
|
||||
{
|
||||
Price = 10,
|
||||
Currency = "USD",
|
||||
@ -2690,7 +2812,7 @@ namespace BTCPayServer.Tests
|
||||
Logs.Tester.LogInformation($"Trying with {nameof(networkFeeMode)}={networkFeeMode}");
|
||||
await user.SetNetworkFeeMode(networkFeeMode);
|
||||
var invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
new Invoice
|
||||
{
|
||||
Price = 10,
|
||||
Currency = "USD",
|
||||
@ -2773,11 +2895,11 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
await user.GrantAccessAsync();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
await user.SetNetworkFeeMode(NetworkFeeMode.Always);
|
||||
var invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
new Invoice
|
||||
{
|
||||
Price = 500,
|
||||
Currency = "USD",
|
||||
@ -2822,8 +2944,8 @@ namespace BTCPayServer.Tests
|
||||
var apps2 = user2.GetController<AppsController>();
|
||||
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp().Result).Model);
|
||||
Assert.NotNull(vm.SelectedAppType);
|
||||
Assert.Null(vm.Name);
|
||||
vm.Name = "test";
|
||||
Assert.Null(vm.AppName);
|
||||
vm.AppName = "test";
|
||||
vm.SelectedAppType = AppType.PointOfSale.ToString();
|
||||
var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.CreateApp(vm).Result);
|
||||
Assert.Equal(nameof(apps.UpdatePointOfSale), redirectToAction.ActionName);
|
||||
@ -2900,10 +3022,26 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Amount = 50.513m,
|
||||
Currency = "USD",
|
||||
Metadata = new JObject() { new JProperty("taxIncluded", 50.516m) }
|
||||
Metadata = new JObject() { new JProperty("taxIncluded", 50.516m), new JProperty("orderId", "000000161") }
|
||||
});
|
||||
Assert.Equal(50.51m, invoice5g.Amount);
|
||||
Assert.Equal(50.51m, (decimal)invoice5g.Metadata["taxIncluded"]);
|
||||
Assert.Equal("000000161", (string)invoice5g.Metadata["orderId"]);
|
||||
|
||||
var zeroInvoice = await greenfield.CreateInvoice(user.StoreId, new CreateInvoiceRequest()
|
||||
{
|
||||
Amount = 0m,
|
||||
Currency = "USD"
|
||||
});
|
||||
Assert.Equal(InvoiceStatus.New, zeroInvoice.Status);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
zeroInvoice = await greenfield.GetInvoice(user.StoreId, zeroInvoice.Id);
|
||||
Assert.Equal(InvoiceStatus.Settled, zeroInvoice.Status);
|
||||
});
|
||||
|
||||
var zeroInvoicePM = await greenfield.GetInvoicePaymentMethods(user.StoreId, zeroInvoice.Id);
|
||||
Assert.Empty(zeroInvoicePM);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3093,6 +3231,20 @@ namespace BTCPayServer.Tests
|
||||
c =>
|
||||
{
|
||||
Assert.False(c.AfterExpiration);
|
||||
Assert.Equal(new PaymentMethodId("BTC", PaymentTypes.BTCLike).ToStringNormalized(),c.PaymentMethod);
|
||||
Assert.NotNull(c.Payment);
|
||||
Assert.Equal(invoice.BitcoinAddress, c.Payment.Destination);
|
||||
Assert.StartsWith(txId.ToString(), c.Payment.Id);
|
||||
|
||||
});
|
||||
user.AssertHasWebhookEvent<WebhookInvoicePaymentSettledEvent>(WebhookEventType.InvoicePaymentSettled,
|
||||
c =>
|
||||
{
|
||||
Assert.False(c.AfterExpiration);
|
||||
Assert.Equal(new PaymentMethodId("BTC", PaymentTypes.BTCLike).ToStringNormalized(),c.PaymentMethod);
|
||||
Assert.NotNull(c.Payment);
|
||||
Assert.Equal(invoice.BitcoinAddress, c.Payment.Destination);
|
||||
Assert.StartsWith(txId.ToString(), c.Payment.Id);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -3144,15 +3296,38 @@ namespace BTCPayServer.Tests
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "AGM") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1 BTC will always be more than 1 AGM
|
||||
}
|
||||
else if (result.ExpectedName == "ripio")
|
||||
{
|
||||
// This test is strange because ripio sometimes change the pairs it supports
|
||||
try
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "ARS") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1 BTC will always be more than 1 ARS
|
||||
}
|
||||
catch (XunitException)
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
e => (e.CurrencyPair == new CurrencyPair("BTC", "USDC")
|
||||
&& e.BidAsk.Bid > 1.0m)); // 1BTC will always be more than 1USD
|
||||
}
|
||||
}
|
||||
else if (result.ExpectedName == "cryptomarket")
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "CLP") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1 BTC will always be more than 1 CLP
|
||||
}
|
||||
else
|
||||
{
|
||||
// This check if the currency pair is using right currency pair
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
e => (e.CurrencyPair == new CurrencyPair("BTC", "USD") ||
|
||||
e.CurrencyPair == new CurrencyPair("BTC", "EUR") ||
|
||||
e.CurrencyPair == new CurrencyPair("BTC", "USDT") ||
|
||||
e.CurrencyPair == new CurrencyPair("BTC", "CAD"))
|
||||
&& e.BidAsk.Bid > 1.0m // 1BTC will always be more than 1USD
|
||||
e.CurrencyPair == new CurrencyPair("BTC", "EUR") ||
|
||||
e.CurrencyPair == new CurrencyPair("BTC", "USDT") ||
|
||||
e.CurrencyPair == new CurrencyPair("BTC", "USDC") ||
|
||||
e.CurrencyPair == new CurrencyPair("BTC", "CAD"))
|
||||
&& e.BidAsk.Bid > 1.0m // 1BTC will always be more than 1USD
|
||||
);
|
||||
}
|
||||
// We are not showing a directly implemented exchange as directly implemented in the UI
|
||||
@ -3228,6 +3403,8 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
var rateResult = value.Value.GetAwaiter().GetResult();
|
||||
Logs.Tester.LogInformation($"Testing {value.Key.ToString()}");
|
||||
if (value.Key.ToString() == "BTX_USD") // Broken shitcoin
|
||||
continue;
|
||||
Assert.True(rateResult.BidAsk != null, $"Impossible to get the rate {rateResult.EvaluatedRule}");
|
||||
}
|
||||
}
|
||||
@ -3520,14 +3697,15 @@ namespace BTCPayServer.Tests
|
||||
Password = user.RegisterDetails.Password
|
||||
})).ActionName);
|
||||
|
||||
var listController = user.GetController<ManageController>();
|
||||
var manageController = user.GetController<Fido2Controller>();
|
||||
|
||||
//by default no fido2 devices available
|
||||
Assert.Empty(Assert
|
||||
.IsType<Fido2AuthenticationViewModel>(Assert
|
||||
.IsType<ViewResult>(await manageController.List()).Model).Credentials);
|
||||
.IsType<TwoFactorAuthenticationViewModel>(Assert
|
||||
.IsType<ViewResult>(await listController.TwoFactorAuthentication()).Model).Credentials);
|
||||
Assert.IsType<CredentialCreateOptions>(Assert
|
||||
.IsType<ViewResult>(await manageController.Create(new AddFido2CredentialViewModel()
|
||||
.IsType<ViewResult>(await manageController.Create(new AddFido2CredentialViewModel
|
||||
{
|
||||
Name = "label"
|
||||
})).Model);
|
||||
@ -3555,8 +3733,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
Assert.NotNull(newDevice.Id);
|
||||
Assert.NotEmpty(Assert
|
||||
.IsType<Fido2AuthenticationViewModel>(Assert
|
||||
.IsType<ViewResult>(await manageController.List()).Model).Credentials);
|
||||
.IsType<TwoFactorAuthenticationViewModel>(Assert
|
||||
.IsType<ViewResult>(await listController.TwoFactorAuthentication()).Model).Credentials);
|
||||
}
|
||||
|
||||
//check if we are showing the fido2 login screen now
|
||||
|
@ -34,6 +34,7 @@ services:
|
||||
- "80"
|
||||
links:
|
||||
- dev
|
||||
- selenium
|
||||
extra_hosts:
|
||||
- "tests:127.0.0.1"
|
||||
volumes:
|
||||
@ -69,7 +70,7 @@ services:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
|
||||
devlnd:
|
||||
image: btcpayserver/bitcoin:0.21.1
|
||||
image: btcpayserver/bitcoin:22.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
@ -83,8 +84,12 @@ services:
|
||||
- postgres
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
selenium:
|
||||
image: selenium/standalone-chrome:3
|
||||
expose:
|
||||
- "4444"
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.2.0
|
||||
image: nicolasdorier/nbxplorer:2.2.7
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
@ -118,7 +123,7 @@ services:
|
||||
|
||||
bitcoind:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/bitcoin:0.21.1
|
||||
image: btcpayserver/bitcoin:22.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
@ -220,14 +225,16 @@ services:
|
||||
links:
|
||||
- bitcoind
|
||||
postgres:
|
||||
image: postgres:9.6.5
|
||||
image: postgres:13.4
|
||||
environment:
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
ports:
|
||||
- "39372:5432"
|
||||
expose:
|
||||
- "5432"
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.13.1-beta-withloop
|
||||
image: btcpayserver/lnd:v0.13.3-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -261,7 +268,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.13.1-beta-withloop
|
||||
image: btcpayserver/lnd:v0.13.3-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -297,7 +304,7 @@ services:
|
||||
|
||||
tor:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/tor:0.4.1.5
|
||||
image: btcpayserver/tor:0.4.6.5
|
||||
container_name: tor
|
||||
environment:
|
||||
TOR_PASSWORD: btcpayserver
|
||||
@ -349,7 +356,7 @@ services:
|
||||
elementsd-liquid:
|
||||
restart: always
|
||||
container_name: btcpayserver_elementsd_liquid
|
||||
image: btcpayserver/elements:0.18.1.7
|
||||
image: btcpayserver/elements:0.18.1.12
|
||||
environment:
|
||||
ELEMENTS_CHAIN: elementsregtest
|
||||
ELEMENTS_EXTRA_ARGS: |
|
||||
|
@ -32,6 +32,7 @@ services:
|
||||
- "80"
|
||||
links:
|
||||
- dev
|
||||
- selenium
|
||||
extra_hosts:
|
||||
- "tests:127.0.0.1"
|
||||
volumes:
|
||||
@ -66,7 +67,7 @@ services:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
|
||||
devlnd:
|
||||
image: btcpayserver/bitcoin:0.21.1
|
||||
image: btcpayserver/bitcoin:22.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
@ -80,8 +81,12 @@ services:
|
||||
- postgres
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
selenium:
|
||||
image: selenium/standalone-chrome:3
|
||||
expose:
|
||||
- "4444"
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.2.0
|
||||
image: nicolasdorier/nbxplorer:2.2.7
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
@ -105,7 +110,7 @@ services:
|
||||
|
||||
bitcoind:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/bitcoin:0.21.1
|
||||
image: btcpayserver/bitcoin:22.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
@ -208,14 +213,16 @@ services:
|
||||
- bitcoind
|
||||
|
||||
postgres:
|
||||
image: postgres:9.6.5
|
||||
image: postgres:13.4
|
||||
environment:
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
ports:
|
||||
- "39372:5432"
|
||||
expose:
|
||||
- "5432"
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.13.1-beta-withloop
|
||||
image: btcpayserver/lnd:v0.13.3-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -249,7 +256,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.13.1-beta-withloop
|
||||
image: btcpayserver/lnd:v0.13.3-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -285,7 +292,7 @@ services:
|
||||
|
||||
tor:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/tor:0.4.1.5
|
||||
image: btcpayserver/tor:0.4.6.5
|
||||
container_name: tor
|
||||
environment:
|
||||
TOR_PASSWORD: btcpayserver
|
||||
|
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
|
||||
@ -21,6 +21,15 @@
|
||||
<None Remove="Build\**" />
|
||||
<None Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<None Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
<Content Update="Views\StorePullPayments\NewPullPayment.cshtml">
|
||||
<Pack>false</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\StorePullPayments\PullPayments.cshtml">
|
||||
<Pack>false</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\StorePullPayments\Payouts.cshtml">
|
||||
<Pack>false</Pack>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Currencies.txt" />
|
||||
@ -29,10 +38,10 @@
|
||||
<EmbeddedResource Include="bundleconfig.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Altcoins)' == 'true'">
|
||||
<PackageReference Include="Nethereum.ABI" Version="3.8.0" />
|
||||
<PackageReference Include="Nethereum.HdWallet" Version="3.8.0" />
|
||||
<PackageReference Include="Nethereum.StandardTokenEIP20" Version="3.8.0" />
|
||||
<PackageReference Include="Nethereum.Web3" Version="3.8.0" />
|
||||
<PackageReference Include="Nethereum.ABI" Version="4.1.0" />
|
||||
<PackageReference Include="Nethereum.HdWallet" Version="4.1.0" />
|
||||
<PackageReference Include="Nethereum.StandardTokenEIP20" Version="4.1.0" />
|
||||
<PackageReference Include="Nethereum.Web3" Version="4.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(Altcoins)' != 'true'">
|
||||
@ -45,8 +54,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BIP78.Sender" Version="0.2.2" />
|
||||
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.1" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.2.12" />
|
||||
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.2" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.2.13" />
|
||||
<PackageReference Include="BuildBundlerMinifier" Version="3.2.449" />
|
||||
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435" />
|
||||
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
|
||||
@ -54,16 +63,17 @@
|
||||
<PackageReference Include="Fido2" Version="2.0.1" />
|
||||
<PackageReference Include="Fido2.AspNet" Version="2.0.1" />
|
||||
<PackageReference Include="HtmlSanitizer" Version="5.0.372" />
|
||||
<PackageReference Include="McMaster.NETCore.Plugins.Mvc" Version="1.3.1" />
|
||||
<PackageReference Include="LNURL" Version="0.0.14" />
|
||||
<PackageReference Include="McMaster.NETCore.Plugins.Mvc" Version="1.4.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.9.8">
|
||||
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="3.3.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="QRCoder" Version="1.4.1" />
|
||||
<PackageReference Include="System.IO.Pipelines" Version="4.7.2" />
|
||||
<PackageReference Include="System.IO.Pipelines" Version="4.7.4" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.39" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.2" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.3" />
|
||||
<PackageReference Include="NicolasDorier.RateLimits" Version="1.1.0" />
|
||||
@ -85,8 +95,8 @@
|
||||
<PackageReference Include="TwentyTwenty.Storage.Google" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Local" Version="2.12.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.1" Condition="'$(RazorCompileOnBuild)' != 'true'" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.19" Condition="'$(RazorCompileOnBuild)' != 'true'" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.19" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -196,12 +206,6 @@
|
||||
<Content Update="Views\Wallets\ListWallets.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\NewPullPayment.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\Payouts.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletPSBTCombine.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
@ -217,9 +221,6 @@
|
||||
<Content Update="Views\Wallets\WalletSendVault.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\PullPayments.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletTransactions.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
|
@ -1,4 +1,3 @@
|
||||
@inject LinkGenerator linkGenerator
|
||||
@inject UserManager<ApplicationUser> UserManager
|
||||
@inject ISettingsRepository SettingsRepository
|
||||
@using BTCPayServer.HostedServices
|
||||
@ -56,47 +55,3 @@ else
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
@{
|
||||
var disabled = (await SettingsRepository.GetPolicies()).DisableInstantNotifications;
|
||||
if (!disabled)
|
||||
{
|
||||
var user = await UserManager.GetUserAsync(User);
|
||||
disabled = user?.DisabledNotifications == "all";
|
||||
}
|
||||
}
|
||||
@if (!disabled)
|
||||
{
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
var supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
|
||||
|
||||
if (supportsWebSockets) {
|
||||
|
||||
var loc = window.location, ws_uri;
|
||||
if (loc.protocol === "https:") {
|
||||
ws_uri = "wss:";
|
||||
} else {
|
||||
ws_uri = "ws:";
|
||||
}
|
||||
ws_uri += "//" + loc.host;
|
||||
ws_uri += "@linkGenerator.GetPathByAction("SubscribeUpdates", "Notifications")";
|
||||
var newDataEndpoint = "@linkGenerator.GetPathByAction("GetNotificationDropdownUI", "Notifications")";
|
||||
|
||||
try {
|
||||
socket = new WebSocket(ws_uri);
|
||||
socket.onmessage = function (e) {
|
||||
$.get(newDataEndpoint, function(data){
|
||||
$("#notifications-nav-item").replaceWith($(data));
|
||||
});
|
||||
};
|
||||
socket.onerror = function (e) {
|
||||
console.error("Error while connecting to websocket for notifications (callback)", e);
|
||||
};
|
||||
}
|
||||
catch (e) {
|
||||
console.error("Error while connecting to websocket for notifications", e);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
}
|
||||
|
12
BTCPayServer/Components/ThemeSwitch/Default.cshtml
Normal file
12
BTCPayServer/Components/ThemeSwitch/Default.cshtml
Normal file
@ -0,0 +1,12 @@
|
||||
@model BTCPayServer.Components.ThemeSwitch.ThemeSwitchViewModel
|
||||
|
||||
<button class="btcpay-theme-switch @Model.CssClass">
|
||||
<svg class="@(string.IsNullOrEmpty(Model.Responsive) || Model.Responsive == "none" ? "d-inline-block" : $"d-{Model.Responsive}-inline-block d-none")" xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10">
|
||||
<path class="btcpay-theme-switch-dark" transform="translate(1 1)" d="M2.72 0A3.988 3.988 0 000 3.78c0 2.21 1.79 4 4 4 1.76 0 3.25-1.14 3.78-2.72-.4.13-.83.22-1.28.22-2.21 0-4-1.79-4-4 0-.45.08-.88.22-1.28z"/>
|
||||
<path class="btcpay-theme-switch-light" transform="translate(1 1)" d="M4 0c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5S4.28 0 4 0zM1.5 1c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm5 0c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM4 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zM.5 3.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm7 0c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM1.5 6c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm5 0c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM4 7c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5S4.28 7 4 7z"/>
|
||||
</svg>
|
||||
@if (!string.IsNullOrEmpty(Model.Responsive))
|
||||
{
|
||||
<span class="d-@Model.Responsive-none d-inline-block"><span class="btcpay-theme-switch-dark">Dark theme</span><span class="btcpay-theme-switch-light">Light theme</span></span>
|
||||
}
|
||||
</button>
|
17
BTCPayServer/Components/ThemeSwitch/ThemeSwitch.cs
Normal file
17
BTCPayServer/Components/ThemeSwitch/ThemeSwitch.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Components.ThemeSwitch
|
||||
{
|
||||
public class ThemeSwitch : ViewComponent
|
||||
{
|
||||
public IViewComponentResult Invoke(string cssClass = null, string responsive = null)
|
||||
{
|
||||
var vm = new ThemeSwitchViewModel
|
||||
{
|
||||
CssClass = cssClass,
|
||||
Responsive = responsive
|
||||
};
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
namespace BTCPayServer.Components.ThemeSwitch
|
||||
{
|
||||
public class ThemeSwitchViewModel
|
||||
{
|
||||
public string CssClass { get; set; }
|
||||
public string Responsive { get; set; }
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
@model IEnumerable<string>
|
||||
@model BTCPayServer.Components.UIExtensionPoint.UiExtensionPointViewModel
|
||||
|
||||
@foreach (var partial in Model)
|
||||
@foreach (var partial in Model.Partials)
|
||||
{
|
||||
await Html.RenderPartialAsync(partial);
|
||||
await Html.RenderPartialAsync(partial, Model.Model);
|
||||
}
|
||||
|
@ -15,9 +15,22 @@ namespace BTCPayServer.Components.UIExtensionPoint
|
||||
_uiExtensions = uiExtensions;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string location)
|
||||
public IViewComponentResult Invoke(string location, object model)
|
||||
{
|
||||
return View(_uiExtensions.Where(extension => extension.Location.Equals(location, StringComparison.InvariantCultureIgnoreCase)).Select(extension => extension.Partial));
|
||||
return View(new UiExtensionPointViewModel()
|
||||
{
|
||||
Partials = _uiExtensions
|
||||
.Where(extension =>
|
||||
extension.Location.Equals(location, StringComparison.InvariantCultureIgnoreCase))
|
||||
.Select(extension => extension.Partial).ToArray(),
|
||||
Model = model
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class UiExtensionPointViewModel
|
||||
{
|
||||
public string[] Partials { get; set; }
|
||||
public object Model { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using BTCPayServer.Configuration;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
@ -68,7 +69,6 @@ namespace BTCPayServer.Configuration
|
||||
|
||||
BundleJsCss = conf.GetOrDefault<bool>("bundlejscss", true);
|
||||
DockerDeployment = conf.GetOrDefault<bool>("dockerdeployment", true);
|
||||
AllowAdminRegistration = conf.GetOrDefault<bool>("allow-admin-registration", false);
|
||||
|
||||
TorrcFile = conf.GetOrDefault<string>("torrcfile", null);
|
||||
TorServices = conf.GetOrDefault<string>("torservices", null)
|
||||
@ -146,10 +146,14 @@ namespace BTCPayServer.Configuration
|
||||
DisableRegistration = conf.GetOrDefault<bool>("disable-registration", true);
|
||||
PluginRemote = conf.GetOrDefault("plugin-remote", "btcpayserver/btcpayserver-plugins");
|
||||
RecommendedPlugins = conf.GetOrDefault("recommended-plugins", "").ToLowerInvariant().Split('\r','\n','\t',' ').Where(s => !string.IsNullOrEmpty(s)).Distinct().ToArray();
|
||||
CheatMode = conf.GetOrDefault("cheatmode", false);
|
||||
if (CheatMode && this.NetworkType == ChainName.Mainnet)
|
||||
throw new ConfigException($"cheatmode can't be used on mainnet");
|
||||
}
|
||||
|
||||
public string PluginRemote { get; set; }
|
||||
public string[] RecommendedPlugins { get; set; }
|
||||
public bool CheatMode { get; set; }
|
||||
|
||||
private SSHSettings ParseSSHConfiguration(IConfiguration conf)
|
||||
{
|
||||
@ -193,7 +197,6 @@ namespace BTCPayServer.Configuration
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public bool AllowAdminRegistration { get; set; }
|
||||
public SSHSettings SSHSettings
|
||||
{
|
||||
get;
|
||||
|
@ -24,10 +24,10 @@ namespace BTCPayServer.Configuration
|
||||
app.Option("--testnet | -testnet", $"Use testnet (deprecated, use --network instead)", CommandOptionType.BoolValue);
|
||||
app.Option("--regtest | -regtest", $"Use regtest (deprecated, use --network instead)", CommandOptionType.BoolValue);
|
||||
app.Option("--signet | -signet", $"Use signet (deprecated, use --network instead)", CommandOptionType.BoolValue);
|
||||
app.Option("--allow-admin-registration", $"For debug only, will show a checkbox when a new user register to add himself as admin. (default: false)", CommandOptionType.BoolValue);
|
||||
app.Option("--chains | -c", $"Chains to support as a comma separated (default: btc; available: {chains})", CommandOptionType.SingleValue);
|
||||
app.Option("--postgres", $"Connection string to a PostgreSQL database", CommandOptionType.SingleValue);
|
||||
app.Option("--mysql", $"Connection string to a MySQL database", CommandOptionType.SingleValue);
|
||||
app.Option("--nocsp", $"Disable CSP (default false)", CommandOptionType.BoolValue);
|
||||
app.Option("--sqlitefile", $"File name to an SQLite database file inside the data directory", CommandOptionType.SingleValue);
|
||||
app.Option("--externalservices", $"Links added to external services inside Server Settings / Services under the format service1:path2;service2:path2.(default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--bundlejscss", $"Bundle JavaScript and CSS files for better performance (default: true)", CommandOptionType.SingleValue);
|
||||
@ -48,6 +48,7 @@ namespace BTCPayServer.Configuration
|
||||
app.Option("--plugin-remote", "Which github repository to fetch the available plugins list (default:btcpayserver/btcpayserver-plugins)", CommandOptionType.SingleValue);
|
||||
app.Option("--recommended-plugins", "Plugins which would be marked as recommended to be installed. Separated by newline or space", CommandOptionType.MultipleValue);
|
||||
app.Option("--xforwardedproto", "If specified, set X-Forwarded-Proto to the specified value, this may be useful if your reverse proxy handle https but is not configured to add X-Forwarded-Proto (example: --xforwardedproto https)", CommandOptionType.SingleValue);
|
||||
app.Option("--cheatmode", "Add elements in the UI to facilitate dev-time testing (Default false)", CommandOptionType.BoolValue);
|
||||
foreach (var network in provider.GetAll().OfType<BTCPayNetwork>())
|
||||
{
|
||||
var crypto = network.CryptoCode.ToLowerInvariant();
|
||||
|
@ -411,7 +411,6 @@ namespace BTCPayServer.Controllers
|
||||
if (policies.LockSubscription && !User.IsInRole(Roles.ServerAdmin))
|
||||
return RedirectToAction(nameof(HomeController.Index), "Home");
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
ViewData["AllowIsAdmin"] = _Options.AllowAdminRegistration;
|
||||
return View();
|
||||
}
|
||||
|
||||
@ -429,7 +428,6 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
ViewData["Logon"] = logon.ToString(CultureInfo.InvariantCulture).ToLowerInvariant();
|
||||
ViewData["AllowIsAdmin"] = _Options.AllowAdminRegistration;
|
||||
var policies = await _SettingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
|
||||
if (policies.LockSubscription && !User.IsInRole(Roles.ServerAdmin))
|
||||
return RedirectToAction(nameof(HomeController.Index), "Home");
|
||||
@ -441,7 +439,7 @@ namespace BTCPayServer.Controllers
|
||||
if (result.Succeeded)
|
||||
{
|
||||
var admin = await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin);
|
||||
if (admin.Count == 0 || (model.IsAdmin && _Options.AllowAdminRegistration))
|
||||
if (admin.Count == 0 || (model.IsAdmin && _Options.CheatMode))
|
||||
{
|
||||
await _RoleManager.CreateAsync(new IdentityRole(Roles.ServerAdmin));
|
||||
await _userManager.AddToRoleAsync(user, Roles.ServerAdmin);
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
@ -32,6 +33,7 @@ namespace BTCPayServer.Controllers
|
||||
Title = settings.Title,
|
||||
StoreId = app.StoreDataId,
|
||||
StoreName = app.StoreData?.StoreName,
|
||||
AppName = app.Name,
|
||||
Enabled = settings.Enabled,
|
||||
EnforceTargetAmount = settings.EnforceTargetAmount,
|
||||
StartDate = settings.StartDate,
|
||||
@ -55,6 +57,7 @@ namespace BTCPayServer.Controllers
|
||||
AppId = appId,
|
||||
SearchTerm = app.TagAllInvoices ? $"storeid:{app.StoreDataId}" : $"orderid:{AppService.GetCrowdfundOrderId(appId)}",
|
||||
DisplayPerksRanking = settings.DisplayPerksRanking,
|
||||
DisplayPerksValue = settings.DisplayPerksValue,
|
||||
SortPerksByPopularity = settings.SortPerksByPopularity,
|
||||
Sounds = string.Join(Environment.NewLine, settings.Sounds),
|
||||
AnimationColors = string.Join(Environment.NewLine, settings.AnimationColors)
|
||||
@ -65,7 +68,11 @@ namespace BTCPayServer.Controllers
|
||||
[Route("{appId}/settings/crowdfund")]
|
||||
public async Task<IActionResult> UpdateCrowdfund(string appId, UpdateCrowdfundViewModel vm, string command)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(vm.TargetCurrency) && _currencies.GetCurrencyData(vm.TargetCurrency, false) == null)
|
||||
var app = await GetOwnedApp(appId, AppType.Crowdfund);
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
vm.TargetCurrency = await GetStoreDefaultCurrentIfEmpty(app.StoreDataId, vm.TargetCurrency);
|
||||
if (_currencies.GetCurrencyData(vm.TargetCurrency, false) == null)
|
||||
ModelState.AddModelError(nameof(vm.TargetCurrency), "Invalid currency");
|
||||
|
||||
try
|
||||
@ -115,11 +122,7 @@ namespace BTCPayServer.Controllers
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
|
||||
var app = await GetOwnedApp(appId, AppType.Crowdfund);
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
|
||||
app.Name = vm.AppName;
|
||||
var newSettings = new CrowdfundSettings()
|
||||
{
|
||||
Title = vm.Title,
|
||||
@ -142,6 +145,7 @@ namespace BTCPayServer.Controllers
|
||||
AnimationsEnabled = vm.AnimationsEnabled,
|
||||
ResetEveryAmount = vm.ResetEveryAmount,
|
||||
ResetEvery = Enum.Parse<CrowdfundResetEvery>(vm.ResetEvery),
|
||||
DisplayPerksValue = vm.DisplayPerksValue,
|
||||
DisplayPerksRanking = vm.DisplayPerksRanking,
|
||||
SortPerksByPopularity = vm.SortPerksByPopularity,
|
||||
Sounds = parsedSounds,
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Data;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.RegularExpressions;
|
||||
@ -17,7 +18,6 @@ namespace BTCPayServer.Controllers
|
||||
public PointOfSaleSettings()
|
||||
{
|
||||
Title = "Tea shop";
|
||||
Currency = "USD";
|
||||
Template =
|
||||
"green tea:\n" +
|
||||
" price: 1\n" +
|
||||
@ -56,6 +56,7 @@ namespace BTCPayServer.Controllers
|
||||
ShowCustomAmount = true;
|
||||
ShowDiscount = true;
|
||||
EnableTips = true;
|
||||
RequiresRefundEmail = RequiresRefundEmail.InheritFromStore;
|
||||
}
|
||||
public string Title { get; set; }
|
||||
public string Currency { get; set; }
|
||||
@ -65,6 +66,7 @@ namespace BTCPayServer.Controllers
|
||||
public bool ShowCustomAmount { get; set; }
|
||||
public bool ShowDiscount { get; set; }
|
||||
public bool EnableTips { get; set; }
|
||||
public RequiresRefundEmail RequiresRefundEmail { get; set; }
|
||||
|
||||
public const string BUTTON_TEXT_DEF = "Buy for {0}";
|
||||
public string ButtonText { get; set; } = BUTTON_TEXT_DEF;
|
||||
@ -96,12 +98,12 @@ namespace BTCPayServer.Controllers
|
||||
var settings = app.GetSettings<PointOfSaleSettings>();
|
||||
settings.DefaultView = settings.EnableShoppingCart ? PosViewType.Cart : settings.DefaultView;
|
||||
settings.EnableShoppingCart = false;
|
||||
|
||||
var vm = new UpdatePointOfSaleViewModel
|
||||
{
|
||||
Id = appId,
|
||||
StoreId = app.StoreDataId,
|
||||
StoreName = app.StoreData?.StoreName,
|
||||
AppName = app.Name,
|
||||
Title = settings.Title,
|
||||
DefaultView = settings.DefaultView,
|
||||
ShowCustomAmount = settings.ShowCustomAmount,
|
||||
@ -119,11 +121,12 @@ namespace BTCPayServer.Controllers
|
||||
NotificationUrl = settings.NotificationUrl,
|
||||
RedirectUrl = settings.RedirectUrl,
|
||||
SearchTerm = $"storeid:{app.StoreDataId}",
|
||||
RedirectAutomatically = settings.RedirectAutomatically.HasValue ? settings.RedirectAutomatically.Value ? "true" : "false" : ""
|
||||
RedirectAutomatically = settings.RedirectAutomatically.HasValue ? settings.RedirectAutomatically.Value ? "true" : "false" : "",
|
||||
RequiresRefundEmail = settings.RequiresRefundEmail
|
||||
};
|
||||
if (HttpContext?.Request != null)
|
||||
{
|
||||
var appUrl = HttpContext.Request.GetAbsoluteRoot().WithTrailingSlash() + $"apps/{appId}/pos";
|
||||
var appUrl = HttpContext.Request.GetAbsoluteUri($"/apps/{appId}/pos");
|
||||
var encoder = HtmlEncoder.Default;
|
||||
if (settings.ShowCustomAmount)
|
||||
{
|
||||
@ -140,6 +143,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
try
|
||||
{
|
||||
|
||||
var items = _AppService.Parse(settings.Template, settings.Currency);
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine($"<form method=\"POST\" action=\"{encoder.Encode(appUrl)}\">");
|
||||
@ -162,11 +166,14 @@ namespace BTCPayServer.Controllers
|
||||
[Route("{appId}/settings/pos")]
|
||||
public async Task<IActionResult> UpdatePointOfSale(string appId, UpdatePointOfSaleViewModel vm)
|
||||
{
|
||||
var app = await GetOwnedApp(appId, AppType.PointOfSale);
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
vm.Currency = await GetStoreDefaultCurrentIfEmpty(app.StoreDataId, vm.Currency);
|
||||
if (_currencies.GetCurrencyData(vm.Currency, false) == null)
|
||||
ModelState.AddModelError(nameof(vm.Currency), "Invalid currency");
|
||||
try
|
||||
@ -181,9 +188,8 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
return View(vm);
|
||||
}
|
||||
var app = await GetOwnedApp(appId, AppType.PointOfSale);
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
|
||||
app.Name = vm.AppName;
|
||||
app.SetSettings(new PointOfSaleSettings
|
||||
{
|
||||
Title = vm.Title,
|
||||
@ -191,7 +197,7 @@ namespace BTCPayServer.Controllers
|
||||
ShowCustomAmount = vm.ShowCustomAmount,
|
||||
ShowDiscount = vm.ShowDiscount,
|
||||
EnableTips = vm.EnableTips,
|
||||
Currency = vm.Currency.ToUpperInvariant(),
|
||||
Currency = vm.Currency,
|
||||
Template = vm.Template,
|
||||
ButtonText = vm.ButtonText,
|
||||
CustomButtonText = vm.CustomButtonText,
|
||||
@ -202,7 +208,8 @@ namespace BTCPayServer.Controllers
|
||||
RedirectUrl = vm.RedirectUrl,
|
||||
Description = vm.Description,
|
||||
EmbeddedCSS = vm.EmbeddedCSS,
|
||||
RedirectAutomatically = string.IsNullOrEmpty(vm.RedirectAutomatically) ? (bool?)null : bool.Parse(vm.RedirectAutomatically)
|
||||
RedirectAutomatically = string.IsNullOrEmpty(vm.RedirectAutomatically) ? (bool?)null : bool.Parse(vm.RedirectAutomatically),
|
||||
RequiresRefundEmail = vm.RequiresRefundEmail,
|
||||
});
|
||||
await _AppService.UpdateOrCreateApp(app);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "App updated";
|
||||
|
@ -1,16 +1,17 @@
|
||||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Mails;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@ -29,6 +30,7 @@ namespace BTCPayServer.Controllers
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
CurrencyNameTable currencies,
|
||||
EmailSenderFactory emailSenderFactory,
|
||||
Services.Stores.StoreRepository storeRepository,
|
||||
AppService AppService)
|
||||
{
|
||||
_UserManager = userManager;
|
||||
@ -37,6 +39,7 @@ namespace BTCPayServer.Controllers
|
||||
_NetworkProvider = networkProvider;
|
||||
_currencies = currencies;
|
||||
_emailSenderFactory = emailSenderFactory;
|
||||
_storeRepository = storeRepository;
|
||||
_AppService = AppService;
|
||||
}
|
||||
|
||||
@ -46,6 +49,7 @@ namespace BTCPayServer.Controllers
|
||||
private readonly BTCPayNetworkProvider _NetworkProvider;
|
||||
private readonly CurrencyNameTable _currencies;
|
||||
private readonly EmailSenderFactory _emailSenderFactory;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly AppService _AppService;
|
||||
|
||||
public string CreatedAppId { get; set; }
|
||||
@ -100,7 +104,7 @@ namespace BTCPayServer.Controllers
|
||||
if (appData == null)
|
||||
return NotFound();
|
||||
if (await _AppService.DeleteApp(appData))
|
||||
TempData[WellKnownTempData.SuccessMessage] = "App removed successfully";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "App deleted successfully.";
|
||||
return RedirectToAction(nameof(ListApps));
|
||||
}
|
||||
|
||||
@ -159,9 +163,25 @@ namespace BTCPayServer.Controllers
|
||||
var appData = new AppData
|
||||
{
|
||||
StoreDataId = selectedStore,
|
||||
Name = vm.Name,
|
||||
Name = vm.AppName,
|
||||
AppType = appType.ToString()
|
||||
};
|
||||
|
||||
var defaultCurrency = await GetStoreDefaultCurrentIfEmpty(appData.StoreDataId, null);
|
||||
switch (appType)
|
||||
{
|
||||
case AppType.Crowdfund:
|
||||
var emptyCrowdfund = new CrowdfundSettings();
|
||||
emptyCrowdfund.TargetCurrency = defaultCurrency;
|
||||
appData.SetSettings(emptyCrowdfund);
|
||||
break;
|
||||
case AppType.PointOfSale:
|
||||
var empty = new PointOfSaleSettings();
|
||||
empty.Currency = defaultCurrency;
|
||||
appData.SetSettings(empty);
|
||||
break;
|
||||
}
|
||||
|
||||
await _AppService.UpdateOrCreateApp(appData);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "App successfully created";
|
||||
CreatedAppId = appData.Id;
|
||||
@ -177,19 +197,22 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{appId}/delete")]
|
||||
async Task<string> GetStoreDefaultCurrentIfEmpty(string storeId, string currency)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(currency))
|
||||
{
|
||||
currency = (await _storeRepository.FindStore(storeId)).GetStoreBlob().DefaultCurrency;
|
||||
}
|
||||
return currency.Trim().ToUpperInvariant();
|
||||
}
|
||||
|
||||
[HttpGet("{appId}/delete")]
|
||||
public async Task<IActionResult> DeleteApp(string appId)
|
||||
{
|
||||
var appData = await GetOwnedApp(appId);
|
||||
if (appData == null)
|
||||
return NotFound();
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Title = $"Delete app {appData.Name} ({appData.AppType})",
|
||||
Description = "This app will be removed from this store",
|
||||
Action = "Delete"
|
||||
});
|
||||
return View("Confirm", new ConfirmModel("Delete app", $"The app <strong>{appData.Name}</strong> and its settings will be permanently deleted. Are you sure?", "Delete"));
|
||||
}
|
||||
|
||||
private Task<AppData> GetOwnedApp(string appId, AppType? type = null)
|
||||
@ -197,7 +220,6 @@ namespace BTCPayServer.Controllers
|
||||
return _AppService.GetAppDataIfOwner(GetUserId(), appId, type);
|
||||
}
|
||||
|
||||
|
||||
private string GetUserId()
|
||||
{
|
||||
return _UserManager.GetUserId(User);
|
||||
|
@ -92,7 +92,7 @@ namespace BTCPayServer.Controllers
|
||||
Prefixed = new[] { 0, 2 }.Contains(numberFormatInfo.CurrencyPositivePattern),
|
||||
SymbolSpace = new[] { 2, 3 }.Contains(numberFormatInfo.CurrencyPositivePattern)
|
||||
},
|
||||
Items = _AppService.Parse(settings.Template, settings.Currency),
|
||||
Items = _AppService.GetPOSItems(settings.Template, settings.Currency),
|
||||
ButtonText = settings.ButtonText,
|
||||
CustomButtonText = settings.CustomButtonText,
|
||||
CustomTipText = settings.CustomTipText,
|
||||
@ -100,8 +100,10 @@ namespace BTCPayServer.Controllers
|
||||
CustomCSSLink = settings.CustomCSSLink,
|
||||
CustomLogoLink = storeBlob.CustomLogo,
|
||||
AppId = appId,
|
||||
Store = store,
|
||||
Description = settings.Description,
|
||||
EmbeddedCSS = settings.EmbeddedCSS
|
||||
EmbeddedCSS = settings.EmbeddedCSS,
|
||||
RequiresRefundEmail = settings.RequiresRefundEmail
|
||||
});
|
||||
}
|
||||
|
||||
@ -114,13 +116,15 @@ namespace BTCPayServer.Controllers
|
||||
[DomainMappingConstraint(AppType.PointOfSale)]
|
||||
public async Task<IActionResult> ViewPointOfSale(string appId,
|
||||
PosViewType viewType,
|
||||
[ModelBinder(typeof(InvariantDecimalModelBinder))] decimal amount,
|
||||
[ModelBinder(typeof(InvariantDecimalModelBinder))] decimal? amount,
|
||||
string email,
|
||||
string orderId,
|
||||
string notificationUrl,
|
||||
string redirectUrl,
|
||||
string choiceKey,
|
||||
string posData = null, CancellationToken cancellationToken = default)
|
||||
string posData = null,
|
||||
RequiresRefundEmail requiresRefundEmail = RequiresRefundEmail.InheritFromStore,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var app = await _AppService.GetApp(appId, AppType.PointOfSale);
|
||||
if (string.IsNullOrEmpty(choiceKey) && amount <= 0)
|
||||
@ -136,19 +140,27 @@ namespace BTCPayServer.Controllers
|
||||
return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId, viewType = viewType });
|
||||
}
|
||||
string title = null;
|
||||
var price = 0.0m;
|
||||
decimal? price = null;
|
||||
Dictionary<string, InvoiceSupportedTransactionCurrency> paymentMethods = null;
|
||||
ViewPointOfSaleViewModel.Item choice = null;
|
||||
if (!string.IsNullOrEmpty(choiceKey))
|
||||
{
|
||||
var choices = _AppService.Parse(settings.Template, settings.Currency);
|
||||
var choices = _AppService.GetPOSItems(settings.Template, settings.Currency);
|
||||
choice = choices.FirstOrDefault(c => c.Id == choiceKey);
|
||||
if (choice == null)
|
||||
return NotFound();
|
||||
title = choice.Title;
|
||||
price = choice.Price.Value;
|
||||
if (amount > price)
|
||||
price = amount;
|
||||
if (choice.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup)
|
||||
{
|
||||
price = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
price = choice.Price.Value;
|
||||
if (amount > price)
|
||||
price = amount;
|
||||
}
|
||||
|
||||
|
||||
if (choice.Inventory.HasValue)
|
||||
{
|
||||
@ -177,7 +189,7 @@ namespace BTCPayServer.Controllers
|
||||
AppService.TryParsePosCartItems(posData, out var cartItems))
|
||||
{
|
||||
|
||||
var choices = _AppService.Parse(settings.Template, settings.Currency);
|
||||
var choices = _AppService.GetPOSItems(settings.Template, settings.Currency);
|
||||
foreach (var cartItem in cartItems)
|
||||
{
|
||||
var itemChoice = choices.FirstOrDefault(c => c.Id == cartItem.Key);
|
||||
@ -218,6 +230,9 @@ namespace BTCPayServer.Controllers
|
||||
PosData = string.IsNullOrEmpty(posData) ? null : posData,
|
||||
RedirectAutomatically = settings.RedirectAutomatically,
|
||||
SupportedTransactionCurrencies = paymentMethods,
|
||||
RequiresRefundEmail = requiresRefundEmail == RequiresRefundEmail.InheritFromStore
|
||||
? store.GetStoreBlob().RequiresRefundEmail
|
||||
: requiresRefundEmail == RequiresRefundEmail.On,
|
||||
}, store, HttpContext.Request.GetAbsoluteRoot(),
|
||||
new List<string>() { AppService.GetAppInternalTag(appId) },
|
||||
cancellationToken);
|
||||
@ -258,8 +273,8 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
return NotFound("A Target Currency must be set for this app in order to be loadable.");
|
||||
}
|
||||
var appInfo = (ViewCrowdfundViewModel)(await _AppService.GetAppInfo(appId));
|
||||
appInfo.HubPath = AppHub.GetHubPath(this.Request);
|
||||
var appInfo = await GetAppInfo(appId);
|
||||
|
||||
if (settings.Enabled)
|
||||
return View(appInfo);
|
||||
if (!isAdmin)
|
||||
@ -277,17 +292,13 @@ namespace BTCPayServer.Controllers
|
||||
[DomainMappingConstraintAttribute(AppType.Crowdfund)]
|
||||
public async Task<IActionResult> ContributeToCrowdfund(string appId, ContributeToCrowdfund request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (request.Amount <= 0)
|
||||
{
|
||||
return NotFound("Please provide an amount greater than 0");
|
||||
}
|
||||
|
||||
var app = await _AppService.GetApp(appId, AppType.Crowdfund, true);
|
||||
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
var settings = app.GetSettings<CrowdfundSettings>();
|
||||
|
||||
|
||||
var isAdmin = await _AppService.GetAppDataIfOwner(GetUserId(), appId, AppType.Crowdfund) != null;
|
||||
|
||||
if (!settings.Enabled && !isAdmin)
|
||||
@ -295,8 +306,7 @@ namespace BTCPayServer.Controllers
|
||||
return NotFound("Crowdfund is not currently active");
|
||||
}
|
||||
|
||||
var info = (ViewCrowdfundViewModel)await _AppService.GetAppInfo(appId);
|
||||
info.HubPath = AppHub.GetHubPath(this.Request);
|
||||
var info = await GetAppInfo(appId);
|
||||
if (!isAdmin &&
|
||||
((settings.StartDate.HasValue && DateTime.Now < settings.StartDate) ||
|
||||
(settings.EndDate.HasValue && DateTime.Now > settings.EndDate) ||
|
||||
@ -309,21 +319,27 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
var store = await _AppService.GetStore(app);
|
||||
var title = settings.Title;
|
||||
var price = request.Amount;
|
||||
decimal? price = request.Amount;
|
||||
Dictionary<string, InvoiceSupportedTransactionCurrency> paymentMethods = null;
|
||||
ViewPointOfSaleViewModel.Item choice = null;
|
||||
if (!string.IsNullOrEmpty(request.ChoiceKey))
|
||||
{
|
||||
var choices = _AppService.Parse(settings.PerksTemplate, settings.TargetCurrency);
|
||||
var choices = _AppService.GetPOSItems(settings.PerksTemplate, settings.TargetCurrency);
|
||||
choice = choices.FirstOrDefault(c => c.Id == request.ChoiceKey);
|
||||
if (choice == null)
|
||||
return NotFound("Incorrect option provided");
|
||||
title = choice.Title;
|
||||
price = choice.Price.Value;
|
||||
if (request.Amount > price)
|
||||
price = request.Amount;
|
||||
|
||||
|
||||
if (choice.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup)
|
||||
{
|
||||
price = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
price = choice.Price.Value;
|
||||
if (request.Amount > price)
|
||||
price = request.Amount;
|
||||
}
|
||||
if (choice.Inventory.HasValue)
|
||||
{
|
||||
if (choice.Inventory <= 0)
|
||||
@ -331,14 +347,21 @@ namespace BTCPayServer.Controllers
|
||||
return NotFound("Option was out of stock");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (choice?.PaymentMethods?.Any() is true)
|
||||
{
|
||||
paymentMethods = choice?.PaymentMethods.ToDictionary(s => s,
|
||||
s => new InvoiceSupportedTransactionCurrency() { Enabled = true });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (request.Amount < 0)
|
||||
{
|
||||
return NotFound("Please provide an amount greater than 0");
|
||||
}
|
||||
|
||||
price = request.Amount;
|
||||
}
|
||||
|
||||
if (!isAdmin && (settings.EnforceTargetAmount && info.TargetAmount.HasValue && price >
|
||||
(info.TargetAmount - (info.Info.CurrentAmount + info.Info.CurrentPendingAmount))))
|
||||
@ -361,7 +384,7 @@ namespace BTCPayServer.Controllers
|
||||
ExtendedNotifications = true,
|
||||
SupportedTransactionCurrencies = paymentMethods,
|
||||
RedirectURL = request.RedirectUrl ??
|
||||
new Uri(new Uri(new Uri(HttpContext.Request.GetAbsoluteRoot()), _BtcPayServerOptions.RootPath), $"apps/{appId}/crowdfund").ToString()
|
||||
HttpContext.Request.GetAbsoluteUri($"/apps/{appId}/crowdfund")
|
||||
}, store, HttpContext.Request.GetAbsoluteRoot(),
|
||||
new List<string> { AppService.GetAppInternalTag(appId) },
|
||||
cancellationToken: cancellationToken);
|
||||
@ -379,9 +402,15 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
return BadRequest(e.Message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task<ViewCrowdfundViewModel> GetAppInfo(string appId)
|
||||
{
|
||||
var info = (ViewCrowdfundViewModel)await _AppService.GetAppInfo(appId);
|
||||
info.HubPath = AppHub.GetHubPath(Request);
|
||||
info.SimpleDisplay = Request.Query.ContainsKey("simple");
|
||||
return info;
|
||||
}
|
||||
|
||||
private string GetUserId()
|
||||
{
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Globalization;
|
||||
@ -55,7 +56,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
[FromQuery]
|
||||
[ModelBinder(typeof(ModelBinders.DateTimeOffsetModelBinder))]
|
||||
DateTimeOffset? endDate = null,
|
||||
string textSearch = null,
|
||||
[FromQuery] string textSearch = null,
|
||||
[FromQuery] bool includeArchived = false)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
@ -162,11 +163,6 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Amount), "The amount should be 0 or more.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(request.Currency))
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Currency), "Currency is required");
|
||||
}
|
||||
request.Checkout = request.Checkout ?? new CreateInvoiceRequest.CheckoutOptions();
|
||||
if (request.Checkout.PaymentMethods?.Any() is true)
|
||||
{
|
||||
@ -355,37 +351,49 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
PaymentMethod = method.GetId().ToStringNormalized(),
|
||||
Destination = details.GetPaymentDestination(),
|
||||
Rate = method.Rate,
|
||||
Due = accounting.Due.ToDecimal(MoneyUnit.BTC),
|
||||
Due = accounting.DueUncapped.ToDecimal(MoneyUnit.BTC),
|
||||
TotalPaid = accounting.Paid.ToDecimal(MoneyUnit.BTC),
|
||||
PaymentMethodPaid = accounting.CryptoPaid.ToDecimal(MoneyUnit.BTC),
|
||||
Amount = accounting.Due.ToDecimal(MoneyUnit.BTC),
|
||||
Amount = accounting.TotalDue.ToDecimal(MoneyUnit.BTC),
|
||||
NetworkFee = accounting.NetworkFee.ToDecimal(MoneyUnit.BTC),
|
||||
PaymentLink =
|
||||
method.GetId().PaymentType.GetPaymentLink(method.Network, details, accounting.Due,
|
||||
Request.GetAbsoluteRoot()),
|
||||
Payments = payments.Select(paymentEntity =>
|
||||
{
|
||||
var data = paymentEntity.GetCryptoPaymentData();
|
||||
return new InvoicePaymentMethodDataModel.Payment()
|
||||
{
|
||||
Destination = data.GetDestination(),
|
||||
Id = data.GetPaymentId(),
|
||||
Status = !paymentEntity.Accounted
|
||||
? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Invalid
|
||||
: data.PaymentConfirmed(paymentEntity, entity.SpeedPolicy) ||
|
||||
data.PaymentCompleted(paymentEntity)
|
||||
? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Settled
|
||||
: InvoicePaymentMethodDataModel.Payment.PaymentStatus.Processing,
|
||||
Fee = paymentEntity.NetworkFee,
|
||||
Value = data.GetValue(),
|
||||
ReceivedDate = paymentEntity.ReceivedTime.DateTime
|
||||
};
|
||||
}).ToList()
|
||||
Payments = payments.Select(paymentEntity => ToPaymentModel(entity, paymentEntity)).ToList()
|
||||
};
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
public static InvoicePaymentMethodDataModel.Payment ToPaymentModel(InvoiceEntity entity, PaymentEntity paymentEntity)
|
||||
{
|
||||
var data = paymentEntity.GetCryptoPaymentData();
|
||||
return new InvoicePaymentMethodDataModel.Payment()
|
||||
{
|
||||
Destination = data.GetDestination(),
|
||||
Id = data.GetPaymentId(),
|
||||
Status = !paymentEntity.Accounted
|
||||
? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Invalid
|
||||
: data.PaymentConfirmed(paymentEntity, entity.SpeedPolicy) || data.PaymentCompleted(paymentEntity)
|
||||
? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Settled
|
||||
: InvoicePaymentMethodDataModel.Payment.PaymentStatus.Processing,
|
||||
Fee = paymentEntity.NetworkFee,
|
||||
Value = data.GetValue(),
|
||||
ReceivedDate = paymentEntity.ReceivedTime.DateTime
|
||||
};
|
||||
}
|
||||
private InvoiceData ToModel(InvoiceEntity entity)
|
||||
{
|
||||
var statuses = new List<InvoiceStatus>();
|
||||
var state = entity.GetInvoiceState();
|
||||
if (state.CanMarkComplete())
|
||||
{
|
||||
statuses.Add(InvoiceStatus.Settled);
|
||||
}
|
||||
|
||||
if (state.CanMarkInvalid())
|
||||
{
|
||||
statuses.Add(InvoiceStatus.Invalid);
|
||||
}
|
||||
return new InvoiceData()
|
||||
{
|
||||
StoreId = entity.StoreId,
|
||||
@ -400,6 +408,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
AdditionalStatus = entity.ExceptionStatus,
|
||||
Currency = entity.Currency,
|
||||
Metadata = entity.Metadata.ToJObject(),
|
||||
AvailableStatusesForManualMarking = statuses.ToArray(),
|
||||
Checkout = new CreateInvoiceRequest.CheckoutOptions()
|
||||
{
|
||||
Expiration = entity.ExpirationTime - entity.InvoiceTime,
|
||||
@ -407,9 +416,11 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
PaymentTolerance = entity.PaymentTolerance,
|
||||
PaymentMethods =
|
||||
entity.GetPaymentMethods().Select(method => method.GetId().ToStringNormalized()).ToArray(),
|
||||
DefaultPaymentMethod = entity.DefaultPaymentMethod,
|
||||
SpeedPolicy = entity.SpeedPolicy,
|
||||
DefaultLanguage = entity.DefaultLanguage,
|
||||
RedirectAutomatically = entity.RedirectAutomatically,
|
||||
RequiresRefundEmail = entity.RequiresRefundEmail,
|
||||
RedirectURL = entity.RedirectURLTemplate
|
||||
}
|
||||
};
|
||||
|
@ -17,7 +17,6 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBXplorer.Models;
|
||||
using YamlDotNet.Core.Tokens;
|
||||
using InvoiceData = BTCPayServer.Client.Models.InvoiceData;
|
||||
using Language = BTCPayServer.Client.Models.Language;
|
||||
using NotificationData = BTCPayServer.Client.Models.NotificationData;
|
||||
@ -37,6 +36,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
private readonly StoreOnChainPaymentMethodsController _chainPaymentMethodsController;
|
||||
private readonly StoreOnChainWalletsController _storeOnChainWalletsController;
|
||||
private readonly StoreLightningNetworkPaymentMethodsController _storeLightningNetworkPaymentMethodsController;
|
||||
private readonly StoreLNURLPayPaymentMethodsController _storeLnurlPayPaymentMethodsController;
|
||||
private readonly HealthController _healthController;
|
||||
private readonly GreenFieldPaymentRequestsController _paymentRequestController;
|
||||
private readonly ApiKeysController _apiKeysController;
|
||||
@ -58,6 +58,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
StoreOnChainPaymentMethodsController chainPaymentMethodsController,
|
||||
StoreOnChainWalletsController storeOnChainWalletsController,
|
||||
StoreLightningNetworkPaymentMethodsController storeLightningNetworkPaymentMethodsController,
|
||||
StoreLNURLPayPaymentMethodsController storeLnurlPayPaymentMethodsController,
|
||||
HealthController healthController,
|
||||
GreenFieldPaymentRequestsController paymentRequestController,
|
||||
ApiKeysController apiKeysController,
|
||||
@ -79,6 +80,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
_chainPaymentMethodsController = chainPaymentMethodsController;
|
||||
_storeOnChainWalletsController = storeOnChainWalletsController;
|
||||
_storeLightningNetworkPaymentMethodsController = storeLightningNetworkPaymentMethodsController;
|
||||
_storeLnurlPayPaymentMethodsController = storeLnurlPayPaymentMethodsController;
|
||||
_healthController = healthController;
|
||||
_paymentRequestController = paymentRequestController;
|
||||
_apiKeysController = apiKeysController;
|
||||
@ -141,6 +143,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
_storeLightningNodeApiController,
|
||||
_internalLightningNodeApiController,
|
||||
_storeLightningNetworkPaymentMethodsController,
|
||||
_storeLnurlPayPaymentMethodsController,
|
||||
_greenFieldInvoiceController,
|
||||
_greenFieldServerInfoController,
|
||||
_storeWebhooksController,
|
||||
@ -165,6 +168,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
private readonly StoreLightningNodeApiController _storeLightningNodeApiController;
|
||||
private readonly InternalLightningNodeApiController _lightningNodeApiController;
|
||||
private readonly StoreLightningNetworkPaymentMethodsController _storeLightningNetworkPaymentMethodsController;
|
||||
private readonly StoreLNURLPayPaymentMethodsController _storeLnurlPayPaymentMethodsController;
|
||||
private readonly GreenFieldInvoiceController _greenFieldInvoiceController;
|
||||
private readonly GreenFieldServerInfoController _greenFieldServerInfoController;
|
||||
private readonly StoreWebhooksController _storeWebhooksController;
|
||||
@ -183,6 +187,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
StoreLightningNodeApiController storeLightningNodeApiController,
|
||||
InternalLightningNodeApiController lightningNodeApiController,
|
||||
StoreLightningNetworkPaymentMethodsController storeLightningNetworkPaymentMethodsController,
|
||||
StoreLNURLPayPaymentMethodsController storeLnurlPayPaymentMethodsController,
|
||||
GreenFieldInvoiceController greenFieldInvoiceController,
|
||||
GreenFieldServerInfoController greenFieldServerInfoController,
|
||||
StoreWebhooksController storeWebhooksController,
|
||||
@ -202,6 +207,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
_storeLightningNodeApiController = storeLightningNodeApiController;
|
||||
_lightningNodeApiController = lightningNodeApiController;
|
||||
_storeLightningNetworkPaymentMethodsController = storeLightningNetworkPaymentMethodsController;
|
||||
_storeLnurlPayPaymentMethodsController = storeLnurlPayPaymentMethodsController;
|
||||
_greenFieldInvoiceController = greenFieldInvoiceController;
|
||||
_greenFieldServerInfoController = greenFieldServerInfoController;
|
||||
_storeWebhooksController = storeWebhooksController;
|
||||
@ -518,16 +524,21 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
}
|
||||
|
||||
public override async Task<OnChainPaymentMethodData> UpdateStoreOnChainPaymentMethod(string storeId,
|
||||
string cryptoCode, OnChainPaymentMethodData paymentMethod,
|
||||
string cryptoCode, UpdateOnChainPaymentMethodRequest paymentMethod,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<OnChainPaymentMethodData>(
|
||||
await _chainPaymentMethodsController.UpdateOnChainPaymentMethod(storeId, cryptoCode, paymentMethod));
|
||||
await _chainPaymentMethodsController.UpdateOnChainPaymentMethod(storeId, cryptoCode, new UpdateOnChainPaymentMethodRequest(
|
||||
enabled: paymentMethod.Enabled,
|
||||
label: paymentMethod.Label,
|
||||
accountKeyPath: paymentMethod.AccountKeyPath,
|
||||
derivationScheme: paymentMethod.DerivationScheme
|
||||
)));
|
||||
}
|
||||
|
||||
public override Task<OnChainPaymentMethodPreviewResultData> PreviewProposedStoreOnChainPaymentMethodAddresses(
|
||||
string storeId, string cryptoCode,
|
||||
OnChainPaymentMethodData paymentMethod, int offset = 0, int amount = 10, CancellationToken token = default)
|
||||
UpdateOnChainPaymentMethodRequest paymentMethod, int offset = 0, int amount = 10, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(GetFromActionResult<OnChainPaymentMethodPreviewResultData>(
|
||||
_chainPaymentMethodsController.GetProposedOnChainPaymentMethodPreview(storeId, cryptoCode,
|
||||
@ -741,7 +752,39 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
return GetFromActionResult<StoreData>(await _storesController.UpdateStore(storeId, request));
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<LNURLPayPaymentMethodData>>
|
||||
GetStoreLNURLPayPaymentMethods(string storeId, bool? enabled,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(GetFromActionResult(
|
||||
_storeLnurlPayPaymentMethodsController.GetLNURLPayPaymentMethods(storeId, enabled)));
|
||||
}
|
||||
|
||||
public override Task<LNURLPayPaymentMethodData> GetStoreLNURLPayPaymentMethod(
|
||||
string storeId, string cryptoCode, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(GetFromActionResult<LNURLPayPaymentMethodData>(
|
||||
_storeLnurlPayPaymentMethodsController.GetLNURLPayPaymentMethod(storeId, cryptoCode)));
|
||||
}
|
||||
|
||||
public override async Task RemoveStoreLNURLPayPaymentMethod(string storeId, string cryptoCode,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
HandleActionResult(
|
||||
await _storeLnurlPayPaymentMethodsController.RemoveLNURLPayPaymentMethod(storeId,
|
||||
cryptoCode));
|
||||
}
|
||||
|
||||
public override async Task<LNURLPayPaymentMethodData> UpdateStoreLNURLPayPaymentMethod(
|
||||
string storeId, string cryptoCode,
|
||||
LNURLPayPaymentMethodData paymentMethod, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LNURLPayPaymentMethodData>(await
|
||||
_storeLnurlPayPaymentMethodsController.UpdateLNURLPayPaymentMethod(storeId, cryptoCode,
|
||||
paymentMethod));
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<LightningNetworkPaymentMethodData>>
|
||||
GetStoreLightningNetworkPaymentMethods(string storeId, bool? enabled,
|
||||
CancellationToken token = default)
|
||||
@ -767,11 +810,11 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
|
||||
public override async Task<LightningNetworkPaymentMethodData> UpdateStoreLightningNetworkPaymentMethod(
|
||||
string storeId, string cryptoCode,
|
||||
LightningNetworkPaymentMethodData paymentMethod, CancellationToken token = default)
|
||||
UpdateLightningNetworkPaymentMethodRequest paymentMethod, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LightningNetworkPaymentMethodData>(await
|
||||
_storeLightningNetworkPaymentMethodsController.UpdateLightningNetworkPaymentMethod(storeId, cryptoCode,
|
||||
paymentMethod));
|
||||
new UpdateLightningNetworkPaymentMethodRequest(paymentMethod.ConnectionString, paymentMethod.Enabled)));
|
||||
}
|
||||
|
||||
public override async Task<IEnumerable<InvoiceData>> GetInvoices(string storeId, string[] orderId = null,
|
||||
@ -873,16 +916,9 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
return Task.FromResult(GetFromActionResult<PermissionMetadata[]>(_homeController.Permissions()));
|
||||
}
|
||||
|
||||
public override async Task<LightningNetworkPaymentMethodData> UpdateStoreLightningNetworkPaymentMethodToInternalNode(string storeId, string cryptoCode,
|
||||
CancellationToken token = default)
|
||||
public override async Task<Dictionary<string, GenericPaymentMethodData>> GetStorePaymentMethods(string storeId, bool? enabled = null, CancellationToken token = default)
|
||||
{
|
||||
//nothing to change, just local client sugar
|
||||
return await base.UpdateStoreLightningNetworkPaymentMethodToInternalNode(storeId, cryptoCode, token);
|
||||
}
|
||||
|
||||
public override Task<Dictionary<string, GenericPaymentMethodData>> GetStorePaymentMethods(string storeId, bool? enabled = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(GetFromActionResult(_storePaymentMethodsController.GetStorePaymentMethods(storeId, enabled)));
|
||||
return GetFromActionResult(await _storePaymentMethodsController.GetStorePaymentMethods(storeId, enabled));
|
||||
}
|
||||
|
||||
public override async Task<OnChainPaymentMethodDataWithSensitiveData> GenerateOnChainWallet(string storeId, string cryptoCode, GenerateOnChainWalletRequest request,
|
||||
|
@ -3,14 +3,12 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@ -101,19 +99,21 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
ModelState.AddModelError(nameof(request.Period), $"The period should be positive");
|
||||
}
|
||||
PaymentMethodId[] paymentMethods = null;
|
||||
if (request.PaymentMethods is string[] paymentMethodsStr)
|
||||
if (request.PaymentMethods is { } paymentMethodsStr)
|
||||
{
|
||||
paymentMethods = paymentMethodsStr.Select(p => new PaymentMethodId(p, PaymentTypes.BTCLike)).ToArray();
|
||||
foreach (var p in paymentMethods)
|
||||
paymentMethods = paymentMethodsStr.Select(s =>
|
||||
{
|
||||
var n = _networkProvider.GetNetwork<BTCPayNetwork>(p.CryptoCode);
|
||||
if (n is null)
|
||||
ModelState.AddModelError(nameof(request.PaymentMethods), "Invalid payment method");
|
||||
if (n.ReadonlyWallet)
|
||||
ModelState.AddModelError(nameof(request.PaymentMethods), "Invalid payment method (We do not support the crypto currency for refund)");
|
||||
}
|
||||
if (paymentMethods.Any(p => _networkProvider.GetNetwork<BTCPayNetwork>(p.CryptoCode) is null))
|
||||
ModelState.AddModelError(nameof(request.PaymentMethods), "Invalid payment method");
|
||||
PaymentMethodId.TryParse(s, out var pmi);
|
||||
return pmi;
|
||||
}).ToArray();
|
||||
var supported = _payoutHandlers.GetSupportedPaymentMethods().ToArray();
|
||||
for (int i = 0; i < paymentMethods.Length; i++)
|
||||
{
|
||||
if (!supported.Contains(paymentMethods[i]))
|
||||
{
|
||||
request.AddModelError(paymentRequest => paymentRequest.PaymentMethods[i], "Invalid or unsupported payment method", this);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -233,7 +233,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
var payoutHandler = _payoutHandlers.FirstOrDefault(handler => handler.CanHandle(paymentMethodId));
|
||||
var payoutHandler = _payoutHandlers.FindPayoutHandler(paymentMethodId);
|
||||
if (payoutHandler is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method");
|
||||
@ -245,14 +245,23 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
if (pp is null)
|
||||
return PullPaymentNotFound();
|
||||
var ppBlob = pp.GetBlob();
|
||||
IClaimDestination destination = await payoutHandler.ParseClaimDestination(paymentMethodId,request.Destination);
|
||||
if (destination is null)
|
||||
var destination = await payoutHandler.ParseClaimDestination(paymentMethodId,request.Destination, true);
|
||||
if (destination.destination is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Destination), "The destination must be an address or a BIP21 URI");
|
||||
ModelState.AddModelError(nameof(request.Destination), destination.error??"The destination is invalid for the payment specified");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
if (request.Amount is decimal v && (v < ppBlob.MinimumClaim || v == 0.0m))
|
||||
if (request.Amount is null && destination.destination.Amount != null)
|
||||
{
|
||||
request.Amount = destination.destination.Amount;
|
||||
}
|
||||
else if (request.Amount != null && destination.destination.Amount != null && request.Amount != destination.destination.Amount)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Amount), $"Amount is implied in destination ({destination.destination.Amount}) that does not match the payout amount provided {request.Amount})");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
if (request.Amount is { } v && (v < ppBlob.MinimumClaim || v == 0.0m))
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Amount), $"Amount too small (should be at least {ppBlob.MinimumClaim})");
|
||||
return this.CreateValidationError(ModelState);
|
||||
@ -260,7 +269,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
var cd = _currencyNameTable.GetCurrencyData(pp.GetBlob().Currency, false);
|
||||
var result = await _pullPaymentService.Claim(new ClaimRequest()
|
||||
{
|
||||
Destination = destination,
|
||||
Destination = destination.destination,
|
||||
PullPaymentId = pullPaymentId,
|
||||
Value = request.Amount,
|
||||
PaymentMethodId = paymentMethodId
|
||||
|
@ -0,0 +1,180 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public class StoreLNURLPayPaymentMethodsController : ControllerBase
|
||||
{
|
||||
private StoreData Store => HttpContext.GetStoreData();
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly ISettingsRepository _settingsRepository;
|
||||
|
||||
public StoreLNURLPayPaymentMethodsController(
|
||||
StoreRepository storeRepository,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
IAuthorizationService authorizationService,
|
||||
ISettingsRepository settingsRepository)
|
||||
{
|
||||
_storeRepository = storeRepository;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_authorizationService = authorizationService;
|
||||
_settingsRepository = settingsRepository;
|
||||
}
|
||||
|
||||
public static IEnumerable<LNURLPayPaymentMethodData> GetLNURLPayPaymentMethods(StoreData store,
|
||||
BTCPayNetworkProvider networkProvider, bool? enabled)
|
||||
{
|
||||
var blob = store.GetStoreBlob();
|
||||
var excludedPaymentMethods = blob.GetExcludedPaymentMethods();
|
||||
|
||||
return store.GetSupportedPaymentMethods(networkProvider)
|
||||
.Where((method) => method.PaymentId.PaymentType == PaymentTypes.LNURLPay)
|
||||
.OfType<LNURLPaySupportedPaymentMethod>()
|
||||
.Select(paymentMethod =>
|
||||
new LNURLPayPaymentMethodData(
|
||||
paymentMethod.PaymentId.CryptoCode,
|
||||
!excludedPaymentMethods.Match(paymentMethod.PaymentId),
|
||||
paymentMethod.UseBech32Scheme, paymentMethod.EnableForStandardInvoices
|
||||
)
|
||||
)
|
||||
.Where((result) => enabled is null || enabled == result.Enabled)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/LNURLPay")]
|
||||
public ActionResult<IEnumerable<LNURLPayPaymentMethodData>> GetLNURLPayPaymentMethods(
|
||||
string storeId,
|
||||
[FromQuery] bool? enabled)
|
||||
{
|
||||
return Ok(GetLNURLPayPaymentMethods(Store, _btcPayNetworkProvider, enabled));
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/LNURLPay/{cryptoCode}")]
|
||||
public IActionResult GetLNURLPayPaymentMethod(string storeId, string cryptoCode)
|
||||
{
|
||||
if (!GetNetwork(cryptoCode, out BTCPayNetwork _))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var method = GetExistingLNURLPayPaymentMethod(cryptoCode);
|
||||
if (method is null)
|
||||
{
|
||||
return this.CreateAPIError(404, "paymentmethod-not-found", "The LNURL Payment Method isn't activated");
|
||||
}
|
||||
|
||||
return Ok(method);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpDelete("~/api/v1/stores/{storeId}/payment-methods/LNURLPay/{cryptoCode}")]
|
||||
public async Task<IActionResult> RemoveLNURLPayPaymentMethod(
|
||||
string storeId,
|
||||
string cryptoCode)
|
||||
{
|
||||
if (!GetNetwork(cryptoCode, out BTCPayNetwork _))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
||||
var store = Store;
|
||||
store.SetSupportedPaymentMethod(id, null);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPut("~/api/v1/stores/{storeId}/payment-methods/LNURLPay/{cryptoCode}")]
|
||||
public async Task<IActionResult> UpdateLNURLPayPaymentMethod(string storeId, string cryptoCode,
|
||||
[FromBody] LNURLPayPaymentMethodData paymentMethodData)
|
||||
{
|
||||
var paymentMethodId = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
||||
|
||||
if (!GetNetwork(cryptoCode, out var network))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var lnMethod = StoreLightningNetworkPaymentMethodsController.GetExistingLightningLikePaymentMethod(_btcPayNetworkProvider,
|
||||
cryptoCode, Store);
|
||||
|
||||
if ((lnMethod is null || lnMethod.Enabled is false) && paymentMethodData.Enabled)
|
||||
{
|
||||
ModelState.AddModelError(nameof(LNURLPayPaymentMethodData.Enabled),
|
||||
"LNURL Pay cannot be enabled unless the lightning payment method is configured and enabled on this store");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
|
||||
LNURLPaySupportedPaymentMethod? paymentMethod = new LNURLPaySupportedPaymentMethod()
|
||||
{
|
||||
CryptoCode = cryptoCode,
|
||||
UseBech32Scheme = paymentMethodData.UseBech32Scheme,
|
||||
EnableForStandardInvoices = paymentMethodData.EnableForStandardInvoices
|
||||
};
|
||||
|
||||
var store = Store;
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);
|
||||
storeBlob.SetExcluded(paymentMethodId, !paymentMethodData.Enabled);
|
||||
store.SetStoreBlob(storeBlob);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
return Ok(GetExistingLNURLPayPaymentMethod(cryptoCode, store));
|
||||
}
|
||||
|
||||
private LNURLPayPaymentMethodData? GetExistingLNURLPayPaymentMethod(string cryptoCode,
|
||||
StoreData? store = null)
|
||||
{
|
||||
store ??= Store;
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
||||
var paymentMethod = store
|
||||
.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.OfType<LNURLPaySupportedPaymentMethod>()
|
||||
.FirstOrDefault(method => method.PaymentId == id);
|
||||
|
||||
var excluded = storeBlob.IsExcluded(id);
|
||||
return paymentMethod is null
|
||||
? null
|
||||
: new LNURLPayPaymentMethodData(
|
||||
paymentMethod.PaymentId.CryptoCode,
|
||||
!excluded,
|
||||
paymentMethod.UseBech32Scheme, paymentMethod.EnableForStandardInvoices
|
||||
);
|
||||
}
|
||||
|
||||
private bool GetNetwork(string cryptoCode, [MaybeNullWhen(false)] out BTCPayNetwork network)
|
||||
{
|
||||
network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
network = network?.SupportLightning is true ? network : null;
|
||||
return network != null;
|
||||
}
|
||||
}
|
||||
}
|
@ -58,7 +58,9 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
paymentMethod.PaymentId.CryptoCode,
|
||||
paymentMethod.GetExternalLightningUrl()?.ToString() ??
|
||||
paymentMethod.GetDisplayableConnectionString(),
|
||||
!excludedPaymentMethods.Match(paymentMethod.PaymentId)
|
||||
!excludedPaymentMethods.Match(paymentMethod.PaymentId),
|
||||
paymentMethod.PaymentId.ToStringNormalized(),
|
||||
paymentMethod.DisableBOLT11PaymentOption
|
||||
)
|
||||
)
|
||||
.Where((result) => enabled is null || enabled == result.Enabled)
|
||||
@ -83,7 +85,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var method = GetExistingLightningLikePaymentMethod(cryptoCode);
|
||||
var method = GetExistingLightningLikePaymentMethod(_btcPayNetworkProvider, cryptoCode, Store);
|
||||
if (method is null)
|
||||
{
|
||||
return NotFound();
|
||||
@ -96,8 +98,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
[HttpDelete("~/api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}")]
|
||||
public async Task<IActionResult> RemoveLightningNetworkPaymentMethod(
|
||||
string storeId,
|
||||
string cryptoCode,
|
||||
int offset = 0, int amount = 10)
|
||||
string cryptoCode)
|
||||
{
|
||||
if (!GetNetwork(cryptoCode, out BTCPayNetwork _))
|
||||
{
|
||||
@ -114,7 +115,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPut("~/api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}")]
|
||||
public async Task<IActionResult> UpdateLightningNetworkPaymentMethod(string storeId, string cryptoCode,
|
||||
[FromBody] LightningNetworkPaymentMethodData paymentMethodData)
|
||||
[FromBody] UpdateLightningNetworkPaymentMethodRequest request)
|
||||
{
|
||||
var paymentMethodId = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
|
||||
@ -123,7 +124,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(paymentMethodData.ConnectionString))
|
||||
if (string.IsNullOrEmpty(request.ConnectionString))
|
||||
{
|
||||
ModelState.AddModelError(nameof(LightningNetworkPaymentMethodData.ConnectionString),
|
||||
"Missing connectionString");
|
||||
@ -133,13 +134,13 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
return this.CreateValidationError(ModelState);
|
||||
|
||||
LightningSupportedPaymentMethod? paymentMethod = null;
|
||||
if (!string.IsNullOrEmpty(paymentMethodData!.ConnectionString))
|
||||
if (!string.IsNullOrEmpty(request!.ConnectionString))
|
||||
{
|
||||
if (paymentMethodData.ConnectionString == LightningSupportedPaymentMethod.InternalNode)
|
||||
if (request.ConnectionString == LightningSupportedPaymentMethod.InternalNode)
|
||||
{
|
||||
if (!await CanUseInternalLightning())
|
||||
{
|
||||
ModelState.AddModelError(nameof(paymentMethodData.ConnectionString),
|
||||
ModelState.AddModelError(nameof(request.ConnectionString),
|
||||
$"You are not authorized to use the internal lightning node");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
@ -152,23 +153,23 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!LightningConnectionString.TryParse(paymentMethodData.ConnectionString, false,
|
||||
if (!LightningConnectionString.TryParse(request.ConnectionString, false,
|
||||
out var connectionString, out var error))
|
||||
{
|
||||
ModelState.AddModelError(nameof(paymentMethodData.ConnectionString), $"Invalid URL ({error})");
|
||||
ModelState.AddModelError(nameof(request.ConnectionString), $"Invalid URL ({error})");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
if (connectionString.ConnectionType == LightningConnectionType.LndGRPC)
|
||||
{
|
||||
ModelState.AddModelError(nameof(paymentMethodData.ConnectionString),
|
||||
ModelState.AddModelError(nameof(request.ConnectionString),
|
||||
$"BTCPay does not support gRPC connections");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
if (!await CanManageServer() && !connectionString.IsSafe())
|
||||
{
|
||||
ModelState.AddModelError(nameof(paymentMethodData.ConnectionString),
|
||||
ModelState.AddModelError(nameof(request.ConnectionString),
|
||||
$"You do not have 'btcpay.server.canmodifyserversettings' rights, so the connection string should not contain 'cookiefilepath', 'macaroondirectorypath', 'macaroonfilepath', and should not point to a local ip or to a dns name ending with '.internal', '.local', '.lan' or '.'.");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
@ -184,20 +185,20 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
var store = Store;
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);
|
||||
storeBlob.SetExcluded(paymentMethodId, !paymentMethodData.Enabled);
|
||||
storeBlob.SetExcluded(paymentMethodId, !request.Enabled);
|
||||
store.SetStoreBlob(storeBlob);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
return Ok(GetExistingLightningLikePaymentMethod(cryptoCode, store));
|
||||
return Ok(GetExistingLightningLikePaymentMethod(_btcPayNetworkProvider, cryptoCode, store));
|
||||
}
|
||||
|
||||
private LightningNetworkPaymentMethodData? GetExistingLightningLikePaymentMethod(string cryptoCode,
|
||||
StoreData? store = null)
|
||||
public static LightningNetworkPaymentMethodData? GetExistingLightningLikePaymentMethod(BTCPayNetworkProvider btcPayNetworkProvider, string cryptoCode,
|
||||
StoreData store)
|
||||
{
|
||||
store ??= Store;
|
||||
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
var paymentMethod = store
|
||||
.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.GetSupportedPaymentMethods(btcPayNetworkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.FirstOrDefault(method => method.PaymentId == id);
|
||||
|
||||
@ -205,7 +206,8 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
return paymentMethod is null
|
||||
? null
|
||||
: new LightningNetworkPaymentMethodData(paymentMethod.PaymentId.CryptoCode,
|
||||
paymentMethod.GetDisplayableConnectionString(), !excluded);
|
||||
paymentMethod.GetDisplayableConnectionString(), !excluded,
|
||||
paymentMethod.PaymentId.ToStringNormalized(), paymentMethod.DisableBOLT11PaymentOption);
|
||||
}
|
||||
|
||||
private bool GetNetwork(string cryptoCode, [MaybeNullWhen(false)] out BTCPayNetwork network)
|
||||
|
@ -91,7 +91,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
await _storeRepository.UpdateStore(store);
|
||||
var rawResult = GetExistingBtcLikePaymentMethod(cryptoCode, store);
|
||||
var result = new OnChainPaymentMethodDataWithSensitiveData(rawResult.CryptoCode, rawResult.DerivationScheme,
|
||||
rawResult.Enabled, rawResult.Label, rawResult.AccountKeyPath, response.GetMnemonic());
|
||||
rawResult.Enabled, rawResult.Label, rawResult.AccountKeyPath, response.GetMnemonic(), derivationSchemeSettings.PaymentId.ToStringNormalized());
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,9 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.Select(strategy =>
|
||||
new OnChainPaymentMethodData(strategy.PaymentId.CryptoCode,
|
||||
strategy.AccountDerivation.ToString(), !excludedPaymentMethods.Match(strategy.PaymentId), strategy.Label, strategy.GetSigningAccountKeySettings().GetRootedKeyPath()))
|
||||
strategy.AccountDerivation.ToString(), !excludedPaymentMethods.Match(strategy.PaymentId),
|
||||
strategy.Label, strategy.GetSigningAccountKeySettings().GetRootedKeyPath(),
|
||||
strategy.PaymentId.ToStringNormalized()))
|
||||
.Where((result) => enabled is null || enabled == result.Enabled)
|
||||
.ToList();
|
||||
}
|
||||
@ -144,7 +146,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
public IActionResult GetProposedOnChainPaymentMethodPreview(
|
||||
string storeId,
|
||||
string cryptoCode,
|
||||
[FromBody] OnChainPaymentMethodData paymentMethodData,
|
||||
[FromBody] UpdateOnChainPaymentMethodRequest paymentMethodData,
|
||||
int offset = 0, int amount = 10)
|
||||
{
|
||||
if (!GetCryptoCodeWallet(cryptoCode, out var network, out BTCPayWallet _))
|
||||
@ -217,7 +219,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
public async Task<IActionResult> UpdateOnChainPaymentMethod(
|
||||
string storeId,
|
||||
string cryptoCode,
|
||||
[FromBody] OnChainPaymentMethodData paymentMethodData)
|
||||
[FromBody] UpdateOnChainPaymentMethodRequest request)
|
||||
{
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
|
||||
|
||||
@ -226,7 +228,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(paymentMethodData?.DerivationScheme))
|
||||
if (string.IsNullOrEmpty(request?.DerivationScheme))
|
||||
{
|
||||
ModelState.AddModelError(nameof(OnChainPaymentMethodData.DerivationScheme),
|
||||
"Missing derivationScheme");
|
||||
@ -239,12 +241,12 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
var store = Store;
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var strategy = DerivationSchemeSettings.Parse(paymentMethodData.DerivationScheme, network);
|
||||
var strategy = DerivationSchemeSettings.Parse(request.DerivationScheme, network);
|
||||
if (strategy != null)
|
||||
await wallet.TrackAsync(strategy.AccountDerivation);
|
||||
strategy.Label = paymentMethodData.Label;
|
||||
strategy.Label = request.Label;
|
||||
var signing = strategy.GetSigningAccountKeySettings();
|
||||
if (paymentMethodData.AccountKeyPath is RootedKeyPath r)
|
||||
if (request.AccountKeyPath is RootedKeyPath r)
|
||||
{
|
||||
signing.AccountKeyPath = r.KeyPath;
|
||||
signing.RootFingerprint = r.MasterFingerprint;
|
||||
@ -256,7 +258,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
}
|
||||
|
||||
store.SetSupportedPaymentMethod(id, strategy);
|
||||
storeBlob.SetExcluded(id, !paymentMethodData.Enabled);
|
||||
storeBlob.SetExcluded(id, !request.Enabled);
|
||||
store.SetStoreBlob(storeBlob);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
return Ok(GetExistingBtcLikePaymentMethod(cryptoCode, store));
|
||||
@ -291,7 +293,8 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
? null
|
||||
: new OnChainPaymentMethodData(paymentMethod.PaymentId.CryptoCode,
|
||||
paymentMethod.AccountDerivation.ToString(), !excluded, paymentMethod.Label,
|
||||
paymentMethod.GetSigningAccountKeySettings().GetRootedKeyPath());
|
||||
paymentMethod.GetSigningAccountKeySettings().GetRootedKeyPath(),
|
||||
paymentMethod.PaymentId.ToStringNormalized());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -313,10 +313,13 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
var address = string.Empty;
|
||||
try
|
||||
{
|
||||
destination.Destination = destination.Destination.Replace(network.UriScheme+":", "bitcoin:", StringComparison.InvariantCultureIgnoreCase);
|
||||
bip21 = new BitcoinUrlBuilder(destination.Destination, network.NBitcoinNetwork);
|
||||
amount ??= bip21.Amount.GetValue(network);
|
||||
address = bip21.Address.ToString();
|
||||
if (bip21.Address is null)
|
||||
request.AddModelError(transactionRequest => transactionRequest.Destinations[index],
|
||||
"This BIP21 destination is missing a bitcoin address", this);
|
||||
else
|
||||
address = bip21.Address.ToString();
|
||||
if (destination.SubtractFromAmount)
|
||||
{
|
||||
request.AddModelError(transactionRequest => transactionRequest.Destinations[index],
|
||||
|
@ -1,10 +1,12 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Security;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
@ -17,20 +19,24 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
private StoreData Store => HttpContext.GetStoreData();
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
|
||||
public StorePaymentMethodsController(BTCPayNetworkProvider btcPayNetworkProvider)
|
||||
public StorePaymentMethodsController(BTCPayNetworkProvider btcPayNetworkProvider, IAuthorizationService authorizationService)
|
||||
{
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods")]
|
||||
public ActionResult<Dictionary<string, GenericPaymentMethodData>> GetStorePaymentMethods(
|
||||
public async Task<ActionResult<Dictionary<string, GenericPaymentMethodData>>> GetStorePaymentMethods(
|
||||
string storeId,
|
||||
[FromQuery] bool? enabled)
|
||||
{
|
||||
var storeBlob = Store.GetStoreBlob();
|
||||
var excludedPaymentMethods = storeBlob.GetExcludedPaymentMethods();
|
||||
var canModifyStore = (await _authorizationService.AuthorizeAsync(User, null,
|
||||
new PolicyRequirement(Policies.CanModifyStoreSettings))).Succeeded;;
|
||||
return Ok(Store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.Where(method =>
|
||||
enabled is null || (enabled is false && excludedPaymentMethods.Match(method.PaymentId)))
|
||||
@ -38,8 +44,9 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
method => method.PaymentId.ToStringNormalized(),
|
||||
method => new GenericPaymentMethodData()
|
||||
{
|
||||
CryptoCode = method.PaymentId.CryptoCode,
|
||||
Enabled = enabled.GetValueOrDefault(!excludedPaymentMethods.Match(method.PaymentId)),
|
||||
Data = method.PaymentId.PaymentType.GetGreenfieldData(method)
|
||||
Data = method.PaymentId.PaymentType.GetGreenfieldData(method, canModifyStore)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -83,8 +83,8 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
|
||||
var store = new Data.StoreData();
|
||||
|
||||
PaymentMethodId.TryParse(request.DefaultPaymentMethod, out var defaultPaymnetMethodId);
|
||||
ToModel(request, store, defaultPaymnetMethodId);
|
||||
PaymentMethodId.TryParse(request.DefaultPaymentMethod, out var defaultPaymentMethodId);
|
||||
ToModel(request, store, defaultPaymentMethodId);
|
||||
await _storeRepository.CreateStore(_userManager.GetUserId(User), store);
|
||||
return Ok(FromModel(store));
|
||||
}
|
||||
@ -104,8 +104,9 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
PaymentMethodId.TryParse(request.DefaultPaymentMethod, out var defaultPaymnetMethodId);
|
||||
ToModel(request, store, defaultPaymnetMethodId);
|
||||
PaymentMethodId.TryParse(request.DefaultPaymentMethod, out var defaultPaymentMethodId);
|
||||
|
||||
ToModel(request, store, defaultPaymentMethodId);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
return Ok(FromModel(store));
|
||||
}
|
||||
@ -119,10 +120,9 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
Name = data.StoreName,
|
||||
Website = data.StoreWebsite,
|
||||
SpeedPolicy = data.SpeedPolicy,
|
||||
DefaultPaymentMethod = data.GetDefaultPaymentId(_btcPayNetworkProvider)?.ToStringNormalized(),
|
||||
DefaultPaymentMethod = data.GetDefaultPaymentId()?.ToStringNormalized(),
|
||||
//blob
|
||||
//we do not include DefaultCurrencyPairs,Spread, PreferredExchange, RateScripting, RateScript in this model and instead opt to set it in stores/storeid/rates endpoints
|
||||
//we do not include CoinSwitchSettings in this model and instead opt to set it in stores/storeid/coinswitch endpoints
|
||||
//we do not include ExcludedPaymentMethods in this model and instead opt to set it in stores/storeid/payment-methods endpoints
|
||||
//we do not include EmailSettings in this model and instead opt to set it in stores/storeid/email endpoints
|
||||
//we do not include PaymentMethodCriteria because moving the CurrencyValueJsonConverter to the Client csproj is hard and requires a refactor (#1571 & #1572)
|
||||
@ -151,7 +151,6 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
private static void ToModel(StoreBaseData restModel, Data.StoreData model, PaymentMethodId defaultPaymentMethod)
|
||||
{
|
||||
var blob = model.GetStoreBlob();
|
||||
|
||||
model.StoreName = restModel.Name;
|
||||
model.StoreName = restModel.Name;
|
||||
model.StoreWebsite = restModel.Website;
|
||||
@ -160,11 +159,11 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
//we do not include the default payment method in this model and instead opt to set it in the stores/storeid/payment-methods endpoints
|
||||
//blob
|
||||
//we do not include DefaultCurrencyPairs;Spread; PreferredExchange; RateScripting; RateScript in this model and instead opt to set it in stores/storeid/rates endpoints
|
||||
//we do not include CoinSwitchSettings in this model and instead opt to set it in stores/storeid/coinswitch endpoints
|
||||
//we do not include ExcludedPaymentMethods in this model and instead opt to set it in stores/storeid/payment-methods endpoints
|
||||
//we do not include EmailSettings in this model and instead opt to set it in stores/storeid/email endpoints
|
||||
//we do not include OnChainMinValue and LightningMaxValue because moving the CurrencyValueJsonConverter to the Client csproj is hard and requires a refactor (#1571 & #1572)
|
||||
blob.NetworkFeeMode = restModel.NetworkFeeMode;
|
||||
blob.DefaultCurrency = restModel.DefaultCurrency;
|
||||
blob.RequiresRefundEmail = restModel.RequiresRefundEmail;
|
||||
blob.LightningAmountInSatoshi = restModel.LightningAmountInSatoshi;
|
||||
blob.LightningPrivateRouteHints = restModel.LightningPrivateRouteHints;
|
||||
@ -194,7 +193,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(request.DefaultPaymentMethod) &&
|
||||
!PaymentMethodId.TryParse(request.DefaultPaymentMethod, out var defaultPaymnetMethodId))
|
||||
!PaymentMethodId.TryParse(request.DefaultPaymentMethod, out var defaultPaymentMethodId))
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Name), "DefaultPaymentMethod is invalid");
|
||||
}
|
||||
|
111
BTCPayServer/Controllers/InvoiceController.Testing.cs
Normal file
111
BTCPayServer/Controllers/InvoiceController.Testing.cs
Normal file
@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Invoices.Export;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using NBitcoin.RPC;
|
||||
using NBitpayClient;
|
||||
using NBXplorer;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using BitpayCreateInvoiceRequest = BTCPayServer.Models.BitpayCreateInvoiceRequest;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
using BTCPayServer.Services;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class InvoiceController
|
||||
{
|
||||
public class FakePaymentRequest
|
||||
{
|
||||
public Decimal Amount { get; set; }
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("i/{invoiceId}/test-payment")]
|
||||
[CheatModeRoute]
|
||||
public async Task<IActionResult> TestPayment(string invoiceId, FakePaymentRequest request, [FromServices] Cheater cheater)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
|
||||
// TODO support altcoins, not just bitcoin
|
||||
//var network = invoice.Networks.GetNetwork(invoice.Currency);
|
||||
var cryptoCode = "BTC";
|
||||
var network = _NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var paymentMethodId = store.GetDefaultPaymentId();
|
||||
|
||||
//var network = NetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
|
||||
var bitcoinAddressString = invoice.GetPaymentMethod(paymentMethodId).GetPaymentMethodDetails().GetPaymentDestination();
|
||||
|
||||
var bitcoinAddressObj = BitcoinAddress.Create(bitcoinAddressString, network.NBitcoinNetwork);
|
||||
var BtcAmount = request.Amount;
|
||||
try
|
||||
{
|
||||
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
|
||||
var rate = paymentMethod.Rate;
|
||||
|
||||
var txid = cheater.CashCow.SendToAddress(bitcoinAddressObj, new Money(BtcAmount, MoneyUnit.BTC)).ToString();
|
||||
|
||||
// TODO The value of totalDue is wrong. How can we get the real total due? invoice.Price is only correct if this is the 2nd payment, not for a 3rd or 4th payment.
|
||||
var totalDue = invoice.Price;
|
||||
return Ok(new
|
||||
{
|
||||
Txid = txid,
|
||||
AmountRemaining = (totalDue - (BtcAmount * rate)) / rate,
|
||||
SuccessMessage = "Created transaction " + txid
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return BadRequest(new
|
||||
{
|
||||
ErrorMessage = e.Message,
|
||||
AmountRemaining = invoice.Price
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("i/{invoiceId}/expire")]
|
||||
[CheatModeRoute]
|
||||
public async Task<IActionResult> TestExpireNow(string invoiceId, [FromServices] Cheater cheater)
|
||||
{
|
||||
try
|
||||
{
|
||||
await cheater.UpdateInvoiceExpiry(invoiceId, DateTimeOffset.Now);
|
||||
return Ok(new { SuccessMessage = "Invoice is now expired." });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return BadRequest(new { ErrorMessage = e.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -13,17 +13,13 @@ using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Plugins.CoinSwitch;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Invoices.Export;
|
||||
using BTCPayServer.Services.Rates;
|
||||
@ -32,6 +28,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using NBXplorer;
|
||||
@ -105,14 +102,15 @@ namespace BTCPayServer.Controllers
|
||||
return NotFound();
|
||||
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
var invoiceState = invoice.GetInvoiceState();
|
||||
var model = new InvoiceDetailsModel()
|
||||
{
|
||||
StoreId = store.Id,
|
||||
StoreName = store.StoreName,
|
||||
StoreLink = Url.Action(nameof(StoresController.UpdateStore), "Stores", new { storeId = store.Id }),
|
||||
StoreLink = Url.Action(nameof(StoresController.PaymentMethods), "Stores", new { storeId = store.Id }),
|
||||
PaymentRequestLink = Url.Action(nameof(PaymentRequestController.ViewPaymentRequest), "PaymentRequest", new { id = invoice.Metadata.PaymentRequestId }),
|
||||
Id = invoice.Id,
|
||||
State = invoice.GetInvoiceState().ToString(),
|
||||
State = invoiceState.ToString(),
|
||||
TransactionSpeed = invoice.SpeedPolicy == SpeedPolicy.HighSpeed ? "high" :
|
||||
invoice.SpeedPolicy == SpeedPolicy.MediumSpeed ? "medium" :
|
||||
invoice.SpeedPolicy == SpeedPolicy.LowMediumSpeed ? "low-medium" :
|
||||
@ -130,11 +128,13 @@ namespace BTCPayServer.Controllers
|
||||
Events = invoice.Events,
|
||||
PosData = PosDataParser.ParsePosData(invoice.Metadata.PosData),
|
||||
Archived = invoice.Archived,
|
||||
CanRefund = CanRefund(invoice.GetInvoiceState()),
|
||||
CanRefund = CanRefund(invoiceState),
|
||||
ShowCheckout = invoice.Status == InvoiceStatusLegacy.New,
|
||||
Deliveries = (await _InvoiceRepository.GetWebhookDeliveries(invoiceId))
|
||||
.Select(c => new Models.StoreViewModels.DeliveryViewModel(c))
|
||||
.ToList()
|
||||
.ToList(),
|
||||
CanMarkInvalid = invoiceState.CanMarkInvalid(),
|
||||
CanMarkComplete = invoiceState.CanMarkComplete(),
|
||||
};
|
||||
model.Addresses = invoice.HistoricalAddresses.Select(h =>
|
||||
new InvoiceDetailsModel.AddressModel
|
||||
@ -165,7 +165,7 @@ namespace BTCPayServer.Controllers
|
||||
[HttpGet]
|
||||
[Route("invoices/{invoiceId}/refund")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Refund(string invoiceId, CancellationToken cancellationToken)
|
||||
public async Task<IActionResult> Refund([FromServices]IEnumerable<IPayoutHandler> payoutHandlers, string invoiceId, CancellationToken cancellationToken)
|
||||
{
|
||||
using var ctx = _dbContextFactory.CreateContext();
|
||||
ctx.ChangeTracker.QueryTrackingBehavior = Microsoft.EntityFrameworkCore.QueryTrackingBehavior.NoTracking;
|
||||
@ -173,7 +173,7 @@ namespace BTCPayServer.Controllers
|
||||
.Include(i => i.CurrentRefund)
|
||||
.Include(i => i.CurrentRefund.PullPaymentData)
|
||||
.Where(i => i.Id == invoiceId)
|
||||
.FirstOrDefaultAsync();
|
||||
.FirstOrDefaultAsync(cancellationToken: cancellationToken);
|
||||
if (invoice is null)
|
||||
return NotFound();
|
||||
if (invoice.CurrentRefund?.PullPaymentDataId is null && GetUserId() is null)
|
||||
@ -190,23 +190,18 @@ namespace BTCPayServer.Controllers
|
||||
else
|
||||
{
|
||||
var paymentMethods = invoice.GetBlob(_NetworkProvider).GetPaymentMethods();
|
||||
var options = paymentMethods
|
||||
.Select(o => o.GetId())
|
||||
.Select(o => o.CryptoCode)
|
||||
.Where(o => _NetworkProvider.GetNetwork<BTCPayNetwork>(o) is BTCPayNetwork n && !n.ReadonlyWallet)
|
||||
.Distinct()
|
||||
.OrderBy(o => o)
|
||||
.Select(o => new PaymentMethodId(o, PaymentTypes.BTCLike))
|
||||
.ToList();
|
||||
var pmis = paymentMethods.Select(method => method.GetId()).ToList();
|
||||
var options = payoutHandlers.GetSupportedPaymentMethods(pmis);
|
||||
var defaultRefund = invoice.Payments
|
||||
.Select(p => p.GetBlob(_NetworkProvider))
|
||||
.Select(p => p?.GetPaymentMethodId())
|
||||
.FirstOrDefault(p => p != null && p.PaymentType == BitcoinPaymentType.Instance);
|
||||
.FirstOrDefault(p => p != null && options.Contains(p));
|
||||
// TODO: What if no option?
|
||||
var refund = new RefundModel();
|
||||
refund.Title = "Select a payment method";
|
||||
refund.AvailablePaymentMethods = new SelectList(options, nameof(PaymentMethodId.CryptoCode), nameof(PaymentMethodId.CryptoCode));
|
||||
refund.SelectedPaymentMethod = defaultRefund?.ToString() ?? options.Select(o => o.CryptoCode).First();
|
||||
refund.AvailablePaymentMethods =
|
||||
new SelectList(options.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString())), "Value", "Text");
|
||||
refund.SelectedPaymentMethod = defaultRefund?.ToString() ?? options.First().ToString();
|
||||
|
||||
// Nothing to select, skip to next
|
||||
if (refund.AvailablePaymentMethods.Count() == 1)
|
||||
@ -230,7 +225,7 @@ namespace BTCPayServer.Controllers
|
||||
return NotFound();
|
||||
if (!CanRefund(invoice.GetInvoiceState()))
|
||||
return NotFound();
|
||||
var paymentMethodId = new PaymentMethodId(model.SelectedPaymentMethod, PaymentTypes.BTCLike);
|
||||
var paymentMethodId = PaymentMethodId.Parse(model.SelectedPaymentMethod);
|
||||
var cdCurrency = _CurrencyNameTable.GetCurrencyData(invoice.Currency, true);
|
||||
var paymentMethodDivisibility = _CurrencyNameTable.GetCurrencyData(paymentMethodId.CryptoCode, false)?.Divisibility ?? 8;
|
||||
RateRules rules;
|
||||
@ -241,7 +236,14 @@ namespace BTCPayServer.Controllers
|
||||
case RefundSteps.SelectPaymentMethod:
|
||||
model.RefundStep = RefundSteps.SelectRate;
|
||||
model.Title = "What to refund?";
|
||||
var paymentMethod = invoice.GetPaymentMethods()[paymentMethodId];
|
||||
var pms = invoice.GetPaymentMethods();
|
||||
var paymentMethod = pms.SingleOrDefault(method => method.GetId() == paymentMethodId);
|
||||
|
||||
//TODO: Make this clean
|
||||
if (paymentMethod is null && paymentMethodId.PaymentType == LightningPaymentType.Instance)
|
||||
{
|
||||
paymentMethod = pms[new PaymentMethodId(paymentMethodId.CryptoCode, PaymentTypes.LNURLPay)];
|
||||
}
|
||||
var cryptoPaid = paymentMethod.Calculate().Paid.ToDecimal(MoneyUnit.BTC);
|
||||
var paidCurrency =
|
||||
Math.Round(cryptoPaid * paymentMethod.Rate,
|
||||
@ -340,10 +342,10 @@ namespace BTCPayServer.Controllers
|
||||
var ppId = await _paymentHostedService.CreatePullPayment(createPullPayment);
|
||||
this.TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Html = "Refund successfully created!<br />Share the link to this page with a customer.<br />The customer needs to enter their address and claim the refund.<br />Once a customer claims the refund, you will get a notification and would need to approve and initiate it from your Wallet > Manage > Payouts.",
|
||||
Html = "Refund successfully created!<br />Share the link to this page with a customer.<br />The customer needs to enter their address and claim the refund.<br />Once a customer claims the refund, you will get a notification and would need to approve and initiate it from your Store > Payouts.",
|
||||
Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
(await ctx.Invoices.FindAsync(invoice.Id)).CurrentRefundId = ppId;
|
||||
(await ctx.Invoices.FindAsync(new[] { invoice.Id }, cancellationToken: cancellationToken)).CurrentRefundId = ppId;
|
||||
ctx.Refunds.Add(new RefundData()
|
||||
{
|
||||
InvoiceDataId = invoice.Id,
|
||||
@ -379,7 +381,8 @@ namespace BTCPayServer.Controllers
|
||||
Overpaid = _CurrencyNameTable.DisplayFormatCurrency(
|
||||
accounting.OverpaidHelper.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode),
|
||||
Address = data.GetPaymentMethodDetails().GetPaymentDestination(),
|
||||
Rate = ExchangeRate(data)
|
||||
Rate = ExchangeRate(data),
|
||||
PaymentMethodRaw = data
|
||||
};
|
||||
}).ToList()
|
||||
};
|
||||
@ -448,23 +451,9 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
if (view == "modal")
|
||||
model.IsModal = true;
|
||||
|
||||
_CSP.Add(new ConsentSecurityPolicy("script-src", "'unsafe-eval'")); // Needed by Vue
|
||||
if (!string.IsNullOrEmpty(model.CustomCSSLink) &&
|
||||
Uri.TryCreate(model.CustomCSSLink, UriKind.Absolute, out var uri))
|
||||
{
|
||||
_CSP.Clear();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(model.CustomLogoLink) &&
|
||||
Uri.TryCreate(model.CustomLogoLink, UriKind.Absolute, out uri))
|
||||
{
|
||||
_CSP.Clear();
|
||||
}
|
||||
|
||||
return View(nameof(Checkout), model);
|
||||
}
|
||||
|
||||
|
||||
[HttpGet]
|
||||
[Route("invoice-noscript")]
|
||||
public async Task<IActionResult> CheckoutNoScript(string? invoiceId, string? id = null, string? paymentMethodId = null, [FromQuery] string? lang = null)
|
||||
@ -490,19 +479,35 @@ namespace BTCPayServer.Controllers
|
||||
bool isDefaultPaymentId = false;
|
||||
if (paymentMethodId is null)
|
||||
{
|
||||
paymentMethodId = store.GetDefaultPaymentId(_NetworkProvider);
|
||||
var enabledPaymentIds = store.GetEnabledPaymentIds(_NetworkProvider) ?? Array.Empty<PaymentMethodId>();
|
||||
PaymentMethodId? invoicePaymentId = invoice.GetDefaultPaymentMethod();
|
||||
PaymentMethodId? storePaymentId = store.GetDefaultPaymentId();
|
||||
if (invoicePaymentId is PaymentMethodId)
|
||||
{
|
||||
if (enabledPaymentIds.Contains(invoicePaymentId))
|
||||
paymentMethodId = invoicePaymentId;
|
||||
}
|
||||
if (paymentMethodId is null && storePaymentId is PaymentMethodId)
|
||||
{
|
||||
if (enabledPaymentIds.Contains(storePaymentId))
|
||||
paymentMethodId = storePaymentId;
|
||||
}
|
||||
if (paymentMethodId is null && invoicePaymentId is PaymentMethodId)
|
||||
{
|
||||
paymentMethodId = invoicePaymentId.FindNearest(enabledPaymentIds);
|
||||
}
|
||||
if (paymentMethodId is null && storePaymentId is PaymentMethodId)
|
||||
{
|
||||
paymentMethodId = storePaymentId.FindNearest(enabledPaymentIds);
|
||||
}
|
||||
if (paymentMethodId is null)
|
||||
{
|
||||
paymentMethodId = enabledPaymentIds.First();
|
||||
}
|
||||
isDefaultPaymentId = true;
|
||||
}
|
||||
BTCPayNetworkBase network = _NetworkProvider.GetNetwork<BTCPayNetworkBase>(paymentMethodId.CryptoCode);
|
||||
if (network == null && isDefaultPaymentId)
|
||||
{
|
||||
//TODO: need to look into a better way for this as it does not scale
|
||||
network = _NetworkProvider.GetAll().OfType<BTCPayNetwork>().FirstOrDefault();
|
||||
paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
|
||||
}
|
||||
if (invoice == null || network == null)
|
||||
return null;
|
||||
if (!invoice.Support(paymentMethodId))
|
||||
if (network is null || !invoice.Support(paymentMethodId))
|
||||
{
|
||||
if (!isDefaultPaymentId)
|
||||
return null;
|
||||
@ -519,9 +524,11 @@ namespace BTCPayServer.Controllers
|
||||
var paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
|
||||
if (!paymentMethodDetails.Activated)
|
||||
{
|
||||
await _InvoiceRepository.ActivateInvoicePaymentMethod(_EventAggregator, _NetworkProvider,
|
||||
_paymentMethodHandlerDictionary, store, invoice, paymentMethod.GetId());
|
||||
return await GetInvoiceModel(invoiceId, paymentMethodId, lang);
|
||||
if (await _InvoiceRepository.ActivateInvoicePaymentMethod(_EventAggregator, _NetworkProvider,
|
||||
_paymentMethodHandlerDictionary, store, invoice, paymentMethod.GetId()))
|
||||
{
|
||||
return await GetInvoiceModel(invoiceId, paymentMethodId, lang);
|
||||
}
|
||||
}
|
||||
var dto = invoice.EntityToDTO();
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
@ -564,7 +571,7 @@ namespace BTCPayServer.Controllers
|
||||
IsUnsetTopUp = invoice.IsUnsetTopUp(),
|
||||
OrderAmountFiat = OrderAmountFromInvoice(network.CryptoCode, invoice),
|
||||
CustomerEmail = invoice.RefundMail,
|
||||
RequiresRefundEmail = storeBlob.RequiresRefundEmail,
|
||||
RequiresRefundEmail = invoice.RequiresRefundEmail ?? storeBlob.RequiresRefundEmail,
|
||||
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
|
||||
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
|
||||
MaxTimeMinutes = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalMinutes,
|
||||
@ -655,7 +662,7 @@ namespace BTCPayServer.Controllers
|
||||
[Route("invoice/{invoiceId}/status/ws")]
|
||||
[Route("invoice/{invoiceId}/{paymentMethodId}/status")]
|
||||
[Route("invoice/status/ws")]
|
||||
public async Task<IActionResult> GetStatusWebSocket(string invoiceId)
|
||||
public async Task<IActionResult> GetStatusWebSocket(string invoiceId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!HttpContext.WebSockets.IsWebSocketRequest)
|
||||
return NotFound();
|
||||
@ -666,12 +673,12 @@ namespace BTCPayServer.Controllers
|
||||
CompositeDisposable leases = new CompositeDisposable();
|
||||
try
|
||||
{
|
||||
leases.Add(_EventAggregator.Subscribe<Events.InvoiceDataChangedEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId)));
|
||||
leases.Add(_EventAggregator.Subscribe<Events.InvoiceNewPaymentDetailsEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId)));
|
||||
leases.Add(_EventAggregator.Subscribe<Events.InvoiceEvent>(async o => await NotifySocket(webSocket, o.Invoice.Id, invoiceId)));
|
||||
leases.Add(_EventAggregator.SubscribeAsync<Events.InvoiceDataChangedEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId)));
|
||||
leases.Add(_EventAggregator.SubscribeAsync<Events.InvoiceNewPaymentDetailsEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId)));
|
||||
leases.Add(_EventAggregator.SubscribeAsync<Events.InvoiceEvent>(async o => await NotifySocket(webSocket, o.Invoice.Id, invoiceId)));
|
||||
while (true)
|
||||
{
|
||||
var message = await webSocket.ReceiveAsync(DummyBuffer, default(CancellationToken));
|
||||
var message = await webSocket.ReceiveAndPingAsync(DummyBuffer, default(CancellationToken));
|
||||
if (message.MessageType == WebSocketMessageType.Close)
|
||||
break;
|
||||
}
|
||||
@ -686,6 +693,8 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
readonly ArraySegment<Byte> DummyBuffer = new ArraySegment<Byte>(new Byte[1]);
|
||||
public string? CreatedInvoiceId;
|
||||
|
||||
private async Task NotifySocket(WebSocket webSocket, string invoiceId, string expectedId)
|
||||
{
|
||||
if (invoiceId != expectedId || webSocket.State != WebSocketState.Open)
|
||||
@ -843,7 +852,12 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
if (!store.GetSupportedPaymentMethods(_NetworkProvider).Any())
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.StoreId), "You need to configure the derivation scheme in order to create an invoice");
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Html = $"To create an invoice, you need to <a href='{Url.Action(nameof(StoresController.PaymentMethods), "Stores", new { storeId = store.Id })}' class='alert-link'>set up your wallet</a> first",
|
||||
AllowDismiss = false
|
||||
});
|
||||
return View(model);
|
||||
}
|
||||
|
||||
@ -863,15 +877,23 @@ namespace BTCPayServer.Controllers
|
||||
SupportedTransactionCurrencies = model.SupportedTransactionCurrencies?.ToDictionary(s => s, s => new InvoiceSupportedTransactionCurrency()
|
||||
{
|
||||
Enabled = true
|
||||
})
|
||||
}),
|
||||
DefaultPaymentMethod = model.DefaultPaymentMethod,
|
||||
NotificationEmail = model.NotificationEmail,
|
||||
ExtendedNotifications = model.NotificationEmail != null,
|
||||
RequiresRefundEmail = model.RequiresRefundEmail == RequiresRefundEmail.InheritFromStore
|
||||
? store.GetStoreBlob().RequiresRefundEmail
|
||||
: model.RequiresRefundEmail == RequiresRefundEmail.On
|
||||
}, store, HttpContext.Request.GetAbsoluteRoot(), cancellationToken: cancellationToken);
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Invoice {result.Data.Id} just created!";
|
||||
CreatedInvoiceId = result.Data.Id;
|
||||
return RedirectToAction(nameof(ListInvoices));
|
||||
}
|
||||
catch (BitpayHttpException ex)
|
||||
{
|
||||
ModelState.TryAddModelError(nameof(model.Currency), $"Error: {ex.Message}");
|
||||
Logs.PayServer.LogError(ex, $"Invoice creation failed due to invalid currency {model.Currency}");
|
||||
ModelState.TryAddModelError(nameof(model.Currency), "Please make sure you entered a valid currency symbol, a rate provider is configured in store settings, and your configured rate provider is both online and providing rates for your selected currency.");
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,6 @@ namespace BTCPayServer.Controllers
|
||||
public partial class InvoiceController : Controller
|
||||
{
|
||||
readonly InvoiceRepository _InvoiceRepository;
|
||||
readonly ContentSecurityPolicies _CSP;
|
||||
readonly RateFetcher _RateProvider;
|
||||
readonly StoreRepository _StoreRepository;
|
||||
readonly UserManager<ApplicationUser> _UserManager;
|
||||
@ -72,7 +71,6 @@ namespace BTCPayServer.Controllers
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_paymentHostedService = paymentHostedService;
|
||||
WebhookNotificationManager = webhookNotificationManager;
|
||||
_CSP = csp;
|
||||
_languageService = languageService;
|
||||
}
|
||||
|
||||
@ -96,7 +94,6 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
throw new BitpayHttpException(400, "The expirationTime is set too soon");
|
||||
}
|
||||
invoice.Currency = invoice.Currency?.Trim().ToUpperInvariant() ?? "USD";
|
||||
entity.Metadata.OrderId = invoice.OrderId;
|
||||
entity.Metadata.PosData = invoice.PosData;
|
||||
entity.ServerUrl = serverUrl;
|
||||
@ -130,6 +127,7 @@ namespace BTCPayServer.Controllers
|
||||
entity.RedirectURLTemplate = invoice.RedirectURL ?? store.StoreWebsite;
|
||||
entity.RedirectAutomatically =
|
||||
invoice.RedirectAutomatically.GetValueOrDefault(storeBlob.RedirectAutomatically);
|
||||
entity.RequiresRefundEmail = invoice.RequiresRefundEmail;
|
||||
entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);
|
||||
|
||||
IPaymentFilter? excludeFilter = null;
|
||||
@ -153,6 +151,8 @@ namespace BTCPayServer.Controllers
|
||||
excludeFilter = PaymentFilter.Where(p => !supportedTransactionCurrencies.Contains(p));
|
||||
}
|
||||
entity.PaymentTolerance = storeBlob.PaymentTolerance;
|
||||
entity.DefaultPaymentMethod = invoice.DefaultPaymentMethod;
|
||||
entity.RequiresRefundEmail = invoice.RequiresRefundEmail;
|
||||
return await CreateInvoiceCoreRaw(entity, store, excludeFilter, null, cancellationToken);
|
||||
}
|
||||
|
||||
@ -160,12 +160,12 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var entity = _InvoiceRepository.CreateNewInvoice();
|
||||
entity.ServerUrl = serverUrl;
|
||||
entity.ExpirationTime = entity.InvoiceTime + (invoice.Checkout.Expiration ?? storeBlob.InvoiceExpiration);
|
||||
entity.MonitoringExpiration = entity.ExpirationTime + (invoice.Checkout.Monitoring ?? storeBlob.MonitoringExpiration);
|
||||
if (invoice.Metadata != null)
|
||||
entity.Metadata = InvoiceMetadata.FromJObject(invoice.Metadata);
|
||||
invoice.Checkout ??= new CreateInvoiceRequest.CheckoutOptions();
|
||||
invoice.Currency = invoice.Currency?.Trim().ToUpperInvariant() ?? "USD";
|
||||
entity.Currency = invoice.Currency;
|
||||
if (invoice.Amount is decimal v)
|
||||
{
|
||||
@ -179,7 +179,9 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
entity.SpeedPolicy = invoice.Checkout.SpeedPolicy ?? store.SpeedPolicy;
|
||||
entity.DefaultLanguage = invoice.Checkout.DefaultLanguage;
|
||||
entity.DefaultPaymentMethod = invoice.Checkout.DefaultPaymentMethod;
|
||||
entity.RedirectAutomatically = invoice.Checkout.RedirectAutomatically ?? storeBlob.RedirectAutomatically;
|
||||
entity.RequiresRefundEmail = invoice.Checkout.RequiresRefundEmail;
|
||||
IPaymentFilter? excludeFilter = null;
|
||||
if (invoice.Checkout.PaymentMethods != null)
|
||||
{
|
||||
@ -190,6 +192,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
entity.PaymentTolerance = invoice.Checkout.PaymentTolerance ?? storeBlob.PaymentTolerance;
|
||||
entity.RedirectURLTemplate = invoice.Checkout.RedirectURL?.Trim();
|
||||
entity.RequiresRefundEmail = invoice.Checkout.RequiresRefundEmail;
|
||||
if (additionalTags != null)
|
||||
entity.InternalTags.AddRange(additionalTags);
|
||||
return await CreateInvoiceCoreRaw(entity, store, excludeFilter, invoice.AdditionalSearchTerms, cancellationToken);
|
||||
@ -199,7 +202,10 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
InvoiceLogs logs = new InvoiceLogs();
|
||||
logs.Write("Creation of invoice starting", InvoiceEventData.EventSeverity.Info);
|
||||
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
if (string.IsNullOrEmpty(entity.Currency))
|
||||
entity.Currency = storeBlob.DefaultCurrency;
|
||||
entity.Currency = entity.Currency.Trim().ToUpperInvariant();
|
||||
entity.Price = Math.Max(0.0m, entity.Price);
|
||||
var currencyInfo = _CurrencyNameTable.GetNumberFormatInfo(entity.Currency, false);
|
||||
if (currencyInfo != null)
|
||||
@ -218,7 +224,6 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
var getAppsTaggingStore = _InvoiceRepository.GetAppsTaggingStore(store.Id);
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
|
||||
if (entity.Metadata.BuyerEmail != null)
|
||||
{
|
||||
@ -257,39 +262,48 @@ namespace BTCPayServer.Controllers
|
||||
List<ISupportedPaymentMethod> supported = new List<ISupportedPaymentMethod>();
|
||||
var paymentMethods = new PaymentMethodDictionary();
|
||||
|
||||
// This loop ends with .ToList so we are querying all payment methods at once
|
||||
// instead of sequentially to improve response time
|
||||
foreach (var o in store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.Where(s => !excludeFilter.Match(s.PaymentId) && _paymentMethodHandlerDictionary.Support(s.PaymentId))
|
||||
.Select(c =>
|
||||
(Handler: _paymentMethodHandlerDictionary[c.PaymentId],
|
||||
SupportedPaymentMethod: c,
|
||||
Network: _NetworkProvider.GetNetwork<BTCPayNetworkBase>(c.PaymentId.CryptoCode)))
|
||||
.Where(c => c.Network != null)
|
||||
.Select(o =>
|
||||
(SupportedPaymentMethod: o.SupportedPaymentMethod,
|
||||
PaymentMethod: CreatePaymentMethodAsync(fetchingByCurrencyPair, o.Handler, o.SupportedPaymentMethod, o.Network, entity, store, logs)))
|
||||
.ToList())
|
||||
{
|
||||
var paymentMethod = await o.PaymentMethod;
|
||||
if (paymentMethod == null)
|
||||
continue;
|
||||
supported.Add(o.SupportedPaymentMethod);
|
||||
paymentMethods.Add(paymentMethod);
|
||||
}
|
||||
bool noNeedForMethods = entity.Type != InvoiceType.TopUp && entity.Price == 0m;
|
||||
|
||||
if (supported.Count == 0)
|
||||
if (!noNeedForMethods)
|
||||
{
|
||||
StringBuilder errors = new StringBuilder();
|
||||
if (!store.GetSupportedPaymentMethods(_NetworkProvider).Any())
|
||||
errors.AppendLine("Warning: No wallet has been linked to your BTCPay Store. See the following link for more information on how to connect your store and wallet. (https://docs.btcpayserver.org/WalletSetup/)");
|
||||
foreach (var error in logs.ToList())
|
||||
|
||||
// This loop ends with .ToList so we are querying all payment methods at once
|
||||
// instead of sequentially to improve response time
|
||||
foreach (var o in store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.Where(s => !excludeFilter.Match(s.PaymentId) &&
|
||||
_paymentMethodHandlerDictionary.Support(s.PaymentId))
|
||||
.Select(c =>
|
||||
(Handler: _paymentMethodHandlerDictionary[c.PaymentId],
|
||||
SupportedPaymentMethod: c,
|
||||
Network: _NetworkProvider.GetNetwork<BTCPayNetworkBase>(c.PaymentId.CryptoCode)))
|
||||
.Where(c => c.Network != null)
|
||||
.Select(o =>
|
||||
(SupportedPaymentMethod: o.SupportedPaymentMethod,
|
||||
PaymentMethod: CreatePaymentMethodAsync(fetchingByCurrencyPair, o.Handler,
|
||||
o.SupportedPaymentMethod, o.Network, entity, store, logs)))
|
||||
.ToList())
|
||||
{
|
||||
errors.AppendLine(error.ToString());
|
||||
var paymentMethod = await o.PaymentMethod;
|
||||
if (paymentMethod == null)
|
||||
continue;
|
||||
supported.Add(o.SupportedPaymentMethod);
|
||||
paymentMethods.Add(paymentMethod);
|
||||
}
|
||||
throw new BitpayHttpException(400, errors.ToString());
|
||||
}
|
||||
|
||||
if (supported.Count == 0)
|
||||
{
|
||||
StringBuilder errors = new StringBuilder();
|
||||
if (!store.GetSupportedPaymentMethods(_NetworkProvider).Any())
|
||||
errors.AppendLine(
|
||||
"Warning: No wallet has been linked to your BTCPay Store. See the following link for more information on how to connect your store and wallet. (https://docs.btcpayserver.org/WalletSetup/)");
|
||||
foreach (var error in logs.ToList())
|
||||
{
|
||||
errors.AppendLine(error.ToString());
|
||||
}
|
||||
|
||||
throw new BitpayHttpException(400, errors.ToString());
|
||||
}
|
||||
}
|
||||
entity.SetSupportedPaymentMethods(supported);
|
||||
entity.SetPaymentMethods(paymentMethods);
|
||||
foreach (var app in await getAppsTaggingStore)
|
||||
@ -375,7 +389,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
var criteria = storeBlob.PaymentMethodCriteria?.Find(methodCriteria => methodCriteria.PaymentMethod == supportedPaymentMethod.PaymentId);
|
||||
if (criteria?.Value != null)
|
||||
if (criteria?.Value != null && entity.Type != InvoiceType.TopUp)
|
||||
{
|
||||
var currentRateToCrypto =
|
||||
await fetchingByCurrencyPair[new CurrencyPair(supportedPaymentMethod.PaymentId.CryptoCode, criteria.Value.Currency)];
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user