Compare commits
239 Commits
v1.6.4
...
form_build
Author | SHA1 | Date | |
---|---|---|---|
71c2bfffc1 | |||
4b952648a4 | |||
8b7b772d34 | |||
713b87d07b | |||
82a690645e | |||
dcade8b4a9 | |||
ad17234a86 | |||
7218c3077d | |||
3e1511c0a8 | |||
bb60c2ac48 | |||
3cb7c64321 | |||
9c88c53798 | |||
50d08de78b | |||
15ab7c051b | |||
4e8126a734 | |||
8195acbbf7 | |||
ec35b75324 | |||
d65835b0fe | |||
426a65d3fe | |||
a4ee1e9805 | |||
2fe2efcb7a | |||
bf0a8c1e62 | |||
f671f8fd26 | |||
e0f15c8792 | |||
4e828f4398 | |||
399fbe2827 | |||
6f547750ae | |||
f0f09180d5 | |||
95c6817d2a | |||
bb9c0989c6 | |||
fa2a3dd7f3 | |||
86361d0f75 | |||
a056e9b570 | |||
b89918ff5d | |||
37693399d6 | |||
d2b10ef4e6 | |||
9f3fca8fd7 | |||
9404819dbe | |||
d959f5096b | |||
bd3710a60f | |||
3d43f3a2b3 | |||
6194c156bd | |||
bc652507d8 | |||
850c26dc13 | |||
eda0f7327e | |||
bf597495ff | |||
d8094e9fee | |||
33c92c1428 | |||
6747f91d29 | |||
469ff5b11f | |||
b3bc4e5745 | |||
44b0addff5 | |||
383520c453 | |||
0c3b345d1b | |||
3513e602f4 | |||
cec83ab4ec | |||
16537509d9 | |||
6bd6a045f4 | |||
5de665dfff | |||
e329368e21 | |||
20025f254c | |||
ec76acd3a6 | |||
1e2acfb296 | |||
2bd4a680ad | |||
4ce504a1e1 | |||
d2f071b8b2 | |||
fdbd7b977a | |||
d19961b7a0 | |||
c156254600 | |||
9b5c6ece90 | |||
bf91efc756 | |||
a253fd5001 | |||
52af129c8b | |||
3942463ac9 | |||
ff572eef7f | |||
2740dfea87 | |||
324112b73b | |||
1d7dee8314 | |||
2d23819944 | |||
17f3b4125b | |||
c8a1024e24 | |||
9a2d2e2d89 | |||
b7af234427 | |||
a374e351e2 | |||
562f88555c | |||
167c5297fa | |||
b281d09694 | |||
853a0ac5ea | |||
ea948cfc3f | |||
fdd13390fb | |||
b2f6f8b3c1 | |||
cd12162b6f | |||
79717d1d64 | |||
e56cbf0baa | |||
05232414ad | |||
4bbc7d9662 | |||
3805b7f287 | |||
63620409a9 | |||
ba423a79e3 | |||
8806ba76eb | |||
9e73260230 | |||
c0125b83d1 | |||
1fa297fb73 | |||
57557748e2 | |||
8b79212a6e | |||
f4af4ec4dc | |||
2e150f4bf4 | |||
4f4aa051c9 | |||
da1dd7448e | |||
0fd47eeee0 | |||
54c9d7283a | |||
848db5f7de | |||
5fb32fe0e9 | |||
adf5b4ca0c | |||
16bfb1dbfe | |||
e5421b8a9f | |||
f9f1a22e3b | |||
9533809631 | |||
6d7c11f1b1 | |||
0286c72256 | |||
763aaa2926 | |||
ae4af7dd13 | |||
4ae2ea32e9 | |||
434298cba6 | |||
a2fa688cde | |||
895462ac7f | |||
e883714446 | |||
e1a235b4e8 | |||
ffa2c59df7 | |||
3f19dc55fa | |||
66c2148a63 | |||
28850f534c | |||
c40c11a822 | |||
b334e1aa00 | |||
b48986bfd6 | |||
ced63baed6 | |||
880635d615 | |||
d9f8c8d3b1 | |||
8155841a1d | |||
30f83d8f3f | |||
96c86160df | |||
b7ea128132 | |||
4bee8e9bfe | |||
bc195e771e | |||
0bc3e94052 | |||
3eb3523b52 | |||
8e2f84a989 | |||
143ec7463f | |||
4a5fd08e51 | |||
0306635a45 | |||
0a4d32cdb5 | |||
d590992d1d | |||
e8766946dd | |||
8c35189b37 | |||
e6390cde97 | |||
db976a6408 | |||
031c3ed055 | |||
cb391f08b9 | |||
5387a6287e | |||
0e4544b2da | |||
e0cbb7bede | |||
ed45b73274 | |||
cadcb586a7 | |||
9e31270459 | |||
dc07f046f2 | |||
5032bbafb1 | |||
1540bfb3a1 | |||
f3f5851118 | |||
9810edcd1a | |||
e334b9162a | |||
836c676057 | |||
1abadd9c5d | |||
e8bd1d8237 | |||
8a7470500a | |||
75689c665d | |||
d84f4f676b | |||
c97b859963 | |||
6b8f4ee1d5 | |||
3532789c35 | |||
267905b5e7 | |||
1626bd7a18 | |||
7106830be9 | |||
3b1946d65c | |||
6dedf4d44f | |||
fe5e2584b1 | |||
8fae38deca | |||
7f8e322e9c | |||
4d0f76f9e8 | |||
5d2b42960b | |||
0098dacdff | |||
51666fbf0e | |||
defb9120fd | |||
11ec72ce8c | |||
f67bd69ecc | |||
db2c29a6e1 | |||
e22e522245 | |||
7c8f4c0405 | |||
01ab21e4c0 | |||
534a2912e1 | |||
1456f4e227 | |||
63e11451ba | |||
7f80674cf2 | |||
16f4ca5fbf | |||
701ba59bd8 | |||
8c6705bccb | |||
e4542c4ac4 | |||
45eea1d6de | |||
4aa94d5a13 | |||
b0253e11bf | |||
1a4a3714c7 | |||
999090dbdb | |||
13e3b515c9 | |||
b6062a94b9 | |||
d0b26e9f69 | |||
d6ae34929e | |||
a51c9d2b2d | |||
6c45ccc73d | |||
6459c7bfad | |||
e6c1b0cf54 | |||
c8558810ad | |||
b0eb0b1de7 | |||
6bdf31efda | |||
3cd2971cec | |||
58784ebbfc | |||
de24a6e71b | |||
8c8a5a4f5e | |||
7f41a1ef09 | |||
15e1169f62 | |||
de92677b69 | |||
377ed95130 | |||
1b1c7ad3b1 | |||
e4e0fb0f35 | |||
bf0cbf24e4 | |||
7d454a4c7b | |||
680f1470cf | |||
1854fd307f | |||
6239f9da75 | |||
40e39b82e8 | |||
c71e671311 |
@ -41,9 +41,10 @@ jobs:
|
||||
- run:
|
||||
command: |
|
||||
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
|
||||
GIT_COMMIT=$(git rev-parse HEAD)
|
||||
#
|
||||
sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-amd64 -f amd64.Dockerfile .
|
||||
sudo docker build --pull --build-arg CONFIGURATION_NAME=Altcoins-Release -t $DOCKERHUB_REPO:$LATEST_TAG-altcoins-amd64 -f amd64.Dockerfile .
|
||||
sudo docker build --build-arg GIT_COMMIT=${GIT_COMMIT} --pull -t $DOCKERHUB_REPO:$LATEST_TAG-amd64 -f amd64.Dockerfile .
|
||||
sudo docker build --build-arg GIT_COMMIT=${GIT_COMMIT} --pull --build-arg CONFIGURATION_NAME=Altcoins-Release -t $DOCKERHUB_REPO:$LATEST_TAG-altcoins-amd64 -f amd64.Dockerfile .
|
||||
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
|
||||
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-amd64
|
||||
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-altcoins-amd64
|
||||
@ -57,9 +58,10 @@ jobs:
|
||||
command: |
|
||||
sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
|
||||
GIT_COMMIT=$(git rev-parse HEAD)
|
||||
#
|
||||
sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 -f arm32v7.Dockerfile .
|
||||
sudo docker build --pull --build-arg CONFIGURATION_NAME=Altcoins-Release -t $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm32v7 -f arm32v7.Dockerfile .
|
||||
sudo docker build --build-arg GIT_COMMIT=${GIT_COMMIT} --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 -f arm32v7.Dockerfile .
|
||||
sudo docker build --build-arg GIT_COMMIT=${GIT_COMMIT} --pull --build-arg CONFIGURATION_NAME=Altcoins-Release -t $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm32v7 -f arm32v7.Dockerfile .
|
||||
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
|
||||
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm32v7
|
||||
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm32v7
|
||||
@ -73,9 +75,10 @@ jobs:
|
||||
command: |
|
||||
sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
|
||||
GIT_COMMIT=$(git rev-parse HEAD)
|
||||
#
|
||||
sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm64v8 -f arm64v8.Dockerfile .
|
||||
sudo docker build --build-arg CONFIGURATION_NAME=Altcoins-Release --pull -t $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm64v8 -f arm64v8.Dockerfile .
|
||||
sudo docker build --build-arg GIT_COMMIT=${GIT_COMMIT} --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm64v8 -f arm64v8.Dockerfile .
|
||||
sudo docker build --build-arg GIT_COMMIT=${GIT_COMMIT} --build-arg CONFIGURATION_NAME=Altcoins-Release --pull -t $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm64v8 -f arm64v8.Dockerfile .
|
||||
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
|
||||
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm64v8
|
||||
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm64v8
|
||||
|
@ -12,7 +12,7 @@ indent_style = space
|
||||
indent_size = 4
|
||||
charset = utf-8
|
||||
|
||||
[launchSettings.json]
|
||||
[*.json]
|
||||
indent_size = 2
|
||||
|
||||
# C# files
|
||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -288,10 +288,6 @@ __pycache__/
|
||||
*.xsd.cs
|
||||
/BTCPayServer/Build/dockerfiles
|
||||
|
||||
# Bundling JS/CSS
|
||||
BTCPayServer/wwwroot/bundles/*
|
||||
!BTCPayServer/wwwroot/bundles/.gitignore
|
||||
|
||||
.vscode/*
|
||||
!.vscode/launch.json
|
||||
!.vscode/tasks.json
|
||||
@ -300,3 +296,5 @@ BTCPayServer/testpwd
|
||||
.DS_Store
|
||||
Packed Plugins
|
||||
Plugins/packed
|
||||
|
||||
BTCPayServer/wwwroot/swagger/v1/openapi.json
|
||||
|
@ -32,9 +32,9 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlSanitizer" Version="5.0.372" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.7" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.9" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.7" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -1,18 +1,19 @@
|
||||
namespace BTCPayServer.Abstractions.Custodians;
|
||||
namespace BTCPayServer.Abstractions.Custodians.Client;
|
||||
|
||||
public class AssetQuoteResult
|
||||
{
|
||||
public string FromAsset { get; }
|
||||
|
||||
public string ToAsset { get; }
|
||||
public decimal Bid { get; }
|
||||
public decimal Ask { get; }
|
||||
public string FromAsset { get; set; }
|
||||
public string ToAsset { get; set; }
|
||||
public decimal Bid { get; set; }
|
||||
public decimal Ask { get; set; }
|
||||
|
||||
public AssetQuoteResult() { }
|
||||
|
||||
public AssetQuoteResult(string fromAsset, string toAsset,decimal bid, decimal ask)
|
||||
{
|
||||
this.FromAsset = fromAsset;
|
||||
this.ToAsset = toAsset;
|
||||
this.Bid = bid;
|
||||
this.Ask = ask;
|
||||
FromAsset = fromAsset;
|
||||
ToAsset = toAsset;
|
||||
Bid = bid;
|
||||
Ask = ask;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Custodians;
|
||||
namespace BTCPayServer.Abstractions.Custodians.Client;
|
||||
|
||||
/**
|
||||
* The result of a market trade. Used as a return type for custodians implementing ICanTrade
|
||||
|
@ -2,7 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Custodians;
|
||||
namespace BTCPayServer.Abstractions.Custodians.Client;
|
||||
|
||||
public class WithdrawResult
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Custodians.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Custodians.Client;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Custodians;
|
||||
|
@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
|
||||
@ -10,6 +12,13 @@ namespace BTCPayServer.Abstractions.Extensions
|
||||
private const string ACTIVE_CATEGORY_KEY = "ActiveCategory";
|
||||
private const string ACTIVE_PAGE_KEY = "ActivePage";
|
||||
private const string ACTIVE_ID_KEY = "ActiveId";
|
||||
private const string ActivePageClass = "active";
|
||||
|
||||
public enum DateDisplayFormat
|
||||
{
|
||||
Localized,
|
||||
Relative
|
||||
}
|
||||
|
||||
public static void SetActivePage<T>(this ViewDataDictionary viewData, T activePage, string title = null, string activeId = null)
|
||||
where T : IConvertible
|
||||
@ -52,7 +61,7 @@ namespace BTCPayServer.Abstractions.Extensions
|
||||
var activeCategory = viewData[ACTIVE_CATEGORY_KEY]?.ToString();
|
||||
var categoryMatch = category.Equals(activeCategory, StringComparison.InvariantCultureIgnoreCase);
|
||||
var idMatch = id == null || activeId == null || id.Equals(activeId);
|
||||
return categoryMatch && idMatch ? "active" : null;
|
||||
return categoryMatch && idMatch ? ActivePageClass : null;
|
||||
}
|
||||
|
||||
public static string IsActivePage<T>(this ViewDataDictionary viewData, T page, object id = null)
|
||||
@ -60,6 +69,14 @@ namespace BTCPayServer.Abstractions.Extensions
|
||||
{
|
||||
return IsActivePage(viewData, page.ToString(), page.GetType().ToString(), id);
|
||||
}
|
||||
|
||||
public static string IsActivePage<T>(this ViewDataDictionary viewData, IEnumerable<T> pages, object id = null)
|
||||
where T : IConvertible
|
||||
{
|
||||
return pages.Any(page => IsActivePage(viewData, page.ToString(), page.GetType().ToString(), id) == ActivePageClass)
|
||||
? ActivePageClass
|
||||
: null;
|
||||
}
|
||||
|
||||
public static string IsActivePage(this ViewDataDictionary viewData, string page, string category, object id = null)
|
||||
{
|
||||
@ -72,29 +89,32 @@ namespace BTCPayServer.Abstractions.Extensions
|
||||
var activeCategory = viewData[ACTIVE_CATEGORY_KEY]?.ToString();
|
||||
var categoryAndPageMatch = (category == null || activeCategory.Equals(category, StringComparison.InvariantCultureIgnoreCase)) && page.Equals(activePage, StringComparison.InvariantCultureIgnoreCase);
|
||||
var idMatch = id == null || activeId == null || id.Equals(activeId);
|
||||
return categoryAndPageMatch && idMatch ? "active" : null;
|
||||
return categoryAndPageMatch && idMatch ? ActivePageClass : null;
|
||||
}
|
||||
|
||||
public static HtmlString ToBrowserDate(this DateTimeOffset date)
|
||||
public static HtmlString ToBrowserDate(this DateTimeOffset date, DateDisplayFormat format = DateDisplayFormat.Localized)
|
||||
{
|
||||
var displayDate = date.ToString("o", CultureInfo.InvariantCulture);
|
||||
return new HtmlString($"<span class='localizeDate'>{displayDate}</span>");
|
||||
var relative = date.ToTimeAgo();
|
||||
var initial = format.ToString().ToLower();
|
||||
var dateTime = date.ToString("o", CultureInfo.InvariantCulture);
|
||||
var displayDate = format == DateDisplayFormat.Relative ? relative : date.ToString("g", CultureInfo.InvariantCulture);
|
||||
return new HtmlString($"<time datetime=\"{dateTime}\" data-relative=\"{relative}\" data-initial=\"{initial}\">{displayDate}</time>");
|
||||
}
|
||||
|
||||
public static HtmlString ToBrowserDate(this DateTime date)
|
||||
public static HtmlString ToBrowserDate(this DateTime date, DateDisplayFormat format = DateDisplayFormat.Localized)
|
||||
{
|
||||
var displayDate = date.ToString("o", CultureInfo.InvariantCulture);
|
||||
return new HtmlString($"<span class='localizeDate'>{displayDate}</span>");
|
||||
var relative = date.ToTimeAgo();
|
||||
var initial = format.ToString().ToLower();
|
||||
var dateTime = date.ToString("o", CultureInfo.InvariantCulture);
|
||||
var displayDate = format == DateDisplayFormat.Relative ? relative : date.ToString("g", CultureInfo.InvariantCulture);
|
||||
return new HtmlString($"<time datetime=\"{dateTime}\" data-relative=\"{relative}\" data-initial=\"{initial}\">{displayDate}</time>");
|
||||
}
|
||||
|
||||
public static string ToTimeAgo(this DateTimeOffset date)
|
||||
{
|
||||
var diff = DateTimeOffset.UtcNow - date;
|
||||
var formatted = diff.TotalSeconds > 0
|
||||
? $"{diff.TimeString()} ago"
|
||||
: $"in {diff.Negate().TimeString()}";
|
||||
return formatted;
|
||||
}
|
||||
public static string ToTimeAgo(this DateTimeOffset date) => (DateTimeOffset.UtcNow - date).ToTimeAgo();
|
||||
|
||||
public static string ToTimeAgo(this DateTime date) => (DateTimeOffset.UtcNow - date).ToTimeAgo();
|
||||
|
||||
public static string ToTimeAgo(this TimeSpan diff) => diff.TotalSeconds > 0 ? $"{diff.TimeString()} ago" : $"in {diff.Negate().TimeString()}";
|
||||
|
||||
public static string TimeString(this TimeSpan timeSpan)
|
||||
{
|
||||
@ -106,16 +126,14 @@ namespace BTCPayServer.Abstractions.Extensions
|
||||
{
|
||||
return $"{(int)timeSpan.TotalMinutes} minute{Plural((int)timeSpan.TotalMinutes)}";
|
||||
}
|
||||
if (timeSpan.Days < 1)
|
||||
{
|
||||
return $"{(int)timeSpan.TotalHours} hour{Plural((int)timeSpan.TotalHours)}";
|
||||
}
|
||||
return $"{(int)timeSpan.TotalDays} day{Plural((int)timeSpan.TotalDays)}";
|
||||
return timeSpan.Days < 1
|
||||
? $"{(int)timeSpan.TotalHours} hour{Plural((int)timeSpan.TotalHours)}"
|
||||
: $"{(int)timeSpan.TotalDays} day{Plural((int)timeSpan.TotalDays)}";
|
||||
}
|
||||
|
||||
private static string Plural(int value)
|
||||
{
|
||||
return value > 1 ? "s" : string.Empty;
|
||||
return value == 1 ? string.Empty : "s";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,35 +1,34 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Form;
|
||||
|
||||
public abstract class Field
|
||||
public class Field
|
||||
{
|
||||
// HTML5 compatible type string like "text", "textarea", "email", "password", etc. Each type is a class and may contain more fields (i.e. "select" would have options).
|
||||
public string Type;
|
||||
|
||||
// The name of the HTML5 node. Should be used as the key for the posted data.
|
||||
public string Name;
|
||||
|
||||
// The translated label of the field.
|
||||
public string Label;
|
||||
|
||||
|
||||
// HTML5 compatible type string like "text", "textarea", "email", "password", etc. Each type is a class and may contain more fields (i.e. "select" would have options).
|
||||
public string Type;
|
||||
|
||||
// The value field is what is currently in the DB or what the user entered, but possibly not saved yet due to validation errors.
|
||||
// If this is the first the user sees the form, then value and original value are the same. Value changes as the user starts interacting with the form.
|
||||
public string Value;
|
||||
|
||||
// The original value is the value that is currently saved in the backend. A "reset" button can be used to revert back to this. Should only be set from the constructor.
|
||||
public string OriginalValue;
|
||||
|
||||
// A useful note shown below the field or via a tooltip / info icon. Should be translated for the user.
|
||||
public string HelpText;
|
||||
|
||||
// The field is considered "valid" if there are no validation errors
|
||||
public List<string> ValidationErrors = new List<string>();
|
||||
|
||||
public bool Required = false;
|
||||
|
||||
public bool IsValid()
|
||||
public virtual bool IsValid()
|
||||
{
|
||||
return ValidationErrors.Count == 0;
|
||||
return ValidationErrors.Count == 0 && Fields.All(field => field.IsValid());
|
||||
}
|
||||
|
||||
[JsonExtensionData] public IDictionary<string, JToken> AdditionalData { get; set; }
|
||||
public List<Field> Fields { get; set; } = new();
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,14 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Form;
|
||||
|
||||
public class Fieldset
|
||||
public class Fieldset : Field
|
||||
{
|
||||
public bool Hidden { get; set; }
|
||||
public string Label { get; set; }
|
||||
|
||||
public Fieldset()
|
||||
{
|
||||
this.Fields = new List<Field>();
|
||||
Type = "fieldset";
|
||||
}
|
||||
|
||||
public string Label { get; set; }
|
||||
public List<Field> Fields { get; set; }
|
||||
}
|
||||
|
@ -1,60 +1,143 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Form;
|
||||
|
||||
public class Form
|
||||
{
|
||||
|
||||
// Messages to be shown at the top of the form indicating user feedback like "Saved successfully" or "Please change X because of Y." or a warning, etc...
|
||||
public List<AlertMessage> TopMessages { get; set; } = new();
|
||||
|
||||
// Groups of fields in the form
|
||||
public List<Fieldset> Fieldsets { get; set; } = new();
|
||||
public List<Field> Fields { get; set; } = new();
|
||||
|
||||
|
||||
// Are all the fields valid in the form?
|
||||
public bool IsValid()
|
||||
{
|
||||
foreach (var fieldset in Fieldsets)
|
||||
{
|
||||
foreach (var field in fieldset.Fields)
|
||||
{
|
||||
if (!field.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return Fields.All(field => field.IsValid());
|
||||
}
|
||||
|
||||
public Field GetFieldByName(string name)
|
||||
{
|
||||
foreach (var fieldset in Fieldsets)
|
||||
return GetFieldByName(name, Fields, null);
|
||||
}
|
||||
|
||||
private static Field GetFieldByName(string name, List<Field> fields, string prefix)
|
||||
{
|
||||
prefix ??= string.Empty;
|
||||
foreach (var field in fields)
|
||||
{
|
||||
foreach (var field in fieldset.Fields)
|
||||
var currentPrefix = prefix;
|
||||
if (!string.IsNullOrEmpty(field.Name))
|
||||
{
|
||||
if (name.Equals(field.Name))
|
||||
|
||||
currentPrefix = $"{prefix}{field.Name}";
|
||||
if (currentPrefix.Equals(name, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return field;
|
||||
}
|
||||
|
||||
currentPrefix += "_";
|
||||
}
|
||||
|
||||
var subFieldResult = GetFieldByName(name, field.Fields, currentPrefix);
|
||||
if (subFieldResult is not null)
|
||||
{
|
||||
return subFieldResult;
|
||||
}
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<string> GetAllNames()
|
||||
{
|
||||
return GetAllNames(Fields);
|
||||
}
|
||||
|
||||
private static List<string> GetAllNames(List<Field> fields)
|
||||
{
|
||||
var names = new List<string>();
|
||||
foreach (var fieldset in Fieldsets)
|
||||
|
||||
foreach (var field in fields)
|
||||
{
|
||||
foreach (var field in fieldset.Fields)
|
||||
string prefix = string.Empty;
|
||||
if (!string.IsNullOrEmpty(field.Name))
|
||||
{
|
||||
names.Add(field.Name);
|
||||
prefix = $"{field.Name}_";
|
||||
}
|
||||
|
||||
if (field.Fields.Any())
|
||||
{
|
||||
names.AddRange(GetAllNames(field.Fields).Select(s => $"{prefix}{s}" ));
|
||||
}
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
public void ApplyValuesFromOtherForm(Form form)
|
||||
{
|
||||
foreach (var fieldset in Fields)
|
||||
{
|
||||
foreach (var field in fieldset.Fields)
|
||||
{
|
||||
field.Value = form
|
||||
.GetFieldByName(
|
||||
$"{(string.IsNullOrEmpty(fieldset.Name) ? string.Empty : fieldset.Name + "_")}{field.Name}")
|
||||
?.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyValuesFromForm(IFormCollection form)
|
||||
{
|
||||
var names = GetAllNames();
|
||||
foreach (var name in names)
|
||||
{
|
||||
var field = GetFieldByName(name);
|
||||
if (field is null || !form.TryGetValue(name, out var val))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
field.Value = val;
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, object> GetValues()
|
||||
{
|
||||
return GetValues(Fields);
|
||||
}
|
||||
|
||||
private static Dictionary<string, object> GetValues(List<Field> fields)
|
||||
{
|
||||
var result = new Dictionary<string, object>();
|
||||
foreach (Field field in fields)
|
||||
{
|
||||
var name = field.Name ?? string.Empty;
|
||||
if (field.Fields.Any())
|
||||
{
|
||||
var values = GetValues(fields);
|
||||
values.Remove(string.Empty, out var keylessValue);
|
||||
|
||||
result.TryAdd(name, values);
|
||||
|
||||
if (keylessValue is not Dictionary<string, object> dict) continue;
|
||||
foreach (KeyValuePair<string,object> keyValuePair in dict)
|
||||
{
|
||||
result.TryAdd(keyValuePair.Key, keyValuePair.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.TryAdd(name, field.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
27
BTCPayServer.Abstractions/Form/HtmlInputField.cs
Normal file
27
BTCPayServer.Abstractions/Form/HtmlInputField.cs
Normal file
@ -0,0 +1,27 @@
|
||||
namespace BTCPayServer.Abstractions.Form;
|
||||
|
||||
public class HtmlInputField : Field
|
||||
{
|
||||
// The translated label of the field.
|
||||
public string Label;
|
||||
|
||||
// The original value is the value that is currently saved in the backend. A "reset" button can be used to revert back to this. Should only be set from the constructor.
|
||||
public string OriginalValue;
|
||||
|
||||
// A useful note shown below the field or via a tooltip / info icon. Should be translated for the user.
|
||||
public string HelpText;
|
||||
|
||||
public bool Required;
|
||||
public HtmlInputField(string label, string name, string value, bool required, string helpText, string type = "text")
|
||||
{
|
||||
Label = label;
|
||||
Name = name;
|
||||
Value = value;
|
||||
OriginalValue = value;
|
||||
Required = required;
|
||||
HelpText = helpText;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
// TODO JSON parsing from string to objects again probably won't work out of the box because of the different field types.
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
namespace BTCPayServer.Abstractions.Form;
|
||||
|
||||
public class TextField : Field
|
||||
{
|
||||
public TextField(string label, string name, string value, bool required, string helpText)
|
||||
{
|
||||
this.Label = label;
|
||||
this.Name = name;
|
||||
this.Value = value;
|
||||
this.OriginalValue = value;
|
||||
this.Required = required;
|
||||
this.HelpText = helpText;
|
||||
this.Type = "text";
|
||||
}
|
||||
|
||||
// TODO JSON parsing from string to objects again probably won't work out of the box because of the different field types.
|
||||
|
||||
|
||||
}
|
@ -28,8 +28,8 @@
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.3.8" />
|
||||
<PackageReference Include="NBitcoin" Version="7.0.10" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.3.15" />
|
||||
<PackageReference Include="NBitcoin" Version="7.0.14" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -19,6 +19,28 @@ namespace BTCPayServer.Client
|
||||
method: HttpMethod.Post), token);
|
||||
return await HandleResponse<PointOfSaleAppData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<CrowdfundAppData> CreateCrowdfundApp(string storeId,
|
||||
CreateCrowdfundAppRequest request, CancellationToken token = default)
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/apps/crowdfund", bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
return await HandleResponse<CrowdfundAppData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<PointOfSaleAppData> UpdatePointOfSaleApp(string appId,
|
||||
CreatePointOfSaleAppRequest request, CancellationToken token = default)
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/apps/pos/{appId}", bodyPayload: request,
|
||||
method: HttpMethod.Put), token);
|
||||
return await HandleResponse<PointOfSaleAppData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<AppDataBase> GetApp(string appId, CancellationToken token = default)
|
||||
{
|
||||
|
@ -56,9 +56,14 @@ namespace BTCPayServer.Client
|
||||
return await HandleResponse<DepositAddressData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<MarketTradeResponseData> TradeMarket(string storeId, string accountId, TradeRequestData request, CancellationToken token = default)
|
||||
public virtual async Task<MarketTradeResponseData> MarketTradeCustodianAccountAsset(string storeId, string accountId, TradeRequestData request, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/custodian-accounts/{accountId}/trades/market", bodyPayload: request, method: HttpMethod.Post), token);
|
||||
|
||||
//var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/users", null, request, HttpMethod.Post), token);
|
||||
//return await HandleResponse<ApplicationUserData>(response);
|
||||
var internalRequest = CreateHttpRequest($"api/v1/stores/{storeId}/custodian-accounts/{accountId}/trades/market", null,
|
||||
request, HttpMethod.Post);
|
||||
var response = await _httpClient.SendAsync(internalRequest, token);
|
||||
return await HandleResponse<MarketTradeResponseData>(response);
|
||||
}
|
||||
|
||||
@ -73,7 +78,6 @@ namespace BTCPayServer.Client
|
||||
var queryPayload = new Dictionary<string, object>();
|
||||
queryPayload.Add("fromAsset", fromAsset);
|
||||
queryPayload.Add("toAsset", toAsset);
|
||||
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/custodian-accounts/{accountId}/trades/quote", queryPayload), token);
|
||||
return await HandleResponse<TradeQuoteResponseData>(response);
|
||||
}
|
||||
|
@ -95,6 +95,24 @@ namespace BTCPayServer.Client
|
||||
method: HttpMethod.Get), token);
|
||||
return await HandleResponse<LightningInvoiceData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LightningInvoiceData[]> GetLightningInvoices(string cryptoCode,
|
||||
bool? pendingOnly = null, long? offsetIndex = null, CancellationToken token = default)
|
||||
{
|
||||
var queryPayload = new Dictionary<string, object>();
|
||||
if (pendingOnly is bool v)
|
||||
{
|
||||
queryPayload.Add("pendingOnly", v.ToString());
|
||||
}
|
||||
if (offsetIndex is > 0)
|
||||
{
|
||||
queryPayload.Add("offsetIndex", offsetIndex);
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/invoices", queryPayload), token);
|
||||
return await HandleResponse<LightningInvoiceData[]>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LightningInvoiceData> CreateLightningInvoice(string cryptoCode, CreateLightningInvoiceRequest request,
|
||||
CancellationToken token = default)
|
||||
|
@ -65,7 +65,7 @@ namespace BTCPayServer.Client
|
||||
return await HandleResponse<string>(response);
|
||||
}
|
||||
|
||||
public virtual async Task PayLightningInvoice(string storeId, string cryptoCode, PayLightningInvoiceRequest request,
|
||||
public virtual async Task<LightningPaymentData> PayLightningInvoice(string storeId, string cryptoCode, PayLightningInvoiceRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
if (request == null)
|
||||
@ -73,7 +73,7 @@ namespace BTCPayServer.Client
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices/pay", bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
await HandleResponse(response);
|
||||
return await HandleResponse<LightningPaymentData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LightningPaymentData> GetLightningPayment(string storeId, string cryptoCode,
|
||||
@ -97,6 +97,24 @@ namespace BTCPayServer.Client
|
||||
method: HttpMethod.Get), token);
|
||||
return await HandleResponse<LightningInvoiceData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LightningInvoiceData[]> GetLightningInvoices(string storeId, string cryptoCode,
|
||||
bool? pendingOnly = null, long? offsetIndex = null, CancellationToken token = default)
|
||||
{
|
||||
var queryPayload = new Dictionary<string, object>();
|
||||
if (pendingOnly is bool v)
|
||||
{
|
||||
queryPayload.Add("pendingOnly", v.ToString());
|
||||
}
|
||||
if (offsetIndex is > 0)
|
||||
{
|
||||
queryPayload.Add("offsetIndex", offsetIndex);
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices", queryPayload), token);
|
||||
return await HandleResponse<LightningInvoiceData[]>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LightningInvoiceData> CreateLightningInvoice(string storeId, string cryptoCode,
|
||||
CreateLightningInvoiceRequest request, CancellationToken token = default)
|
||||
|
@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<OnChainWalletObjectData> GetOnChainWalletObject(string storeId, string cryptoCode, OnChainWalletObjectId objectId, bool? includeNeighbourData = null, CancellationToken token = default)
|
||||
{
|
||||
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||
if (includeNeighbourData is bool v)
|
||||
parameters.Add("includeNeighbourData", v);
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectId.Type}/{objectId.Id}", parameters, method: HttpMethod.Get), token);
|
||||
try
|
||||
{
|
||||
return await HandleResponse<OnChainWalletObjectData>(response);
|
||||
}
|
||||
catch (GreenfieldAPIException err) when (err.APIError.Code == "wallet-object-not-found")
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public virtual async Task<OnChainWalletObjectData[]> GetOnChainWalletObjects(string storeId, string cryptoCode, GetWalletObjectsRequest query = null, CancellationToken token = default)
|
||||
{
|
||||
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||
if (query?.Type is string s)
|
||||
parameters.Add("type", s);
|
||||
if (query?.Ids is string[] ids)
|
||||
parameters.Add("ids", ids);
|
||||
if (query?.IncludeNeighbourData is bool v)
|
||||
parameters.Add("includeNeighbourData", v);
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects", parameters, method:HttpMethod.Get), token);
|
||||
return await HandleResponse<OnChainWalletObjectData[]>(response);
|
||||
}
|
||||
public virtual async Task RemoveOnChainWalletObject(string storeId, string cryptoCode, OnChainWalletObjectId objectId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectId.Type}/{objectId.Id}", method:HttpMethod.Delete), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
public virtual async Task<OnChainWalletObjectData> AddOrUpdateOnChainWalletObject(string storeId, string cryptoCode, AddOnChainWalletObjectRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects", method:HttpMethod.Post, bodyPayload: request), token);
|
||||
return await HandleResponse<OnChainWalletObjectData>(response);
|
||||
}
|
||||
public virtual async Task AddOrUpdateOnChainWalletLink(string storeId, string cryptoCode,
|
||||
OnChainWalletObjectId objectId,
|
||||
AddOnChainWalletObjectLinkRequest request = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectId.Type}/{objectId.Id}/links", method:HttpMethod.Post, bodyPayload: request), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
public virtual async Task RemoveOnChainWalletLinks(string storeId, string cryptoCode,
|
||||
OnChainWalletObjectId objectId,
|
||||
OnChainWalletObjectId link,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectId.Type}/{objectId.Id}/links/{link.Type}/{link.Id}", method:HttpMethod.Delete), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
}
|
||||
}
|
@ -37,6 +37,20 @@ namespace BTCPayServer.Client
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public virtual async Task<Client.Models.InvoiceData> PayPaymentRequest(string storeId, string paymentRequestId, PayPaymentRequestRequest request, CancellationToken token = default)
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
if (storeId is null)
|
||||
throw new ArgumentNullException(nameof(storeId));
|
||||
if (paymentRequestId is null)
|
||||
throw new ArgumentNullException(nameof(paymentRequestId));
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-requests/{paymentRequestId}/pay", bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
return await HandleResponse<Client.Models.InvoiceData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<PaymentRequestData> CreatePaymentRequest(string storeId,
|
||||
CreatePaymentRequestRequest request, CancellationToken token = default)
|
||||
{
|
||||
|
@ -53,6 +53,16 @@ namespace BTCPayServer.Client
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/pull-payments/{HttpUtility.UrlEncode(pullPaymentId)}/payouts", bodyPayload: payoutRequest, method: HttpMethod.Post), cancellationToken);
|
||||
return await HandleResponse<PayoutData>(response);
|
||||
}
|
||||
public virtual async Task<PayoutData> GetPullPaymentPayout(string pullPaymentId, string payoutId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/pull-payments/{HttpUtility.UrlEncode(pullPaymentId)}/payouts/{payoutId}", method: HttpMethod.Get), cancellationToken);
|
||||
return await HandleResponse<PayoutData>(response);
|
||||
}
|
||||
public virtual async Task<PayoutData> GetStorePayout(string storeId, string payoutId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/payouts/{payoutId}", method: HttpMethod.Get), cancellationToken);
|
||||
return await HandleResponse<PayoutData>(response);
|
||||
}
|
||||
public virtual async Task<PayoutData> CreatePayout(string storeId, CreatePayoutThroughStoreRequest payoutRequest, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/payouts", bodyPayload: payoutRequest, method: HttpMethod.Post), cancellationToken);
|
||||
@ -69,7 +79,7 @@ namespace BTCPayServer.Client
|
||||
return await HandleResponse<PayoutData>(response);
|
||||
}
|
||||
|
||||
public async Task MarkPayoutPaid(string storeId, string payoutId,
|
||||
public virtual async Task MarkPayoutPaid(string storeId, string payoutId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
@ -78,5 +88,14 @@ namespace BTCPayServer.Client
|
||||
method: HttpMethod.Post), cancellationToken);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
public virtual async Task MarkPayout(string storeId, string payoutId, MarkPayoutRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest(
|
||||
$"api/v1/stores/{HttpUtility.UrlEncode(storeId)}/payouts/{HttpUtility.UrlEncode(payoutId)}/mark",
|
||||
method: HttpMethod.Post, bodyPayload: request), cancellationToken);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,53 @@
|
||||
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<StoreRateConfiguration> GetStoreRateConfiguration(string storeId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/rates/configuration", method: HttpMethod.Get),
|
||||
token);
|
||||
return await HandleResponse<StoreRateConfiguration>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<List<RateSource>> GetRateSources(
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"misc/rate-sources", method: HttpMethod.Get),
|
||||
token);
|
||||
return await HandleResponse<List<RateSource>>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<StoreRateConfiguration> UpdateStoreRateConfiguration(string storeId,
|
||||
StoreRateConfiguration request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/rates/configuration", bodyPayload: request,
|
||||
method: HttpMethod.Put),
|
||||
token);
|
||||
return await HandleResponse<StoreRateConfiguration>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<List<StoreRatePreviewResult>> PreviewUpdateStoreRateConfiguration(string storeId,
|
||||
StoreRateConfiguration request,
|
||||
string[] currencyPair,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/rates/configuration/preview", bodyPayload: request,
|
||||
queryPayload: new Dictionary<string, object>() {{"currencyPair", currencyPair}},
|
||||
method: HttpMethod.Post),
|
||||
token);
|
||||
return await HandleResponse<List<StoreRatePreviewResult>>(response);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +1,31 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
[JsonObject(MemberSerialization.OptIn)]
|
||||
public class AssetPairData
|
||||
{
|
||||
public AssetPairData()
|
||||
{
|
||||
}
|
||||
|
||||
public AssetPairData(string assetBought, string assetSold)
|
||||
public AssetPairData(string assetBought, string assetSold, decimal minimumTradeQty)
|
||||
{
|
||||
AssetBought = assetBought;
|
||||
AssetSold = assetSold;
|
||||
MinimumTradeQty = minimumTradeQty;
|
||||
}
|
||||
|
||||
|
||||
[JsonProperty]
|
||||
public string AssetBought { set; get; }
|
||||
|
||||
[JsonProperty]
|
||||
public string AssetSold { set; get; }
|
||||
|
||||
[JsonProperty]
|
||||
public decimal MinimumTradeQty { set; get; }
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return AssetBought + "/" + AssetSold;
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
@ -25,7 +26,7 @@ namespace BTCPayServer.Client.Models
|
||||
public string Template { get; set; } = null;
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public PosViewType DefaultView { get; set; }
|
||||
public bool ShowCustomAmount { get; set; } = true;
|
||||
public bool ShowCustomAmount { get; set; } = false;
|
||||
public bool ShowDiscount { get; set; } = true;
|
||||
public bool EnableTips { get; set; } = true;
|
||||
public string CustomAmountPayButtonText { get; set; } = null;
|
||||
@ -36,6 +37,48 @@ namespace BTCPayServer.Client.Models
|
||||
public string RedirectUrl { get; set; } = null;
|
||||
public bool? RedirectAutomatically { get; set; } = null;
|
||||
public bool? RequiresRefundEmail { get; set; } = null;
|
||||
public string FormId { get; set; } = null;
|
||||
public string EmbeddedCSS { get; set; } = null;
|
||||
public CheckoutType? CheckoutType { get; set; } = null;
|
||||
}
|
||||
|
||||
public enum CrowdfundResetEvery
|
||||
{
|
||||
Never,
|
||||
Hour,
|
||||
Day,
|
||||
Month,
|
||||
Year
|
||||
}
|
||||
|
||||
public class CreateCrowdfundAppRequest : CreateAppRequest
|
||||
{
|
||||
public string Title { get; set; } = null;
|
||||
public bool? Enabled { get; set; } = null;
|
||||
public bool? EnforceTargetAmount { get; set; } = null;
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset? StartDate { get; set; } = null;
|
||||
public string TargetCurrency { get; set; } = null;
|
||||
public string Description { get; set; } = null;
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset? EndDate { get; set; } = null;
|
||||
public decimal? TargetAmount { get; set; } = null;
|
||||
public string CustomCSSLink { get; set; } = null;
|
||||
public string MainImageUrl { get; set; } = null;
|
||||
public string EmbeddedCSS { get; set; } = null;
|
||||
public string NotificationUrl { get; set; } = null;
|
||||
public string Tagline { get; set; } = null;
|
||||
public string PerksTemplate { get; set; } = null;
|
||||
public bool? SoundsEnabled { get; set; } = null;
|
||||
public string DisqusShortname { get; set; } = null;
|
||||
public bool? AnimationsEnabled { get; set; } = null;
|
||||
public int? ResetEveryAmount { get; set; } = null;
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public CrowdfundResetEvery ResetEvery { get; set; } = CrowdfundResetEvery.Never;
|
||||
public bool? DisplayPerksValue { get; set; } = null;
|
||||
public bool? DisplayPerksRanking { get; set; } = null;
|
||||
public bool? SortPerksByPopularity { get; set; } = null;
|
||||
public string[] Sounds { get; set; } = null;
|
||||
public string[] AnimationColors { get; set; } = null;
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class CustodianData
|
||||
{
|
||||
public string Code { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string[] TradableAssetPairs { get; set; }
|
||||
public Dictionary<string, AssetPairData> TradableAssetPairs { get; set; }
|
||||
public string[] WithdrawablePaymentMethods { get; set; }
|
||||
public string[] DepositablePaymentMethods { get; set; }
|
||||
|
||||
|
@ -85,6 +85,7 @@ namespace BTCPayServer.Client.Models
|
||||
public bool? RedirectAutomatically { get; set; }
|
||||
public bool? RequiresRefundEmail { get; set; } = null;
|
||||
public string DefaultLanguage { get; set; }
|
||||
public CheckoutType? CheckoutType { get; set; }
|
||||
}
|
||||
}
|
||||
public class InvoiceData : InvoiceDataBase
|
||||
|
@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
[Obsolete]
|
||||
public class LabelData
|
||||
{
|
||||
public string Type { get; set; }
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.Lightning;
|
||||
using Newtonsoft.Json;
|
||||
@ -26,5 +27,8 @@ namespace BTCPayServer.Client.Models
|
||||
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
public LightMoney AmountReceived { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public Dictionary<ulong, string> CustomRecords { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,13 @@ namespace BTCPayServer.Client.Models
|
||||
[JsonProperty("nodeURIs", ItemConverterType = typeof(NodeUriJsonConverter))]
|
||||
public NodeInfo[] NodeURIs { get; set; }
|
||||
public int BlockHeight { get; set; }
|
||||
public string Alias { get; set; }
|
||||
public string Color { get; set; }
|
||||
public string Version { get; set; }
|
||||
public long? PeersCount { get; set; }
|
||||
public long? ActiveChannelsCount { get; set; }
|
||||
public long? InactiveChannelsCount { get; set; }
|
||||
public long? PendingChannelsCount { get; set; }
|
||||
}
|
||||
|
||||
public class LightningChannelData
|
||||
|
14
BTCPayServer.Client/Models/MarkPayoutRequest.cs
Normal file
14
BTCPayServer.Client/Models/MarkPayoutRequest.cs
Normal file
@ -0,0 +1,14 @@
|
||||
#nullable enable
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class MarkPayoutRequest
|
||||
{
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public PayoutState State { get; set; } = PayoutState.Completed;
|
||||
|
||||
public JObject? PaymentProof { get; set; }
|
||||
}
|
@ -10,4 +10,6 @@ public class OnChainAutomatedPayoutSettings
|
||||
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
|
||||
public TimeSpan IntervalSeconds { get; set; }
|
||||
|
||||
public int? FeeBlockTarget { get; set; }
|
||||
}
|
||||
|
@ -4,16 +4,94 @@ using BTCPayServer.JsonConverters;
|
||||
using NBitcoin;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class OnChainWalletObjectId
|
||||
{
|
||||
public OnChainWalletObjectId()
|
||||
{
|
||||
|
||||
}
|
||||
public OnChainWalletObjectId(string type, string id)
|
||||
{
|
||||
Type = type;
|
||||
Id = id;
|
||||
}
|
||||
public string Type { get; set; }
|
||||
public string Id { get; set; }
|
||||
}
|
||||
public class AddOnChainWalletObjectLinkRequest : OnChainWalletObjectId
|
||||
{
|
||||
public AddOnChainWalletObjectLinkRequest()
|
||||
{
|
||||
|
||||
}
|
||||
public AddOnChainWalletObjectLinkRequest(string objectType, string objectId) : base(objectType, objectId)
|
||||
{
|
||||
|
||||
}
|
||||
public JObject Data { get; set; }
|
||||
}
|
||||
|
||||
public class GetWalletObjectsRequest
|
||||
{
|
||||
public string Type { get; set; }
|
||||
public string[] Ids { get; set; }
|
||||
public bool? IncludeNeighbourData { get; set; }
|
||||
}
|
||||
|
||||
public class AddOnChainWalletObjectRequest : OnChainWalletObjectId
|
||||
{
|
||||
public AddOnChainWalletObjectRequest()
|
||||
{
|
||||
|
||||
}
|
||||
public AddOnChainWalletObjectRequest(string objectType, string objectId) : base(objectType, objectId)
|
||||
{
|
||||
|
||||
}
|
||||
public JObject Data { get; set; }
|
||||
}
|
||||
|
||||
public class OnChainWalletObjectData : OnChainWalletObjectId
|
||||
{
|
||||
public OnChainWalletObjectData()
|
||||
{
|
||||
|
||||
}
|
||||
public OnChainWalletObjectData(string type, string id) : base(type, id)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class OnChainWalletObjectLink : OnChainWalletObjectId
|
||||
{
|
||||
public OnChainWalletObjectLink()
|
||||
{
|
||||
|
||||
}
|
||||
public OnChainWalletObjectLink(string type, string id) : base(type, id)
|
||||
{
|
||||
|
||||
}
|
||||
public JObject LinkData { get; set; }
|
||||
public JObject ObjectData { get; set; }
|
||||
}
|
||||
public JObject Data { get; set; }
|
||||
public OnChainWalletObjectLink[] Links { get; set; }
|
||||
}
|
||||
|
||||
public class OnChainWalletTransactionData
|
||||
{
|
||||
[JsonConverter(typeof(UInt256JsonConverter))]
|
||||
public uint256 TransactionHash { get; set; }
|
||||
|
||||
public string Comment { get; set; }
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
public Dictionary<string, LabelData> Labels { get; set; } = new Dictionary<string, LabelData>();
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Amount { get; set; }
|
||||
|
@ -15,7 +15,9 @@ namespace BTCPayServer.Client.Models
|
||||
[JsonConverter(typeof(OutpointJsonConverter))]
|
||||
public OutPoint Outpoint { get; set; }
|
||||
public string Link { get; set; }
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
public Dictionary<string, LabelData> Labels { get; set; }
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
[JsonConverter(typeof(DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
[JsonConverter(typeof(KeyPathJsonConverter))]
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using BTCPayServer.Lightning;
|
||||
@ -19,5 +20,8 @@ namespace BTCPayServer.Client.Models
|
||||
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
public LightMoney Amount { get; set; }
|
||||
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
|
||||
public TimeSpan? SendTimeout { get; set; }
|
||||
}
|
||||
}
|
||||
|
15
BTCPayServer.Client/Models/PayPaymentRequestRequest.cs
Normal file
15
BTCPayServer.Client/Models/PayPaymentRequestRequest.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class PayPaymentRequestRequest
|
||||
{
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal? Amount { get; set; }
|
||||
public bool? AllowPendingInvoiceReuse { get; set; }
|
||||
}
|
||||
}
|
@ -24,5 +24,9 @@ namespace BTCPayServer.Client.Models
|
||||
|
||||
[JsonExtensionData]
|
||||
public IDictionary<string, JToken> AdditionalData { get; set; }
|
||||
|
||||
public string FormId { get; set; }
|
||||
|
||||
public string FormResponse { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ namespace BTCPayServer.Client.Models
|
||||
public DateTimeOffset CreatedTime { get; set; }
|
||||
public string Id { get; set; }
|
||||
public bool Archived { get; set; }
|
||||
|
||||
public enum PaymentRequestStatus
|
||||
{
|
||||
Pending = 0,
|
||||
|
@ -2,6 +2,7 @@ using System;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
@ -29,5 +30,6 @@ namespace BTCPayServer.Client.Models
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public PayoutState State { get; set; }
|
||||
public int Revision { get; set; }
|
||||
public JObject PaymentProof { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -17,4 +17,9 @@ namespace BTCPayServer.Client.Models
|
||||
{
|
||||
// We can add POS specific things here later
|
||||
}
|
||||
|
||||
public class CrowdfundAppData : AppDataBase
|
||||
{
|
||||
// We can add Crowdfund specific things here later
|
||||
}
|
||||
}
|
||||
|
7
BTCPayServer.Client/Models/RateSource.cs
Normal file
7
BTCPayServer.Client/Models/RateSource.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class RateSource
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
@ -31,6 +31,8 @@ namespace BTCPayServer.Client.Models
|
||||
public bool AnyoneCanCreateInvoice { get; set; }
|
||||
public string DefaultCurrency { get; set; }
|
||||
public bool RequiresRefundEmail { get; set; }
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public CheckoutType CheckoutType { get; set; }
|
||||
public bool LightningAmountInSatoshi { get; set; }
|
||||
public bool LightningPrivateRouteHints { get; set; }
|
||||
public bool OnChainWithLnInvoiceFallback { get; set; }
|
||||
@ -66,6 +68,12 @@ namespace BTCPayServer.Client.Models
|
||||
public IDictionary<string, JToken> AdditionalData { get; set; }
|
||||
}
|
||||
|
||||
public enum CheckoutType
|
||||
{
|
||||
V1,
|
||||
V2
|
||||
}
|
||||
|
||||
public enum NetworkFeeMode
|
||||
{
|
||||
MultiplePaymentsOnly,
|
||||
|
10
BTCPayServer.Client/Models/StoreRateConfiguration.cs
Normal file
10
BTCPayServer.Client/Models/StoreRateConfiguration.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class StoreRateConfiguration
|
||||
{
|
||||
public decimal Spread { get; set; }
|
||||
public bool IsCustomScript { get; set; }
|
||||
public string EffectiveScript { get; set; }
|
||||
public string PreferredSource { get; set; }
|
||||
}
|
||||
}
|
10
BTCPayServer.Client/Models/StoreRatePreviewResult.cs
Normal file
10
BTCPayServer.Client/Models/StoreRatePreviewResult.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class StoreRatePreviewResult
|
||||
{
|
||||
public string CurrencyPair { get; set; }
|
||||
public decimal? Rate { get; set; }
|
||||
public List<string> Errors { get; set; }
|
||||
}
|
7
BTCPayServer.Client/Models/StoreRateResult.cs
Normal file
7
BTCPayServer.Client/Models/StoreRateResult.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class StoreRateResult
|
||||
{
|
||||
public string CurrencyPair { get; set; }
|
||||
public decimal Rate { get; set; }
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public partial class BTCPayNetworkProvider
|
||||
{
|
||||
public void InitMonetaryUnit()
|
||||
{
|
||||
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("MUE");
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "MonetaryUnit",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.monetaryunit.org/#/MUE/mainnet/tx/{0}" : "https://explorer.monetaryunit.org/#/MUE/mainnet/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"MUE_X = MUE_BTC * BTC_X",
|
||||
"MUE_BTC = bittrex(MUE_BTC)"
|
||||
},
|
||||
CryptoImagePath = "imlegacy/monetaryunit.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("31'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ namespace BTCPayServer
|
||||
"LBTC_X = LBTC_BTC * BTC_X",
|
||||
"LBTC_BTC = 1",
|
||||
},
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://liquid.network/tx/{0}" : "https://liquid.network/testnet/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
CryptoImagePath = "imlegacy/liquid.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
|
@ -21,7 +21,7 @@ namespace BTCPayServer
|
||||
},
|
||||
AssetId = new uint256("ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2"),
|
||||
DisplayName = "Liquid Tether",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://liquid.network/tx/{0}" : "https://liquid.network/testnet/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
CryptoImagePath = "imlegacy/liquid-tether.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
@ -44,7 +44,7 @@ namespace BTCPayServer
|
||||
Divisibility = 2,
|
||||
AssetId = new uint256("aa775044c32a7df391902b3659f46dfe004ccb2644ce2ddc7dba31e889391caf"),
|
||||
DisplayName = "Ethiopian Birr",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://liquid.network/tx/{0}" : "https://liquid.network/testnet/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
CryptoImagePath = "imlegacy/etb.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
@ -66,7 +66,7 @@ namespace BTCPayServer
|
||||
},
|
||||
AssetId = new uint256("0e99c1a6da379d1f4151fb9df90449d40d0608f6cb33a5bcbfc8c265f42bab0a"),
|
||||
DisplayName = "Liquid CAD",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://liquid.network/tx/{0}" : "https://liquid.network/testnet/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
CryptoImagePath = "imlegacy/lcad.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
|
@ -137,6 +137,7 @@ namespace BTCPayServer
|
||||
|
||||
public string CryptoImagePath { get; set; }
|
||||
public string[] DefaultRateRules { get; set; } = Array.Empty<string>();
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return CryptoCode;
|
||||
|
@ -13,9 +13,9 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Bitcoin",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/tx/{0}" :
|
||||
NetworkType == Bitcoin.Instance.Signet.ChainName ? "https://explorer.bc-2.jp/tx/{0}"
|
||||
: "https://blockstream.info/testnet/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://mempool.space/tx/{0}" :
|
||||
NetworkType == Bitcoin.Instance.Signet.ChainName ? "https://mempool.space/signet/tx/{0}"
|
||||
: "https://mempool.space/testnet/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
CryptoImagePath = "imlegacy/bitcoin.svg",
|
||||
LightningImagePath = "imlegacy/bitcoin-lightning.svg",
|
||||
|
@ -58,7 +58,7 @@ namespace BTCPayServer
|
||||
InitZcash();
|
||||
InitChaincoin();
|
||||
// InitArgoneum();//their rate source is down 9/15/20.
|
||||
InitMonetaryUnit();
|
||||
// InitMonetaryUnit(); Not supported from Bittrex from 11/23/2022, dead shitcoin
|
||||
|
||||
// Assume that electrum mappings are same as BTC if not specified
|
||||
foreach (var network in _Networks.Values.OfType<BTCPayNetwork>())
|
||||
|
@ -4,7 +4,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="4.2.0" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="4.2.1" />
|
||||
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.18" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Altcoins)' != 'true'">
|
||||
<Compile Remove="Altcoins\**\*.cs"></Compile>
|
||||
|
@ -180,7 +180,7 @@ namespace BTCPayServer.Logging
|
||||
logBuilder.Append(": ");
|
||||
var lenAfter = logBuilder.ToString().Length;
|
||||
while (lenAfter++ < 18)
|
||||
logBuilder.Append(" ");
|
||||
logBuilder.Append(' ');
|
||||
// scope information
|
||||
GetScopeInformation(logBuilder);
|
||||
|
||||
|
@ -59,7 +59,11 @@ namespace BTCPayServer.Data
|
||||
public DbSet<U2FDevice> U2FDevices { get; set; }
|
||||
public DbSet<Fido2Credential> Fido2Credentials { get; set; }
|
||||
public DbSet<UserStore> UserStore { get; set; }
|
||||
[Obsolete]
|
||||
public DbSet<WalletData> Wallets { get; set; }
|
||||
public DbSet<WalletObjectData> WalletObjects { get; set; }
|
||||
public DbSet<WalletObjectLinkData> WalletObjectLinks { get; set; }
|
||||
[Obsolete]
|
||||
public DbSet<WalletTransactionData> WalletTransactions { get; set; }
|
||||
public DbSet<WebhookDeliveryData> WebhookDeliveries { get; set; }
|
||||
public DbSet<WebhookData> Webhooks { get; set; }
|
||||
@ -109,7 +113,11 @@ namespace BTCPayServer.Data
|
||||
Fido2Credential.OnModelCreating(builder);
|
||||
BTCPayServer.Data.UserStore.OnModelCreating(builder);
|
||||
//WalletData.OnModelCreating(builder);
|
||||
WalletObjectData.OnModelCreating(builder, Database);
|
||||
WalletObjectLinkData.OnModelCreating(builder, Database);
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
WalletTransactionData.OnModelCreating(builder);
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
WebhookDeliveryData.OnModelCreating(builder);
|
||||
LightningAddressData.OnModelCreating(builder);
|
||||
PayoutProcessorData.OnModelCreating(builder);
|
||||
|
@ -3,11 +3,11 @@
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.7">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.9">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.9" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
|
||||
|
12
BTCPayServer.Data/Data/FormData.cs
Normal file
12
BTCPayServer.Data/Data/FormData.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
namespace BTCPayServer.Data.Data;
|
||||
|
||||
public class FormData
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Config { get; set; }
|
||||
}
|
@ -21,4 +21,9 @@ public class PayoutProcessorData
|
||||
.HasOne(o => o.Store)
|
||||
.WithMany(data => data.PayoutProcessors).OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Processor} {PaymentMethod} {StoreId}";
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
[Obsolete]
|
||||
public class WalletData
|
||||
{
|
||||
[System.ComponentModel.DataAnnotations.Key]
|
||||
|
87
BTCPayServer.Data/Data/WalletObjectData.cs
Normal file
87
BTCPayServer.Data/Data/WalletObjectData.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class WalletObjectData
|
||||
{
|
||||
public class Types
|
||||
{
|
||||
public const string Label = "label";
|
||||
public const string Tx = "tx";
|
||||
public const string Payjoin = "payjoin";
|
||||
public const string Invoice = "invoice";
|
||||
public const string PaymentRequest = "payment-request";
|
||||
public const string App = "app";
|
||||
public const string PayjoinExposed = "pj-exposed";
|
||||
public const string Payout = "payout";
|
||||
public const string PullPayment = "pull-payment";
|
||||
}
|
||||
public string WalletId { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Data { get; set; }
|
||||
|
||||
public List<WalletObjectLinkData> ChildLinks { get; set; }
|
||||
public List<WalletObjectLinkData> ParentLinks { get; set; }
|
||||
|
||||
public IEnumerable<(string type, string id, string linkdata, string objectdata)> GetLinks()
|
||||
{
|
||||
if (ChildLinks is not null)
|
||||
foreach (var c in ChildLinks)
|
||||
{
|
||||
yield return (c.ChildType, c.ChildId, c.Data, c.Child?.Data);
|
||||
}
|
||||
if (ParentLinks is not null)
|
||||
foreach (var c in ParentLinks)
|
||||
{
|
||||
yield return (c.ParentType, c.ParentId, c.Data, c.Parent?.Data);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<WalletObjectData> GetNeighbours()
|
||||
{
|
||||
if (ChildLinks != null)
|
||||
foreach (var c in ChildLinks)
|
||||
{
|
||||
if (c.Child != null)
|
||||
yield return c.Child;
|
||||
}
|
||||
if (ParentLinks != null)
|
||||
foreach (var c in ParentLinks)
|
||||
{
|
||||
if (c.Parent != null)
|
||||
yield return c.Parent;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
|
||||
{
|
||||
builder.Entity<WalletObjectData>().HasKey(o =>
|
||||
new
|
||||
{
|
||||
o.WalletId,
|
||||
o.Type,
|
||||
o.Id,
|
||||
});
|
||||
builder.Entity<WalletObjectData>().HasIndex(o =>
|
||||
new
|
||||
{
|
||||
o.Type,
|
||||
o.Id
|
||||
});
|
||||
|
||||
if (databaseFacade.IsNpgsql())
|
||||
{
|
||||
builder.Entity<WalletObjectData>()
|
||||
.Property(o => o.Data)
|
||||
.HasColumnType("JSONB");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
61
BTCPayServer.Data/Data/WalletObjectLinkData.cs
Normal file
61
BTCPayServer.Data/Data/WalletObjectLinkData.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class WalletObjectLinkData
|
||||
{
|
||||
public string WalletId { get; set; }
|
||||
public string ParentType { get; set; }
|
||||
public string ParentId { get; set; }
|
||||
public string ChildType { get; set; }
|
||||
public string ChildId { get; set; }
|
||||
public string Data { get; set; }
|
||||
|
||||
public WalletObjectData Parent { get; set; }
|
||||
public WalletObjectData Child { get; set; }
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
|
||||
{
|
||||
builder.Entity<WalletObjectLinkData>().HasKey(o =>
|
||||
new
|
||||
{
|
||||
o.WalletId,
|
||||
o.ParentType,
|
||||
o.ParentId,
|
||||
o.ChildType,
|
||||
o.ChildId,
|
||||
});
|
||||
builder.Entity<WalletObjectLinkData>().HasIndex(o => new
|
||||
{
|
||||
o.WalletId,
|
||||
o.ChildType,
|
||||
o.ChildId,
|
||||
});
|
||||
|
||||
builder.Entity<WalletObjectLinkData>()
|
||||
.HasOne(o => o.Parent)
|
||||
.WithMany(o => o.ChildLinks)
|
||||
.HasForeignKey(o => new { o.WalletId, o.ParentType, o.ParentId })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<WalletObjectLinkData>()
|
||||
.HasOne(o => o.Child)
|
||||
.WithMany(o => o.ParentLinks)
|
||||
.HasForeignKey(o => new { o.WalletId, o.ChildType, o.ChildId })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
if (databaseFacade.IsNpgsql())
|
||||
{
|
||||
builder.Entity<WalletObjectLinkData>()
|
||||
.Property(o => o.Data)
|
||||
.HasColumnType("JSONB");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
[Obsolete]
|
||||
public class WalletTransactionData
|
||||
{
|
||||
public string WalletDataId { get; set; }
|
||||
|
81
BTCPayServer.Data/Migrations/20220929132704_label.cs
Normal file
81
BTCPayServer.Data/Migrations/20220929132704_label.cs
Normal file
@ -0,0 +1,81 @@
|
||||
// <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;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20220929132704_label")]
|
||||
public partial class label : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "WalletObjects",
|
||||
columns: table => new
|
||||
{
|
||||
WalletId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Type = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Data = table.Column<string>(type: migrationBuilder.IsNpgsql() ? "JSONB" : "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_WalletObjects", x => new { x.WalletId, x.Type, x.Id });
|
||||
});
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WalletObjects_Type_Id",
|
||||
table: "WalletObjects",
|
||||
columns: new[] { "Type", "Id" });
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "WalletObjectLinks",
|
||||
columns: table => new
|
||||
{
|
||||
WalletId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ParentType = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ParentId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ChildType = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ChildId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Data = table.Column<string>(type: migrationBuilder.IsNpgsql() ? "JSONB" : "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_WalletObjectLinks", x => new { x.WalletId, x.ParentType, x.ParentId, x.ChildType, x.ChildId });
|
||||
table.ForeignKey(
|
||||
name: "FK_WalletObjectLinks_WalletObjects_WalletId_ChildType_ChildId",
|
||||
columns: x => new { x.WalletId, x.ChildType, x.ChildId },
|
||||
principalTable: "WalletObjects",
|
||||
principalColumns: new[] { "WalletId", "Type", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_WalletObjectLinks_WalletObjects_WalletId_ParentType_ParentId",
|
||||
columns: x => new { x.WalletId, x.ParentType, x.ParentId },
|
||||
principalTable: "WalletObjects",
|
||||
principalColumns: new[] { "WalletId", "Type", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WalletObjectLinks_WalletId_ChildType_ChildId",
|
||||
table: "WalletObjectLinks",
|
||||
columns: new[] { "WalletId", "ChildType", "ChildId" });
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "WalletObjectLinks");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "WalletObjects");
|
||||
}
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ namespace BTCPayServer.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.1");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.9");
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
@ -189,6 +189,7 @@ namespace BTCPayServer.Migrations
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@ -845,6 +846,54 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("Wallets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WalletObjectData", b =>
|
||||
{
|
||||
b.Property<string>("WalletId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Data")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("WalletId", "Type", "Id");
|
||||
|
||||
b.HasIndex("Type", "Id");
|
||||
|
||||
b.ToTable("WalletObjects");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WalletObjectLinkData", b =>
|
||||
{
|
||||
b.Property<string>("WalletId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ParentType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ParentId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ChildType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ChildId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Data")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("WalletId", "ParentType", "ParentId", "ChildType", "ChildId");
|
||||
|
||||
b.HasIndex("WalletId", "ChildType", "ChildId");
|
||||
|
||||
b.ToTable("WalletObjectLinks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WalletTransactionData", b =>
|
||||
{
|
||||
b.Property<string>("WalletDataId")
|
||||
@ -1333,6 +1382,25 @@ namespace BTCPayServer.Migrations
|
||||
b.Navigation("StoreData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WalletObjectLinkData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.WalletObjectData", "Child")
|
||||
.WithMany("ParentLinks")
|
||||
.HasForeignKey("WalletId", "ChildType", "ChildId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BTCPayServer.Data.WalletObjectData", "Parent")
|
||||
.WithMany("ChildLinks")
|
||||
.HasForeignKey("WalletId", "ParentType", "ParentId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Child");
|
||||
|
||||
b.Navigation("Parent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WalletTransactionData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.WalletData", "WalletData")
|
||||
@ -1475,6 +1543,13 @@ namespace BTCPayServer.Migrations
|
||||
b.Navigation("WalletTransactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WalletObjectData", b =>
|
||||
{
|
||||
b.Navigation("ChildLinks");
|
||||
|
||||
b.Navigation("ParentLinks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WebhookData", b =>
|
||||
{
|
||||
b.Navigation("Deliveries");
|
||||
|
@ -26,6 +26,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="McMaster.NETCore.Plugins.Mvc" Version="1.4.0" />
|
||||
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
|
||||
<None Include="icon.png" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
@ -8,6 +8,7 @@ using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using McMaster.NETCore.Plugins;
|
||||
using NBitcoin.Crypto;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitcoin.Secp256k1;
|
||||
@ -33,7 +34,8 @@ namespace BTCPayServer.PluginPacker
|
||||
throw new Exception($"{rootDLLPath} could not be found");
|
||||
}
|
||||
|
||||
var assembly = Assembly.LoadFrom(rootDLLPath);
|
||||
var plugin = PluginLoader.CreateFromAssemblyFile(rootDLLPath, false, new[] { typeof(IBTCPayServerPlugin) });
|
||||
var assembly = plugin.LoadAssembly(name);
|
||||
var extension = GetAllExtensionTypesFromAssembly(assembly).FirstOrDefault();
|
||||
if (extension is null)
|
||||
{
|
||||
|
@ -13,7 +13,7 @@
|
||||
<EmbeddedResource Include="Resources\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.7">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.9">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
@ -4,9 +4,9 @@
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" />
|
||||
<PackageReference Include="NBitcoin" Version="7.0.10" />
|
||||
<PackageReference Include="NBitcoin" Version="7.0.14" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="1.0.2" />
|
||||
</ItemGroup>
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Services.Rates;
|
||||
|
||||
namespace BTCPayServer.Rating
|
||||
@ -56,6 +57,13 @@ namespace BTCPayServer.Rating
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (splitted.Length > 2)
|
||||
{
|
||||
// Some shitcoin have _ their own ticker name... Since we don't care about those, let's
|
||||
// parse it anyway assuming the first part is one currency.
|
||||
value = new CurrencyPair(splitted[0], string.Join("_", splitted.Skip(1).ToArray()));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -44,9 +44,6 @@ namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
if (notFoundSymbols.TryGetValue(ticker.Key, out _))
|
||||
return null;
|
||||
if (ticker.Key.Contains("XMR"))
|
||||
{
|
||||
}
|
||||
try
|
||||
{
|
||||
CurrencyPair pair;
|
||||
|
@ -168,7 +168,7 @@ namespace BTCPayServer.Services.Rates
|
||||
sb.Append(url);
|
||||
if (payload != null)
|
||||
{
|
||||
sb.Append("?");
|
||||
sb.Append('?');
|
||||
sb.Append(String.Join('&', payload.Select(kv => $"{kv.Key}={kv.Value}").OfType<object>().ToArray()));
|
||||
}
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, sb.ToString());
|
||||
@ -177,9 +177,15 @@ namespace BTCPayServer.Services.Rates
|
||||
var result = JsonConvert.DeserializeObject<T>(stringResult);
|
||||
if (result is JToken json)
|
||||
{
|
||||
if (!(json is JArray) && json["result"] is JObject {Count: > 0} pairResult)
|
||||
{
|
||||
return (T)(object)(pairResult);
|
||||
}
|
||||
|
||||
if (!(json is JArray) && json["error"] is JArray error && error.Count != 0)
|
||||
{
|
||||
throw new APIException(error[0].ToStringInvariant());
|
||||
throw new APIException(string.Join("\n",
|
||||
error.Select(token => token.ToStringInvariant()).Distinct()));
|
||||
}
|
||||
result = (T)(object)(json["result"] ?? json);
|
||||
}
|
||||
|
42
BTCPayServer.Rating/Providers/YadioRateProvider.cs
Normal file
42
BTCPayServer.Rating/Providers/YadioRateProvider.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;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
public class YadioRateProvider : IRateProvider
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
public YadioRateProvider(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
}
|
||||
|
||||
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _httpClient.GetAsync("https://api.yadio.io/exrates/BTC", cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
|
||||
var results = jobj["BTC"];
|
||||
var list = new List<PairRate>();
|
||||
foreach (var item in results)
|
||||
{
|
||||
|
||||
string name = ((JProperty)item).Name;
|
||||
int value = results[name].Value<int>();
|
||||
|
||||
list.Add(new PairRate(new CurrencyPair("BTC", name), new BidAsk(value)));
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
@ -84,6 +84,7 @@ namespace BTCPayServer.Services.Rates
|
||||
yield return new AvailableRateProvider("coinbasepro", "Coinbase Pro", "https://api.pro.coinbase.com/products");
|
||||
|
||||
yield return new AvailableRateProvider("argoneum", "Argoneum", "https://rates.argoneum.net/rates");
|
||||
yield return new AvailableRateProvider("yadio", "Yadio", "https://api.yadio.io/exrates/BTC");
|
||||
}
|
||||
void InitExchanges()
|
||||
{
|
||||
@ -104,6 +105,7 @@ namespace BTCPayServer.Services.Rates
|
||||
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("yadio", new YadioRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_YADIO")));
|
||||
// Providers.Add("argoneum", new ArgoneumRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_ARGONEUM")));
|
||||
|
||||
|
||||
|
@ -11,6 +11,8 @@ using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Plugins.PointOfSale.Controllers;
|
||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
@ -617,6 +619,7 @@ namespace BTCPayServer.Tests
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
user.RegisterDerivationScheme("LTC");
|
||||
var apps = user.GetController<UIAppsController>();
|
||||
var pos = user.GetController<UIPointOfSaleController>();
|
||||
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
|
||||
var appType = AppType.PointOfSale.ToString();
|
||||
vm.AppName = "test";
|
||||
@ -624,8 +627,10 @@ namespace BTCPayServer.Tests
|
||||
Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
|
||||
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
|
||||
var app = appList.Apps[0];
|
||||
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType });
|
||||
var vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType };
|
||||
apps.HttpContext.SetAppData(appData);
|
||||
pos.HttpContext.SetAppData(appData);
|
||||
var vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
vmpos.Title = "hello";
|
||||
vmpos.Currency = "CAD";
|
||||
vmpos.ButtonText = "{0} Purchase";
|
||||
@ -642,11 +647,11 @@ donation:
|
||||
price: 1.02
|
||||
custom: true
|
||||
";
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
Assert.Equal("hello", vmpos.Title);
|
||||
|
||||
var publicApps = user.GetController<UIAppsPublicController>();
|
||||
var publicApps = user.GetController<UIPointOfSaleController>();
|
||||
var vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync<ViewPointOfSaleViewModel>();
|
||||
Assert.Equal("hello", vmview.Title);
|
||||
Assert.Equal(3, vmview.Items.Length);
|
||||
@ -698,7 +703,7 @@ donation:
|
||||
})
|
||||
{
|
||||
TestLogs.LogInformation($"Testing for {test.Code}");
|
||||
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
vmpos.Title = "hello";
|
||||
vmpos.Currency = test.Item1;
|
||||
vmpos.ButtonText = "{0} Purchase";
|
||||
@ -714,8 +719,8 @@ donation:
|
||||
price: 1.02
|
||||
custom: true
|
||||
";
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
publicApps = user.GetController<UIAppsPublicController>();
|
||||
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
publicApps = user.GetController<UIPointOfSaleController>();
|
||||
vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync<ViewPointOfSaleViewModel>();
|
||||
Assert.Equal(test.Code, vmview.CurrencyCode);
|
||||
Assert.Equal(test.ExpectedSymbol,
|
||||
@ -731,7 +736,7 @@ donation:
|
||||
}
|
||||
|
||||
//test inventory related features
|
||||
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
vmpos.Title = "hello";
|
||||
vmpos.Currency = "BTC";
|
||||
vmpos.Template = @"
|
||||
@ -741,7 +746,7 @@ inventoryitem:
|
||||
inventory: 1
|
||||
noninventoryitem:
|
||||
price: 10.0";
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
|
||||
//inventoryitem has 1 item available
|
||||
await tester.WaitForEvent<AppInventoryUpdaterHostedService.UpdateAppInventory>(() =>
|
||||
@ -777,13 +782,13 @@ noninventoryitem:
|
||||
//check that item is back in stock
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
Assert.Equal(1,
|
||||
appService.Parse(vmpos.Template, "BTC").Single(item => item.Id == "inventoryitem").Inventory);
|
||||
}, 10000);
|
||||
|
||||
//test payment methods option
|
||||
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
vmpos.Title = "hello";
|
||||
vmpos.Currency = "BTC";
|
||||
vmpos.Template = @"
|
||||
@ -794,7 +799,7 @@ btconly:
|
||||
- BTC
|
||||
normal:
|
||||
price: 1.0";
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, null, null, null, null, "btconly").Result);
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
@ -838,8 +843,8 @@ g:
|
||||
custom: topup
|
||||
";
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
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);
|
||||
|
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
@ -19,12 +19,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
|
||||
<PackageReference Include="Selenium.Support" Version="4.1.1" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="4.1.1" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="103.0.5060.5300" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="105.0.5195.5200" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
|
@ -30,7 +30,7 @@ namespace BTCPayServer.Tests
|
||||
s.AddDerivationScheme();
|
||||
s.GoToStore(StoreNavPages.CheckoutAppearance);
|
||||
s.Driver.FindElement(By.Id("RequiresRefundEmail")).Click();
|
||||
s.Driver.FindElement(By.Name("command")).Click();
|
||||
s.Driver.FindElement(By.Id("Save")).Click();
|
||||
|
||||
var emailAlreadyThereInvoiceId = s.CreateInvoice(100, "USD", "a@g.com");
|
||||
s.GoToInvoiceCheckout(emailAlreadyThereInvoiceId);
|
||||
|
212
BTCPayServer.Tests/Checkoutv2Tests.cs
Normal file
212
BTCPayServer.Tests/Checkoutv2Tests.cs
Normal file
@ -0,0 +1,212 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Views.Stores;
|
||||
using NBitcoin;
|
||||
using OpenQA.Selenium;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
[Trait("Selenium", "Selenium")]
|
||||
[Collection(nameof(NonParallelizableCollectionDefinition))]
|
||||
public class CheckoutV2Tests : UnitTestBase
|
||||
{
|
||||
private const int TestTimeout = TestUtils.TestTimeout;
|
||||
|
||||
public CheckoutV2Tests(ITestOutputHelper helper) : base(helper)
|
||||
{
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanConfigureCheckout()
|
||||
{
|
||||
using var s = CreateSeleniumTester();
|
||||
s.Server.ActivateLightning();
|
||||
await s.StartAsync();
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser(true);
|
||||
s.CreateNewStore();
|
||||
s.EnableCheckoutV2();
|
||||
s.AddLightningNode();
|
||||
s.AddDerivationScheme();
|
||||
|
||||
// Configure store url
|
||||
var storeUrl = "https://satoshisteaks.com/";
|
||||
s.GoToStore();
|
||||
s.Driver.FindElement(By.Id("StoreWebsite")).SendKeys(storeUrl);
|
||||
s.Driver.FindElement(By.Id("Save")).Click();
|
||||
Assert.Contains("Store successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
// Default payment method
|
||||
var invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC_LightningLike");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
|
||||
Assert.Equal(2, s.Driver.FindElements(By.CssSelector(".payment-method")).Count);
|
||||
Assert.Contains("Lightning", s.Driver.WaitForElement(By.CssSelector(".payment-method.active")).Text);
|
||||
Assert.DoesNotContain("LNURL", s.Driver.PageSource);
|
||||
var payUrl = s.Driver.FindElement(By.CssSelector(".btn-primary")).GetAttribute("href");
|
||||
Assert.StartsWith("lightning:", payUrl);
|
||||
|
||||
// Lightning amount in Sats
|
||||
Assert.Contains("BTC", s.Driver.FindElement(By.Id("AmountDue")).Text);
|
||||
s.GoToHome();
|
||||
s.GoToLightningSettings();
|
||||
s.Driver.SetCheckbox(By.Id("LightningAmountInSatoshi"), true);
|
||||
s.Driver.FindElement(By.Id("save")).Click();
|
||||
Assert.Contains("BTC Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
Assert.Contains("Sats", s.Driver.FindElement(By.Id("AmountDue")).Text);
|
||||
|
||||
// Expire
|
||||
var expirySeconds = s.Driver.FindElement(By.Id("ExpirySeconds"));
|
||||
expirySeconds.Clear();
|
||||
expirySeconds.SendKeys("3");
|
||||
s.Driver.FindElement(By.Id("Expire")).Click();
|
||||
|
||||
var paymentInfo = s.Driver.WaitForElement(By.Id("PaymentInfo"));
|
||||
Assert.Contains("This invoice will expire in", paymentInfo.Text);
|
||||
Assert.DoesNotContain("Please send", paymentInfo.Text);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
var expiredSection = s.Driver.FindElement(By.Id("expired"));
|
||||
Assert.True(expiredSection.Displayed);
|
||||
Assert.Contains("Invoice Expired", expiredSection.Text);
|
||||
});
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ReceiptLink")));
|
||||
Assert.Equal(storeUrl, s.Driver.FindElement(By.Id("StoreLink")).GetAttribute("href"));
|
||||
|
||||
// Test payment
|
||||
s.GoToHome();
|
||||
invoiceId = s.CreateInvoice();
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
|
||||
// Details
|
||||
s.Driver.ToggleCollapse("PaymentDetails");
|
||||
var details = s.Driver.FindElement(By.CssSelector(".payment-details"));
|
||||
Assert.Contains("Total Price", details.Text);
|
||||
Assert.Contains("Total Fiat", details.Text);
|
||||
Assert.Contains("Exchange Rate", details.Text);
|
||||
Assert.Contains("Amount Due", details.Text);
|
||||
Assert.Contains("Recommended Fee", details.Text);
|
||||
|
||||
// Pay partial amount
|
||||
await Task.Delay(200);
|
||||
var address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-destination");
|
||||
var amountFraction = "0.00001";
|
||||
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(address, Network.RegTest),
|
||||
Money.Parse(amountFraction));
|
||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||
|
||||
// Fake Pay
|
||||
s.Driver.FindElement(By.Id("FakePayAmount")).FillIn(amountFraction);
|
||||
s.Driver.FindElement(By.Id("FakePay")).Click();
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
Assert.Contains("Created transaction",
|
||||
s.Driver.WaitForElement(By.Id("CheatSuccessMessage")).Text);
|
||||
s.Server.ExplorerNode.Generate(1);
|
||||
paymentInfo = s.Driver.WaitForElement(By.Id("PaymentInfo"));
|
||||
Assert.Contains("The invoice hasn't been paid in full", paymentInfo.Text);
|
||||
Assert.Contains("Please send", paymentInfo.Text);
|
||||
});
|
||||
|
||||
// Mine
|
||||
s.Driver.FindElement(By.Id("Mine")).Click();
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
Assert.Contains("Mined 1 block",
|
||||
s.Driver.WaitForElement(By.Id("CheatSuccessMessage")).Text);
|
||||
});
|
||||
|
||||
// Pay full amount
|
||||
var amountDue = s.Driver.FindElement(By.Id("AmountDue")).GetAttribute("data-amount-due");
|
||||
s.Driver.FindElement(By.Id("FakePayAmount")).FillIn(amountDue);
|
||||
s.Driver.FindElement(By.Id("FakePay")).Click();
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.Server.ExplorerNode.Generate(1);
|
||||
var paidSection = s.Driver.WaitForElement(By.Id("paid"));
|
||||
Assert.True(paidSection.Displayed);
|
||||
Assert.Contains("Invoice Paid", paidSection.Text);
|
||||
});
|
||||
s.Driver.FindElement(By.Id("ReceiptLink"));
|
||||
Assert.Equal(storeUrl, s.Driver.FindElement(By.Id("StoreLink")).GetAttribute("href"));
|
||||
|
||||
// BIP21
|
||||
s.GoToHome();
|
||||
s.GoToStore(StoreNavPages.CheckoutAppearance);
|
||||
s.Driver.SetCheckbox(By.Id("OnChainWithLnInvoiceFallback"), true);
|
||||
s.Driver.FindElement(By.Id("Save")).Click();
|
||||
Assert.Contains("Store successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
invoiceId = s.CreateInvoice();
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
Assert.Empty(s.Driver.FindElements(By.CssSelector(".payment-method")));
|
||||
payUrl = s.Driver.FindElement(By.CssSelector(".btn-primary")).GetAttribute("href");
|
||||
Assert.StartsWith("bitcoin:", payUrl);
|
||||
Assert.Contains("&LIGHTNING=", payUrl);
|
||||
|
||||
// BIP21 with topup invoice (which is only available with Bitcoin onchain)
|
||||
s.GoToHome();
|
||||
invoiceId = s.CreateInvoice(amount: null);
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
Assert.Empty(s.Driver.FindElements(By.CssSelector(".payment-method")));
|
||||
payUrl = s.Driver.FindElement(By.CssSelector(".btn-primary")).GetAttribute("href");
|
||||
Assert.StartsWith("bitcoin:", payUrl);
|
||||
Assert.DoesNotContain("&LIGHTNING=", payUrl);
|
||||
|
||||
// Expiry message should not show amount for topup invoice
|
||||
expirySeconds = s.Driver.FindElement(By.Id("ExpirySeconds"));
|
||||
expirySeconds.Clear();
|
||||
expirySeconds.SendKeys("5");
|
||||
s.Driver.FindElement(By.Id("Expire")).Click();
|
||||
|
||||
paymentInfo = s.Driver.WaitForElement(By.Id("PaymentInfo"));
|
||||
Assert.Contains("This invoice will expire in", paymentInfo.Text);
|
||||
Assert.DoesNotContain("Please send", paymentInfo.Text);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanUseCheckoutAsModal()
|
||||
{
|
||||
using var s = CreateSeleniumTester();
|
||||
await s.StartAsync();
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser();
|
||||
s.CreateNewStore();
|
||||
s.EnableCheckoutV2();
|
||||
s.GoToStore();
|
||||
s.AddDerivationScheme();
|
||||
var invoiceId = s.CreateInvoice(0.001m, "BTC", "a@x.com");
|
||||
var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
|
||||
s.Driver.Navigate()
|
||||
.GoToUrl(new Uri(s.ServerUri, $"tests/index.html?invoice={invoiceId}"));
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
Assert.True(s.Driver.FindElement(By.Name("btcpay")).Displayed);
|
||||
});
|
||||
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(invoice
|
||||
.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike))
|
||||
.GetPaymentMethodDetails().GetPaymentDestination(), Network.RegTest),
|
||||
new Money(0.001m, MoneyUnit.BTC));
|
||||
|
||||
IWebElement closebutton = null;
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
var frameElement = s.Driver.FindElement(By.Name("btcpay"));
|
||||
var iframe = s.Driver.SwitchTo().Frame(frameElement);
|
||||
closebutton = iframe.FindElement(By.Id("close"));
|
||||
Assert.True(closebutton.Displayed);
|
||||
});
|
||||
closebutton.Click();
|
||||
s.Driver.AssertElementNotFound(By.Name("btcpay"));
|
||||
Assert.Equal(s.Driver.Url,
|
||||
new Uri(s.ServerUri, $"tests/index.html?invoice={invoiceId}").ToString());
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@ using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Plugins.Crowdfund.Controllers;
|
||||
using BTCPayServer.Plugins.Crowdfund.Models;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
@ -35,6 +37,7 @@ namespace BTCPayServer.Tests
|
||||
var stores = user.GetController<UIStoresController>();
|
||||
var apps = user.GetController<UIAppsController>();
|
||||
var apps2 = user2.GetController<UIAppsController>();
|
||||
var crowdfund = user.GetController<UICrowdfundController>();
|
||||
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
|
||||
var appType = AppType.Crowdfund.ToString();
|
||||
Assert.NotNull(vm.SelectedAppType);
|
||||
@ -42,10 +45,12 @@ namespace BTCPayServer.Tests
|
||||
vm.AppName = "test";
|
||||
vm.SelectedAppType = appType;
|
||||
var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
|
||||
Assert.Equal(nameof(apps.UpdateCrowdfund), redirectToAction.ActionName);
|
||||
Assert.Equal(nameof(crowdfund.UpdateCrowdfund), redirectToAction.ActionName);
|
||||
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
|
||||
var app = appList.Apps[0];
|
||||
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType});
|
||||
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType };
|
||||
apps.HttpContext.SetAppData(appData);
|
||||
crowdfund.HttpContext.SetAppData(appData);
|
||||
var appList2 =
|
||||
Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps2.ListApps(user2.StoreId).Result).Model);
|
||||
Assert.Single(appList.Apps);
|
||||
@ -72,6 +77,7 @@ namespace BTCPayServer.Tests
|
||||
await user.GrantAccessAsync();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
var apps = user.GetController<UIAppsController>();
|
||||
var crowdfund = user.GetController<UICrowdfundController>();
|
||||
var vm = apps.CreateApp(user.StoreId).AssertViewModel<CreateAppViewModel>();
|
||||
var appType = AppType.Crowdfund.ToString();
|
||||
vm.AppName = "test";
|
||||
@ -79,18 +85,20 @@ namespace BTCPayServer.Tests
|
||||
Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
|
||||
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
|
||||
var app = appList.Apps[0];
|
||||
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType });
|
||||
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType };
|
||||
apps.HttpContext.SetAppData(appData);
|
||||
crowdfund.HttpContext.SetAppData(appData);
|
||||
|
||||
//Scenario 1: Not Enabled - Not Allowed
|
||||
var crowdfundViewModel = await apps.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>();
|
||||
var crowdfundViewModel = await crowdfund.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>();
|
||||
crowdfundViewModel.TargetCurrency = "BTC";
|
||||
crowdfundViewModel.Enabled = false;
|
||||
crowdfundViewModel.EndDate = null;
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
|
||||
var anonAppPubsController = tester.PayTester.GetController<UIAppsPublicController>();
|
||||
var publicApps = user.GetController<UIAppsPublicController>();
|
||||
var anonAppPubsController = tester.PayTester.GetController<UICrowdfundController>();
|
||||
var crowdfundController = user.GetController<UICrowdfundController>();
|
||||
|
||||
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
|
||||
{
|
||||
@ -100,19 +108,19 @@ namespace BTCPayServer.Tests
|
||||
Assert.IsType<NotFoundResult>(await anonAppPubsController.ViewCrowdfund(app.Id, string.Empty));
|
||||
|
||||
//Scenario 2: Not Enabled But Admin - Allowed
|
||||
Assert.IsType<OkObjectResult>(await publicApps.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
|
||||
Assert.IsType<OkObjectResult>(await crowdfundController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
|
||||
{
|
||||
RedirectToCheckout = false,
|
||||
Amount = new decimal(0.01)
|
||||
}, default));
|
||||
Assert.IsType<ViewResult>(await publicApps.ViewCrowdfund(app.Id, string.Empty));
|
||||
Assert.IsType<ViewResult>(await crowdfundController.ViewCrowdfund(app.Id, string.Empty));
|
||||
Assert.IsType<NotFoundResult>(await anonAppPubsController.ViewCrowdfund(app.Id, string.Empty));
|
||||
|
||||
//Scenario 3: Enabled But Start Date > Now - Not Allowed
|
||||
crowdfundViewModel.StartDate = DateTime.Today.AddDays(2);
|
||||
crowdfundViewModel.Enabled = true;
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
|
||||
{
|
||||
Amount = new decimal(0.01)
|
||||
@ -123,7 +131,7 @@ namespace BTCPayServer.Tests
|
||||
crowdfundViewModel.EndDate = DateTime.Today.AddDays(-1);
|
||||
crowdfundViewModel.Enabled = true;
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
|
||||
{
|
||||
Amount = new decimal(0.01)
|
||||
@ -136,7 +144,7 @@ namespace BTCPayServer.Tests
|
||||
crowdfundViewModel.TargetAmount = 1;
|
||||
crowdfundViewModel.TargetCurrency = "BTC";
|
||||
crowdfundViewModel.EnforceTargetAmount = true;
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
|
||||
{
|
||||
Amount = new decimal(1.01)
|
||||
@ -160,6 +168,7 @@ namespace BTCPayServer.Tests
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
await user.SetNetworkFeeMode(NetworkFeeMode.Never);
|
||||
var apps = user.GetController<UIAppsController>();
|
||||
var crowdfund = user.GetController<UICrowdfundController>();
|
||||
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
|
||||
var appType = AppType.Crowdfund.ToString();
|
||||
vm.AppName = "test";
|
||||
@ -167,20 +176,21 @@ namespace BTCPayServer.Tests
|
||||
Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
|
||||
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
|
||||
var app = appList.Apps[0];
|
||||
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType });
|
||||
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType };
|
||||
apps.HttpContext.SetAppData(appData);
|
||||
crowdfund.HttpContext.SetAppData(appData);
|
||||
|
||||
TestLogs.LogInformation("We create an invoice with a hardcap");
|
||||
var crowdfundViewModel = await apps.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>();
|
||||
var crowdfundViewModel = await crowdfund.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>();
|
||||
crowdfundViewModel.Enabled = true;
|
||||
crowdfundViewModel.EndDate = null;
|
||||
crowdfundViewModel.TargetAmount = 100;
|
||||
crowdfundViewModel.TargetCurrency = "BTC";
|
||||
crowdfundViewModel.UseAllStoreInvoices = true;
|
||||
crowdfundViewModel.EnforceTargetAmount = true;
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
|
||||
var anonAppPubsController = tester.PayTester.GetController<UIAppsPublicController>();
|
||||
var publicApps = user.GetController<UIAppsPublicController>();
|
||||
var publicApps = user.GetController<UICrowdfundController>();
|
||||
|
||||
var model = Assert.IsType<ViewCrowdfundViewModel>(Assert
|
||||
.IsType<ViewResult>(publicApps.ViewCrowdfund(app.Id, String.Empty).Result).Model);
|
||||
@ -232,7 +242,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains(AppService.GetAppInternalTag(app.Id), invoiceEntity.InternalTags);
|
||||
|
||||
crowdfundViewModel.UseAllStoreInvoices = false;
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
|
||||
TestLogs.LogInformation("Because UseAllStoreInvoices is false, let's make sure the invoice is not tagged");
|
||||
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice
|
||||
@ -251,7 +261,7 @@ namespace BTCPayServer.Tests
|
||||
TestLogs.LogInformation("After turning setting a softcap, let's check that only actual payments are counted");
|
||||
crowdfundViewModel.EnforceTargetAmount = false;
|
||||
crowdfundViewModel.UseAllStoreInvoices = true;
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice
|
||||
{
|
||||
Buyer = new Buyer { email = "test@fwf.com" },
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:6.0.101-bullseye-slim AS builder
|
||||
FROM mcr.microsoft.com/dotnet/sdk:6.0.401-bullseye-slim AS builder
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends chromium-driver \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
@ -125,6 +125,12 @@ retry:
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
public static void FillIn(this IWebElement el, string text)
|
||||
{
|
||||
el.Clear();
|
||||
el.SendKeys(text);
|
||||
}
|
||||
|
||||
public static void ScrollTo(this IWebDriver driver, IWebElement element)
|
||||
{
|
||||
|
@ -33,12 +33,14 @@ using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitcoin.Scripting.Parser;
|
||||
using NBXplorer;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -481,93 +483,6 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
#endif
|
||||
|
||||
[Fact]
|
||||
public void CanParseLegacyLabels()
|
||||
{
|
||||
static void AssertContainsRawLabel(WalletTransactionInfo info)
|
||||
{
|
||||
foreach (var item in new[] { "blah", "lol", "hello" })
|
||||
{
|
||||
Assert.True(info.Labels.ContainsKey(item));
|
||||
var rawLabel = Assert.IsType<RawLabel>(info.Labels[item]);
|
||||
Assert.Equal("raw", rawLabel.Type);
|
||||
Assert.Equal(item, rawLabel.Text);
|
||||
}
|
||||
}
|
||||
var data = new WalletTransactionData();
|
||||
data.Labels = "blah,lol,hello,lol";
|
||||
var info = data.GetBlobInfo();
|
||||
Assert.Equal(3, info.Labels.Count);
|
||||
AssertContainsRawLabel(info);
|
||||
data.SetBlobInfo(info);
|
||||
Assert.Contains("raw", data.Labels);
|
||||
Assert.Contains("{", data.Labels);
|
||||
Assert.Contains("[", data.Labels);
|
||||
info = data.GetBlobInfo();
|
||||
AssertContainsRawLabel(info);
|
||||
|
||||
|
||||
data = new WalletTransactionData()
|
||||
{
|
||||
Labels = "pos",
|
||||
Blob = Encoders.Hex.DecodeData("1f8b08000000000000037abf7b7fb592737e6e6e6a5e89929592522d000000ffff030036bc6ad911000000")
|
||||
};
|
||||
info = data.GetBlobInfo();
|
||||
var label = Assert.Single(info.Labels);
|
||||
Assert.Equal("raw", label.Value.Type);
|
||||
Assert.Equal("pos", label.Value.Text);
|
||||
Assert.Equal("pos", label.Key);
|
||||
|
||||
|
||||
static void AssertContainsLabel(WalletTransactionInfo info)
|
||||
{
|
||||
Assert.Equal(2, info.Labels.Count);
|
||||
var invoiceLabel = Assert.IsType<ReferenceLabel>(info.Labels["invoice"]);
|
||||
Assert.Equal("BFm1MCJPBCDeRoWXvPcwnM", invoiceLabel.Reference);
|
||||
Assert.Equal("invoice", invoiceLabel.Text);
|
||||
Assert.Equal("invoice", invoiceLabel.Type);
|
||||
|
||||
var appLabel = Assert.IsType<ReferenceLabel>(info.Labels["app"]);
|
||||
Assert.Equal("87kj5yKay8mB4UUZcJhZH5TqDKMD3CznjwLjiu1oYZXe", appLabel.Reference);
|
||||
Assert.Equal("app", appLabel.Text);
|
||||
Assert.Equal("app", appLabel.Type);
|
||||
}
|
||||
data = new WalletTransactionData()
|
||||
{
|
||||
Labels = "[\"{\\n \\\"value\\\": \\\"invoice\\\",\\n \\\"id\\\": \\\"BFm1MCJPBCDeRoWXvPcwnM\\\"\\n}\",\"{\\n \\\"value\\\": \\\"app\\\",\\n \\\"id\\\": \\\"87kj5yKay8mB4UUZcJhZH5TqDKMD3CznjwLjiu1oYZXe\\\"\\n}\"]",
|
||||
};
|
||||
info = data.GetBlobInfo();
|
||||
AssertContainsLabel(info);
|
||||
data.SetBlobInfo(info);
|
||||
info = data.GetBlobInfo();
|
||||
AssertContainsLabel(info);
|
||||
|
||||
static void AssertPayoutLabel(WalletTransactionInfo info)
|
||||
{
|
||||
Assert.Single(info.Labels);
|
||||
var l = Assert.IsType<PayoutLabel>(info.Labels["payout"]);
|
||||
Assert.Single(Assert.Single(l.PullPaymentPayouts, k => k.Key == "pullPaymentId").Value, "payoutId");
|
||||
Assert.Equal("walletId", l.WalletId);
|
||||
}
|
||||
|
||||
var payoutId = "payoutId";
|
||||
var pullPaymentId = "pullPaymentId";
|
||||
var walletId = "walletId";
|
||||
// How it was serialized before
|
||||
|
||||
data = new WalletTransactionData()
|
||||
{
|
||||
Labels = new JArray(JObject.FromObject(new { value = "payout", id = payoutId, pullPaymentId, walletId })).ToString()
|
||||
};
|
||||
info = data.GetBlobInfo();
|
||||
AssertPayoutLabel(info);
|
||||
data.SetBlobInfo(info);
|
||||
info = data.GetBlobInfo();
|
||||
AssertPayoutLabel(info);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Fact]
|
||||
public void DeterministicUTXOSorter()
|
||||
{
|
||||
@ -1292,6 +1207,9 @@ namespace BTCPayServer.Tests
|
||||
[Fact]
|
||||
public void CanParseRateRules()
|
||||
{
|
||||
var pair = CurrencyPair.Parse("USD_EMAT_IC");
|
||||
Assert.Equal("USD", pair.Left);
|
||||
Assert.Equal("EMAT_IC", pair.Right);
|
||||
// Check happy path
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.AppendLine("// Some cool comments");
|
||||
@ -1387,7 +1305,7 @@ namespace BTCPayServer.Tests
|
||||
rule2.Reevaluate();
|
||||
Assert.False(rule2.HasError);
|
||||
Assert.Equal("5000 * 2000.4 * 1.1", rule2.ToString(true));
|
||||
Assert.Equal(rule2.BidAsk.Bid, 5000m * 2000.4m * 1.1m);
|
||||
Assert.Equal(5000m * 2000.4m * 1.1m, rule2.BidAsk.Bid);
|
||||
////////
|
||||
|
||||
// Make sure parenthesis are correctly calculated
|
||||
@ -1808,5 +1726,32 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
public void PaymentMethodIdConverterIsGraceful()
|
||||
{
|
||||
var pmi = "\"BTC_hasjdfhasjkfjlajn\"";
|
||||
JsonTextReader reader = new(new StringReader(pmi));
|
||||
reader.Read();
|
||||
Assert.Null(new PaymentMethodIdJsonConverter().ReadJson(reader, typeof(PaymentMethodId), null,
|
||||
JsonSerializer.CreateDefault()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanBeBracefulAfterObsoleteShitcoin()
|
||||
{
|
||||
var blob = new StoreBlob();
|
||||
blob.PaymentMethodCriteria = new List<PaymentMethodCriteria>()
|
||||
{
|
||||
new()
|
||||
{
|
||||
Above = true,
|
||||
Value = new CurrencyValue() {Currency = "BTC", Value = 0.1m},
|
||||
PaymentMethod = new PaymentMethodId("BTC", PaymentTypes.BTCLike)
|
||||
}
|
||||
};
|
||||
var newBlob = Encoding.UTF8.GetBytes(
|
||||
new Serializer(null).ToString(blob).Replace( "paymentMethod\":\"BTC\"","paymentMethod\":\"ETH_ZYC\""));
|
||||
Assert.Empty(StoreDataExtensions.GetStoreBlob(new StoreData() {StoreBlob = newBlob}).PaymentMethodCriteria);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,10 +14,12 @@ using BTCPayServer.Events;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services.Custodian.Client.MockCustodian;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Notifications;
|
||||
using BTCPayServer.Services.Notifications.Blobs;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@ -87,7 +89,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal("missing-permission", e.APIError.Code);
|
||||
Assert.NotNull(e.APIError.Message);
|
||||
GreenfieldPermissionAPIError permissionError = Assert.IsType<GreenfieldPermissionAPIError>(e.APIError);
|
||||
Assert.Equal(permissionError.MissingPermission, Policies.CanModifyStoreSettings);
|
||||
Assert.Equal(Policies.CanModifyStoreSettings, permissionError.MissingPermission);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
@ -194,7 +196,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanCreateReadAndDeletePointOfSaleApp()
|
||||
public async Task CanCreateReadUpdateAndDeletePointOfSaleApp()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
@ -202,8 +204,58 @@ namespace BTCPayServer.Tests
|
||||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
var client = await user.CreateClient();
|
||||
|
||||
// Test creating a POS app
|
||||
var app = await client.CreatePointOfSaleApp(user.StoreId, new CreatePointOfSaleAppRequest() { AppName = "test app from API" });
|
||||
// Test validation for creating the app
|
||||
await AssertValidationError(new[] { "AppName" },
|
||||
async () => await client.CreatePointOfSaleApp(user.StoreId, new CreatePointOfSaleAppRequest() {}));
|
||||
await AssertValidationError(new[] { "AppName" },
|
||||
async () => await client.CreatePointOfSaleApp(
|
||||
user.StoreId,
|
||||
new CreatePointOfSaleAppRequest()
|
||||
{
|
||||
AppName = "this is a really long app name this is a really long app name this is a really long app name",
|
||||
}
|
||||
)
|
||||
);
|
||||
await AssertValidationError(new[] { "Currency" },
|
||||
async () => await client.CreatePointOfSaleApp(
|
||||
user.StoreId,
|
||||
new CreatePointOfSaleAppRequest()
|
||||
{
|
||||
AppName = "good name",
|
||||
Currency = "fake currency"
|
||||
}
|
||||
)
|
||||
);
|
||||
await AssertValidationError(new[] { "Template" },
|
||||
async () => await client.CreatePointOfSaleApp(
|
||||
user.StoreId,
|
||||
new CreatePointOfSaleAppRequest()
|
||||
{
|
||||
AppName = "good name",
|
||||
Template = "lol invalid template"
|
||||
}
|
||||
)
|
||||
);
|
||||
await AssertValidationError(new[] { "AppName", "Currency", "Template" },
|
||||
async () => await client.CreatePointOfSaleApp(
|
||||
user.StoreId,
|
||||
new CreatePointOfSaleAppRequest()
|
||||
{
|
||||
Currency = "fake currency",
|
||||
Template = "lol invalid template"
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Test creating a POS app successfully
|
||||
var app = await client.CreatePointOfSaleApp(
|
||||
user.StoreId,
|
||||
new CreatePointOfSaleAppRequest()
|
||||
{
|
||||
AppName = "test app from API",
|
||||
Currency = "JPY"
|
||||
}
|
||||
);
|
||||
Assert.Equal("test app from API", app.Name);
|
||||
Assert.Equal(user.StoreId, app.StoreId);
|
||||
Assert.Equal("PointOfSale", app.AppType);
|
||||
@ -219,6 +271,11 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(app.StoreId, retrievedApp.StoreId);
|
||||
Assert.Equal(app.AppType, retrievedApp.AppType);
|
||||
|
||||
// Test that we can update the app data
|
||||
await client.UpdatePointOfSaleApp(app.Id, new CreatePointOfSaleAppRequest() { AppName = "new app name" });
|
||||
retrievedApp = await client.GetApp(app.Id);
|
||||
Assert.Equal("new app name", retrievedApp.Name);
|
||||
|
||||
// Make sure we return a 404 if we try to delete an app that doesn't exist
|
||||
await AssertHttpError(404, async () =>
|
||||
{
|
||||
@ -231,6 +288,117 @@ namespace BTCPayServer.Tests
|
||||
await client.GetApp(retrievedApp.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanCreateCrowdfundApp()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
var client = await user.CreateClient();
|
||||
|
||||
// Test validation for creating the app
|
||||
await AssertValidationError(new[] { "AppName" },
|
||||
async () => await client.CreateCrowdfundApp(user.StoreId, new CreateCrowdfundAppRequest() {}));
|
||||
await AssertValidationError(new[] { "AppName" },
|
||||
async () => await client.CreateCrowdfundApp(
|
||||
user.StoreId,
|
||||
new CreateCrowdfundAppRequest()
|
||||
{
|
||||
AppName = "this is a really long app name this is a really long app name this is a really long app name",
|
||||
}
|
||||
)
|
||||
);
|
||||
await AssertValidationError(new[] { "TargetCurrency" },
|
||||
async () => await client.CreateCrowdfundApp(
|
||||
user.StoreId,
|
||||
new CreateCrowdfundAppRequest()
|
||||
{
|
||||
AppName = "good name",
|
||||
TargetCurrency = "fake currency"
|
||||
}
|
||||
)
|
||||
);
|
||||
await AssertValidationError(new[] { "PerksTemplate" },
|
||||
async () => await client.CreateCrowdfundApp(
|
||||
user.StoreId,
|
||||
new CreateCrowdfundAppRequest()
|
||||
{
|
||||
AppName = "good name",
|
||||
PerksTemplate = "lol invalid template"
|
||||
}
|
||||
)
|
||||
);
|
||||
await AssertValidationError(new[] { "AppName", "TargetCurrency", "PerksTemplate" },
|
||||
async () => await client.CreateCrowdfundApp(
|
||||
user.StoreId,
|
||||
new CreateCrowdfundAppRequest()
|
||||
{
|
||||
TargetCurrency = "fake currency",
|
||||
PerksTemplate = "lol invalid template"
|
||||
}
|
||||
)
|
||||
);
|
||||
await AssertValidationError(new[] { "AnimationColors" },
|
||||
async () => await client.CreateCrowdfundApp(
|
||||
user.StoreId,
|
||||
new CreateCrowdfundAppRequest()
|
||||
{
|
||||
AppName = "good name",
|
||||
AnimationColors = new string[] {}
|
||||
}
|
||||
)
|
||||
);
|
||||
await AssertValidationError(new[] { "AnimationColors" },
|
||||
async () => await client.CreateCrowdfundApp(
|
||||
user.StoreId,
|
||||
new CreateCrowdfundAppRequest()
|
||||
{
|
||||
AppName = "good name",
|
||||
AnimationColors = new string[] { " ", " " }
|
||||
}
|
||||
)
|
||||
);
|
||||
await AssertValidationError(new[] { "Sounds" },
|
||||
async () => await client.CreateCrowdfundApp(
|
||||
user.StoreId,
|
||||
new CreateCrowdfundAppRequest()
|
||||
{
|
||||
AppName = "good name",
|
||||
Sounds = new string[] { " " }
|
||||
}
|
||||
)
|
||||
);
|
||||
await AssertValidationError(new[] { "Sounds" },
|
||||
async () => await client.CreateCrowdfundApp(
|
||||
user.StoreId,
|
||||
new CreateCrowdfundAppRequest()
|
||||
{
|
||||
AppName = "good name",
|
||||
Sounds = new string[] { " ", " ", " " }
|
||||
}
|
||||
)
|
||||
);
|
||||
await AssertValidationError(new[] { "EndDate" },
|
||||
async () => await client.CreateCrowdfundApp(
|
||||
user.StoreId,
|
||||
new CreateCrowdfundAppRequest()
|
||||
{
|
||||
AppName = "good name",
|
||||
StartDate = DateTime.Parse("1998-01-01"),
|
||||
EndDate = DateTime.Parse("1997-12-31")
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Test creating a crowdfund app
|
||||
var app = await client.CreateCrowdfundApp(user.StoreId, new CreateCrowdfundAppRequest() { AppName = "test app from API" });
|
||||
Assert.Equal("test app from API", app.Name);
|
||||
Assert.Equal(user.StoreId, app.StoreId);
|
||||
Assert.Equal("Crowdfund", app.AppType);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
@ -740,6 +908,100 @@ namespace BTCPayServer.Tests
|
||||
await AssertAPIError("invalid-state", async () => await client.MarkPayoutPaid(storeId, payout.Id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanProcessPayoutsExternally()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
var acc = tester.NewAccount();
|
||||
acc.Register();
|
||||
await acc.CreateStoreAsync();
|
||||
var storeId = (await acc.RegisterDerivationSchemeAsync("BTC", importKeysToNBX: true)).StoreId;
|
||||
var client = await acc.CreateClient();
|
||||
var address = await tester.ExplorerNode.GetNewAddressAsync();
|
||||
var payout = await client.CreatePayout(storeId, new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
Approved = false,
|
||||
PaymentMethod = "BTC",
|
||||
Amount = 0.0001m,
|
||||
Destination = address.ToString()
|
||||
});
|
||||
await AssertAPIError("invalid-state", async () =>
|
||||
{
|
||||
await client.MarkPayout(storeId, payout.Id, new MarkPayoutRequest() {State = PayoutState.Completed});
|
||||
|
||||
});
|
||||
|
||||
await client.ApprovePayout(storeId, payout.Id, new ApprovePayoutRequest());
|
||||
|
||||
await client.MarkPayout(storeId, payout.Id, new MarkPayoutRequest() {State = PayoutState.Completed});
|
||||
Assert.Equal(PayoutState.Completed,(await client.GetStorePayouts(storeId,false)).Single(data => data.Id == payout.Id ).State );
|
||||
Assert.Null((await client.GetStorePayouts(storeId,false)).Single(data => data.Id == payout.Id ).PaymentProof );
|
||||
|
||||
foreach (var state in new []{ PayoutState.AwaitingApproval, PayoutState.Cancelled, PayoutState.Completed, PayoutState.AwaitingApproval, PayoutState.InProgress})
|
||||
{
|
||||
await AssertAPIError("invalid-state", async () =>
|
||||
{
|
||||
await client.MarkPayout(storeId, payout.Id, new MarkPayoutRequest() {State = state});
|
||||
});
|
||||
}
|
||||
payout = await client.CreatePayout(storeId, new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
Approved = true,
|
||||
PaymentMethod = "BTC",
|
||||
Amount = 0.0001m,
|
||||
Destination = address.ToString()
|
||||
});
|
||||
|
||||
payout = await client.GetStorePayout(storeId, payout.Id);
|
||||
Assert.NotNull(payout);
|
||||
Assert.Equal(PayoutState.AwaitingPayment, payout.State);
|
||||
await AssertValidationError(new []{"PaymentProof"}, async () =>
|
||||
{
|
||||
await client.MarkPayout(storeId, payout.Id, new MarkPayoutRequest() {State = PayoutState.Completed, PaymentProof = JObject.FromObject(new
|
||||
{
|
||||
test = "zyx"
|
||||
})});
|
||||
});
|
||||
await client.MarkPayout(storeId, payout.Id, new MarkPayoutRequest() {State = PayoutState.InProgress, PaymentProof = JObject.FromObject(new
|
||||
{
|
||||
proofType = "external-proof"
|
||||
})});
|
||||
payout = await client.GetStorePayout(storeId, payout.Id);
|
||||
Assert.NotNull(payout);
|
||||
Assert.Equal(PayoutState.InProgress, payout.State);
|
||||
Assert.True(payout.PaymentProof.TryGetValue("proofType", out var savedType));
|
||||
Assert.Equal("external-proof",savedType);
|
||||
|
||||
await client.MarkPayout(storeId, payout.Id, new MarkPayoutRequest() {State = PayoutState.AwaitingPayment, PaymentProof = JObject.FromObject(new
|
||||
{
|
||||
proofType = "external-proof",
|
||||
id="finality proof",
|
||||
link="proof.com"
|
||||
})});
|
||||
payout = await client.GetStorePayout(storeId, payout.Id);
|
||||
Assert.NotNull(payout);
|
||||
Assert.Null(payout.PaymentProof);
|
||||
Assert.Equal(PayoutState.AwaitingPayment, payout.State);
|
||||
|
||||
await client.MarkPayout(storeId, payout.Id, new MarkPayoutRequest() {State = PayoutState.Completed, PaymentProof = JObject.FromObject(new
|
||||
{
|
||||
proofType = "external-proof",
|
||||
id="finality proof",
|
||||
link="proof.com"
|
||||
})});
|
||||
payout = await client.GetStorePayout(storeId, payout.Id);
|
||||
Assert.NotNull(payout);
|
||||
Assert.Equal(PayoutState.Completed, payout.State);
|
||||
Assert.True(payout.PaymentProof.TryGetValue("proofType", out savedType));
|
||||
Assert.True(payout.PaymentProof.TryGetValue("link", out var savedLink));
|
||||
Assert.True(payout.PaymentProof.TryGetValue("id", out var savedId));
|
||||
Assert.Equal("external-proof",savedType);
|
||||
Assert.Equal("finality proof",savedId);
|
||||
Assert.Equal("proof.com",savedLink);
|
||||
}
|
||||
|
||||
private DateTimeOffset RoundSeconds(DateTimeOffset dateTimeOffset)
|
||||
{
|
||||
return new DateTimeOffset(dateTimeOffset.Year, dateTimeOffset.Month, dateTimeOffset.Day, dateTimeOffset.Hour, dateTimeOffset.Minute, dateTimeOffset.Second, dateTimeOffset.Offset);
|
||||
@ -1112,25 +1374,93 @@ namespace BTCPayServer.Tests
|
||||
await client.ArchivePaymentRequest(user.StoreId, paymentRequest.Id);
|
||||
Assert.DoesNotContain(paymentRequest.Id,
|
||||
(await client.GetPaymentRequests(user.StoreId)).Select(data => data.Id));
|
||||
|
||||
//let's test some payment stuff
|
||||
var archivedPrId = paymentRequest.Id;
|
||||
//let's test some payment stuff with the UI
|
||||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
var paymentTestPaymentRequest = await client.CreatePaymentRequest(user.StoreId,
|
||||
new CreatePaymentRequestRequest() { Amount = 0.1m, Currency = "BTC", Title = "Payment test title" });
|
||||
|
||||
var invoiceId = Assert.IsType<string>(Assert.IsType<OkObjectResult>(await user.GetController<UIPaymentRequestController>()
|
||||
.PayPaymentRequest(paymentTestPaymentRequest.Id, false)).Value);
|
||||
var invoice = user.BitPay.GetInvoice(invoiceId);
|
||||
await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
|
||||
|
||||
async Task Pay(string invoiceId, bool partialPayment = false)
|
||||
{
|
||||
await tester.ExplorerNode.SendToAddressAsync(
|
||||
BitcoinAddress.Create(invoice.BitcoinAddress, tester.ExplorerNode.Network), invoice.BtcDue);
|
||||
TestLogs.LogInformation($"Paying invoice {invoiceId}");
|
||||
var invoice = user.BitPay.GetInvoice(invoiceId);
|
||||
await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
|
||||
{
|
||||
TestLogs.LogInformation($"Paying address {invoice.BitcoinAddress}");
|
||||
await tester.ExplorerNode.SendToAddressAsync(
|
||||
BitcoinAddress.Create(invoice.BitcoinAddress, tester.ExplorerNode.Network), invoice.BtcDue);
|
||||
});
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
Assert.Equal(Invoice.STATUS_PAID, user.BitPay.GetInvoice(invoiceId).Status);
|
||||
if (!partialPayment)
|
||||
Assert.Equal(PaymentRequestData.PaymentRequestStatus.Completed, (await client.GetPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id)).Status);
|
||||
});
|
||||
}
|
||||
await Pay(invoiceId);
|
||||
|
||||
//Same thing, but with the API
|
||||
paymentTestPaymentRequest = await client.CreatePaymentRequest(user.StoreId,
|
||||
new CreatePaymentRequestRequest() { Amount = 0.1m, Currency = "BTC", Title = "Payment test title" });
|
||||
var paidPrId = paymentTestPaymentRequest.Id;
|
||||
var invoiceData = await client.PayPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id, new PayPaymentRequestRequest());
|
||||
await Pay(invoiceData.Id);
|
||||
|
||||
// Let's tests some unhappy path
|
||||
paymentTestPaymentRequest = await client.CreatePaymentRequest(user.StoreId,
|
||||
new CreatePaymentRequestRequest() { Amount = 0.1m, AllowCustomPaymentAmounts = false, Currency = "BTC", Title = "Payment test title" });
|
||||
await AssertValidationError(new[] { "Amount" }, () => client.PayPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id, new PayPaymentRequestRequest() { Amount = -0.04m }));
|
||||
await AssertValidationError(new[] { "Amount" }, () => client.PayPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id, new PayPaymentRequestRequest() { Amount = 0.04m }));
|
||||
await client.UpdatePaymentRequest(user.StoreId, paymentTestPaymentRequest.Id, new UpdatePaymentRequestRequest()
|
||||
{
|
||||
Amount = 0.1m,
|
||||
AllowCustomPaymentAmounts = true,
|
||||
Currency = "BTC",
|
||||
Title = "Payment test title"
|
||||
});
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
Assert.Equal(Invoice.STATUS_PAID, user.BitPay.GetInvoice(invoiceId).Status);
|
||||
Assert.Equal(PaymentRequestData.PaymentRequestStatus.Completed, (await client.GetPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id)).Status);
|
||||
});
|
||||
await AssertValidationError(new[] { "Amount" }, () => client.PayPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id, new PayPaymentRequestRequest() { Amount = -0.04m }));
|
||||
invoiceData = await client.PayPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id, new PayPaymentRequestRequest() { Amount = 0.04m });
|
||||
Assert.Equal(0.04m, invoiceData.Amount);
|
||||
var firstPaymentId = invoiceData.Id;
|
||||
await AssertAPIError("archived", () => client.PayPaymentRequest(user.StoreId, archivedPrId, new PayPaymentRequestRequest()));
|
||||
|
||||
await client.UpdatePaymentRequest(user.StoreId, paymentTestPaymentRequest.Id, new UpdatePaymentRequestRequest()
|
||||
{
|
||||
Amount = 0.1m,
|
||||
AllowCustomPaymentAmounts = true,
|
||||
Currency = "BTC",
|
||||
Title = "Payment test title",
|
||||
ExpiryDate = DateTimeOffset.UtcNow - TimeSpan.FromDays(1.0)
|
||||
});
|
||||
|
||||
await AssertAPIError("expired", () => client.PayPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id, new PayPaymentRequestRequest()));
|
||||
await AssertAPIError("already-paid", () => client.PayPaymentRequest(user.StoreId, paidPrId, new PayPaymentRequestRequest()));
|
||||
|
||||
await client.UpdatePaymentRequest(user.StoreId, paymentTestPaymentRequest.Id, new UpdatePaymentRequestRequest()
|
||||
{
|
||||
Amount = 0.1m,
|
||||
AllowCustomPaymentAmounts = true,
|
||||
Currency = "BTC",
|
||||
Title = "Payment test title",
|
||||
ExpiryDate = null
|
||||
});
|
||||
|
||||
await Pay(firstPaymentId, true);
|
||||
invoiceData = await client.PayPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id, new PayPaymentRequestRequest());
|
||||
|
||||
Assert.Equal(0.06m, invoiceData.Amount);
|
||||
Assert.Equal("BTC", invoiceData.Currency);
|
||||
|
||||
var expectedInvoiceId = invoiceData.Id;
|
||||
invoiceData = await client.PayPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id, new PayPaymentRequestRequest() { AllowPendingInvoiceReuse = true });
|
||||
Assert.Equal(expectedInvoiceId, invoiceData.Id);
|
||||
|
||||
var notExpectedInvoiceId = invoiceData.Id;
|
||||
invoiceData = await client.PayPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id, new PayPaymentRequestRequest() { AllowPendingInvoiceReuse = false });
|
||||
Assert.NotEqual(notExpectedInvoiceId, invoiceData.Id);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
@ -1268,7 +1598,7 @@ namespace BTCPayServer.Tests
|
||||
Checkout = new CreateInvoiceRequest.CheckoutOptions()
|
||||
{
|
||||
RedirectAutomatically = true,
|
||||
RequiresRefundEmail = true
|
||||
RequiresRefundEmail = true,
|
||||
},
|
||||
AdditionalSearchTerms = new string[] { "Banana" }
|
||||
});
|
||||
@ -1597,25 +1927,35 @@ namespace BTCPayServer.Tests
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess(true);
|
||||
await user.GrantAccessAsync(true);
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.CLightning, false);
|
||||
|
||||
var merchant = tester.NewAccount();
|
||||
merchant.GrantAccess(true);
|
||||
await merchant.GrantAccessAsync(true);
|
||||
merchant.RegisterLightningNode("BTC", LightningConnectionType.LndREST);
|
||||
var merchantClient = await merchant.CreateClient($"{Policies.CanUseLightningNodeInStore}:{merchant.StoreId}");
|
||||
var merchantInvoice = await merchantClient.CreateLightningInvoice(merchant.StoreId, "BTC", new CreateLightningInvoiceRequest(LightMoney.Satoshis(1_000), "hey", TimeSpan.FromSeconds(60)));
|
||||
// The default client is using charge, so we should not be able to query channels
|
||||
var client = await user.CreateClient(Policies.CanUseInternalLightningNode);
|
||||
var chargeClient = await user.CreateClient(Policies.CanUseInternalLightningNode);
|
||||
|
||||
var info = await client.GetLightningNodeInfo("BTC");
|
||||
var info = await chargeClient.GetLightningNodeInfo("BTC");
|
||||
Assert.Single(info.NodeURIs);
|
||||
Assert.NotEqual(0, info.BlockHeight);
|
||||
Assert.NotNull(info.Alias);
|
||||
Assert.NotNull(info.Color);
|
||||
Assert.NotNull(info.Version);
|
||||
Assert.NotNull(info.PeersCount);
|
||||
Assert.NotNull(info.ActiveChannelsCount);
|
||||
Assert.NotNull(info.InactiveChannelsCount);
|
||||
Assert.NotNull(info.PendingChannelsCount);
|
||||
|
||||
await AssertAPIError("lightning-node-unavailable", () => client.GetLightningNodeChannels("BTC"));
|
||||
var gex = await AssertAPIError("lightning-node-unavailable", () => chargeClient.ConnectToLightningNode("BTC", new ConnectToNodeRequest(NodeInfo.Parse($"{new Key().PubKey.ToHex()}@localhost:3827"))));
|
||||
Assert.Contains("NotSupported", gex.Message);
|
||||
|
||||
await AssertAPIError("lightning-node-unavailable", () => chargeClient.GetLightningNodeChannels("BTC"));
|
||||
// Not permission for the store!
|
||||
await AssertAPIError("missing-permission", () => client.GetLightningNodeChannels(user.StoreId, "BTC"));
|
||||
var invoiceData = await client.CreateLightningInvoice("BTC", new CreateLightningInvoiceRequest()
|
||||
await AssertAPIError("missing-permission", () => chargeClient.GetLightningNodeChannels(user.StoreId, "BTC"));
|
||||
var invoiceData = await chargeClient.CreateLightningInvoice("BTC", new CreateLightningInvoiceRequest()
|
||||
{
|
||||
Amount = LightMoney.Satoshis(1000),
|
||||
Description = "lol",
|
||||
@ -1623,9 +1963,17 @@ namespace BTCPayServer.Tests
|
||||
PrivateRouteHints = false
|
||||
});
|
||||
var chargeInvoice = invoiceData;
|
||||
Assert.NotNull(await client.GetLightningInvoice("BTC", invoiceData.Id));
|
||||
Assert.NotNull(await chargeClient.GetLightningInvoice("BTC", invoiceData.Id));
|
||||
|
||||
client = await user.CreateClient($"{Policies.CanUseLightningNodeInStore}:{user.StoreId}");
|
||||
// check list for internal node
|
||||
var invoices = await chargeClient.GetLightningInvoices("BTC");
|
||||
var pendingInvoices = await chargeClient.GetLightningInvoices("BTC", true);
|
||||
Assert.NotEmpty(invoices);
|
||||
Assert.Contains(invoices, i => i.Id == invoiceData.Id);
|
||||
Assert.NotEmpty(pendingInvoices);
|
||||
Assert.Contains(pendingInvoices, i => i.Id == invoiceData.Id);
|
||||
|
||||
var client = await user.CreateClient($"{Policies.CanUseLightningNodeInStore}:{user.StoreId}");
|
||||
// Not permission for the server
|
||||
await AssertAPIError("missing-permission", () => client.GetLightningNodeChannels("BTC"));
|
||||
|
||||
@ -1643,10 +1991,22 @@ namespace BTCPayServer.Tests
|
||||
|
||||
Assert.NotNull(await client.GetLightningInvoice(user.StoreId, "BTC", invoiceData.Id));
|
||||
|
||||
await client.PayLightningInvoice(user.StoreId, "BTC", new PayLightningInvoiceRequest()
|
||||
// check pending list
|
||||
var merchantPendingInvoices = await merchantClient.GetLightningInvoices(merchant.StoreId, "BTC", true);
|
||||
Assert.NotEmpty(merchantPendingInvoices);
|
||||
Assert.Contains(merchantPendingInvoices, i => i.Id == merchantInvoice.Id);
|
||||
|
||||
var payResponse = await client.PayLightningInvoice(user.StoreId, "BTC", new PayLightningInvoiceRequest
|
||||
{
|
||||
BOLT11 = merchantInvoice.BOLT11
|
||||
});
|
||||
Assert.Equal(merchantInvoice.BOLT11, payResponse.BOLT11);
|
||||
Assert.Equal(LightningPaymentStatus.Complete, payResponse.Status);
|
||||
Assert.NotNull(payResponse.Preimage);
|
||||
Assert.NotNull(payResponse.FeeAmount);
|
||||
Assert.NotNull(payResponse.TotalAmount);
|
||||
Assert.NotNull(payResponse.PaymentHash);
|
||||
|
||||
await Assert.ThrowsAsync<GreenfieldValidationException>(async () => await client.PayLightningInvoice(user.StoreId, "BTC", new PayLightningInvoiceRequest()
|
||||
{
|
||||
BOLT11 = "lol"
|
||||
@ -1663,6 +2023,15 @@ namespace BTCPayServer.Tests
|
||||
var invoice = await merchantClient.GetLightningInvoice(merchant.StoreId, "BTC", merchantInvoice.Id);
|
||||
Assert.NotNull(invoice.PaidAt);
|
||||
Assert.Equal(LightMoney.Satoshis(1000), invoice.Amount);
|
||||
|
||||
// check list for store with paid invoice
|
||||
var merchantInvoices = await merchantClient.GetLightningInvoices(merchant.StoreId, "BTC");
|
||||
merchantPendingInvoices = await merchantClient.GetLightningInvoices(merchant.StoreId, "BTC", true);
|
||||
Assert.NotEmpty(merchantInvoices);
|
||||
Assert.Empty(merchantPendingInvoices);
|
||||
// if the test ran too many times the invoice might be on a later page
|
||||
if (merchantInvoices.Length < 100) Assert.Contains(merchantInvoices, i => i.Id == merchantInvoice.Id);
|
||||
|
||||
// Amount received might be bigger because of internal implementation shit from lightning
|
||||
Assert.True(LightMoney.Satoshis(1000) <= invoice.AmountReceived);
|
||||
|
||||
@ -1670,7 +2039,6 @@ namespace BTCPayServer.Tests
|
||||
Assert.Single(info.NodeURIs);
|
||||
Assert.NotEqual(0, info.BlockHeight);
|
||||
|
||||
|
||||
// As admin, can use the internal node through our store.
|
||||
await user.MakeAdmin(true);
|
||||
await user.RegisterInternalLightningNodeAsync("BTC");
|
||||
@ -1680,7 +2048,7 @@ namespace BTCPayServer.Tests
|
||||
await AssertPermissionError("btcpay.server.canuseinternallightningnode", () => client.GetLightningNodeInfo(user.StoreId, "BTC"));
|
||||
// However, even as a guest, you should be able to create an invoice
|
||||
var guest = tester.NewAccount();
|
||||
guest.GrantAccess(false);
|
||||
await guest.GrantAccessAsync();
|
||||
await user.AddGuest(guest.UserId);
|
||||
client = await guest.CreateClient(Policies.CanCreateLightningInvoiceInStore);
|
||||
await client.CreateLightningInvoice(user.StoreId, "BTC", new CreateLightningInvoiceRequest()
|
||||
@ -2207,6 +2575,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
Assert.Equal(transaction.TransactionHash, txdata.TransactionHash);
|
||||
Assert.Equal(String.Empty, transaction.Comment);
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
Assert.Equal(new Dictionary<string, LabelData>(), transaction.Labels);
|
||||
|
||||
// transaction patch tests
|
||||
@ -2227,7 +2596,7 @@ namespace BTCPayServer.Tests
|
||||
}.ToJson(),
|
||||
patchedTransaction.Labels.ToJson()
|
||||
);
|
||||
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode);
|
||||
@ -2455,6 +2824,50 @@ namespace BTCPayServer.Tests
|
||||
await newUserBasicClient.GetCurrentUser();
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanUseLNPayoutProcessor()
|
||||
{
|
||||
LightningPendingPayoutListener.SecondsDelay = 0;
|
||||
using var tester = CreateServerTester();
|
||||
|
||||
tester.ActivateLightning();
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
|
||||
var admin = tester.NewAccount();
|
||||
|
||||
await admin.GrantAccessAsync(true);
|
||||
|
||||
var adminClient = await admin.CreateClient(Policies.Unrestricted);
|
||||
admin.RegisterLightningNode("BTC", LightningConnectionType.LndREST);
|
||||
var payoutAmount = LightMoney.Satoshis(1000);
|
||||
var inv = await tester.MerchantLnd.Client.CreateInvoice(payoutAmount, "Donation to merchant", TimeSpan.FromHours(1), default);
|
||||
var resp = await tester.CustomerLightningD.Pay(inv.BOLT11);
|
||||
Assert.Equal(PayResult.Ok, resp.Result);
|
||||
|
||||
|
||||
|
||||
var customerInvoice = await tester.CustomerLightningD.CreateInvoice(LightMoney.FromUnit(10, LightMoneyUnit.Satoshi),
|
||||
Guid.NewGuid().ToString(), TimeSpan.FromDays(40));
|
||||
var payout = await adminClient.CreatePayout(admin.StoreId,
|
||||
new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
Approved = true, PaymentMethod = "BTC_LightningNetwork", Destination = customerInvoice.BOLT11
|
||||
});
|
||||
Assert.Empty(await adminClient.GetStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork"));
|
||||
await adminClient.UpdateStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork",
|
||||
new LightningAutomatedPayoutSettings() {IntervalSeconds = TimeSpan.FromSeconds(2)});
|
||||
Assert.Equal(2, Assert.Single( await adminClient.GetStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork")).IntervalSeconds.TotalSeconds);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var payoutC =
|
||||
(await adminClient.GetStorePayouts(admin.StoreId, false)).Single(data => data.Id == payout.Id);
|
||||
Assert.Equal(PayoutState.Completed , payoutC.State);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanUsePayoutProcessorsThroughAPI()
|
||||
@ -2574,7 +2987,134 @@ namespace BTCPayServer.Tests
|
||||
Assert.Empty(payouts.Where(data => data.State != PayoutState.InProgress));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanUseWalletObjectsAPI()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
|
||||
var admin = tester.NewAccount();
|
||||
await admin.GrantAccessAsync(true);
|
||||
|
||||
var client = await admin.CreateClient(Policies.Unrestricted);
|
||||
|
||||
Assert.Empty(await client.GetOnChainWalletObjects(admin.StoreId, "BTC"));
|
||||
var test = new OnChainWalletObjectId("test", "test");
|
||||
Assert.NotNull(await client.AddOrUpdateOnChainWalletObject(admin.StoreId, "BTC", new AddOnChainWalletObjectRequest(test.Type, test.Id)));
|
||||
|
||||
Assert.Single(await client.GetOnChainWalletObjects(admin.StoreId, "BTC"));
|
||||
Assert.NotNull(await client.GetOnChainWalletObject(admin.StoreId, "BTC", test));
|
||||
Assert.Null(await client.GetOnChainWalletObject(admin.StoreId, "BTC", new OnChainWalletObjectId("test-wrong", "test")));
|
||||
Assert.Null(await client.GetOnChainWalletObject(admin.StoreId, "BTC", new OnChainWalletObjectId("test", "test-wrong")));
|
||||
|
||||
await client.RemoveOnChainWalletObject(admin.StoreId, "BTC", new OnChainWalletObjectId("test", "test"));
|
||||
|
||||
Assert.Empty(await client.GetOnChainWalletObjects(admin.StoreId, "BTC"));
|
||||
|
||||
var test1 = new OnChainWalletObjectId("test", "test1");
|
||||
var test2 = new OnChainWalletObjectId("test", "test2");
|
||||
await client.AddOrUpdateOnChainWalletObject(admin.StoreId, "BTC", new AddOnChainWalletObjectRequest(test.Type, test.Id));
|
||||
// Those links don't exists
|
||||
await AssertAPIError("wallet-object-not-found", () => client.AddOrUpdateOnChainWalletLink(admin.StoreId, "BTC", test, new AddOnChainWalletObjectLinkRequest(test1.Type, test1.Id)));
|
||||
await AssertAPIError("wallet-object-not-found", () => client.AddOrUpdateOnChainWalletLink(admin.StoreId, "BTC", test, new AddOnChainWalletObjectLinkRequest(test2.Type, test2.Id)));
|
||||
|
||||
Assert.Single(await client.GetOnChainWalletObjects(admin.StoreId, "BTC"));
|
||||
|
||||
await client.AddOrUpdateOnChainWalletObject(admin.StoreId, "BTC", new AddOnChainWalletObjectRequest(test1.Type, test1.Id));
|
||||
await client.AddOrUpdateOnChainWalletObject(admin.StoreId, "BTC", new AddOnChainWalletObjectRequest(test2.Type, test2.Id));
|
||||
|
||||
await client.AddOrUpdateOnChainWalletLink(admin.StoreId, "BTC", test, new AddOnChainWalletObjectLinkRequest(test1.Type, test1.Id));
|
||||
await client.AddOrUpdateOnChainWalletLink(admin.StoreId, "BTC", test, new AddOnChainWalletObjectLinkRequest(test2.Type, test2.Id));
|
||||
|
||||
var objs = await client.GetOnChainWalletObjects(admin.StoreId, "BTC");
|
||||
Assert.Equal(3, objs.Length);
|
||||
var middleObj = objs.Single(data => data.Id == "test" && data.Type == "test");
|
||||
Assert.Equal(2, middleObj.Links.Length);
|
||||
Assert.Contains("test1", middleObj.Links.Select(l => l.Id));
|
||||
Assert.Contains("test2", middleObj.Links.Select(l => l.Id));
|
||||
|
||||
var test1Obj = objs.Single(data => data.Id == "test1" && data.Type == "test");
|
||||
var test2Obj = objs.Single(data => data.Id == "test2" && data.Type == "test");
|
||||
Assert.Single(test1Obj.Links.Select(l => l.Id), l => l == "test");
|
||||
Assert.Single(test2Obj.Links.Select(l => l.Id), l => l == "test");
|
||||
|
||||
await client.RemoveOnChainWalletLinks(admin.StoreId, "BTC",
|
||||
test1,
|
||||
test);
|
||||
|
||||
var testObj = await client.GetOnChainWalletObject(admin.StoreId, "BTC", test);
|
||||
Assert.Single(testObj.Links.Select(l => l.Id), l => l == "test2");
|
||||
Assert.Single(testObj.Links);
|
||||
test1Obj = await client.GetOnChainWalletObject(admin.StoreId, "BTC", test1);
|
||||
Assert.Empty(test1Obj.Links);
|
||||
|
||||
await client.AddOrUpdateOnChainWalletLink(admin.StoreId, "BTC",
|
||||
test1,
|
||||
new AddOnChainWalletObjectLinkRequest(test.Type, test.Id) { Data = new JObject() { ["testData"] = "lol" } });
|
||||
|
||||
// Add some data to test1
|
||||
await client.AddOrUpdateOnChainWalletObject(admin.StoreId, "BTC", new AddOnChainWalletObjectRequest() { Type = test1.Type, Id = test1.Id, Data = new JObject() { ["testData"] = "test1" } });
|
||||
|
||||
// Create a new type
|
||||
await client.AddOrUpdateOnChainWalletObject(admin.StoreId, "BTC", new AddOnChainWalletObjectRequest() { Type = "newtype", Id = test1.Id });
|
||||
|
||||
testObj = await client.GetOnChainWalletObject(admin.StoreId, "BTC", test);
|
||||
Assert.Single(testObj.Links.Where(l => l.Id == "test1" && l.LinkData["testData"]?.Value<string>() == "lol"));
|
||||
Assert.Single(testObj.Links.Where(l => l.Id == "test1" && l.ObjectData["testData"]?.Value<string>() == "test1"));
|
||||
testObj = await client.GetOnChainWalletObject(admin.StoreId, "BTC", test, false);
|
||||
Assert.Single(testObj.Links.Where(l => l.Id == "test1" && l.LinkData["testData"]?.Value<string>() == "lol"));
|
||||
Assert.Single(testObj.Links.Where(l => l.Id == "test1" && l.ObjectData is null));
|
||||
|
||||
async Task TestWalletRepository(bool useInefficient)
|
||||
{
|
||||
// We should have 4 nodes, two `test` type and one `newtype`
|
||||
// Only the node `test` `test` is connected to `test1`
|
||||
var wid = new WalletId(admin.StoreId, "BTC");
|
||||
var repo = tester.PayTester.GetService<WalletRepository>();
|
||||
var allObjects = await repo.GetWalletObjects((new(wid, null) { UseInefficientPath = useInefficient }));
|
||||
var allObjectsNoWallet = await repo.GetWalletObjects((new() { UseInefficientPath = useInefficient }));
|
||||
var allObjectsNoWalletAndType = await repo.GetWalletObjects((new() { Type = "test", UseInefficientPath = useInefficient }));
|
||||
var allTests = await repo.GetWalletObjects((new(wid, "test") { UseInefficientPath = useInefficient }));
|
||||
var twoTests2 = await repo.GetWalletObjects((new(wid, "test", new[] { "test1", "test2", "test-unk" }) { UseInefficientPath = useInefficient }));
|
||||
var oneTest = await repo.GetWalletObjects((new(wid, "test", new[] { "test" }) { UseInefficientPath = useInefficient }));
|
||||
var oneTestWithoutData = await repo.GetWalletObjects((new(wid, "test", new[] { "test" }) { UseInefficientPath = useInefficient, IncludeNeighbours = false }));
|
||||
var idsTypes = await repo.GetWalletObjects((new(wid) { TypesIds = new[] { new ObjectTypeId("test", "test1"), new ObjectTypeId("test", "test2") }, UseInefficientPath = useInefficient }));
|
||||
|
||||
Assert.Equal(4, allObjects.Count);
|
||||
// We are reusing a db in this test, as such we may have other wallets here.
|
||||
Assert.True(allObjectsNoWallet.Count >= 4);
|
||||
Assert.True(allObjectsNoWalletAndType.Count >= 3);
|
||||
Assert.Equal(3, allTests.Count);
|
||||
Assert.Equal(2, twoTests2.Count);
|
||||
Assert.Single(oneTest);
|
||||
Assert.NotNull(oneTest.First().Value.GetNeighbours().Select(n => n.Data).FirstOrDefault());
|
||||
Assert.Single(oneTestWithoutData);
|
||||
Assert.Null(oneTestWithoutData.First().Value.GetNeighbours().Select(n => n.Data).FirstOrDefault());
|
||||
Assert.Equal(2, idsTypes.Count);
|
||||
}
|
||||
await TestWalletRepository(false);
|
||||
await TestWalletRepository(true);
|
||||
|
||||
{
|
||||
var allObjects = await client.GetOnChainWalletObjects(admin.StoreId, "BTC");
|
||||
var allTests = await client.GetOnChainWalletObjects(admin.StoreId, "BTC", new GetWalletObjectsRequest() { Type = "test" });
|
||||
var twoTests2 = await client.GetOnChainWalletObjects(admin.StoreId, "BTC", new GetWalletObjectsRequest() { Type = "test", Ids = new[] { "test1", "test2", "test-unk" } });
|
||||
var oneTest = await client.GetOnChainWalletObjects(admin.StoreId, "BTC", new GetWalletObjectsRequest() { Type = "test", Ids=new[] { "test" } });
|
||||
var oneTestWithoutData = await client.GetOnChainWalletObjects(admin.StoreId, "BTC", new GetWalletObjectsRequest() { Type = "test", Ids = new[] { "test" }, IncludeNeighbourData = false });
|
||||
|
||||
Assert.Equal(4, allObjects.Length);
|
||||
Assert.Equal(3, allTests.Length);
|
||||
Assert.Equal(2, twoTests2.Length);
|
||||
Assert.Single(oneTest);
|
||||
Assert.NotNull(oneTest.First().Links.Select(n => n.ObjectData).FirstOrDefault());
|
||||
Assert.Single(oneTestWithoutData);
|
||||
Assert.Null(oneTestWithoutData.First().Links.Select(n => n.ObjectData).FirstOrDefault());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
@ -2593,8 +3133,67 @@ namespace BTCPayServer.Tests
|
||||
Assert.NotNull(custodians);
|
||||
Assert.NotEmpty(custodians);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task StoreRateConfigTests()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
var unauthClient = new BTCPayServerClient(tester.PayTester.ServerUri);
|
||||
await AssertHttpError(401, async () => await unauthClient.GetRateSources());
|
||||
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync();
|
||||
var clientBasic = await user.CreateClient();
|
||||
Assert.NotEmpty(await clientBasic.GetRateSources());
|
||||
var config = await clientBasic.GetStoreRateConfiguration(user.StoreId);
|
||||
Assert.NotNull(config);
|
||||
Assert.False(config.IsCustomScript);
|
||||
Assert.Equal("X_X = coingecko(X_X);", config.EffectiveScript);
|
||||
Assert.Equal("coingecko", config.PreferredSource);
|
||||
|
||||
Assert.Equal(0.9m,
|
||||
Assert.Single(await clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId,
|
||||
new StoreRateConfiguration() {IsCustomScript = true, EffectiveScript = "BTC_XYZ = 1;", Spread = 10m,},
|
||||
new[] {"BTC_XYZ"})).Rate);
|
||||
|
||||
Assert.True((await clientBasic.UpdateStoreRateConfiguration(user.StoreId,
|
||||
new StoreRateConfiguration() { IsCustomScript = true, EffectiveScript = "BTC_XYZ = 1", Spread = 10m,}))
|
||||
.IsCustomScript);
|
||||
|
||||
config = await clientBasic.GetStoreRateConfiguration(user.StoreId);
|
||||
Assert.NotNull(config);
|
||||
Assert.NotNull(config.EffectiveScript);
|
||||
Assert.Equal("BTC_XYZ = 1;", config.EffectiveScript);
|
||||
Assert.Equal(10m, config.Spread);
|
||||
Assert.Null(config.PreferredSource);
|
||||
|
||||
Assert.NotNull((await clientBasic.GetStoreRateConfiguration(user.StoreId)).EffectiveScript);
|
||||
Assert.NotNull((await clientBasic.UpdateStoreRateConfiguration(user.StoreId,
|
||||
new StoreRateConfiguration() { IsCustomScript = false, PreferredSource = "coingecko"}))
|
||||
.PreferredSource);
|
||||
|
||||
config = await clientBasic.GetStoreRateConfiguration(user.StoreId);
|
||||
Assert.Equal("X_X = coingecko(X_X);", config.EffectiveScript);
|
||||
|
||||
await AssertValidationError(new[] { "EffectiveScript", "PreferredSource" }, () =>
|
||||
clientBasic.UpdateStoreRateConfiguration(user.StoreId, new StoreRateConfiguration() { IsCustomScript = false, EffectiveScript = "BTC_XYZ = 1;" }));
|
||||
|
||||
await AssertValidationError(new[] { "EffectiveScript" }, () =>
|
||||
clientBasic.UpdateStoreRateConfiguration(user.StoreId, new StoreRateConfiguration() { IsCustomScript = true, EffectiveScript = "BTC_XYZ rg8w*# 1;" }));
|
||||
await AssertValidationError(new[] { "PreferredSource" }, () =>
|
||||
clientBasic.UpdateStoreRateConfiguration(user.StoreId, new StoreRateConfiguration() { IsCustomScript = true, EffectiveScript = "", PreferredSource = "coingecko" }));
|
||||
|
||||
await AssertValidationError(new[] { "PreferredSource", "Spread" }, () =>
|
||||
clientBasic.UpdateStoreRateConfiguration(user.StoreId, new StoreRateConfiguration() { IsCustomScript = false, PreferredSource = "coingeckoOOO", Spread = -1m }));
|
||||
|
||||
await AssertValidationError(new[] { "currencyPair" }, () =>
|
||||
clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfiguration() { IsCustomScript = false, PreferredSource = "coingecko" }, new[] { "BTC_USD_USD_BTC" }));
|
||||
await AssertValidationError(new[] { "PreferredSource", "currencyPair" }, () =>
|
||||
clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfiguration() { IsCustomScript = false, PreferredSource = "coingeckoOOO" }, new[] { "BTC_USD_USD_BTC" }));
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CustodianAccountControllerTests()
|
||||
@ -2859,13 +3458,13 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// Test: Trade, unauth
|
||||
var tradeRequest = new TradeRequestData {FromAsset = MockCustodian.TradeFromAsset, ToAsset = MockCustodian.TradeToAsset, Qty = MockCustodian.TradeQtyBought.ToString(CultureInfo.InvariantCulture)};
|
||||
await AssertHttpError(401, async () => await unauthClient.TradeMarket(storeId, accountId, tradeRequest));
|
||||
await AssertHttpError(401, async () => await unauthClient.MarketTradeCustodianAccountAsset(storeId, accountId, tradeRequest));
|
||||
|
||||
// Test: Trade, auth, but wrong permission
|
||||
await AssertHttpError(403, async () => await managerClient.TradeMarket(storeId, accountId, tradeRequest));
|
||||
await AssertHttpError(403, async () => await managerClient.MarketTradeCustodianAccountAsset(storeId, accountId, tradeRequest));
|
||||
|
||||
// Test: Trade, correct permission, correct assets, correct amount
|
||||
var newTradeResult = await tradeClient.TradeMarket(storeId, accountId, tradeRequest);
|
||||
var newTradeResult = await tradeClient.MarketTradeCustodianAccountAsset(storeId, accountId, tradeRequest);
|
||||
Assert.NotNull(newTradeResult);
|
||||
Assert.Equal(accountId, newTradeResult.AccountId);
|
||||
Assert.Equal(mockCustodian.Code, newTradeResult.CustodianCode);
|
||||
@ -2886,23 +3485,27 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// Test: GetTradeQuote, SATS
|
||||
var satsTradeRequest = new TradeRequestData {FromAsset = MockCustodian.TradeFromAsset, ToAsset = "SATS", Qty = MockCustodian.TradeQtyBought.ToString(CultureInfo.InvariantCulture)};
|
||||
await AssertApiError(400, "use-asset-synonym", async () => await tradeClient.TradeMarket(storeId, accountId, satsTradeRequest));
|
||||
await AssertApiError(400, "use-asset-synonym", async () => await tradeClient.MarketTradeCustodianAccountAsset(storeId, accountId, satsTradeRequest));
|
||||
|
||||
// TODO Test: Trade with percentage qty
|
||||
|
||||
// Test: Trade with wrong decimal format (example: JavaScript scientific format)
|
||||
var wrongQtyTradeRequest = new TradeRequestData {FromAsset = MockCustodian.TradeFromAsset, ToAsset = MockCustodian.TradeToAsset, Qty = "6.1e-7"};
|
||||
await AssertApiError(400,"bad-qty-format", async () => await tradeClient.MarketTradeCustodianAccountAsset(storeId, accountId, wrongQtyTradeRequest));
|
||||
|
||||
// Test: Trade, wrong assets method
|
||||
var wrongAssetsTradeRequest = new TradeRequestData {FromAsset = "WRONG", ToAsset = MockCustodian.TradeToAsset, Qty = MockCustodian.TradeQtyBought.ToString(CultureInfo.InvariantCulture)};
|
||||
await AssertHttpError(WrongTradingPairException.HttpCode, async () => await tradeClient.TradeMarket(storeId, accountId, wrongAssetsTradeRequest));
|
||||
await AssertHttpError(WrongTradingPairException.HttpCode, async () => await tradeClient.MarketTradeCustodianAccountAsset(storeId, accountId, wrongAssetsTradeRequest));
|
||||
|
||||
// Test: wrong account ID
|
||||
await AssertHttpError(404, async () => await tradeClient.TradeMarket(storeId, "WRONG-ACCOUNT-ID", tradeRequest));
|
||||
await AssertHttpError(404, async () => await tradeClient.MarketTradeCustodianAccountAsset(storeId, "WRONG-ACCOUNT-ID", tradeRequest));
|
||||
|
||||
// Test: wrong store ID
|
||||
await AssertHttpError(403, async () => await tradeClient.TradeMarket("WRONG-STORE-ID", accountId, tradeRequest));
|
||||
await AssertHttpError(403, async () => await tradeClient.MarketTradeCustodianAccountAsset("WRONG-STORE-ID", accountId, tradeRequest));
|
||||
|
||||
// Test: Trade, correct assets, wrong amount
|
||||
var wrongQtyTradeRequest = new TradeRequestData {FromAsset = MockCustodian.TradeFromAsset, ToAsset = MockCustodian.TradeToAsset, Qty = "0.01"};
|
||||
await AssertApiError(400, "insufficient-funds", async () => await tradeClient.TradeMarket(storeId, accountId, wrongQtyTradeRequest));
|
||||
var insufficientFundsTradeRequest = new TradeRequestData {FromAsset = MockCustodian.TradeFromAsset, ToAsset = MockCustodian.TradeToAsset, Qty = "0.01"};
|
||||
await AssertApiError(400, "insufficient-funds", async () => await tradeClient.MarketTradeCustodianAccountAsset(storeId, accountId, insufficientFundsTradeRequest));
|
||||
|
||||
|
||||
// Test: GetTradeQuote, unauth
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Custodians;
|
||||
using BTCPayServer.Abstractions.Custodians.Client;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
using BTCPayServer.Client.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@ -76,7 +77,7 @@ public class MockCustodian : ICustodian, ICanDeposit, ICanTrade, ICanWithdraw
|
||||
public List<AssetPairData> GetTradableAssetPairs()
|
||||
{
|
||||
var r = new List<AssetPairData>();
|
||||
r.Add(new AssetPairData("BTC", "EUR"));
|
||||
r.Add(new AssetPairData("BTC", "EUR", (decimal) 0.0001));
|
||||
return r;
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,8 @@ using System.Threading.Tasks;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Plugins.PointOfSale.Controllers;
|
||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@ -28,6 +30,7 @@ namespace BTCPayServer.Tests
|
||||
await user.GrantAccessAsync();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
var apps = user.GetController<UIAppsController>();
|
||||
var pos = user.GetController<UIPointOfSaleController>();
|
||||
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
|
||||
var appType = AppType.PointOfSale.ToString();
|
||||
vm.AppName = "test";
|
||||
@ -35,8 +38,10 @@ namespace BTCPayServer.Tests
|
||||
Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
|
||||
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
|
||||
var app = appList.Apps[0];
|
||||
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType });
|
||||
var vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType };
|
||||
apps.HttpContext.SetAppData(appData);
|
||||
pos.HttpContext.SetAppData(appData);
|
||||
var vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
vmpos.Template = @"
|
||||
apple:
|
||||
price: 5.0
|
||||
@ -48,9 +53,9 @@ donation:
|
||||
price: 1.02
|
||||
custom: true
|
||||
";
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
var publicApps = user.GetController<UIAppsPublicController>();
|
||||
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
var publicApps = user.GetController<UIPointOfSaleController>();
|
||||
var vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync<ViewPointOfSaleViewModel>();
|
||||
|
||||
// apple shouldn't be available since we it's set to "disabled: true" above
|
||||
|
@ -185,6 +185,15 @@ namespace BTCPayServer.Tests
|
||||
return (name, storeId);
|
||||
}
|
||||
|
||||
public void EnableCheckoutV2(bool bip21 = false)
|
||||
{
|
||||
GoToStore(StoreNavPages.CheckoutAppearance);
|
||||
Driver.SetCheckbox(By.Id("UseNewCheckout"), true);
|
||||
Driver.WaitForElement(By.Id("OnChainWithLnInvoiceFallback"));
|
||||
Driver.SetCheckbox(By.Id("OnChainWithLnInvoiceFallback"), bip21);
|
||||
Driver.FindElement(By.Id("Save")).Click();
|
||||
}
|
||||
|
||||
public Mnemonic GenerateWallet(string cryptoCode = "BTC", string seed = "", bool? importkeys = null, bool isHotWallet = false, ScriptPubKeyType format = ScriptPubKeyType.Segwit)
|
||||
{
|
||||
var isImport = !string.IsNullOrEmpty(seed);
|
||||
|
@ -30,7 +30,6 @@ using Newtonsoft.Json.Linq;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Support.Extensions;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
using Renci.SshNet.Security.Cryptography;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
@ -65,6 +64,61 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.Quit();
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanUseForms()
|
||||
{
|
||||
using var s = CreateSeleniumTester();
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser(true);
|
||||
s.CreateNewStore();
|
||||
s.GenerateWallet(isHotWallet: true);
|
||||
|
||||
// Point Of Sale
|
||||
s.Driver.FindElement(By.Id("StoreNav-CreateApp")).Click();
|
||||
new SelectElement(s.Driver.FindElement(By.Id("SelectedAppType"))).SelectByValue("PointOfSale");
|
||||
s.Driver.FindElement(By.Id("AppName")).SendKeys(Guid.NewGuid().ToString());
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
Assert.Contains("App successfully created", s.FindAlertMessage().Text);
|
||||
|
||||
new SelectElement(s.Driver.FindElement(By.Id("FormId"))).SelectByValue("Email");
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).Click();
|
||||
Assert.Contains("App updated", s.FindAlertMessage().Text);
|
||||
|
||||
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
||||
var windows = s.Driver.WindowHandles;
|
||||
Assert.Equal(2, windows.Count);
|
||||
s.Driver.SwitchTo().Window(windows[1]);
|
||||
s.Driver.FindElement(By.CssSelector("button[type='submit']")).Click();
|
||||
|
||||
Assert.Contains("Enter your email", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Name("buyerEmail")).SendKeys("aa@aa.com");
|
||||
s.Driver.FindElement(By.CssSelector("input[type='submit']")).Click();
|
||||
|
||||
s.PayInvoice(true);
|
||||
var invoiceId = s.Driver.Url[(s.Driver.Url.LastIndexOf("/", StringComparison.Ordinal) + 1)..];
|
||||
s.GoToInvoice(invoiceId);
|
||||
Assert.Contains("aa@aa.com", s.Driver.PageSource);
|
||||
|
||||
// Payment Request
|
||||
s.Driver.FindElement(By.Id("StoreNav-PaymentRequests")).Click();
|
||||
s.Driver.FindElement(By.Id("CreatePaymentRequest")).Click();
|
||||
s.Driver.FindElement(By.Id("Title")).SendKeys("Pay123");
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("700");
|
||||
new SelectElement(s.Driver.FindElement(By.Id("FormId"))).SelectByValue("Email");
|
||||
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
||||
|
||||
s.Driver.FindElement(By.XPath("//a[starts-with(@id, 'Edit-')]")).Click();
|
||||
var editUrl = s.Driver.Url;
|
||||
s.Driver.FindElement(By.Id("ViewPaymentRequest")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("[data-test='form-button']")).Click();
|
||||
Assert.Contains("Enter your email", s.Driver.PageSource);
|
||||
|
||||
s.Driver.FindElement(By.Name("buyerEmail")).SendKeys("aa@aa.com");
|
||||
s.Driver.FindElement(By.CssSelector("input[type='submit']")).Click();
|
||||
s.Driver.Navigate().GoToUrl(editUrl);
|
||||
Assert.Contains("aa@aa.com", s.Driver.PageSource);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanUseCPFP()
|
||||
{
|
||||
@ -448,9 +502,12 @@ namespace BTCPayServer.Tests
|
||||
s.AddDerivationScheme();
|
||||
s.GoToInvoices();
|
||||
var i = s.CreateInvoice();
|
||||
s.GoToInvoiceCheckout(i);
|
||||
s.PayInvoice(true);
|
||||
TestUtils.Eventually(() => s.Driver.FindElement(By.LinkText("View receipt")).Click());
|
||||
await s.Server.PayTester.InvoiceRepository.MarkInvoiceStatus(i, InvoiceStatus.Settled);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
s.Driver.FindElement(By.Id($"Receipt")).Click();
|
||||
});
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
@ -473,7 +530,11 @@ namespace BTCPayServer.Tests
|
||||
s.GoToInvoiceCheckout(i);
|
||||
var checkouturi = s.Driver.Url;
|
||||
s.PayInvoice();
|
||||
TestUtils.Eventually(() => s.Driver.FindElement(By.LinkText("View receipt")).Click());
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
s.Driver.FindElement(By.Id("receipt-btn")).Click();
|
||||
});
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
@ -481,9 +542,10 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("invoice-processing", s.Driver.PageSource);
|
||||
});
|
||||
s.GoToUrl(checkouturi);
|
||||
s.MineBlockOnInvoiceCheckout();
|
||||
|
||||
await s.Server.PayTester.InvoiceRepository.MarkInvoiceStatus(i, InvoiceStatus.Settled);
|
||||
|
||||
TestUtils.Eventually(() => s.Driver.FindElement(By.LinkText("View receipt")).Click());
|
||||
TestUtils.Eventually(() => s.Driver.FindElement(By.Id("receipt-btn")).Click());
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
@ -637,15 +699,26 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("enable-pay-button")).Click();
|
||||
s.Driver.FindElement(By.Id("disable-pay-button")).Click();
|
||||
s.FindAlertMessage();
|
||||
s.GoToStore(StoreNavPages.General);
|
||||
s.GoToStore();
|
||||
Assert.False(s.Driver.FindElement(By.Id("AnyoneCanCreateInvoice")).Selected);
|
||||
s.Driver.SetCheckbox(By.Id("AnyoneCanCreateInvoice"), true);
|
||||
s.Driver.FindElement(By.Id("Save")).Click();
|
||||
s.FindAlertMessage();
|
||||
Assert.True(s.Driver.FindElement(By.Id("AnyoneCanCreateInvoice")).Selected);
|
||||
|
||||
// Store settings: Set and unset brand color
|
||||
s.GoToStore();
|
||||
s.Driver.FindElement(By.Id("BrandColor")).SendKeys("#f7931a");
|
||||
s.Driver.FindElement(By.Id("Save")).Click();
|
||||
Assert.Contains("Store successfully updated", s.FindAlertMessage().Text);
|
||||
Assert.Equal("#f7931a", s.Driver.FindElement(By.Id("BrandColor")).GetAttribute("value"));
|
||||
s.Driver.FindElement(By.Id("BrandColor")).Clear();
|
||||
s.Driver.FindElement(By.Id("Save")).Click();
|
||||
Assert.Contains("Store successfully updated", s.FindAlertMessage().Text);
|
||||
Assert.Equal(string.Empty, s.Driver.FindElement(By.Id("BrandColor")).GetAttribute("value"));
|
||||
|
||||
// Alice should be able to delete the store
|
||||
s.GoToStore(StoreNavPages.General);
|
||||
s.GoToStore();
|
||||
s.Driver.FindElement(By.Id("DeleteStore")).Click();
|
||||
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
|
||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
@ -801,6 +874,16 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("TargetCurrency")).Clear();
|
||||
s.Driver.FindElement(By.Id("TargetCurrency")).SendKeys("JPY");
|
||||
s.Driver.FindElement(By.Id("TargetAmount")).SendKeys("700");
|
||||
|
||||
// test wrong dates
|
||||
s.Driver.ExecuteJavaScript("const now = new Date();document.getElementById('StartDate').value = now.toISOString();" +
|
||||
"const yst = new Date(now.setDate(now.getDate() -1));document.getElementById('EndDate').value = yst.toISOString()");
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).Click();
|
||||
Assert.Contains("End date cannot be before start date", s.Driver.PageSource);
|
||||
Assert.DoesNotContain("App updated", s.Driver.PageSource);
|
||||
|
||||
// unset end date
|
||||
s.Driver.ExecuteJavaScript("document.getElementById('EndDate').value = ''");
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).Click();
|
||||
Assert.Contains("App updated", s.FindAlertMessage().Text);
|
||||
|
||||
@ -843,9 +926,8 @@ namespace BTCPayServer.Tests
|
||||
var viewUrl = s.Driver.Url;
|
||||
|
||||
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.GoToUrl(editUrl);
|
||||
s.Driver.ExecuteJavaScript("document.getElementById('ExpiryDate').value = '2021-01-21T21:00:00.000Z'");
|
||||
@ -863,8 +945,13 @@ namespace BTCPayServer.Tests
|
||||
|
||||
s.GoToUrl(viewUrl);
|
||||
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());
|
||||
|
||||
// test invoice creation, click with JS, because the button is inside a sticky header
|
||||
s.Driver.ExecuteJavaScript("document.querySelector('[data-test=\"pay-button\"]').click()");
|
||||
// checkout v1
|
||||
s.Driver.WaitForElement(By.CssSelector("invoice"));
|
||||
Assert.Contains("Awaiting Payment", s.Driver.PageSource);
|
||||
|
||||
// archive (from details page)
|
||||
s.GoToUrl(editUrl);
|
||||
@ -993,10 +1080,10 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
// This one should be checked
|
||||
Assert.Contains($"value=\"InvoiceProcessing\" checked", s.Driver.PageSource);
|
||||
Assert.Contains($"value=\"InvoiceCreated\" checked", s.Driver.PageSource);
|
||||
Assert.Contains("value=\"InvoiceProcessing\" checked", s.Driver.PageSource);
|
||||
Assert.Contains("value=\"InvoiceCreated\" checked", s.Driver.PageSource);
|
||||
// This one never been checked
|
||||
Assert.DoesNotContain($"value=\"InvoiceReceivedPayment\" checked", s.Driver.PageSource);
|
||||
Assert.DoesNotContain("value=\"InvoiceReceivedPayment\" checked", s.Driver.PageSource);
|
||||
|
||||
s.Driver.FindElement(By.Name("update")).Click();
|
||||
s.FindAlertMessage();
|
||||
@ -1027,6 +1114,7 @@ namespace BTCPayServer.Tests
|
||||
s.GoToStore(StoreNavPages.Webhooks);
|
||||
s.Driver.FindElement(By.LinkText("Modify")).Click();
|
||||
var elements = s.Driver.FindElements(By.ClassName("redeliver"));
|
||||
|
||||
// One worked, one failed
|
||||
s.Driver.FindElement(By.ClassName("fa-times"));
|
||||
s.Driver.FindElement(By.ClassName("fa-check"));
|
||||
@ -1300,6 +1388,46 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.AssertElementNotFound(By.Id("ActionsDropdownToggle"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanEditPullPaymentUI()
|
||||
{
|
||||
using var s = CreateSeleniumTester();
|
||||
s.Server.ActivateLightning(LightningConnectionType.LndREST);
|
||||
await s.StartAsync();
|
||||
await s.Server.EnsureChannelsSetup();
|
||||
s.RegisterNewUser(true);
|
||||
s.CreateNewStore();
|
||||
s.GenerateWallet("BTC", "", true, true);
|
||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||
await s.FundStoreWallet(denomination: 50.0m);
|
||||
|
||||
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("Create")).Click();
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
|
||||
s.Driver.FindElement(By.LinkText("PP1")).Click();
|
||||
var name = s.Driver.FindElement(By.Id("Name"));
|
||||
name.Clear();
|
||||
name.SendKeys("PP1 Edited");
|
||||
var description = s.Driver.FindElement(By.ClassName("card-block"));
|
||||
description.SendKeys("Description Edit");
|
||||
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
||||
|
||||
s.Driver.FindElement(By.LinkText("PP1 Edited")).Click();
|
||||
|
||||
Assert.Contains("Description Edit", s.Driver.PageSource);
|
||||
Assert.Contains("PP1 Edited", s.Driver.PageSource);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
|
@ -72,14 +72,14 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanQueryDirectProviders()
|
||||
public async Task CanQueryDirectProviders()
|
||||
{
|
||||
// TODO: Check once in a while whether or not they are working again
|
||||
string[] brokenShitcoinCasinos = { "okex" };
|
||||
string[] brokenShitcoinCasinos = {};
|
||||
var skipped = 0;
|
||||
var factory = FastTests.CreateBTCPayRateFactory();
|
||||
var directlySupported = factory.GetSupportedExchanges().Where(s => s.Source == RateSource.Direct)
|
||||
.Select(s => s.Id).ToHashSet();
|
||||
var all = string.Join("\r\n", factory.GetSupportedExchanges().Select(e => e.Id).ToArray());
|
||||
foreach (var result in factory
|
||||
.Providers
|
||||
.Where(p => p.Value is BackgroundFetcherRateProvider bf &&
|
||||
@ -91,14 +91,26 @@ namespace BTCPayServer.Tests
|
||||
var name = result.ExpectedName;
|
||||
if (brokenShitcoinCasinos.Contains(name))
|
||||
{
|
||||
TestLogs.LogInformation($"Skipping {name}");
|
||||
TestLogs.LogInformation($"Skipping {name}: Broken shitcoin casino");
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
TestLogs.LogInformation($"Testing {name}");
|
||||
|
||||
result.Fetcher.InvalidateCache();
|
||||
var exchangeRates = new ExchangeRates(name, result.ResultAsync.Result);
|
||||
|
||||
ExchangeRates exchangeRates = null;
|
||||
try
|
||||
{
|
||||
exchangeRates = new ExchangeRates(name, result.ResultAsync.Result);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
TestLogs.LogInformation($"Skipping {name}: {exception.Message}");
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
result.Fetcher.InvalidateCache();
|
||||
Assert.NotNull(exchangeRates);
|
||||
Assert.NotEmpty(exchangeRates);
|
||||
@ -126,6 +138,12 @@ namespace BTCPayServer.Tests
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "CLP") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1 BTC will always be more than 1 CLP
|
||||
}
|
||||
else if (name == "yadio")
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[name],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "LBP") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1 BTC will always be more than 1 LBP (I hope)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (name == "kraken")
|
||||
@ -154,12 +172,12 @@ namespace BTCPayServer.Tests
|
||||
// Kraken emit one request only after first GetRates
|
||||
factory.Providers["kraken"].GetRatesAsync(default).GetAwaiter().GetResult();
|
||||
|
||||
using (var c = new HttpClient())
|
||||
{
|
||||
var p = new ExchangeSharpRateProvider<ExchangeSharp.ExchangeKrakenAPI>(c);
|
||||
var rates = p.GetRatesAsync(default).GetAwaiter().GetResult();
|
||||
Assert.Contains(rates, e => e.CurrencyPair == new CurrencyPair("XXMR", "XXBT") && e.BidAsk.Bid < 1.0m);
|
||||
}
|
||||
var p = new KrakenExchangeRateProvider();
|
||||
var rates = await p.GetRatesAsync(default);
|
||||
Assert.Contains(rates, e => e.CurrencyPair == new CurrencyPair("XMR", "BTC") && e.BidAsk.Bid < 1.0m);
|
||||
|
||||
// Check we didn't skip too many exchanges
|
||||
Assert.InRange(skipped, 0, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -293,17 +311,39 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
// This test verify that no malicious js is added in the minified files.
|
||||
// We should extend the tests to other js files, but we can do as we go...
|
||||
|
||||
using HttpClient client = new HttpClient();
|
||||
var actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "bootstrap", "bootstrap.bundle.min.js");
|
||||
using var client = new HttpClient();
|
||||
var actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "bootstrap", "bootstrap.bundle.min.js").Trim();
|
||||
var version = Regex.Match(actual, "Bootstrap v([0-9]+.[0-9]+.[0-9]+)").Groups[1].Value;
|
||||
var expected = await (await client.GetAsync($"https://cdn.jsdelivr.net/npm/bootstrap@{version}/dist/js/bootstrap.bundle.min.js")).Content.ReadAsStringAsync();
|
||||
Assert.Equal(expected, actual.Replace("\r\n", "\n", StringComparison.OrdinalIgnoreCase));
|
||||
var expected = (await (await client.GetAsync($"https://cdn.jsdelivr.net/npm/bootstrap@{version}/dist/js/bootstrap.bundle.min.js")).Content.ReadAsStringAsync()).Trim();
|
||||
Assert.Equal(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "clipboard.js", "clipboard.js");
|
||||
expected = await (await client.GetAsync($"https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.8/clipboard.js")).Content.ReadAsStringAsync();
|
||||
Assert.Equal(expected, actual.Replace("\r\n", "\n", StringComparison.OrdinalIgnoreCase));
|
||||
expected = (await (await client.GetAsync("https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.8/clipboard.js")).Content.ReadAsStringAsync()).Trim();
|
||||
Assert.Equal(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "vuejs", "vue.min.js").Trim();
|
||||
version = Regex.Match(actual, "Vue\\.js v([0-9]+.[0-9]+.[0-9]+)").Groups[1].Value;
|
||||
expected = (await (await client.GetAsync($"https://cdnjs.cloudflare.com/ajax/libs/vue/{version}/vue.min.js")).Content.ReadAsStringAsync()).Trim();
|
||||
Assert.Equal(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "i18next", "i18next.min.js").Trim();
|
||||
expected = (await (await client.GetAsync("https://cdnjs.cloudflare.com/ajax/libs/i18next/22.0.6/i18next.min.js")).Content.ReadAsStringAsync()).Trim();
|
||||
Assert.Equal(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "i18next", "i18nextHttpBackend.min.js").Trim();
|
||||
expected = (await (await client.GetAsync("https://cdnjs.cloudflare.com/ajax/libs/i18next-http-backend/2.0.1/i18nextHttpBackend.min.js")).Content.ReadAsStringAsync()).Trim();
|
||||
Assert.Equal(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "i18next", "vue-i18next.js").Trim();
|
||||
expected = (await (await client.GetAsync("https://unpkg.com/@panter/vue-i18next@0.15.2/dist/vue-i18next.js")).Content.ReadAsStringAsync()).Trim();
|
||||
Assert.Equal(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "vue-qrcode", "vue-qrcode.min.js").Trim();
|
||||
version = Regex.Match(actual, "vue-qrcode v([0-9]+.[0-9]+.[0-9]+)").Groups[1].Value;
|
||||
expected = (await (await client.GetAsync($"https://unpkg.com/@chenfengyuan/vue-qrcode@{version}/dist/vue-qrcode.min.js")).Content.ReadAsStringAsync()).Trim();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
string GetFileContent(params string[] path)
|
||||
{
|
||||
var l = path.ToList();
|
||||
|
@ -35,10 +35,12 @@ using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Payments.PayJoin.Sender;
|
||||
using BTCPayServer.Plugins.PointOfSale.Controllers;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Labels;
|
||||
using BTCPayServer.Services.Mails;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Storage.Models;
|
||||
@ -50,6 +52,7 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitcoin.Payment;
|
||||
@ -651,8 +654,8 @@ namespace BTCPayServer.Tests
|
||||
(string)store2.TempData[WellKnownTempData.ErrorMessage], StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Fact(Timeout = LongRunningTestTimeout * 2)]
|
||||
[Trait("Flaky", "Flaky")]
|
||||
public async Task CanUseTorClient()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
@ -784,9 +787,9 @@ namespace BTCPayServer.Tests
|
||||
tx = Assert.Single(transactions.Transactions);
|
||||
|
||||
Assert.Equal("hello", tx.Comment);
|
||||
Assert.Contains("test", tx.Labels.Select(l => l.Text));
|
||||
Assert.Contains("test2", tx.Labels.Select(l => l.Text));
|
||||
Assert.Equal(2, tx.Labels.GroupBy(l => l.Color).Count());
|
||||
Assert.Contains("test", tx.Tags.Select(l => l.Text));
|
||||
Assert.Contains("test2", tx.Tags.Select(l => l.Text));
|
||||
Assert.Equal(2, tx.Tags.GroupBy(l => l.Color).Count());
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(
|
||||
await walletController.ModifyTransaction(walletId, tx.Id, removelabel: "test2"));
|
||||
@ -796,12 +799,9 @@ namespace BTCPayServer.Tests
|
||||
tx = Assert.Single(transactions.Transactions);
|
||||
|
||||
Assert.Equal("hello", tx.Comment);
|
||||
Assert.Contains("test", tx.Labels.Select(l => l.Text));
|
||||
Assert.DoesNotContain("test2", tx.Labels.Select(l => l.Text));
|
||||
Assert.Single(tx.Labels.GroupBy(l => l.Color));
|
||||
|
||||
var walletInfo = await tester.PayTester.GetService<WalletRepository>().GetWalletInfo(walletId);
|
||||
Assert.Single(walletInfo.LabelColors); // the test2 color should have been removed
|
||||
Assert.Contains("test", tx.Tags.Select(l => l.Text));
|
||||
Assert.DoesNotContain("test2", tx.Tags.Select(l => l.Text));
|
||||
Assert.Single(tx.Tags.GroupBy(l => l.Color));
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
@ -1752,7 +1752,7 @@ namespace BTCPayServer.Tests
|
||||
await user.GrantAccessAsync();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
await user.SetNetworkFeeMode(NetworkFeeMode.Always);
|
||||
var invoice = user.BitPay.CreateInvoice(
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
{
|
||||
Price = 10,
|
||||
@ -1764,11 +1764,10 @@ namespace BTCPayServer.Tests
|
||||
}, Facade.Merchant);
|
||||
|
||||
var networkFee = new FeeRate(invoice.MinerFees["BTC"].SatoshiPerBytes).GetFee(100);
|
||||
// ensure 0 invoices exported because there are no payments yet
|
||||
var jsonResult = user.GetController<UIInvoiceController>().Export("json").GetAwaiter().GetResult();
|
||||
var result = Assert.IsType<ContentResult>(jsonResult);
|
||||
Assert.Equal("application/json", result.ContentType);
|
||||
Assert.Equal("[]", result.Content);
|
||||
Assert.Single(JArray.Parse(result.Content));
|
||||
|
||||
var cashCow = tester.ExplorerNode;
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
|
||||
@ -1953,6 +1952,7 @@ namespace BTCPayServer.Tests
|
||||
var stores = user.GetController<UIStoresController>();
|
||||
var apps = user.GetController<UIAppsController>();
|
||||
var apps2 = user2.GetController<UIAppsController>();
|
||||
var pos = user.GetController<UIPointOfSaleController>();
|
||||
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
|
||||
var appType = AppType.PointOfSale.ToString();
|
||||
Assert.NotNull(vm.SelectedAppType);
|
||||
@ -1960,12 +1960,14 @@ namespace BTCPayServer.Tests
|
||||
vm.AppName = "test";
|
||||
vm.SelectedAppType = appType;
|
||||
var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
|
||||
Assert.Equal(nameof(apps.UpdatePointOfSale), redirectToAction.ActionName);
|
||||
Assert.Equal(nameof(pos.UpdatePointOfSale), redirectToAction.ActionName);
|
||||
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
|
||||
var appList2 =
|
||||
Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps2.ListApps(user2.StoreId).Result).Model);
|
||||
var app = appList.Apps[0];
|
||||
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType });
|
||||
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType };
|
||||
apps.HttpContext.SetAppData(appData);
|
||||
pos.HttpContext.SetAppData(appData);
|
||||
Assert.Single(appList.Apps);
|
||||
Assert.Empty(appList2.Apps);
|
||||
Assert.Equal("test", appList.Apps[0].AppName);
|
||||
@ -2518,6 +2520,79 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(lnMethod.IsInternalNode);
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Obsolete]
|
||||
public async Task CanDoLabelMigrations()
|
||||
{
|
||||
using var tester = CreateServerTester(newDb: true);
|
||||
await tester.StartAsync();
|
||||
var dbf = tester.PayTester.GetService<ApplicationDbContextFactory>();
|
||||
int walletCount = 1000;
|
||||
var wallet = "walletttttttttttttttttttttttttttt";
|
||||
using (var db = dbf.CreateContext())
|
||||
{
|
||||
for (int i = 0; i < walletCount; i++)
|
||||
{
|
||||
var walletData = new WalletData() { Id = $"S-{wallet}{i}-BTC" };
|
||||
walletData.Blob = ZipUtils.Zip("{\"LabelColors\": { \"label1\" : \"black\", \"payout\":\"green\" }}");
|
||||
db.Wallets.Add(walletData);
|
||||
}
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
uint256 firstTxId = null;
|
||||
using (var db = dbf.CreateContext())
|
||||
{
|
||||
int transactionCount = 10_000;
|
||||
for (int i = 0; i < transactionCount; i++)
|
||||
{
|
||||
var txId = RandomUtils.GetUInt256();
|
||||
var wt = new WalletTransactionData()
|
||||
{
|
||||
WalletDataId = $"S-{wallet}{i % walletCount}-BTC",
|
||||
TransactionId = txId.ToString(),
|
||||
};
|
||||
firstTxId ??= txId;
|
||||
if (i != 10)
|
||||
wt.Blob = ZipUtils.Zip("{\"Comment\":\"test\"}");
|
||||
if (i % 1240 != 0)
|
||||
{
|
||||
wt.Labels = "[{\"type\":\"raw\", \"text\":\"label1\"}]";
|
||||
}
|
||||
else if (i == 0)
|
||||
{
|
||||
wt.Labels = "[{\"type\":\"raw\", \"text\":\"label1\"},{\"type\":\"raw\", \"text\":\"labelo" + i + "\"}, " +
|
||||
"{\"type\":\"payout\", \"text\":\"payout\", \"pullPaymentPayouts\":{\"pp1\":[\"p1\",\"p2\"],\"pp2\":[\"p3\"]}}]";
|
||||
}
|
||||
else
|
||||
{
|
||||
wt.Labels = "[{\"type\":\"raw\", \"text\":\"label1\"},{\"type\":\"raw\", \"text\":\"labelo" + i + "\"}]";
|
||||
}
|
||||
db.WalletTransactions.Add(wt);
|
||||
}
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
await RestartMigration(tester);
|
||||
var migrator = tester.PayTester.GetService<IEnumerable<IHostedService>>().OfType<DbMigrationsHostedService>().First();
|
||||
await migrator.MigratedTransactionLabels(0);
|
||||
|
||||
var walletRepo = tester.PayTester.GetService<WalletRepository>();
|
||||
var wi1 = await walletRepo.GetWalletLabels(new WalletId($"{wallet}0", "BTC"));
|
||||
Assert.Equal(3, wi1.Length);
|
||||
Assert.Contains(wi1, o => o.Label == "label1" && o.Color == "black");
|
||||
Assert.Contains(wi1, o => o.Label == "labelo0" && o.Color == "#000");
|
||||
Assert.Contains(wi1, o => o.Label == "payout" && o.Color == "green");
|
||||
|
||||
var txInfo = await walletRepo.GetWalletTransactionsInfo(new WalletId($"{wallet}0", "BTC"), new[] { firstTxId.ToString() });
|
||||
Assert.Equal("test", txInfo.Values.First().Comment);
|
||||
// Should have the 2 raw labels, and one legacy label for payouts
|
||||
Assert.Equal(3, txInfo.Values.First().LegacyLabels.Count);
|
||||
var payoutLabel = txInfo.Values.First().LegacyLabels.Select(l => l.Value).OfType<PayoutLabel>().First();
|
||||
Assert.Equal(2, payoutLabel.PullPaymentPayouts.Count);
|
||||
Assert.Equal(2, payoutLabel.PullPaymentPayouts["pp1"].Count);
|
||||
Assert.Single(payoutLabel.PullPaymentPayouts["pp2"]);
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
|
67
BTCPayServer.Tests/docker-bitcoin-multisig-setup.sh
Executable file
67
BTCPayServer.Tests/docker-bitcoin-multisig-setup.sh
Executable file
@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Creates a 2-of-3 multisig setup, following the procedure described here:
|
||||
# https://github.com/bitcoin/bitcoin/blob/master/doc/multisig-tutorial.md
|
||||
# https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md
|
||||
#
|
||||
# Usage:
|
||||
# ./docker-bitcoin-multisig-setup.sh custom-name
|
||||
#
|
||||
# The custom name/prefix is optional and defaults to "multisig".
|
||||
prefix="${1:-"multi_sig"}"
|
||||
|
||||
declare -A xpubs
|
||||
|
||||
printf "\n👛 Create descriptor wallets\n\n"
|
||||
for ((n=1;n<=3;n++)); do
|
||||
# Create descriptor wallets, surpress error output in case wallet already exists
|
||||
./docker-bitcoin-cli.sh -named createwallet wallet_name="${prefix}_part_${n}" descriptors=true > /dev/null 2>&1
|
||||
|
||||
# Collect xpubs
|
||||
./docker-bitcoin-cli.sh -rpcwallet="${prefix}_part_${n}" listdescriptors > /dev/null 2>&1
|
||||
xpubs["internal_xpub_${n}"]=$(./docker-bitcoin-cli.sh -rpcwallet="${prefix}_part_${n}" listdescriptors | jq '.descriptors | [.[] | select(.desc | startswith("wpkh") and contains("/1/*"))][0] | .desc' | grep -Po '(?<=\().*(?=\))')
|
||||
xpubs["external_xpub_${n}"]=$(./docker-bitcoin-cli.sh -rpcwallet="${prefix}_part_${n}" listdescriptors | jq '.descriptors | [.[] | select(.desc | startswith("wpkh") and contains("/0/*"))][0] | .desc' | grep -Po '(?<=\().*(?=\))')
|
||||
done
|
||||
|
||||
for x in "${!xpubs[@]}"; do
|
||||
printf "[%s]=%s\n" "$x" "${xpubs[$x]}";
|
||||
done
|
||||
|
||||
external_desc="wsh(sortedmulti(2,${xpubs["external_xpub_1"]},${xpubs["external_xpub_2"]},${xpubs["external_xpub_3"]}))"
|
||||
internal_desc="wsh(sortedmulti(2,${xpubs["internal_xpub_1"]},${xpubs["internal_xpub_2"]},${xpubs["internal_xpub_3"]}))"
|
||||
|
||||
external_desc_sum=$(./docker-bitcoin-cli.sh getdescriptorinfo $external_desc | jq '.descriptor')
|
||||
internal_desc_sum=$(./docker-bitcoin-cli.sh getdescriptorinfo $internal_desc | jq '.descriptor')
|
||||
|
||||
multisig_ext_desc="{\"desc\": $external_desc_sum, \"active\": true, \"internal\": false, \"timestamp\": \"now\"}"
|
||||
multisig_int_desc="{\"desc\": $internal_desc_sum, \"active\": true, \"internal\": true, \"timestamp\": \"now\"}"
|
||||
|
||||
multisig_desc="[$multisig_ext_desc, $multisig_int_desc]"
|
||||
|
||||
# Create multisig wallet, surpress error output in case wallet already exists
|
||||
|
||||
printf "\n🔐 Create multisig wallet\n"
|
||||
printf "\nExternal descriptor: $external_desc\n"
|
||||
printf "\nInternal descriptor: $internal_desc\n"
|
||||
|
||||
multisig_name="${prefix}_wallet"
|
||||
./docker-bitcoin-cli.sh -named createwallet wallet_name="$multisig_name" disable_private_keys=true blank=true descriptors=true > /dev/null 2>&1
|
||||
./docker-bitcoin-cli.sh -rpcwallet="$multisig_name" importdescriptors "$multisig_desc" > /dev/null 2>&1
|
||||
|
||||
# Fund the wallet from the default wallet
|
||||
printf "\n💰 Fund multisig wallet\n"
|
||||
|
||||
newaddress=$(./docker-bitcoin-cli.sh -rpcwallet="$multisig_name" getnewaddress "MultiSig Funding" | tr -d "[:cntrl:]")
|
||||
txid=$(./docker-bitcoin-cli.sh -rpcwallet="" sendtoaddress "$newaddress" 0.615)
|
||||
printf "\nReceiving address: $newaddress\n"
|
||||
printf "\nTransaction ID: $txid\n"
|
||||
|
||||
# Confirm everything worked
|
||||
printf "\nℹ️ Multisig wallet info\n\n"
|
||||
./docker-bitcoin-cli.sh -rpcwallet="$multisig_name" getwalletinfo
|
||||
|
||||
# Unload wallets to prevent having to specify which wallet to use in BTCPay, NBXplorer etc.
|
||||
for ((n=1;n<=3;n++)); do
|
||||
./docker-bitcoin-cli.sh unloadwallet "${prefix}_part_${n}" > /dev/null 2>&1
|
||||
done
|
||||
./docker-bitcoin-cli.sh unloadwallet "$multisig_name" > /dev/null 2>&1
|
@ -71,7 +71,7 @@ services:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
|
||||
devlnd:
|
||||
image: btcpayserver/bitcoin:22.0
|
||||
image: btcpayserver/bitcoin:23.0-1
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
@ -90,7 +90,7 @@ services:
|
||||
expose:
|
||||
- "4444"
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.3.14
|
||||
image: nicolasdorier/nbxplorer:2.3.40
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
@ -126,7 +126,7 @@ services:
|
||||
|
||||
bitcoind:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/bitcoin:22.0
|
||||
image: btcpayserver/bitcoin:23.0-1
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
@ -237,7 +237,7 @@ services:
|
||||
- "5432"
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.15.0-beta
|
||||
image: btcpayserver/lnd:v0.15.4-beta-1
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -272,7 +272,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.15.0-beta
|
||||
image: btcpayserver/lnd:v0.15.4-beta-1
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
|
@ -68,7 +68,7 @@ services:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
|
||||
devlnd:
|
||||
image: btcpayserver/bitcoin:22.0
|
||||
image: btcpayserver/bitcoin:23.0-1
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
@ -87,7 +87,7 @@ services:
|
||||
expose:
|
||||
- "4444"
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.3.14
|
||||
image: nicolasdorier/nbxplorer:2.3.40
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
@ -113,7 +113,7 @@ services:
|
||||
|
||||
bitcoind:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/bitcoin:22.0
|
||||
image: btcpayserver/bitcoin:23.0-1
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
@ -225,7 +225,7 @@ services:
|
||||
- "5432"
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.15.0-beta
|
||||
image: btcpayserver/lnd:v0.15.4-beta-1
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -262,7 +262,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.15.0-beta
|
||||
image: btcpayserver/lnd:v0.15.4-beta-1
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
|
16
BTCPayServer.Tests/docker-customer-lncli-holdinvoice.sh
Executable file
16
BTCPayServer.Tests/docker-customer-lncli-holdinvoice.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
PREIMAGE=$(cat /dev/urandom | tr -dc 'a-f0-9' | fold -w 64 | head -n 1)
|
||||
HASH=`node -e "console.log(require('crypto').createHash('sha256').update(Buffer.from('$PREIMAGE', 'hex')).digest('hex'))"`
|
||||
PAYREQ=$(./docker-customer-lncli.sh addholdinvoice $HASH $@ | jq -r ".payment_request")
|
||||
|
||||
echo "HASH: $HASH"
|
||||
echo "PREIMAGE: $PREIMAGE"
|
||||
echo "PAY REQ: $PAYREQ"
|
||||
echo ""
|
||||
echo "SETTLE: ./docker-customer-lncli.sh settleinvoice $PREIMAGE"
|
||||
echo "CANCEL: ./docker-customer-lncli.sh cancelinvoice $HASH"
|
||||
echo "LOOKUP: ./docker-customer-lncli.sh lookupinvoice $HASH"
|
||||
echo ""
|
||||
echo "TRACK: ./docker-merchant-lncli.sh trackpayment $HASH"
|
||||
echo "PAY: ./docker-merchant-lncli.sh payinvoice $PAYREQ"
|
@ -4,21 +4,23 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
<RunAnalyzersDuringLiveAnalysis>False</RunAnalyzersDuringLiveAnalysis>
|
||||
<RunAnalyzersDuringBuild>False</RunAnalyzersDuringBuild>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Condition="'$(GitCommit)' != ''" Include="BTCPayServer.GitCommitAttribute">
|
||||
<_Parameter1>$(GitCommit)</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Build\**" />
|
||||
<Compile Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<Compile Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
<Content Remove="Build\**" />
|
||||
<Content Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<Content Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
<EmbeddedResource Remove="Build\**" />
|
||||
<EmbeddedResource Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<EmbeddedResource Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
<None Remove="Build\**" />
|
||||
<None Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<None Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
<Content Update="Views\UIStorePullPayments\NewPullPayment.cshtml">
|
||||
<Pack>false</Pack>
|
||||
@ -33,9 +35,6 @@
|
||||
<ItemGroup>
|
||||
<None Remove="Currencies.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="bundleconfig.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(Altcoins)' != 'true'">
|
||||
<Content Remove="Services\Altcoins\**\*" />
|
||||
@ -48,23 +47,15 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BIP78.Sender" Version="0.2.2" />
|
||||
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.2" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.3.12" />
|
||||
<PackageReference Include="BuildBundlerMinifier" Version="3.2.449" />
|
||||
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435" />
|
||||
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.4.8" />
|
||||
<PackageReference Include="CsvHelper" Version="15.0.5" />
|
||||
<PackageReference Include="Dapper" Version="2.0.123" />
|
||||
<PackageReference Include="Fido2" Version="2.0.2" />
|
||||
<PackageReference Include="Fido2.AspNet" Version="2.0.2" />
|
||||
<PackageReference Include="HtmlSanitizer" Version="5.0.372" />
|
||||
<PackageReference Include="LNURL" Version="0.0.24" />
|
||||
<PackageReference Include="LNURL" Version="0.0.26" />
|
||||
<PackageReference Include="MailKit" Version="3.3.0" />
|
||||
<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="3.3.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||
<PackageReference Include="System.IO.Pipelines" Version="6.0.3" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.39" />
|
||||
@ -72,15 +63,10 @@
|
||||
<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.2.3" />
|
||||
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.18" />
|
||||
<PackageReference Include="Serilog" Version="2.9.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
|
||||
<PackageReference Include="SSH.NET" Version="2020.0.2" />
|
||||
<PackageReference Include="Text.Analyzers" Version="3.3.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -90,8 +76,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="6.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.9" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.9" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
63
BTCPayServer/ColorPalette.cs
Normal file
63
BTCPayServer/ColorPalette.cs
Normal file
@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using NBitcoin.Crypto;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class ColorPalette
|
||||
{
|
||||
public const string Pattern = "^#[0-9a-fA-F]{6}$";
|
||||
public static bool IsValid(string color)
|
||||
{
|
||||
return Regex.Match(color, Pattern).Success;
|
||||
}
|
||||
public string TextColor(string bgColor)
|
||||
{
|
||||
int nThreshold = 105;
|
||||
var bg = ColorTranslator.FromHtml(bgColor);
|
||||
int bgDelta = Convert.ToInt32((bg.R * 0.299) + (bg.G * 0.587) + (bg.B * 0.114));
|
||||
Color color = (255 - bgDelta < nThreshold) ? Color.Black : Color.White;
|
||||
return ColorTranslator.ToHtml(color);
|
||||
}
|
||||
// Borrowed from https://github.com/ManageIQ/guides/blob/master/labels.md
|
||||
public static readonly ColorPalette Default = new ColorPalette(new string[] {
|
||||
"#fbca04",
|
||||
"#0e8a16",
|
||||
"#ff7619",
|
||||
"#84b6eb",
|
||||
"#5319e7",
|
||||
"#cdcdcd",
|
||||
"#cc317c",
|
||||
});
|
||||
private ColorPalette(string[] labels)
|
||||
{
|
||||
Labels = labels;
|
||||
}
|
||||
|
||||
public readonly string[] Labels;
|
||||
|
||||
public string DeterministicColor(string label)
|
||||
{
|
||||
switch (label)
|
||||
{
|
||||
case "payjoin":
|
||||
return "#51b13e";
|
||||
case "invoice":
|
||||
return "#cedc21";
|
||||
case "payment-request":
|
||||
return "#489D77";
|
||||
case "app":
|
||||
return "#5093B6";
|
||||
case "pj-exposed":
|
||||
return "#51b13e";
|
||||
case "payout":
|
||||
return "#3F88AF";
|
||||
default:
|
||||
var num = NBitcoin.Utils.ToUInt32(Hashes.SHA256(Encoding.UTF8.GetBytes(label)), 0, true);
|
||||
return Labels[num % Labels.Length];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
@model BTCPayServer.Components.AppSales.AppSalesViewModel
|
||||
|
||||
@{
|
||||
var controller = $"UI{Model.App.AppType}";
|
||||
var action = $"Update{Model.App.AppType}";
|
||||
var label = Model.App.AppType == nameof(AppType.Crowdfund) ? "Contributions" : "Sales";
|
||||
}
|
||||
@ -10,7 +11,7 @@
|
||||
<div id="AppSales-@Model.App.Id" class="widget app-sales">
|
||||
<header class="mb-3">
|
||||
<h3>@Model.App.Name @label</h3>
|
||||
<a asp-controller="UIApps" asp-action="@action" asp-route-appId="@Model.App.Id">Manage</a>
|
||||
<a asp-controller="@controller" asp-action="@action" asp-route-appId="@Model.App.Id">Manage</a>
|
||||
</header>
|
||||
@if (Model.InitialRendering)
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
@model BTCPayServer.Components.AppTopItems.AppTopItemsViewModel
|
||||
|
||||
@{
|
||||
var controller = $"UI{Model.App.AppType}";
|
||||
var action = $"Update{Model.App.AppType}";
|
||||
var label = Model.App.AppType == nameof(AppType.Crowdfund) ? "contribution" : "sale";
|
||||
}
|
||||
@ -9,7 +10,7 @@
|
||||
<div id="AppTopItems-@Model.App.Id" class="widget app-top-items">
|
||||
<header class="mb-3">
|
||||
<h3>Top @(Model.App.AppType == nameof(AppType.Crowdfund) ? "Perks" : "Items")</h3>
|
||||
<a asp-controller="UIApps" asp-action="@action" asp-route-appId="@Model.App.Id">View All</a>
|
||||
<a asp-controller="@controller" asp-action="@action" asp-route-appId="@Model.App.Id">View All</a>
|
||||
</header>
|
||||
@if (Model.InitialRendering)
|
||||
{
|
||||
|
20
BTCPayServer/Components/MainLogo/Default.cshtml
Normal file
20
BTCPayServer/Components/MainLogo/Default.cshtml
Normal file
@ -0,0 +1,20 @@
|
||||
@using BTCPayServer.Services
|
||||
@using BTCPayServer.Abstractions.Contracts
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@using BTCPayServer.Abstractions.TagHelpers
|
||||
@inject ThemeSettings Theme
|
||||
@inject IFileService FileService
|
||||
@model BTCPayServer.Components.MainLogo.MainLogoViewModel
|
||||
|
||||
@if (!string.IsNullOrEmpty(Theme.LogoFileId))
|
||||
{
|
||||
var logoSrc = await FileService.GetFileUrl(Context.Request.GetAbsoluteRootUri(), Theme.LogoFileId);
|
||||
<img src="@logoSrc" alt="BTCPay Server" class="main-logo main-logo-custom @Model.CssClass" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<svg xmlns="http://www.w3.org/2000/svg" role="img" alt="BTCPay Server" class="main-logo main-logo-btcpay @Model.CssClass">
|
||||
<use href="/img/logo.svg#small" class="main-logo-btcpay--small"/>
|
||||
<use href="/img/logo.svg#large" class="main-logo-btcpay--large"/>
|
||||
</svg>
|
||||
}
|
16
BTCPayServer/Components/MainLogo/MainLogo.cs
Normal file
16
BTCPayServer/Components/MainLogo/MainLogo.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Components.MainLogo
|
||||
{
|
||||
public class MainLogo : ViewComponent
|
||||
{
|
||||
public IViewComponentResult Invoke(string cssClass = null)
|
||||
{
|
||||
var vm = new MainLogoViewModel
|
||||
{
|
||||
CssClass = cssClass,
|
||||
};
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
}
|
7
BTCPayServer/Components/MainLogo/MainLogoViewModel.cs
Normal file
7
BTCPayServer/Components/MainLogo/MainLogoViewModel.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace BTCPayServer.Components.MainLogo
|
||||
{
|
||||
public class MainLogoViewModel
|
||||
{
|
||||
public string CssClass { get; set; }
|
||||
}
|
||||
}
|
@ -7,11 +7,9 @@
|
||||
@using BTCPayServer.Views.Wallets
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@using BTCPayServer.Client
|
||||
@using BTCPayServer.Components.Icon
|
||||
@using BTCPayServer.Components.ThemeSwitch
|
||||
@using BTCPayServer.Components.UIExtensionPoint
|
||||
@using BTCPayServer.Services
|
||||
@using BTCPayServer.Views.CustodianAccounts
|
||||
@inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContext;
|
||||
@inject BTCPayServerEnvironment Env
|
||||
@inject SignInManager<ApplicationUser> SignInManager
|
||||
@inject PoliciesSettings PoliciesSettings
|
||||
@ -35,7 +33,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a asp-area="" asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@Model.Store.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(StoreNavPages.Rates) @ViewData.IsActivePage(StoreNavPages.CheckoutAppearance) @ViewData.IsActivePage(StoreNavPages.General) @ViewData.IsActivePage(StoreNavPages.Tokens) @ViewData.IsActivePage(StoreNavPages.Users) @ViewData.IsActivePage(StoreNavPages.Plugins) @ViewData.IsActivePage(StoreNavPages.Webhooks)" id="StoreNav-StoreSettings">
|
||||
<a asp-area="" asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@Model.Store.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(new [] {StoreNavPages.Rates, StoreNavPages.CheckoutAppearance, StoreNavPages.General, StoreNavPages.Tokens, StoreNavPages.Users, StoreNavPages.Plugins, StoreNavPages.Webhooks, StoreNavPages.PayoutProcessors, StoreNavPages.Emails})" id="StoreNav-StoreSettings">
|
||||
<vc:icon symbol="settings"/>
|
||||
<span>Settings</span>
|
||||
</a>
|
||||
@ -93,23 +91,21 @@
|
||||
|
||||
</li>
|
||||
}
|
||||
|
||||
<vc:ui-extension-point location="store-wallets-nav" model="@Model"/>
|
||||
@if (PoliciesSettings.Experimental)
|
||||
{
|
||||
@foreach (var custodianAccount in Model.CustodianAccounts)
|
||||
{
|
||||
<li class="nav-item">
|
||||
<a asp-area="" asp-controller="UICustodianAccounts" asp-action="ViewCustodianAccount" asp-route-storeId="@custodianAccount.StoreId" asp-route-accountId="@custodianAccount.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(CustodianAccountsNavPages.View, custodianAccount.Id)" id="@($"StoreNav-CustodianAccount-{custodianAccount.Id}")">
|
||||
<!--
|
||||
TODO which icon should we use?
|
||||
-->
|
||||
@* TODO which icon should we use? *@
|
||||
<span>@custodianAccount.Name</span>
|
||||
<span class="badge bg-warning ms-1" style="font-size:10px;">Experimental</span>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
<li class="nav-item">
|
||||
<a asp-area="" asp-controller="UICustodianAccounts" asp-action="CreateCustodianAccount" asp-route-storeId="@Model.Store.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(CustodianAccountsNavPages.Create)" id="StoreNav-CreateApp">
|
||||
<a asp-area="" asp-controller="UICustodianAccounts" asp-action="CreateCustodianAccount" asp-route-storeId="@Model.Store.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(CustodianAccountsNavPages.Create)" id="StoreNav-CreateCustodianAccount">
|
||||
<vc:icon symbol="new"/>
|
||||
<span>Add Custodian</span>
|
||||
<span class="badge bg-warning ms-1" style="font-size:10px;">Experimental</span>
|
||||
@ -171,12 +167,7 @@
|
||||
<ul class="navbar-nav">
|
||||
@foreach (var app in Model.Apps)
|
||||
{
|
||||
<li class="nav-item">
|
||||
<a asp-area="" asp-controller="UIApps" asp-action="@app.Action" asp-route-appId="@app.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(AppsNavPages.Update, app.Id)" id="@($"StoreNav-App-{app.Id}")">
|
||||
<vc:icon symbol="@app.AppType.ToLower()"/>
|
||||
<span>@app.AppName</span>
|
||||
</a>
|
||||
</li>
|
||||
<vc:ui-extension-point location="apps-nav" model="@app"/>
|
||||
}
|
||||
<li class="nav-item">
|
||||
<a asp-area="" asp-controller="UIApps" asp-action="CreateApp" asp-route-storeId="@Model.Store.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(AppsNavPages.Create)" id="StoreNav-CreateApp">
|
||||
@ -237,7 +228,7 @@
|
||||
})()
|
||||
</script>
|
||||
}
|
||||
else if (Env.IsSecure)
|
||||
else if (Env.IsSecure(HttpContext.HttpContext))
|
||||
{
|
||||
<ul class="navbar-nav">
|
||||
@if (!PoliciesSettings.LockSubscription)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user