Compare commits

..

3 Commits

Author SHA1 Message Date
e6a609a1a4 bump and changelog for 1.6.7 2022-08-10 10:06:06 +02:00
892ea2fd7b Fix Kraken rate provider 2022-08-10 09:55:15 +02:00
746a0600bf Upgrade Lightning library 2022-08-10 09:55:01 +02:00
458 changed files with 17328 additions and 15244 deletions

View File

@ -41,10 +41,9 @@ jobs:
- run:
command: |
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
GIT_COMMIT=$(git rev-parse HEAD)
#
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 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 login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-amd64
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-altcoins-amd64
@ -58,10 +57,9 @@ 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 --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 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 login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm32v7
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm32v7
@ -75,10 +73,9 @@ 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 --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 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 login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm64v8
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm64v8

View File

@ -12,7 +12,7 @@ indent_style = space
indent_size = 4
charset = utf-8
[*.json]
[launchSettings.json]
indent_size = 2
# C# files

6
.gitignore vendored
View File

@ -288,6 +288,10 @@ __pycache__/
*.xsd.cs
/BTCPayServer/Build/dockerfiles
# Bundling JS/CSS
BTCPayServer/wwwroot/bundles/*
!BTCPayServer/wwwroot/bundles/.gitignore
.vscode/*
!.vscode/launch.json
!.vscode/tasks.json
@ -296,5 +300,3 @@ BTCPayServer/testpwd
.DS_Store
Packed Plugins
Plugins/packed
BTCPayServer/wwwroot/swagger/v1/openapi.json

View File

@ -32,9 +32,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="HtmlSanitizer" Version="5.0.372" />
<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="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="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1" />
</ItemGroup>
<ItemGroup>

View File

@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
@ -12,13 +10,6 @@ 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
@ -61,7 +52,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 ? ActivePageClass : null;
return categoryMatch && idMatch ? "active" : null;
}
public static string IsActivePage<T>(this ViewDataDictionary viewData, T page, object id = null)
@ -69,14 +60,6 @@ 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)
{
@ -89,32 +72,29 @@ 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 ? ActivePageClass : null;
return categoryAndPageMatch && idMatch ? "active" : null;
}
public static HtmlString ToBrowserDate(this DateTimeOffset date, DateDisplayFormat format = DateDisplayFormat.Localized)
public static HtmlString ToBrowserDate(this DateTimeOffset date)
{
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>");
var displayDate = date.ToString("o", CultureInfo.InvariantCulture);
return new HtmlString($"<span class='localizeDate'>{displayDate}</span>");
}
public static HtmlString ToBrowserDate(this DateTime date, DateDisplayFormat format = DateDisplayFormat.Localized)
public static HtmlString ToBrowserDate(this DateTime date)
{
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>");
var displayDate = date.ToString("o", CultureInfo.InvariantCulture);
return new HtmlString($"<span class='localizeDate'>{displayDate}</span>");
}
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 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 TimeString(this TimeSpan timeSpan)
{
@ -126,14 +106,16 @@ namespace BTCPayServer.Abstractions.Extensions
{
return $"{(int)timeSpan.TotalMinutes} minute{Plural((int)timeSpan.TotalMinutes)}";
}
return timeSpan.Days < 1
? $"{(int)timeSpan.TotalHours} hour{Plural((int)timeSpan.TotalHours)}"
: $"{(int)timeSpan.TotalDays} day{Plural((int)timeSpan.TotalDays)}";
if (timeSpan.Days < 1)
{
return $"{(int)timeSpan.TotalHours} hour{Plural((int)timeSpan.TotalHours)}";
}
return $"{(int)timeSpan.TotalDays} day{Plural((int)timeSpan.TotalDays)}";
}
private static string Plural(int value)
{
return value == 1 ? string.Empty : "s";
return value > 1 ? "s" : string.Empty;
}
}
}

View File

@ -1,34 +1,35 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Abstractions.Form;
public class Field
public abstract class Field
{
// The name of the HTML5 node. Should be used as the key for the posted data.
public string Name;
// 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;
// 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 virtual bool IsValid()
{
return ValidationErrors.Count == 0 && Fields.All(field => field.IsValid());
}
public bool Required = false;
[JsonExtensionData] public IDictionary<string, JToken> AdditionalData { get; set; }
public List<Field> Fields { get; set; } = new();
public bool IsValid()
{
return ValidationErrors.Count == 0;
}
}

View File

@ -1,12 +1,14 @@
using System.Collections.Generic;
namespace BTCPayServer.Abstractions.Form;
public class Fieldset : Field
public class Fieldset
{
public bool Hidden { get; set; }
public string Label { get; set; }
public Fieldset()
{
Type = "fieldset";
this.Fields = new List<Field>();
}
public string Label { get; set; }
public List<Field> Fields { get; set; }
}

View File

@ -1,143 +1,60 @@
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<Field> Fields { get; set; } = new();
public List<Fieldset> Fieldsets { get; set; } = new();
// Are all the fields valid in the form?
public bool IsValid()
{
return Fields.All(field => field.IsValid());
foreach (var fieldset in Fieldsets)
{
foreach (var field in fieldset.Fields)
{
if (!field.IsValid())
{
return false;
}
}
}
return true;
}
public Field GetFieldByName(string name)
{
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 fieldset in Fieldsets)
{
var currentPrefix = prefix;
if (!string.IsNullOrEmpty(field.Name))
foreach (var field in fieldset.Fields)
{
currentPrefix = $"{prefix}{field.Name}";
if (currentPrefix.Equals(name, StringComparison.InvariantCultureIgnoreCase))
if (name.Equals(field.Name))
{
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 field in fields)
foreach (var fieldset in Fieldsets)
{
string prefix = string.Empty;
if (!string.IsNullOrEmpty(field.Name))
foreach (var field in fieldset.Fields)
{
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;
}
}

View File

@ -1,27 +0,0 @@
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.
}

View File

@ -0,0 +1,19 @@
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.
}

View File

@ -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.15" />
<PackageReference Include="NBitcoin" Version="7.0.14" />
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.3.8" />
<PackageReference Include="NBitcoin" Version="7.0.10" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
<ItemGroup>

View File

@ -19,28 +19,6 @@ 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)
{

View File

@ -95,24 +95,6 @@ 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)

View File

@ -65,7 +65,7 @@ namespace BTCPayServer.Client
return await HandleResponse<string>(response);
}
public virtual async Task<LightningPaymentData> PayLightningInvoice(string storeId, string cryptoCode, PayLightningInvoiceRequest request,
public virtual async Task 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);
return await HandleResponse<LightningPaymentData>(response);
await HandleResponse(response);
}
public virtual async Task<LightningPaymentData> GetLightningPayment(string storeId, string cryptoCode,
@ -97,24 +97,6 @@ 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)

View File

@ -1,82 +0,0 @@
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);
}
}
}

View File

@ -37,20 +37,6 @@ 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)
{

View File

@ -53,16 +53,6 @@ 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);
@ -79,7 +69,7 @@ namespace BTCPayServer.Client
return await HandleResponse<PayoutData>(response);
}
public virtual async Task MarkPayoutPaid(string storeId, string payoutId,
public async Task MarkPayoutPaid(string storeId, string payoutId,
CancellationToken cancellationToken = default)
{
var response = await _httpClient.SendAsync(
@ -88,14 +78,5 @@ 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);
}
}
}

View File

@ -1,53 +0,0 @@
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);
}
}
}

View File

@ -1,4 +1,3 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
@ -26,7 +25,7 @@ namespace BTCPayServer.Client.Models
public string Template { get; set; } = null;
[JsonConverter(typeof(StringEnumConverter))]
public PosViewType DefaultView { get; set; }
public bool ShowCustomAmount { get; set; } = false;
public bool ShowCustomAmount { get; set; } = true;
public bool ShowDiscount { get; set; } = true;
public bool EnableTips { get; set; } = true;
public string CustomAmountPayButtonText { get; set; } = null;
@ -37,48 +36,6 @@ 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;
}
}

View File

@ -85,7 +85,6 @@ 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

View File

@ -1,11 +1,9 @@
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; }

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using BTCPayServer.Client.JsonConverters;
using BTCPayServer.Lightning;
using Newtonsoft.Json;
@ -27,8 +26,5 @@ namespace BTCPayServer.Client.Models
[JsonConverter(typeof(LightMoneyJsonConverter))]
public LightMoney AmountReceived { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<ulong, string> CustomRecords { get; set; }
}
}

View File

@ -9,13 +9,6 @@ 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

View File

@ -1,14 +0,0 @@
#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; }
}

View File

@ -10,6 +10,4 @@ public class OnChainAutomatedPayoutSettings
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
public TimeSpan IntervalSeconds { get; set; }
public int? FeeBlockTarget { get; set; }
}

View File

@ -4,94 +4,16 @@ 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; }

View File

@ -15,9 +15,7 @@ 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))]

View File

@ -1,4 +1,3 @@
using System;
using BTCPayServer.Client.JsonConverters;
using BTCPayServer.JsonConverters;
using BTCPayServer.Lightning;
@ -20,8 +19,5 @@ namespace BTCPayServer.Client.Models
[JsonConverter(typeof(LightMoneyJsonConverter))]
public LightMoney Amount { get; set; }
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
public TimeSpan? SendTimeout { get; set; }
}
}

View File

@ -1,15 +0,0 @@
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; }
}
}

View File

@ -24,9 +24,5 @@ namespace BTCPayServer.Client.Models
[JsonExtensionData]
public IDictionary<string, JToken> AdditionalData { get; set; }
public string FormId { get; set; }
public string FormResponse { get; set; }
}
}

View File

@ -12,6 +12,7 @@ namespace BTCPayServer.Client.Models
public DateTimeOffset CreatedTime { get; set; }
public string Id { get; set; }
public bool Archived { get; set; }
public enum PaymentRequestStatus
{
Pending = 0,

View File

@ -2,7 +2,6 @@ using System;
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Client.Models
{
@ -30,6 +29,5 @@ namespace BTCPayServer.Client.Models
[JsonConverter(typeof(StringEnumConverter))]
public PayoutState State { get; set; }
public int Revision { get; set; }
public JObject PaymentProof { get; set; }
}
}

View File

@ -17,9 +17,4 @@ namespace BTCPayServer.Client.Models
{
// We can add POS specific things here later
}
public class CrowdfundAppData : AppDataBase
{
// We can add Crowdfund specific things here later
}
}

View File

@ -1,7 +0,0 @@
namespace BTCPayServer.Client.Models;
public class RateSource
{
public string Id { get; set; }
public string Name { get; set; }
}

View File

@ -31,8 +31,6 @@ 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; }
@ -68,12 +66,6 @@ namespace BTCPayServer.Client.Models
public IDictionary<string, JToken> AdditionalData { get; set; }
}
public enum CheckoutType
{
V1,
V2
}
public enum NetworkFeeMode
{
MultiplePaymentsOnly,

View File

@ -1,10 +0,0 @@
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; }
}
}

View File

@ -1,10 +0,0 @@
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; }
}

View File

@ -1,7 +0,0 @@
namespace BTCPayServer.Client.Models;
public class StoreRateResult
{
public string CurrencyPair { get; set; }
public decimal Rate { get; set; }
}

View File

@ -0,0 +1,28 @@
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'")
});
}
}
}

View File

@ -22,7 +22,7 @@ namespace BTCPayServer
"LBTC_X = LBTC_BTC * BTC_X",
"LBTC_BTC = 1",
},
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://liquid.network/tx/{0}" : "https://liquid.network/testnet/tx/{0}",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
CryptoImagePath = "imlegacy/liquid.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),

View File

@ -21,7 +21,7 @@ namespace BTCPayServer
},
AssetId = new uint256("ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2"),
DisplayName = "Liquid Tether",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://liquid.network/tx/{0}" : "https://liquid.network/testnet/tx/{0}",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/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://liquid.network/tx/{0}" : "https://liquid.network/testnet/tx/{0}",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/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://liquid.network/tx/{0}" : "https://liquid.network/testnet/tx/{0}",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
CryptoImagePath = "imlegacy/lcad.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),

View File

@ -137,7 +137,6 @@ namespace BTCPayServer
public string CryptoImagePath { get; set; }
public string[] DefaultRateRules { get; set; } = Array.Empty<string>();
public override string ToString()
{
return CryptoCode;

View File

@ -13,9 +13,9 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Bitcoin",
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}",
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}",
NBXplorerNetwork = nbxplorerNetwork,
CryptoImagePath = "imlegacy/bitcoin.svg",
LightningImagePath = "imlegacy/bitcoin-lightning.svg",

View File

@ -58,7 +58,7 @@ namespace BTCPayServer
InitZcash();
InitChaincoin();
// InitArgoneum();//their rate source is down 9/15/20.
// InitMonetaryUnit(); Not supported from Bittrex from 11/23/2022, dead shitcoin
InitMonetaryUnit();
// Assume that electrum mappings are same as BTC if not specified
foreach (var network in _Networks.Values.OfType<BTCPayNetwork>())

View File

@ -4,8 +4,7 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="NBXplorer.Client" Version="4.2.1" />
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.18" />
<PackageReference Include="NBXplorer.Client" Version="4.2.0" />
</ItemGroup>
<ItemGroup Condition="'$(Altcoins)' != 'true'">
<Compile Remove="Altcoins\**\*.cs"></Compile>

View File

@ -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);

View File

@ -59,11 +59,7 @@ 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; }
@ -113,11 +109,7 @@ 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);

View File

@ -3,11 +3,11 @@
<Import Project="../Build/Common.csproj" />
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.9">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.7" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />

View File

@ -1,12 +0,0 @@
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; }
}

View File

@ -21,9 +21,4 @@ public class PayoutProcessorData
.HasOne(o => o.Store)
.WithMany(data => data.PayoutProcessors).OnDelete(DeleteBehavior.Cascade);
}
public override string ToString()
{
return $"{Processor} {PaymentMethod} {StoreId}";
}
}

View File

@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
namespace BTCPayServer.Data
{
[Obsolete]
public class WalletData
{
[System.ComponentModel.DataAnnotations.Key]

View File

@ -1,87 +0,0 @@
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");
}
}
}
}

View File

@ -1,61 +0,0 @@
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");
}
}
}
}

View File

@ -1,9 +1,7 @@
using System;
using Microsoft.EntityFrameworkCore;
namespace BTCPayServer.Data
{
[Obsolete]
public class WalletTransactionData
{
public string WalletDataId { get; set; }

View File

@ -1,81 +0,0 @@
// <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");
}
}
}

View File

@ -16,7 +16,7 @@ namespace BTCPayServer.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.9");
modelBuilder.HasAnnotation("ProductVersion", "6.0.1");
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
{
@ -189,7 +189,6 @@ namespace BTCPayServer.Migrations
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("TEXT");
@ -846,54 +845,6 @@ 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")
@ -1382,25 +1333,6 @@ 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")
@ -1543,13 +1475,6 @@ 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");

View File

@ -26,7 +26,6 @@
</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>

View File

@ -8,7 +8,6 @@ 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;
@ -34,8 +33,7 @@ namespace BTCPayServer.PluginPacker
throw new Exception($"{rootDLLPath} could not be found");
}
var plugin = PluginLoader.CreateFromAssemblyFile(rootDLLPath, false, new[] { typeof(IBTCPayServerPlugin) });
var assembly = plugin.LoadAssembly(name);
var assembly = Assembly.LoadFrom(rootDLLPath);
var extension = GetAllExtensionTypesFromAssembly(assembly).FirstOrDefault();
if (extension is null)
{

View File

@ -13,7 +13,7 @@
<EmbeddedResource Include="Resources\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.9">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -4,9 +4,9 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" />
<PackageReference Include="NBitcoin" Version="7.0.14" />
<PackageReference Include="NBitcoin" Version="7.0.10" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="1.0.2" />
</ItemGroup>

View File

@ -1,5 +1,4 @@
using System;
using System.Linq;
using BTCPayServer.Services.Rates;
namespace BTCPayServer.Rating
@ -57,13 +56,6 @@ 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;
}

View File

@ -44,6 +44,9 @@ namespace BTCPayServer.Services.Rates
{
if (notFoundSymbols.TryGetValue(ticker.Key, out _))
return null;
if (ticker.Key.Contains("XMR"))
{
}
try
{
CurrencyPair pair;

View File

@ -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());

View File

@ -1,42 +0,0 @@
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();
}
}
}

View File

@ -84,7 +84,6 @@ 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()
{
@ -105,7 +104,6 @@ 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")));

View File

@ -11,8 +11,6 @@ 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;
@ -619,7 +617,6 @@ 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";
@ -627,10 +624,8 @@ 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];
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>();
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>();
vmpos.Title = "hello";
vmpos.Currency = "CAD";
vmpos.ButtonText = "{0} Purchase";
@ -647,11 +642,11 @@ donation:
price: 1.02
custom: true
";
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result);
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
Assert.Equal("hello", vmpos.Title);
var publicApps = user.GetController<UIPointOfSaleController>();
var publicApps = user.GetController<UIAppsPublicController>();
var vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync<ViewPointOfSaleViewModel>();
Assert.Equal("hello", vmview.Title);
Assert.Equal(3, vmview.Items.Length);
@ -703,7 +698,7 @@ donation:
})
{
TestLogs.LogInformation($"Testing for {test.Code}");
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos.Title = "hello";
vmpos.Currency = test.Item1;
vmpos.ButtonText = "{0} Purchase";
@ -719,8 +714,8 @@ donation:
price: 1.02
custom: true
";
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
publicApps = user.GetController<UIPointOfSaleController>();
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result);
publicApps = user.GetController<UIAppsPublicController>();
vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync<ViewPointOfSaleViewModel>();
Assert.Equal(test.Code, vmview.CurrencyCode);
Assert.Equal(test.ExpectedSymbol,
@ -736,7 +731,7 @@ donation:
}
//test inventory related features
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos.Title = "hello";
vmpos.Currency = "BTC";
vmpos.Template = @"
@ -746,7 +741,7 @@ inventoryitem:
inventory: 1
noninventoryitem:
price: 10.0";
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result);
//inventoryitem has 1 item available
await tester.WaitForEvent<AppInventoryUpdaterHostedService.UpdateAppInventory>(() =>
@ -782,13 +777,13 @@ noninventoryitem:
//check that item is back in stock
await TestUtils.EventuallyAsync(async () =>
{
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos = await apps.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 pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos.Title = "hello";
vmpos.Currency = "BTC";
vmpos.Template = @"
@ -799,7 +794,7 @@ btconly:
- BTC
normal:
price: 1.0";
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
Assert.IsType<RedirectToActionResult>(apps.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
@ -843,8 +838,8 @@ g:
custom: topup
";
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result);
vmpos = await apps.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);

View File

@ -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.3.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<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="105.0.5195.5200" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="103.0.5060.5300" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>

View File

@ -30,7 +30,7 @@ namespace BTCPayServer.Tests
s.AddDerivationScheme();
s.GoToStore(StoreNavPages.CheckoutAppearance);
s.Driver.FindElement(By.Id("RequiresRefundEmail")).Click();
s.Driver.FindElement(By.Id("Save")).Click();
s.Driver.FindElement(By.Name("command")).Click();
var emailAlreadyThereInvoiceId = s.CreateInvoice(100, "USD", "a@g.com");
s.GoToInvoiceCheckout(emailAlreadyThereInvoiceId);

View File

@ -1,212 +0,0 @@
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());
}
}
}

View File

@ -4,8 +4,6 @@ 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;
@ -37,7 +35,6 @@ 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);
@ -45,12 +42,10 @@ namespace BTCPayServer.Tests
vm.AppName = "test";
vm.SelectedAppType = appType;
var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
Assert.Equal(nameof(crowdfund.UpdateCrowdfund), redirectToAction.ActionName);
Assert.Equal(nameof(apps.UpdateCrowdfund), redirectToAction.ActionName);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var app = appList.Apps[0];
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType };
apps.HttpContext.SetAppData(appData);
crowdfund.HttpContext.SetAppData(appData);
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType});
var appList2 =
Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps2.ListApps(user2.StoreId).Result).Model);
Assert.Single(appList.Apps);
@ -77,7 +72,6 @@ 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";
@ -85,20 +79,18 @@ 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];
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType };
apps.HttpContext.SetAppData(appData);
crowdfund.HttpContext.SetAppData(appData);
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType });
//Scenario 1: Not Enabled - Not Allowed
var crowdfundViewModel = await crowdfund.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>();
var crowdfundViewModel = await apps.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>();
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.Enabled = false;
crowdfundViewModel.EndDate = null;
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
var anonAppPubsController = tester.PayTester.GetController<UICrowdfundController>();
var crowdfundController = user.GetController<UICrowdfundController>();
var anonAppPubsController = tester.PayTester.GetController<UIAppsPublicController>();
var publicApps = user.GetController<UIAppsPublicController>();
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{
@ -108,19 +100,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 crowdfundController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
Assert.IsType<OkObjectResult>(await publicApps.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{
RedirectToCheckout = false,
Amount = new decimal(0.01)
}, default));
Assert.IsType<ViewResult>(await crowdfundController.ViewCrowdfund(app.Id, string.Empty));
Assert.IsType<ViewResult>(await publicApps.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>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{
Amount = new decimal(0.01)
@ -131,7 +123,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.EndDate = DateTime.Today.AddDays(-1);
crowdfundViewModel.Enabled = true;
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{
Amount = new decimal(0.01)
@ -144,7 +136,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.TargetAmount = 1;
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.EnforceTargetAmount = true;
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{
Amount = new decimal(1.01)
@ -168,7 +160,6 @@ 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";
@ -176,21 +167,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];
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType };
apps.HttpContext.SetAppData(appData);
crowdfund.HttpContext.SetAppData(appData);
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType });
TestLogs.LogInformation("We create an invoice with a hardcap");
var crowdfundViewModel = await crowdfund.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>();
var crowdfundViewModel = await apps.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>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
var publicApps = user.GetController<UICrowdfundController>();
var anonAppPubsController = tester.PayTester.GetController<UIAppsPublicController>();
var publicApps = user.GetController<UIAppsPublicController>();
var model = Assert.IsType<ViewCrowdfundViewModel>(Assert
.IsType<ViewResult>(publicApps.ViewCrowdfund(app.Id, String.Empty).Result).Model);
@ -242,7 +232,7 @@ namespace BTCPayServer.Tests
Assert.Contains(AppService.GetAppInternalTag(app.Id), invoiceEntity.InternalTags);
crowdfundViewModel.UseAllStoreInvoices = false;
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(apps.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
@ -261,7 +251,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>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice
{
Buyer = new Buyer { email = "test@fwf.com" },

View File

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:6.0.401-bullseye-slim AS builder
FROM mcr.microsoft.com/dotnet/sdk:6.0.101-bullseye-slim AS builder
RUN apt-get update && apt-get install -y --no-install-recommends chromium-driver \
&& rm -rf /var/lib/apt/lists/*

View File

@ -125,12 +125,6 @@ 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)
{

View File

@ -33,14 +33,12 @@ 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
{
@ -483,6 +481,93 @@ 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()
{
@ -1207,9 +1292,6 @@ 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");
@ -1305,7 +1387,7 @@ namespace BTCPayServer.Tests
rule2.Reevaluate();
Assert.False(rule2.HasError);
Assert.Equal("5000 * 2000.4 * 1.1", rule2.ToString(true));
Assert.Equal(5000m * 2000.4m * 1.1m, rule2.BidAsk.Bid);
Assert.Equal(rule2.BidAsk.Bid, 5000m * 2000.4m * 1.1m);
////////
// Make sure parenthesis are correctly calculated
@ -1726,32 +1808,5 @@ 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);
}
}
}

View File

@ -14,12 +14,10 @@ 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;
@ -89,7 +87,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(Policies.CanModifyStoreSettings, permissionError.MissingPermission);
Assert.Equal(permissionError.MissingPermission, Policies.CanModifyStoreSettings);
}
[Fact(Timeout = TestTimeout)]
@ -196,7 +194,7 @@ namespace BTCPayServer.Tests
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanCreateReadUpdateAndDeletePointOfSaleApp()
public async Task CanCreateReadAndDeletePointOfSaleApp()
{
using var tester = CreateServerTester();
await tester.StartAsync();
@ -204,58 +202,8 @@ namespace BTCPayServer.Tests
await user.RegisterDerivationSchemeAsync("BTC");
var client = await user.CreateClient();
// 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"
}
);
// Test creating a POS app
var app = await client.CreatePointOfSaleApp(user.StoreId, new CreatePointOfSaleAppRequest() { AppName = "test app from API" });
Assert.Equal("test app from API", app.Name);
Assert.Equal(user.StoreId, app.StoreId);
Assert.Equal("PointOfSale", app.AppType);
@ -271,11 +219,6 @@ 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 () =>
{
@ -288,117 +231,6 @@ 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")]
@ -908,100 +740,6 @@ 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);
@ -1374,93 +1112,25 @@ namespace BTCPayServer.Tests
await client.ArchivePaymentRequest(user.StoreId, paymentRequest.Id);
Assert.DoesNotContain(paymentRequest.Id,
(await client.GetPaymentRequests(user.StoreId)).Select(data => data.Id));
var archivedPrId = paymentRequest.Id;
//let's test some payment stuff with the UI
//let's test some payment stuff
await user.RegisterDerivationSchemeAsync("BTC");
var paymentTestPaymentRequest = await client.CreatePaymentRequest(user.StoreId,
new CreatePaymentRequestRequest() { Amount = 0.1m, Currency = "BTC", Title = "Payment test title" });
var invoiceId = Assert.IsType<string>(Assert.IsType<OkObjectResult>(await user.GetController<UIPaymentRequestController>()
.PayPaymentRequest(paymentTestPaymentRequest.Id, false)).Value);
async Task Pay(string invoiceId, bool partialPayment = false)
var invoice = user.BitPay.GetInvoice(invoiceId);
await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
{
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 tester.ExplorerNode.SendToAddressAsync(
BitcoinAddress.Create(invoice.BitcoinAddress, tester.ExplorerNode.Network), invoice.BtcDue);
});
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);
await TestUtils.EventuallyAsync(async () =>
{
Assert.Equal(Invoice.STATUS_PAID, user.BitPay.GetInvoice(invoiceId).Status);
Assert.Equal(PaymentRequestData.PaymentRequestStatus.Completed, (await client.GetPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id)).Status);
});
}
[Fact(Timeout = TestTimeout)]
@ -1598,7 +1268,7 @@ namespace BTCPayServer.Tests
Checkout = new CreateInvoiceRequest.CheckoutOptions()
{
RedirectAutomatically = true,
RequiresRefundEmail = true,
RequiresRefundEmail = true
},
AdditionalSearchTerms = new string[] { "Banana" }
});
@ -1927,35 +1597,25 @@ namespace BTCPayServer.Tests
await tester.StartAsync();
await tester.EnsureChannelsSetup();
var user = tester.NewAccount();
await user.GrantAccessAsync(true);
user.GrantAccess(true);
user.RegisterLightningNode("BTC", LightningConnectionType.CLightning, false);
var merchant = tester.NewAccount();
await merchant.GrantAccessAsync(true);
merchant.GrantAccess(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 chargeClient = await user.CreateClient(Policies.CanUseInternalLightningNode);
var client = await user.CreateClient(Policies.CanUseInternalLightningNode);
var info = await chargeClient.GetLightningNodeInfo("BTC");
var info = await client.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);
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"));
await AssertAPIError("lightning-node-unavailable", () => client.GetLightningNodeChannels("BTC"));
// Not permission for the store!
await AssertAPIError("missing-permission", () => chargeClient.GetLightningNodeChannels(user.StoreId, "BTC"));
var invoiceData = await chargeClient.CreateLightningInvoice("BTC", new CreateLightningInvoiceRequest()
await AssertAPIError("missing-permission", () => client.GetLightningNodeChannels(user.StoreId, "BTC"));
var invoiceData = await client.CreateLightningInvoice("BTC", new CreateLightningInvoiceRequest()
{
Amount = LightMoney.Satoshis(1000),
Description = "lol",
@ -1963,17 +1623,9 @@ namespace BTCPayServer.Tests
PrivateRouteHints = false
});
var chargeInvoice = invoiceData;
Assert.NotNull(await chargeClient.GetLightningInvoice("BTC", invoiceData.Id));
Assert.NotNull(await client.GetLightningInvoice("BTC", invoiceData.Id));
// 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}");
client = await user.CreateClient($"{Policies.CanUseLightningNodeInStore}:{user.StoreId}");
// Not permission for the server
await AssertAPIError("missing-permission", () => client.GetLightningNodeChannels("BTC"));
@ -1991,22 +1643,10 @@ namespace BTCPayServer.Tests
Assert.NotNull(await client.GetLightningInvoice(user.StoreId, "BTC", invoiceData.Id));
// 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
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"
@ -2023,15 +1663,6 @@ 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);
@ -2039,6 +1670,7 @@ 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");
@ -2048,7 +1680,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();
await guest.GrantAccessAsync();
guest.GrantAccess(false);
await user.AddGuest(guest.UserId);
client = await guest.CreateClient(Policies.CanCreateLightningInvoiceInStore);
await client.CreateLightningInvoice(user.StoreId, "BTC", new CreateLightningInvoiceRequest()
@ -2575,7 +2207,6 @@ 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
@ -2596,7 +2227,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);
@ -2824,50 +2455,6 @@ 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()
@ -2987,134 +2574,7 @@ 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")]
@ -3133,67 +2593,8 @@ 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()

View File

@ -2,8 +2,6 @@ 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;
@ -30,7 +28,6 @@ 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";
@ -38,10 +35,8 @@ 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];
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>();
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>();
vmpos.Template = @"
apple:
price: 5.0
@ -53,9 +48,9 @@ donation:
price: 1.02
custom: true
";
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
var publicApps = user.GetController<UIPointOfSaleController>();
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result);
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
var publicApps = user.GetController<UIAppsPublicController>();
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

View File

@ -185,15 +185,6 @@ 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);

View File

@ -30,6 +30,7 @@ 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;
@ -64,61 +65,6 @@ 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()
{
@ -502,12 +448,9 @@ namespace BTCPayServer.Tests
s.AddDerivationScheme();
s.GoToInvoices();
var i = s.CreateInvoice();
await s.Server.PayTester.InvoiceRepository.MarkInvoiceStatus(i, InvoiceStatus.Settled);
TestUtils.Eventually(() =>
{
s.Driver.Navigate().Refresh();
s.Driver.FindElement(By.Id($"Receipt")).Click();
});
s.GoToInvoiceCheckout(i);
s.PayInvoice(true);
TestUtils.Eventually(() => s.Driver.FindElement(By.LinkText("View receipt")).Click());
TestUtils.Eventually(() =>
{
s.Driver.Navigate().Refresh();
@ -530,11 +473,7 @@ namespace BTCPayServer.Tests
s.GoToInvoiceCheckout(i);
var checkouturi = s.Driver.Url;
s.PayInvoice();
TestUtils.Eventually(() =>
{
s.Driver.Navigate().Refresh();
s.Driver.FindElement(By.Id("receipt-btn")).Click();
});
TestUtils.Eventually(() => s.Driver.FindElement(By.LinkText("View receipt")).Click());
TestUtils.Eventually(() =>
{
s.Driver.Navigate().Refresh();
@ -542,10 +481,9 @@ namespace BTCPayServer.Tests
Assert.Contains("invoice-processing", s.Driver.PageSource);
});
s.GoToUrl(checkouturi);
await s.Server.PayTester.InvoiceRepository.MarkInvoiceStatus(i, InvoiceStatus.Settled);
s.MineBlockOnInvoiceCheckout();
TestUtils.Eventually(() => s.Driver.FindElement(By.Id("receipt-btn")).Click());
TestUtils.Eventually(() => s.Driver.FindElement(By.LinkText("View receipt")).Click());
TestUtils.Eventually(() =>
{
s.Driver.Navigate().Refresh();
@ -699,26 +637,15 @@ 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();
s.GoToStore(StoreNavPages.General);
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();
s.GoToStore(StoreNavPages.General);
s.Driver.FindElement(By.Id("DeleteStore")).Click();
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
@ -874,16 +801,6 @@ 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);
@ -926,8 +843,9 @@ 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'");
@ -945,13 +863,8 @@ 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());
// 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);
Assert.Equal("Pay Invoice",
s.Driver.FindElement(By.CssSelector("[data-test='pay-button']")).Text.Trim());
// archive (from details page)
s.GoToUrl(editUrl);
@ -1080,10 +993,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();
@ -1114,7 +1027,6 @@ 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"));
@ -1388,46 +1300,6 @@ 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")]

View File

@ -75,11 +75,11 @@ namespace BTCPayServer.Tests
public async Task CanQueryDirectProviders()
{
// TODO: Check once in a while whether or not they are working again
string[] brokenShitcoinCasinos = {};
var skipped = 0;
string[] brokenShitcoinCasinos = { };
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,26 +91,14 @@ namespace BTCPayServer.Tests
var name = result.ExpectedName;
if (brokenShitcoinCasinos.Contains(name))
{
TestLogs.LogInformation($"Skipping {name}: Broken shitcoin casino");
skipped++;
TestLogs.LogInformation($"Skipping {name}");
continue;
}
TestLogs.LogInformation($"Testing {name}");
result.Fetcher.InvalidateCache();
ExchangeRates exchangeRates = null;
try
{
exchangeRates = new ExchangeRates(name, result.ResultAsync.Result);
}
catch (Exception exception)
{
TestLogs.LogInformation($"Skipping {name}: {exception.Message}");
skipped++;
continue;
}
var exchangeRates = new ExchangeRates(name, result.ResultAsync.Result);
result.Fetcher.InvalidateCache();
Assert.NotNull(exchangeRates);
Assert.NotEmpty(exchangeRates);
@ -138,12 +126,6 @@ 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")
@ -172,12 +154,11 @@ namespace BTCPayServer.Tests
// Kraken emit one request only after first GetRates
factory.Providers["kraken"].GetRatesAsync(default).GetAwaiter().GetResult();
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]
@ -311,39 +292,17 @@ 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 var client = new HttpClient();
var actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "bootstrap", "bootstrap.bundle.min.js").Trim();
using HttpClient client = new HttpClient();
var actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "bootstrap", "bootstrap.bundle.min.js");
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()).Trim();
Assert.Equal(expected, actual);
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));
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()).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);
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));
}
string GetFileContent(params string[] path)
{
var l = path.ToList();

View File

@ -35,12 +35,10 @@ 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;
@ -52,7 +50,6 @@ 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;
@ -654,8 +651,8 @@ namespace BTCPayServer.Tests
(string)store2.TempData[WellKnownTempData.ErrorMessage], StringComparison.CurrentCultureIgnoreCase);
}
[Fact(Timeout = LongRunningTestTimeout * 2)]
[Trait("Flaky", "Flaky")]
[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanUseTorClient()
{
using var tester = CreateServerTester();
@ -787,9 +784,9 @@ namespace BTCPayServer.Tests
tx = Assert.Single(transactions.Transactions);
Assert.Equal("hello", tx.Comment);
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.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.IsType<RedirectToActionResult>(
await walletController.ModifyTransaction(walletId, tx.Id, removelabel: "test2"));
@ -799,9 +796,12 @@ namespace BTCPayServer.Tests
tx = Assert.Single(transactions.Transactions);
Assert.Equal("hello", tx.Comment);
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));
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
}
[Fact(Timeout = LongRunningTestTimeout)]
@ -1752,7 +1752,7 @@ namespace BTCPayServer.Tests
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
await user.SetNetworkFeeMode(NetworkFeeMode.Always);
var invoice = await user.BitPay.CreateInvoiceAsync(
var invoice = user.BitPay.CreateInvoice(
new Invoice
{
Price = 10,
@ -1764,10 +1764,11 @@ 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.Single(JArray.Parse(result.Content));
Assert.Equal("[]", result.Content);
var cashCow = tester.ExplorerNode;
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
@ -1952,7 +1953,6 @@ 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,14 +1960,12 @@ namespace BTCPayServer.Tests
vm.AppName = "test";
vm.SelectedAppType = appType;
var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
Assert.Equal(nameof(pos.UpdatePointOfSale), redirectToAction.ActionName);
Assert.Equal(nameof(apps.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];
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType };
apps.HttpContext.SetAppData(appData);
pos.HttpContext.SetAppData(appData);
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType });
Assert.Single(appList.Apps);
Assert.Empty(appList2.Apps);
Assert.Equal("test", appList.Apps[0].AppName);
@ -2520,79 +2518,6 @@ 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")]

View File

@ -1,67 +0,0 @@
#!/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

View File

@ -71,7 +71,7 @@ services:
- "sshd_datadir:/root/.ssh"
devlnd:
image: btcpayserver/bitcoin:23.0-1
image: btcpayserver/bitcoin:22.0
environment:
BITCOIN_NETWORK: regtest
BITCOIN_WALLETDIR: "/data/wallets"
@ -90,7 +90,7 @@ services:
expose:
- "4444"
nbxplorer:
image: nicolasdorier/nbxplorer:2.3.40
image: nicolasdorier/nbxplorer:2.3.14
restart: unless-stopped
ports:
- "32838:32838"
@ -126,7 +126,7 @@ services:
bitcoind:
restart: unless-stopped
image: btcpayserver/bitcoin:23.0-1
image: btcpayserver/bitcoin:22.0
environment:
BITCOIN_NETWORK: regtest
BITCOIN_WALLETDIR: "/data/wallets"
@ -237,7 +237,7 @@ services:
- "5432"
merchant_lnd:
image: btcpayserver/lnd:v0.15.4-beta-1
image: btcpayserver/lnd:v0.15.0-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
@ -272,7 +272,7 @@ services:
- bitcoind
customer_lnd:
image: btcpayserver/lnd:v0.15.4-beta-1
image: btcpayserver/lnd:v0.15.0-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"

View File

@ -68,7 +68,7 @@ services:
- "sshd_datadir:/root/.ssh"
devlnd:
image: btcpayserver/bitcoin:23.0-1
image: btcpayserver/bitcoin:22.0
environment:
BITCOIN_NETWORK: regtest
BITCOIN_WALLETDIR: "/data/wallets"
@ -87,7 +87,7 @@ services:
expose:
- "4444"
nbxplorer:
image: nicolasdorier/nbxplorer:2.3.40
image: nicolasdorier/nbxplorer:2.3.14
restart: unless-stopped
ports:
- "32838:32838"
@ -113,7 +113,7 @@ services:
bitcoind:
restart: unless-stopped
image: btcpayserver/bitcoin:23.0-1
image: btcpayserver/bitcoin:22.0
environment:
BITCOIN_NETWORK: regtest
BITCOIN_WALLETDIR: "/data/wallets"
@ -225,7 +225,7 @@ services:
- "5432"
merchant_lnd:
image: btcpayserver/lnd:v0.15.4-beta-1
image: btcpayserver/lnd:v0.15.0-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
@ -262,7 +262,7 @@ services:
- bitcoind
customer_lnd:
image: btcpayserver/lnd:v0.15.4-beta-1
image: btcpayserver/lnd:v0.15.0-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"

View File

@ -1,16 +0,0 @@
#!/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"

View File

@ -4,23 +4,21 @@
<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>
@ -35,6 +33,9 @@
<ItemGroup>
<None Remove="Currencies.txt" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="bundleconfig.json" />
</ItemGroup>
<ItemGroup Condition="'$(Altcoins)' != 'true'">
<Content Remove="Services\Altcoins\**\*" />
@ -47,15 +48,23 @@
<ItemGroup>
<PackageReference Include="BIP78.Sender" Version="0.2.2" />
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.2" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.4.8" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.3.13" />
<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="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.26" />
<PackageReference Include="LNURL" Version="0.0.24" />
<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" />
@ -63,10 +72,15 @@
<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>
@ -76,8 +90,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.9" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.7" />
</ItemGroup>
<ItemGroup>

View File

@ -1,63 +0,0 @@
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];
}
}
}
}

View File

@ -3,7 +3,6 @@
@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";
}
@ -11,7 +10,7 @@
<div id="AppSales-@Model.App.Id" class="widget app-sales">
<header class="mb-3">
<h3>@Model.App.Name @label</h3>
<a asp-controller="@controller" asp-action="@action" asp-route-appId="@Model.App.Id">Manage</a>
<a asp-controller="UIApps" asp-action="@action" asp-route-appId="@Model.App.Id">Manage</a>
</header>
@if (Model.InitialRendering)
{

View File

@ -2,7 +2,6 @@
@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";
}
@ -10,7 +9,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="@controller" asp-action="@action" asp-route-appId="@Model.App.Id">View All</a>
<a asp-controller="UIApps" asp-action="@action" asp-route-appId="@Model.App.Id">View All</a>
</header>
@if (Model.InitialRendering)
{

View File

@ -1,20 +0,0 @@
@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>
}

View File

@ -1,16 +0,0 @@
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);
}
}
}

View File

@ -1,7 +0,0 @@
namespace BTCPayServer.Components.MainLogo
{
public class MainLogoViewModel
{
public string CssClass { get; set; }
}
}

View File

@ -7,9 +7,11 @@
@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
@ -33,7 +35,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(new [] {StoreNavPages.Rates, StoreNavPages.CheckoutAppearance, StoreNavPages.General, StoreNavPages.Tokens, StoreNavPages.Users, StoreNavPages.Plugins, StoreNavPages.Webhooks, StoreNavPages.PayoutProcessors, StoreNavPages.Emails})" 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(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">
<vc:icon symbol="settings"/>
<span>Settings</span>
</a>
@ -91,14 +93,16 @@
</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>
@ -167,7 +171,12 @@
<ul class="navbar-nav">
@foreach (var app in Model.Apps)
{
<vc:ui-extension-point location="apps-nav" model="@app"/>
<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>
}
<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">
@ -228,7 +237,7 @@
})()
</script>
}
else if (Env.IsSecure(HttpContext.HttpContext))
else if (Env.IsSecure)
{
<ul class="navbar-nav">
@if (!PoliciesSettings.LockSubscription)

View File

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@ -72,11 +71,12 @@ namespace BTCPayServer.Components.MainNav
vm.Apps = apps.Select(a => new StoreApp
{
Id = a.Id,
IsOwner = a.IsOwner,
AppName = a.AppName,
AppType = Enum.Parse<AppType>(a.AppType)
AppType = a.AppType,
IsOwner = a.IsOwner
}).ToList();
if (PoliciesSettings.Experimental)
{
// Custodian Accounts

View File

@ -1,7 +1,6 @@
using System.Collections.Generic;
using BTCPayServer.Data;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Services.Apps;
namespace BTCPayServer.Components.MainNav
{
@ -19,7 +18,8 @@ namespace BTCPayServer.Components.MainNav
{
public string Id { get; set; }
public string AppName { get; set; }
public AppType AppType { get; set; }
public string AppType { get; set; }
public string Action { get => $"Update{AppType}"; }
public bool IsOwner { get; set; }
}
}

View File

@ -123,10 +123,7 @@
</div>
@if (Model.Balance.OffchainBalance != null && Model.Balance.OnchainBalance != null)
{
<button class="d-inline-flex align-items-center btn btn-link text-primary fw-semibold p-0 mt-3 ms-n1" type="button" data-bs-toggle="collapse" data-bs-target=".balance-details" aria-expanded="false" aria-controls="balanceDetailsOffchain balanceDetailsOnchain">
<vc:icon symbol="caret-down"/>
<span class="ms-1">Details</span>
</button>
<a class="d-inline-block mt-3" role="button" data-bs-toggle="collapse" data-bs-target=".balance-details" aria-expanded="false" aria-controls="balanceDetailsOffchain balanceDetailsOnchain">Show details</a>
}
}
else

View File

@ -1,24 +1,19 @@
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Services
@inject SignInManager<ApplicationUser> SignInManager
@inject BTCPayServerEnvironment Env
@inject IFileService FileService
@inject BTCPayServer.Services.BTCPayServerEnvironment _env
@inject SignInManager<ApplicationUser> _signInManager
@model BTCPayServer.Components.StoreSelector.StoreSelectorViewModel
@functions {
@* ReSharper disable once CSharpWarnings::CS1998 *@
#pragma warning disable 1998
private async Task LogoContent()
{
<vc:main-logo />
@if (Env.NetworkType != NBitcoin.ChainName.Mainnet)
var logoSrc = $"{ViewContext.HttpContext.Request.PathBase}/img/logo.svg";
<svg xmlns="http://www.w3.org/2000/svg" role="img" alt="BTCPay Server" class="logo"><use href="@logoSrc#small" class="logo-small" /><use href="@logoSrc#large" class="logo-large" /></svg>
@if (_env.NetworkType != NBitcoin.ChainName.Mainnet)
{
var type = Env.NetworkType.ToString();
<small class="badge bg-warning rounded-pill ms-1 ms-sm-0" title="@type">@type.Replace("Testnet", "TN").Replace("Regtest", "RT")</small>
<span class="badge bg-warning ms-1 ms-sm-0" style="font-size:10px;">@_env.NetworkType.ToString()</span>
}
}
private static string StoreName(string title)
private string StoreName(string title)
{
return string.IsNullOrEmpty(title) ? "Unnamed Store" : title;
}
@ -26,15 +21,15 @@
}
@if (Model.CurrentStoreId == null)
{
<a asp-controller="UIHome" asp-action="Index" id="StoreSelectorHome" class="navbar-brand py-2">@{await LogoContent();}</a>
<a href="~/" class="navbar-brand py-2 js-scroll-trigger">@{await LogoContent();}</a>
}
else if (Model.CurrentStoreIsOwner)
{
<a asp-controller="UIStores" asp-action="Dashboard" asp-route-storeId="@Model.CurrentStoreId" id="StoreSelectorHome" class="navbar-brand py-2">@{await LogoContent();}</a>
<a asp-controller="UIStores" asp-action="Dashboard" asp-route-storeId="@Model.CurrentStoreId" class="navbar-brand py-2 js-scroll-trigger">@{await LogoContent();}</a>
}
else
{
<a asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@Model.CurrentStoreId" id="StoreSelectorHome" class="navbar-brand py-2">@{await LogoContent();}</a>
<a asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@Model.CurrentStoreId" class="navbar-brand py-2 js-scroll-trigger">@{await LogoContent();}</a>
}
<div id="StoreSelector">
@ -42,14 +37,7 @@ else
{
<div id="StoreSelectorDropdown" class="dropdown only-for-js">
<button id="StoreSelectorToggle" class="btn btn-secondary dropdown-toggle rounded-pill px-3 @(Model.CurrentStoreId == null ? "text-secondary" : "")" type="button" data-bs-toggle="dropdown" aria-expanded="false">
@if (!string.IsNullOrEmpty(Model.CurrentStoreLogoFileId))
{
<img class="logo" src="@(await FileService.GetFileUrl(Context.Request.GetAbsoluteRootUri(), Model.CurrentStoreLogoFileId))" alt="@Model.CurrentDisplayName" />
}
else
{
<vc:icon symbol="store"/>
}
<vc:icon symbol="store"/>
<span>@(Model.CurrentStoreId == null ? "Select Store" : Model.CurrentDisplayName)</span>
<vc:icon symbol="caret-down"/>
</button>
@ -72,7 +60,7 @@ else
</ul>
</div>
}
else if (SignInManager.IsSignedIn(User))
else if (_signInManager.IsSignedIn(User))
{
<a asp-controller="UIUserStores" asp-action="CreateStore" class="btn btn-primary w-100 rounded-pill" id="StoreSelectorCreate">Create Store</a>
}

View File

@ -50,15 +50,12 @@ namespace BTCPayServer.Components.StoreSelector
.OrderBy(s => s.Text)
.ToList();
var blob = currentStore?.GetStoreBlob();
var vm = new StoreSelectorViewModel
{
Options = options,
CurrentStoreId = currentStore?.Id,
CurrentDisplayName = currentStore?.StoreName,
CurrentStoreIsOwner = currentStore?.Role == StoreRoles.Owner,
CurrentStoreLogoFileId = blob?.LogoFileId
CurrentStoreIsOwner = currentStore?.Role == StoreRoles.Owner
};
return View(vm);

View File

@ -6,7 +6,6 @@ namespace BTCPayServer.Components.StoreSelector
{
public List<StoreSelectorOption> Options { get; set; }
public string CurrentStoreId { get; set; }
public string CurrentStoreLogoFileId { get; set; }
public string CurrentDisplayName { get; set; }
public bool CurrentStoreIsOwner { get; set; }
}

View File

@ -65,7 +65,11 @@ namespace BTCPayServer.Configuration
if (conf.GetOrDefault<bool>("launchsettings", false) && NetworkType != ChainName.Regtest)
throw new ConfigException($"You need to run BTCPayServer with the run.sh or run.ps1 script");
BundleJsCss = conf.GetOrDefault<bool>("bundlejscss", true);
DockerDeployment = conf.GetOrDefault<bool>("dockerdeployment", true);
TorrcFile = conf.GetOrDefault<string>("torrcfile", null);
TorServices = conf.GetOrDefault<string>("torservices", null)
?.Split(new[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries);
@ -108,9 +112,9 @@ namespace BTCPayServer.Configuration
{
Logs.Configuration.LogWarning($"The SSH key is not supported ({ex.Message}), try to generate the key with ssh-keygen using \"-m PEM\". Skipping SSH configuration...");
}
catch (Exception ex)
catch
{
Logs.Configuration.LogWarning(ex, "Error while loading SSH settings");
throw new ConfigException($"sshkeyfilepassword is invalid");
}
}
@ -140,15 +144,14 @@ namespace BTCPayServer.Configuration
}
DisableRegistration = conf.GetOrDefault<bool>("disable-registration", true);
var pluginRemote = conf.GetOrDefault<string>("plugin-remote", null);
if (pluginRemote != null)
Logs.Configuration.LogWarning("plugin-remote is an obsolete configuration setting, please remove it from configuration");
PluginRemote = conf.GetOrDefault("plugin-remote", "btcpayserver/btcpayserver-plugins");
RecommendedPlugins = conf.GetOrDefault("recommended-plugins", "").ToLowerInvariant().Split('\r', '\n', '\t', ' ').Where(s => !string.IsNullOrEmpty(s)).Distinct().ToArray();
CheatMode = conf.GetOrDefault("cheatmode", false);
if (CheatMode && this.NetworkType == ChainName.Mainnet)
throw new ConfigException($"cheatmode can't be used on mainnet");
}
public string PluginRemote { get; set; }
public string[] RecommendedPlugins { get; set; }
public bool CheatMode { get; set; }
@ -189,7 +192,16 @@ namespace BTCPayServer.Configuration
public string RootPath { get; set; }
public bool DockerDeployment { get; set; }
public SSHSettings SSHSettings { get; set; }
public bool BundleJsCss
{
get;
set;
}
public SSHSettings SSHSettings
{
get;
set;
}
public string TorrcFile { get; set; }
public string[] TorServices { get; set; }
public Uri UpdateUrl { get; set; }

View File

@ -31,6 +31,7 @@ namespace BTCPayServer.Configuration
app.Option("--nocsp", $"Disable CSP (default false)", CommandOptionType.BoolValue);
app.Option("--sqlitefile", $"File name to an SQLite database file inside the data directory", CommandOptionType.SingleValue);
app.Option("--externalservices", $"Links added to external services inside Server Settings / Services under the format service1:path2;service2:path2.(default: empty)", CommandOptionType.SingleValue);
app.Option("--bundlejscss", $"Bundle JavaScript and CSS files for better performance (default: true)", CommandOptionType.SingleValue);
app.Option("--rootpath", "The root path in the URL to access BTCPay (default: /)", CommandOptionType.SingleValue);
app.Option("--sshconnection", "SSH server to manage BTCPay under the form user@server:port (default: root@externalhost or empty)", CommandOptionType.SingleValue);
app.Option("--sshpassword", "SSH password to manage BTCPay (default: empty)", CommandOptionType.SingleValue);
@ -45,7 +46,7 @@ namespace BTCPayServer.Configuration
app.Option("--debuglog", "A rolling log file for debug messages.", CommandOptionType.SingleValue);
app.Option("--debugloglevel", "The severity you log (default:information)", CommandOptionType.SingleValue);
app.Option("--disable-registration", "Disables new user registrations (default:true)", CommandOptionType.SingleValue);
app.Option("--plugin-remote", "Obsolete, do not use", CommandOptionType.SingleValue);
app.Option("--plugin-remote", "Which github repository to fetch the available plugins list (default:btcpayserver/btcpayserver-plugins)", CommandOptionType.SingleValue);
app.Option("--recommended-plugins", "Plugins which would be marked as recommended to be installed. Separated by newline or space", CommandOptionType.MultipleValue);
app.Option("--xforwardedproto", "If specified, set X-Forwarded-Proto to the specified value, this may be useful if your reverse proxy handle https but is not configured to add X-Forwarded-Proto (example: --xforwardedproto https)", CommandOptionType.SingleValue);
app.Option("--cheatmode", "Add some helper UI to facilitate dev-time testing (Default false)", CommandOptionType.BoolValue);
@ -138,7 +139,6 @@ namespace BTCPayServer.Configuration
{
builder.AppendLine(CultureInfo.InvariantCulture, $"#{n.CryptoCode}.explorer.url={n.NBXplorerNetwork.DefaultSettings.DefaultUrl}");
builder.AppendLine(CultureInfo.InvariantCulture, $"#{n.CryptoCode}.explorer.cookiefile={ n.NBXplorerNetwork.DefaultSettings.DefaultCookieFile}");
builder.AppendLine(CultureInfo.InvariantCulture, $"#{n.CryptoCode}.blockexplorerlink=https://mempool.space/tx/{{0}}");
if (n.SupportLightning)
{
builder.AppendLine(CultureInfo.InvariantCulture, $"#{n.CryptoCode}.lightning=/root/.lightning/lightning-rpc");

View File

@ -77,8 +77,7 @@ namespace BTCPayServer.Configuration
}
}
if (new[] { ExternalServiceTypes.Charge, ExternalServiceTypes.RTL, ExternalServiceTypes.ThunderHub,
ExternalServiceTypes.Spark, ExternalServiceTypes.Configurator, ExternalServiceTypes.Torq }.Contains(serviceType))
if (new[] { ExternalServiceTypes.Charge, ExternalServiceTypes.RTL, ExternalServiceTypes.ThunderHub, ExternalServiceTypes.Spark, ExternalServiceTypes.Configurator }.Contains(serviceType))
{
// Read access key from cookie file
if (connectionString.CookieFilePath != null)
@ -98,8 +97,7 @@ namespace BTCPayServer.Configuration
}
connectionString.CookieFilePath = null;
if (serviceType == ExternalServiceTypes.RTL || serviceType == ExternalServiceTypes.Configurator ||
serviceType == ExternalServiceTypes.ThunderHub || serviceType == ExternalServiceTypes.Torq)
if (serviceType == ExternalServiceTypes.RTL || serviceType == ExternalServiceTypes.Configurator || serviceType == ExternalServiceTypes.ThunderHub)
{
connectionString.AccessKey = cookieFileContent;
}

Some files were not shown because too many files have changed in this diff Show More