Compare commits
97 Commits
v1.4.6
...
user-disab
Author | SHA1 | Date | |
---|---|---|---|
597116bb26 | |||
64534efe71 | |||
1235ced355 | |||
23d383be67 | |||
fb90ff2fbb | |||
4706aa95e6 | |||
8981414705 | |||
7ec978fcdb | |||
d58803a058 | |||
fe6b7dc1e3 | |||
c9f0988b95 | |||
cd9a52706c | |||
bfdb1b4af9 | |||
e5174b4a29 | |||
8feb60c30d | |||
6bd7fb64ab | |||
b9602243d3 | |||
b7a930ef18 | |||
add206ae2d | |||
28ce095fb4 | |||
13952a4b79 | |||
05ec398346 | |||
dea2dd52be | |||
debe3cda4b | |||
5b5aa2c721 | |||
e201ddd74c | |||
4a1580169d | |||
001ca7de60 | |||
184be4e27b | |||
7652645dda | |||
ef6016857b | |||
e449ca2c95 | |||
b0f00773d6 | |||
451eee549b | |||
77da261fea | |||
e23c9ee608 | |||
37cb87a9c6 | |||
211db8e0f0 | |||
d074d60dad | |||
0bff5e2236 | |||
dca986eb2e | |||
326eb1135b | |||
b2f7b4e6b9 | |||
5129d6aa6b | |||
a8cf334616 | |||
3b4d06a1e5 | |||
c7969476b0 | |||
7bf24df03a | |||
5ef41294e4 | |||
2eb68655c7 | |||
23049439c0 | |||
ce6cd40b92 | |||
c36b0c16b0 | |||
c15f182377 | |||
165cb345b4 | |||
9b6d2beb4d | |||
722c39a6ff | |||
e344749d2f | |||
fe782bc3b6 | |||
d372cbad74 | |||
1e1198f4ec | |||
5effc96cff | |||
b8d4a1be05 | |||
36a25e6efa | |||
1240e7914d | |||
e4683b1ea1 | |||
749c22a0c3 | |||
6867774627 | |||
80944972e9 | |||
c1f608c0d8 | |||
4dfbb08db3 | |||
ead1dffd98 | |||
5a16e4d132 | |||
a89491e343 | |||
b1b00ae886 | |||
cde5bd87d8 | |||
3231d5d179 | |||
03e49ea2bf | |||
7d3eef092c | |||
30d0410b49 | |||
eb2a887f77 | |||
e77b8d29cf | |||
490ec299c5 | |||
e47c2aa24d | |||
3eb9fdca6a | |||
a4173a93b7 | |||
ad762cf239 | |||
5a478607dc | |||
4abc6eb387 | |||
c313bba288 | |||
73eaf97afb | |||
8d25df5d4e | |||
4a05f16050 | |||
3ef1423263 | |||
898652189b | |||
2976edf333 | |||
248be11e4d |
@ -2,7 +2,7 @@ version: 2
|
||||
jobs:
|
||||
fast_tests:
|
||||
machine:
|
||||
enabled: true
|
||||
image: ubuntu-2004:202111-02
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
@ -10,7 +10,7 @@ jobs:
|
||||
cd .circleci && ./run-tests.sh "Fast=Fast|ThirdParty=ThirdParty" && ./can-build.sh
|
||||
selenium_tests:
|
||||
machine:
|
||||
enabled: true
|
||||
image: ubuntu-2004:202111-02
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
@ -18,7 +18,7 @@ jobs:
|
||||
cd .circleci && ./run-tests.sh "Selenium=Selenium"
|
||||
integration_tests:
|
||||
machine:
|
||||
enabled: true
|
||||
image: ubuntu-2004:202111-02
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
@ -26,8 +26,7 @@ jobs:
|
||||
cd .circleci && ./run-tests.sh "Integration=Integration"
|
||||
trigger_docs_build:
|
||||
machine:
|
||||
enabled: true
|
||||
image: ubuntu-2004:202201-02
|
||||
image: ubuntu-2004:202111-02
|
||||
steps:
|
||||
- run:
|
||||
command: |
|
||||
@ -36,7 +35,7 @@ jobs:
|
||||
# publish jobs require $DOCKERHUB_REPO, $DOCKERHUB_USER, $DOCKERHUB_PASS defined
|
||||
amd64:
|
||||
machine:
|
||||
enabled: true
|
||||
image: ubuntu-2004:202111-02
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
@ -51,7 +50,7 @@ jobs:
|
||||
|
||||
arm32v7:
|
||||
machine:
|
||||
enabled: true
|
||||
image: ubuntu-2004:202111-02
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
@ -67,7 +66,7 @@ jobs:
|
||||
|
||||
arm64v8:
|
||||
machine:
|
||||
enabled: true
|
||||
image: ubuntu-2004:202111-02
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
@ -83,7 +82,6 @@ jobs:
|
||||
|
||||
multiarch:
|
||||
machine:
|
||||
enabled: true
|
||||
image: ubuntu-2004:202201-02
|
||||
steps:
|
||||
- run:
|
||||
|
@ -6,7 +6,6 @@ namespace BTCPayServer.Configuration
|
||||
public string PluginDir { get; set; }
|
||||
public string TempStorageDir { get; set; }
|
||||
public string StorageDir { get; set; }
|
||||
public string TempDir { get; set; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
7
BTCPayServer.Abstractions/Constants/WellKnownTempData.cs
Normal file
7
BTCPayServer.Abstractions/Constants/WellKnownTempData.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace BTCPayServer.Abstractions.Constants;
|
||||
|
||||
public class WellKnownTempData
|
||||
{
|
||||
public const string SuccessMessage = nameof(SuccessMessage);
|
||||
public const string ErrorMessage = nameof(ErrorMessage);
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Contracts
|
||||
{
|
||||
public interface IBTCPayServerClientFactory
|
||||
{
|
||||
Task<BTCPayServerClient> Create(string userId, params string[] storeIds);
|
||||
Task<BTCPayServerClient> Create(string userId, string[] storeIds, HttpContext httpRequest);
|
||||
}
|
||||
}
|
||||
|
17
BTCPayServer.Abstractions/Contracts/IFileService.cs
Normal file
17
BTCPayServer.Abstractions/Contracts/IFileService.cs
Normal file
@ -0,0 +1,17 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Contracts;
|
||||
|
||||
public interface IFileService
|
||||
{
|
||||
Task<bool> IsAvailable();
|
||||
Task<IStoredFile> AddFile(IFormFile file, string userId);
|
||||
Task<IStoredFile> AddFile(Uri file, string userId);
|
||||
Task<string?> GetFileUrl(Uri baseUri, string fileId);
|
||||
Task<string?> GetTemporaryFileUrl(Uri baseUri, string fileId, DateTimeOffset expiry,
|
||||
bool isDownload);
|
||||
Task RemoveFile(string fileId, string userId);
|
||||
}
|
12
BTCPayServer.Abstractions/Contracts/IStoredFile.cs
Normal file
12
BTCPayServer.Abstractions/Contracts/IStoredFile.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Contracts;
|
||||
|
||||
public interface IStoredFile
|
||||
{
|
||||
string Id { get; set; }
|
||||
string FileName { get; set; }
|
||||
string StorageFileName { get; set; }
|
||||
DateTime Timestamp { get; set; }
|
||||
string ApplicationUserId { get; set; }
|
||||
}
|
9
BTCPayServer.Abstractions/Contracts/ISwaggerProvider.cs
Normal file
9
BTCPayServer.Abstractions/Contracts/ISwaggerProvider.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Contracts;
|
||||
|
||||
public interface ISwaggerProvider
|
||||
{
|
||||
Task<JObject> Fetch();
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
using System.Text.Json;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Extensions
|
||||
{
|
||||
public static class SetStatusMessageModelExtensions
|
||||
{
|
||||
public static void SetStatusMessageModel(this ITempDataDictionary tempData, StatusMessageModel statusMessage)
|
||||
{
|
||||
if (statusMessage == null)
|
||||
{
|
||||
tempData.Remove("StatusMessageModel");
|
||||
return;
|
||||
}
|
||||
|
||||
tempData["StatusMessageModel"] = JsonSerializer.Serialize(statusMessage, new JsonSerializerOptions());
|
||||
}
|
||||
}
|
||||
}
|
43
BTCPayServer.Abstractions/Extensions/GreenfieldExtensions.cs
Normal file
43
BTCPayServer.Abstractions/Extensions/GreenfieldExtensions.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Client.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Extensions;
|
||||
|
||||
public static class GreenfieldExtensions
|
||||
{
|
||||
public static IActionResult CreateValidationError(this ControllerBase controller, ModelStateDictionary modelState)
|
||||
{
|
||||
return controller.UnprocessableEntity(modelState.ToGreenfieldValidationError());
|
||||
}
|
||||
|
||||
public static List<GreenfieldValidationError> ToGreenfieldValidationError(this ModelStateDictionary modelState)
|
||||
{
|
||||
List<GreenfieldValidationError> errors = new List<GreenfieldValidationError>();
|
||||
foreach (var error in modelState)
|
||||
{
|
||||
foreach (var errorMessage in error.Value.Errors)
|
||||
{
|
||||
errors.Add(new GreenfieldValidationError(error.Key, errorMessage.ErrorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
public static IActionResult CreateAPIError(this ControllerBase controller, string errorCode, string errorMessage)
|
||||
{
|
||||
return controller.BadRequest(new GreenfieldAPIError(errorCode, errorMessage));
|
||||
}
|
||||
|
||||
public static IActionResult CreateAPIError(this ControllerBase controller, int httpCode, string errorCode, string errorMessage)
|
||||
{
|
||||
return controller.StatusCode(httpCode, new GreenfieldAPIError(errorCode, errorMessage));
|
||||
}
|
||||
|
||||
public static IActionResult CreateAPIPermissionError(this ControllerBase controller, string missingPermission, string message = null)
|
||||
{
|
||||
return controller.StatusCode(403, new GreenfieldPermissionAPIError(missingPermission, message));
|
||||
}
|
||||
}
|
120
BTCPayServer.Abstractions/Extensions/HttpRequestExtensions.cs
Normal file
120
BTCPayServer.Abstractions/Extensions/HttpRequestExtensions.cs
Normal file
@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Extensions;
|
||||
|
||||
public static class HttpRequestExtensions
|
||||
{
|
||||
public static bool IsOnion(this HttpRequest request)
|
||||
{
|
||||
if (request?.Host.Host == null)
|
||||
return false;
|
||||
return request.Host.Host.EndsWith(".onion", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static string GetAbsoluteRoot(this HttpRequest request)
|
||||
{
|
||||
return string.Concat(
|
||||
request.Scheme,
|
||||
"://",
|
||||
request.Host.ToUriComponent(),
|
||||
request.PathBase.ToUriComponent());
|
||||
}
|
||||
|
||||
public static Uri GetAbsoluteRootUri(this HttpRequest request)
|
||||
{
|
||||
return new Uri(request.GetAbsoluteRoot());
|
||||
}
|
||||
|
||||
public static string GetCurrentUrl(this HttpRequest request)
|
||||
{
|
||||
return string.Concat(
|
||||
request.Scheme,
|
||||
"://",
|
||||
request.Host.ToUriComponent(),
|
||||
request.PathBase.ToUriComponent(),
|
||||
request.Path.ToUriComponent());
|
||||
}
|
||||
|
||||
public static string GetCurrentPath(this HttpRequest request)
|
||||
{
|
||||
return string.Concat(
|
||||
request.PathBase.ToUriComponent(),
|
||||
request.Path.ToUriComponent());
|
||||
}
|
||||
|
||||
public static string GetCurrentPathWithQueryString(this HttpRequest request)
|
||||
{
|
||||
return request.PathBase + request.Path + request.QueryString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If 'toto' and RootPath is 'rootpath' returns '/rootpath/toto'
|
||||
/// If 'toto' and RootPath is empty returns '/toto'
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetRelativePath(this HttpRequest request, string path)
|
||||
{
|
||||
if (path.Length > 0 && path[0] != '/')
|
||||
path = $"/{path}";
|
||||
return string.Concat(
|
||||
request.PathBase.ToUriComponent(),
|
||||
path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If 'https://example.com/toto' returns 'https://example.com/toto'
|
||||
/// If 'toto' and RootPath is 'rootpath' returns '/rootpath/toto'
|
||||
/// If 'toto' and RootPath is empty returns '/toto'
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetRelativePathOrAbsolute(this HttpRequest request, string path)
|
||||
{
|
||||
if (!Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out var uri) ||
|
||||
uri.IsAbsoluteUri)
|
||||
return path;
|
||||
|
||||
if (path.Length > 0 && path[0] != '/')
|
||||
path = $"/{path}";
|
||||
return string.Concat(
|
||||
request.PathBase.ToUriComponent(),
|
||||
path);
|
||||
}
|
||||
|
||||
public static string GetAbsoluteUri(this HttpRequest request, string redirectUrl)
|
||||
{
|
||||
bool isRelative =
|
||||
(redirectUrl.Length > 0 && redirectUrl[0] == '/')
|
||||
|| !new Uri(redirectUrl, UriKind.RelativeOrAbsolute).IsAbsoluteUri;
|
||||
return isRelative ? request.GetAbsoluteRoot() + redirectUrl : redirectUrl;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will return an absolute URL.
|
||||
/// If `relativeOrAsbolute` is absolute, returns it.
|
||||
/// If `relativeOrAsbolute` is relative, send absolute url based on the HOST of this request (without PathBase)
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="relativeOrAbsolte"></param>
|
||||
/// <returns></returns>
|
||||
public static Uri GetAbsoluteUriNoPathBase(this HttpRequest request, Uri relativeOrAbsolute = null)
|
||||
{
|
||||
if (relativeOrAbsolute == null)
|
||||
{
|
||||
return new Uri(string.Concat(
|
||||
request.Scheme,
|
||||
"://",
|
||||
request.Host.ToUriComponent()), UriKind.Absolute);
|
||||
}
|
||||
if (relativeOrAbsolute.IsAbsoluteUri)
|
||||
return relativeOrAbsolute;
|
||||
return new Uri(string.Concat(
|
||||
request.Scheme,
|
||||
"://",
|
||||
request.Host.ToUriComponent()) + relativeOrAbsolute.ToString().WithStartingSlash(), UriKind.Absolute);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
using System.Text.Json;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Extensions;
|
||||
|
||||
public static class SetStatusMessageModelExtensions
|
||||
{
|
||||
public static void SetStatusMessageModel(this ITempDataDictionary tempData, StatusMessageModel statusMessage)
|
||||
{
|
||||
if (statusMessage == null)
|
||||
{
|
||||
tempData.Remove("StatusMessageModel");
|
||||
return;
|
||||
}
|
||||
|
||||
tempData["StatusMessageModel"] = JsonSerializer.Serialize(statusMessage, new JsonSerializerOptions());
|
||||
}
|
||||
|
||||
public static StatusMessageModel GetStatusMessageModel(this ITempDataDictionary tempData)
|
||||
{
|
||||
tempData.TryGetValue(WellKnownTempData.SuccessMessage, out var successMessage);
|
||||
tempData.TryGetValue(WellKnownTempData.ErrorMessage, out var errorMessage);
|
||||
tempData.TryGetValue("StatusMessageModel", out var model);
|
||||
if (successMessage != null || errorMessage != null)
|
||||
{
|
||||
var parsedModel = new StatusMessageModel();
|
||||
parsedModel.Message = (string)successMessage ?? (string)errorMessage;
|
||||
if (successMessage != null)
|
||||
{
|
||||
parsedModel.Severity = StatusMessageModel.StatusSeverity.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
parsedModel.Severity = StatusMessageModel.StatusSeverity.Error;
|
||||
}
|
||||
return parsedModel;
|
||||
}
|
||||
else if (model != null && model is string str)
|
||||
{
|
||||
return JObject.Parse(str).ToObject<StatusMessageModel>();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool HasStatusMessage(this ITempDataDictionary tempData)
|
||||
{
|
||||
return (tempData.Peek(WellKnownTempData.SuccessMessage) ??
|
||||
tempData.Peek(WellKnownTempData.ErrorMessage) ??
|
||||
tempData.Peek("StatusMessageModel")) != null;
|
||||
}
|
||||
|
||||
public static bool HasErrorMessage(this ITempDataDictionary tempData)
|
||||
{
|
||||
return GetStatusMessageModel(tempData)?.Severity == StatusMessageModel.StatusSeverity.Error;
|
||||
}
|
||||
}
|
45
BTCPayServer.Abstractions/Extensions/StringExtensions.cs
Normal file
45
BTCPayServer.Abstractions/Extensions/StringExtensions.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Extensions;
|
||||
|
||||
public static class StringExtensions
|
||||
{
|
||||
|
||||
public static bool IsValidFileName(this string fileName)
|
||||
{
|
||||
return !fileName.ToCharArray().Any(c => Path.GetInvalidFileNameChars().Contains(c)
|
||||
|| c == Path.AltDirectorySeparatorChar
|
||||
|| c == Path.DirectorySeparatorChar
|
||||
|| c == Path.PathSeparator
|
||||
|| c == '\\');
|
||||
}
|
||||
|
||||
public static string Truncate(this string value, int maxLength)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return value;
|
||||
return value.Length <= maxLength ? value : value.Substring(0, maxLength);
|
||||
}
|
||||
|
||||
public static string WithTrailingSlash(this string str)
|
||||
{
|
||||
if (str.EndsWith("/", StringComparison.InvariantCulture))
|
||||
return str;
|
||||
return str + "/";
|
||||
}
|
||||
public static string WithStartingSlash(this string str)
|
||||
{
|
||||
if (str.StartsWith("/", StringComparison.InvariantCulture))
|
||||
return str;
|
||||
return $"/{str}";
|
||||
}
|
||||
public static string WithoutEndingSlash(this string str)
|
||||
{
|
||||
if (str.EndsWith("/", StringComparison.InvariantCulture))
|
||||
return str.Substring(0, str.Length - 1);
|
||||
return str;
|
||||
}
|
||||
|
||||
}
|
@ -29,7 +29,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NBitcoin" Version="7.0.1" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.3.1" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.3.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -65,6 +65,17 @@ namespace BTCPayServer.Client
|
||||
return await HandleResponse<LightningPaymentData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LightningPaymentData> GetLightningPayment(string cryptoCode,
|
||||
string paymentHash, CancellationToken token = default)
|
||||
{
|
||||
if (paymentHash == null)
|
||||
throw new ArgumentNullException(nameof(paymentHash));
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/payments/{paymentHash}",
|
||||
method: HttpMethod.Get), token);
|
||||
return await HandleResponse<LightningPaymentData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LightningInvoiceData> GetLightningInvoice(string cryptoCode,
|
||||
string invoiceId, CancellationToken token = default)
|
||||
{
|
||||
|
@ -67,6 +67,17 @@ namespace BTCPayServer.Client
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LightningPaymentData> GetLightningPayment(string storeId, string cryptoCode,
|
||||
string paymentHash, CancellationToken token = default)
|
||||
{
|
||||
if (paymentHash == null)
|
||||
throw new ArgumentNullException(nameof(paymentHash));
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/payments/{paymentHash}",
|
||||
method: HttpMethod.Get), token);
|
||||
return await HandleResponse<LightningPaymentData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LightningInvoiceData> GetLightningInvoice(string storeId, string cryptoCode,
|
||||
string invoiceId, CancellationToken token = default)
|
||||
{
|
||||
|
@ -20,7 +20,7 @@ namespace BTCPayServer.Client
|
||||
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain",
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain",
|
||||
query), token);
|
||||
return await HandleResponse<IEnumerable<OnChainPaymentMethodData>>(response);
|
||||
}
|
||||
@ -30,7 +30,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}"), token);
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}"), token);
|
||||
return await HandleResponse<OnChainPaymentMethodData>(response);
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}",
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}",
|
||||
method: HttpMethod.Delete), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
@ -49,7 +49,7 @@ namespace BTCPayServer.Client
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}",
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}",
|
||||
bodyPayload: paymentMethod, method: HttpMethod.Put), token);
|
||||
return await HandleResponse<OnChainPaymentMethodData>(response);
|
||||
}
|
||||
@ -61,7 +61,7 @@ namespace BTCPayServer.Client
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/preview",
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/preview",
|
||||
bodyPayload: paymentMethod,
|
||||
queryPayload: new Dictionary<string, object>() { { "offset", offset }, { "amount", amount } },
|
||||
method: HttpMethod.Post), token);
|
||||
@ -73,7 +73,7 @@ namespace BTCPayServer.Client
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/preview",
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/preview",
|
||||
queryPayload: new Dictionary<string, object>() { { "offset", offset }, { "amount", amount } },
|
||||
method: HttpMethod.Get), token);
|
||||
return await HandleResponse<OnChainPaymentMethodPreviewResultData>(response);
|
||||
@ -84,7 +84,7 @@ namespace BTCPayServer.Client
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/generate",
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/generate",
|
||||
bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
return await HandleResponse<OnChainPaymentMethodDataWithSensitiveData>(response);
|
||||
|
@ -16,7 +16,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet"), token);
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet"), token);
|
||||
return await HandleResponse<OnChainWalletOverviewData>(response);
|
||||
}
|
||||
public virtual async Task<OnChainWalletFeeRateData> GetOnChainFeeRate(string storeId, string cryptoCode, int? blockTarget = null,
|
||||
@ -29,7 +29,7 @@ namespace BTCPayServer.Client
|
||||
}
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/feeRate", queryParams), token);
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/feeRate", queryParams), token);
|
||||
return await HandleResponse<OnChainWalletFeeRateData>(response);
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/address", new Dictionary<string, object>()
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/address", new Dictionary<string, object>()
|
||||
{
|
||||
{"forceGenerate", forceGenerate}
|
||||
}), token);
|
||||
@ -50,7 +50,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/address", method: HttpMethod.Delete), token);
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/address", method: HttpMethod.Delete), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@ namespace BTCPayServer.Client
|
||||
}
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/transactions", query), token);
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions", query), token);
|
||||
return await HandleResponse<IEnumerable<OnChainWalletTransactionData>>(response);
|
||||
}
|
||||
|
||||
@ -75,7 +75,18 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/transactions/{transactionId}"), token);
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions/{transactionId}"), token);
|
||||
return await HandleResponse<OnChainWalletTransactionData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainWalletTransactionData> PatchOnChainWalletTransaction(
|
||||
string storeId, string cryptoCode, string transactionId,
|
||||
PatchOnChainTransactionRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions/{transactionId}", queryPayload: null, bodyPayload: request, HttpMethod.Patch), token);
|
||||
return await HandleResponse<OnChainWalletTransactionData>(response);
|
||||
}
|
||||
|
||||
@ -85,7 +96,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/utxos"), token);
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/utxos"), token);
|
||||
return await HandleResponse<IEnumerable<OnChainWalletUTXOData>>(response);
|
||||
}
|
||||
|
||||
@ -100,7 +111,7 @@ namespace BTCPayServer.Client
|
||||
}
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/transactions", null, request, HttpMethod.Post), token);
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions", null, request, HttpMethod.Post), token);
|
||||
return await HandleResponse<OnChainWalletTransactionData>(response);
|
||||
}
|
||||
|
||||
@ -115,7 +126,7 @@ namespace BTCPayServer.Client
|
||||
}
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/transactions", null, request, HttpMethod.Post), token);
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions", null, request, HttpMethod.Post), token);
|
||||
return Transaction.Parse(await HandleResponse<string>(response), network);
|
||||
}
|
||||
}
|
||||
|
37
BTCPayServer.Client/BTCPayServerClient.StoreEmail.cs
Normal file
37
BTCPayServer.Client/BTCPayServerClient.StoreEmail.cs
Normal file
@ -0,0 +1,37 @@
|
||||
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<EmailSettingsData> GetStoreEmailSettings(string storeId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/email", method: HttpMethod.Get),
|
||||
token);
|
||||
return await HandleResponse<EmailSettingsData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<EmailSettingsData> UpdateStoreEmailSettings(string storeId, EmailSettingsData request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/email", bodyPayload: request, method: HttpMethod.Put),
|
||||
token);
|
||||
return await HandleResponse<EmailSettingsData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task SendEmail(string storeId, SendEmailRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/email/send", bodyPayload: request, method: HttpMethod.Post),
|
||||
token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ namespace BTCPayServer.Client
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public virtual async Task<StoreData> AddStoreUser(string storeId, StoreUserData request,
|
||||
public virtual async Task AddStoreUser(string storeId, StoreUserData request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
if (request == null)
|
||||
@ -31,7 +31,7 @@ namespace BTCPayServer.Client
|
||||
using var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/users", bodyPayload: request, method: HttpMethod.Post),
|
||||
token);
|
||||
return await HandleResponse<StoreData>(response);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,15 @@ namespace BTCPayServer.Client
|
||||
return await HandleResponse<ApplicationUserData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task ToggleUser(string idOrEmail, bool enabled, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/users/{idOrEmail}/toggle", null,new
|
||||
{
|
||||
enabled
|
||||
} , HttpMethod.Post), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public virtual async Task<ApplicationUserData[]> GetUsers( CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/users/", null, HttpMethod.Get), token);
|
||||
|
@ -35,5 +35,7 @@ namespace BTCPayServer.Client.Models
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset? Created { get; set; }
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.JsonConverters;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
@ -11,15 +11,19 @@ namespace BTCPayServer.Client.Models
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public CreateLightningInvoiceRequest(LightMoney amount, string description, TimeSpan expiry)
|
||||
{
|
||||
Amount = amount;
|
||||
Description = description;
|
||||
Expiry = expiry;
|
||||
}
|
||||
[JsonConverter(typeof(BTCPayServer.Client.JsonConverters.LightMoneyJsonConverter))]
|
||||
|
||||
[JsonConverter(typeof(JsonConverters.LightMoneyJsonConverter))]
|
||||
public LightMoney Amount { get; set; }
|
||||
public string Description { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.UInt256JsonConverter))]
|
||||
public uint256 DescriptionHash { get; set; }
|
||||
[JsonConverter(typeof(JsonConverters.TimeSpanJsonConverter.Seconds))]
|
||||
public TimeSpan Expiry { get; set; }
|
||||
public bool PrivateRouteHints { get; set; }
|
||||
|
33
BTCPayServer.Client/Models/EmailSettingsData.cs
Normal file
33
BTCPayServer.Client/Models/EmailSettingsData.cs
Normal file
@ -0,0 +1,33 @@
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class EmailSettingsData
|
||||
{
|
||||
public string Server
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public int? Port
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Login
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Password
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string FromDisplay
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string From
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
@ -1,11 +1,28 @@
|
||||
using System;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.Lightning;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class LightningPaymentData
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public string PaymentHash { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public LightningPaymentStatus Status { get; set; }
|
||||
|
||||
[JsonProperty("BOLT11")]
|
||||
public string BOLT11 { get; set; }
|
||||
|
||||
public string Preimage { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset? CreatedAt { get; set; }
|
||||
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
public LightMoney TotalAmount { get; set; }
|
||||
|
||||
|
@ -21,9 +21,9 @@ namespace BTCPayServer.Client.Models
|
||||
[JsonConverter(typeof(UInt256JsonConverter))]
|
||||
public uint256 BlockHash { get; set; }
|
||||
|
||||
public int? BlockHeight { get; set; }
|
||||
public long? BlockHeight { get; set; }
|
||||
|
||||
public int Confirmations { get; set; }
|
||||
public long Confirmations { get; set; }
|
||||
|
||||
[JsonConverter(typeof(DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
|
@ -21,6 +21,6 @@ namespace BTCPayServer.Client.Models
|
||||
[JsonConverter(typeof(KeyPathJsonConverter))]
|
||||
public KeyPath KeyPath { get; set; }
|
||||
public string Address { get; set; }
|
||||
public int Confirmations { get; set; }
|
||||
public long Confirmations { get; set; }
|
||||
}
|
||||
}
|
||||
|
12
BTCPayServer.Client/Models/PatchOnChainTransactionRequest.cs
Normal file
12
BTCPayServer.Client/Models/PatchOnChainTransactionRequest.cs
Normal file
@ -0,0 +1,12 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class PatchOnChainTransactionRequest
|
||||
{
|
||||
|
||||
public string? Comment { get; set; } = null;
|
||||
public List<string>? Labels { get; set; } = null;
|
||||
}
|
||||
}
|
10
BTCPayServer.Client/Models/SendEmailRequest.cs
Normal file
10
BTCPayServer.Client/Models/SendEmailRequest.cs
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class SendEmailRequest
|
||||
{
|
||||
public string Email;
|
||||
public string Subject;
|
||||
public string Body;
|
||||
}
|
||||
}
|
@ -177,7 +177,9 @@ namespace BTCPayServer.Client
|
||||
case Policies.CanViewProfile when this.Policy == Policies.CanModifyProfile:
|
||||
case Policies.CanModifyPaymentRequests when this.Policy == Policies.CanModifyStoreSettings:
|
||||
case Policies.CanViewPaymentRequests when this.Policy == Policies.CanModifyStoreSettings:
|
||||
case Policies.CanManagePullPayments when this.Policy == Policies.CanModifyStoreSettings:
|
||||
case Policies.CanViewPaymentRequests when this.Policy == Policies.CanViewStoreSettings:
|
||||
case Policies.CanViewPaymentRequests when this.Policy == Policies.CanModifyPaymentRequests:
|
||||
case Policies.CanCreateLightningInvoiceInternalNode when this.Policy == Policies.CanUseInternalLightningNode:
|
||||
case Policies.CanCreateLightningInvoiceInStore when this.Policy == Policies.CanUseLightningNodeInStore:
|
||||
case Policies.CanViewNotificationsForUser when this.Policy == Policies.CanManageNotificationsForUser:
|
||||
|
@ -11,7 +11,7 @@ namespace BTCPayServer
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Althash",
|
||||
DisplayName = "Htmlcoin",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.htmlcoin.com/api/tx/{0}" : "https://explorer.htmlcoin.com/api/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
DefaultRateRules = new[]
|
||||
@ -21,7 +21,7 @@ namespace BTCPayServer
|
||||
},
|
||||
CryptoImagePath = "imlegacy/althash.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("88'") : new KeyPath("1'")
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("172'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,6 @@ namespace BTCPayServer
|
||||
InitViacoin();
|
||||
InitMonero();
|
||||
InitZcash();
|
||||
InitPolis();
|
||||
InitChaincoin();
|
||||
// InitArgoneum();//their rate source is down 9/15/20.
|
||||
InitMonetaryUnit();
|
||||
|
@ -1,10 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="4.1.3" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="4.2.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Altcoins)' != 'true'">
|
||||
<Compile Remove="Altcoins\**\*.cs"></Compile>
|
||||
|
@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class StoredFile
|
||||
public class StoredFile : IStoredFile
|
||||
{
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public string Id { get; set; }
|
||||
|
@ -1274,14 +1274,7 @@
|
||||
"crypto":true
|
||||
},
|
||||
{
|
||||
"name":"POLIS",
|
||||
"code":"POLIS",
|
||||
"divisibility":8,
|
||||
"symbol":null,
|
||||
"crypto":true
|
||||
},
|
||||
{
|
||||
"name":"Althash",
|
||||
"name":"Htmlcoin",
|
||||
"code":"HTML",
|
||||
"divisibility":8,
|
||||
"symbol":null,
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,25 +0,0 @@
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Rating;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
public class PolisRateProvider : IRateProvider
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
public PolisRateProvider(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
}
|
||||
|
||||
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _httpClient.GetAsync("https://obol.polispay.com/complex/btc/polis", cancellationToken); //Returns complex rate from BTC to POLIS
|
||||
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
|
||||
var value = jobj["data"].Value<decimal>();
|
||||
return new[] { new PairRate(new CurrencyPair("BTC", "POLIS"), new BidAsk(value)) };
|
||||
}
|
||||
}
|
||||
}
|
@ -78,8 +78,6 @@ namespace BTCPayServer.Services.Rates
|
||||
yield return new AvailableRateProvider("ripio", "Ripio", "https://api.exchange.ripio.com/api/v1/rate/all/");
|
||||
yield return new AvailableRateProvider("cryptomarket", "CryptoMarket", "https://api.exchange.cryptomkt.com/api/3/public/ticker/");
|
||||
|
||||
yield return new AvailableRateProvider("polispay", "PolisPay", "https://obol.polispay.com/complex/btc/polis");
|
||||
|
||||
yield return new AvailableRateProvider("bitfinex", "Bitfinex", "https://api.bitfinex.com/v2/tickers?symbols=tBTCUSD,tLTCUSD,tLTCBTC,tETHUSD,tETHBTC,tETCBTC,tETCUSD,tRRTUSD,tRRTBTC,tZECUSD,tZECBTC,tXMRUSD,tXMRBTC,tDSHUSD,tDSHBTC,tBTCEUR,tBTCJPY,tXRPUSD,tXRPBTC,tIOTUSD,tIOTBTC,tIOTETH,tEOSUSD,tEOSBTC,tEOSETH,tSANUSD,tSANBTC,tSANETH,tOMGUSD,tOMGBTC,tOMGETH,tNEOUSD,tNEOBTC,tNEOETH,tETPUSD,tETPBTC,tETPETH,tQTMUSD,tQTMBTC,tQTMETH,tAVTUSD,tAVTBTC,tAVTETH,tEDOUSD,tEDOBTC,tEDOETH,tBTGUSD,tBTGBTC,tDATUSD,tDATBTC,tDATETH,tQSHUSD,tQSHBTC,tQSHETH,tYYWUSD,tYYWBTC,tYYWETH,tGNTUSD,tGNTBTC,tGNTETH,tSNTUSD,tSNTBTC,tSNTETH,tIOTEUR,tBATUSD,tBATBTC,tBATETH,tMNAUSD,tMNABTC,tMNAETH,tFUNUSD,tFUNBTC,tFUNETH,tZRXUSD,tZRXBTC,tZRXETH,tTNBUSD,tTNBBTC,tTNBETH,tSPKUSD,tSPKBTC,tSPKETH,tTRXUSD,tTRXBTC,tTRXETH,tRCNUSD,tRCNBTC,tRCNETH,tRLCUSD,tRLCBTC,tRLCETH,tAIDUSD,tAIDBTC,tAIDETH,tSNGUSD,tSNGBTC,tSNGETH,tREPUSD,tREPBTC,tREPETH,tELFUSD,tELFBTC,tELFETH,tNECUSD,tNECBTC,tNECETH,tBTCGBP,tETHEUR,tETHJPY,tETHGBP,tNEOEUR,tNEOJPY,tNEOGBP,tEOSEUR,tEOSJPY,tEOSGBP,tIOTJPY,tIOTGBP,tIOSUSD,tIOSBTC,tIOSETH,tAIOUSD,tAIOBTC,tAIOETH,tREQUSD,tREQBTC,tREQETH,tRDNUSD,tRDNBTC,tRDNETH,tLRCUSD,tLRCBTC,tLRCETH,tWAXUSD,tWAXBTC,tWAXETH,tDAIUSD,tDAIBTC,tDAIETH,tAGIUSD,tAGIBTC,tAGIETH,tBFTUSD,tBFTBTC,tBFTETH,tMTNUSD,tMTNBTC,tMTNETH,tODEUSD,tODEBTC,tODEETH,tANTUSD,tANTBTC,tANTETH,tDTHUSD,tDTHBTC,tDTHETH,tMITUSD,tMITBTC,tMITETH,tSTJUSD,tSTJBTC,tSTJETH,tXLMUSD,tXLMEUR,tXLMJPY,tXLMGBP,tXLMBTC,tXLMETH,tXVGUSD,tXVGEUR,tXVGJPY,tXVGGBP,tXVGBTC,tXVGETH,tBCIUSD,tBCIBTC,tMKRUSD,tMKRBTC,tMKRETH,tKNCUSD,tKNCBTC,tKNCETH,tPOAUSD,tPOABTC,tPOAETH,tEVTUSD,tLYMUSD,tLYMBTC,tLYMETH,tUTKUSD,tUTKBTC,tUTKETH,tVEEUSD,tVEEBTC,tVEEETH,tDADUSD,tDADBTC,tDADETH,tORSUSD,tORSBTC,tORSETH,tAUCUSD,tAUCBTC,tAUCETH,tPOYUSD,tPOYBTC,tPOYETH,tFSNUSD,tFSNBTC,tFSNETH,tCBTUSD,tCBTBTC,tCBTETH,tZCNUSD,tZCNBTC,tZCNETH,tSENUSD,tSENBTC,tSENETH,tNCAUSD,tNCABTC,tNCAETH,tCNDUSD,tCNDBTC,tCNDETH,tCTXUSD,tCTXBTC,tCTXETH,tPAIUSD,tPAIBTC,tSEEUSD,tSEEBTC,tSEEETH,tESSUSD,tESSBTC,tESSETH,tATMUSD,tATMBTC,tATMETH,tHOTUSD,tHOTBTC,tHOTETH,tDTAUSD,tDTABTC,tDTAETH,tIQXUSD,tIQXBTC,tIQXEOS,tWPRUSD,tWPRBTC,tWPRETH,tZILUSD,tZILBTC,tZILETH,tBNTUSD,tBNTBTC,tBNTETH,tABSUSD,tABSETH,tXRAUSD,tXRAETH,tMANUSD,tMANETH,tBBNUSD,tBBNETH,tNIOUSD,tNIOETH,tDGXUSD,tDGXETH,tVETUSD,tVETBTC,tVETETH,tUTNUSD,tUTNETH,tTKNUSD,tTKNETH,tGOTUSD,tGOTEUR,tGOTETH,tXTZUSD,tXTZBTC,tCNNUSD,tCNNETH,tBOXUSD,tBOXETH,tTRXEUR,tTRXGBP,tTRXJPY,tMGOUSD,tMGOETH,tRTEUSD,tRTEETH,tYGGUSD,tYGGETH,tMLNUSD,tMLNETH,tWTCUSD,tWTCETH,tCSXUSD,tCSXETH,tOMNUSD,tOMNBTC,tINTUSD,tINTETH,tDRNUSD,tDRNETH,tPNKUSD,tPNKETH,tDGBUSD,tDGBBTC,tBSVUSD,tBSVBTC,tBABUSD,tBABBTC,tWLOUSD,tWLOXLM,tVLDUSD,tVLDETH,tENJUSD,tENJETH,tONLUSD,tONLETH,tRBTUSD,tRBTBTC,tUSTUSD,tEUTEUR,tEUTUSD,tGSDUSD,tUDCUSD,tTSDUSD,tPAXUSD,tRIFUSD,tRIFBTC,tPASUSD,tPASETH,tVSYUSD,tVSYBTC,tZRXDAI,tMKRDAI,tOMGDAI,tBTTUSD,tBTTBTC,tBTCUST,tETHUST,tCLOUSD,tCLOBTC,tIMPUSD,tIMPETH,tLTCUST,tEOSUST,tBABUST,tSCRUSD,tSCRETH,tGNOUSD,tGNOETH,tGENUSD,tGENETH,tATOUSD,tATOBTC,tATOETH,tWBTUSD,tXCHUSD,tEUSUSD,tWBTETH,tXCHETH,tEUSETH,tLEOUSD,tLEOBTC,tLEOUST,tLEOEOS,tLEOETH,tASTUSD,tASTETH,tFOAUSD,tFOAETH,tUFRUSD,tUFRETH,tZBTUSD,tZBTUST,tOKBUSD,tUSKUSD,tGTXUSD,tKANUSD,tOKBUST,tOKBETH,tOKBBTC,tUSKUST,tUSKETH,tUSKBTC,tUSKEOS,tGTXUST,tKANUST,tAMPUSD,tALGUSD,tALGBTC,tALGUST,tBTCXCH,tSWMUSD,tSWMETH,tTRIUSD,tTRIETH,tLOOUSD,tLOOETH,tAMPUST,tDUSK:USD,tDUSK:BTC,tUOSUSD,tUOSBTC,tRRBUSD,tRRBUST,tDTXUSD,tDTXUST,tAMPBTC,tFTTUSD,tFTTUST,tPAXUST,tUDCUST,tTSDUST,tBTC:CNHT,tUST:CNHT,tCNH:CNHT,tCHZUSD,tCHZUST,tBTCF0:USTF0,tETHF0:USTF0");
|
||||
yield return new AvailableRateProvider("okex", "OKEx", "https://www.okex.com/api/futures/v3/instruments/ticker");
|
||||
yield return new AvailableRateProvider("coinbasepro", "Coinbase Pro", "https://api.pro.coinbase.com/products");
|
||||
@ -104,7 +102,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("polispay", new PolisRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_POLIS")));
|
||||
// Providers.Add("argoneum", new ArgoneumRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_ARGONEUM")));
|
||||
|
||||
|
||||
|
@ -358,7 +358,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
if (multiCurrency)
|
||||
user.RegisterDerivationScheme("LTC");
|
||||
foreach (var rateSelection in new[] { "FiatText", "CurrentRateText", "RateThenText" })
|
||||
foreach (var rateSelection in new[] { "FiatTextRadio", "CurrentRateTextRadio", "RateThenTextRadio" })
|
||||
await CanCreateRefundsCore(s, user, multiCurrency, rateSelection);
|
||||
}
|
||||
}
|
||||
@ -402,11 +402,11 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id(rateSelection)).Click();
|
||||
s.Driver.FindElement(By.Id("ok")).Click();
|
||||
Assert.Contains("pull-payments", s.Driver.Url);
|
||||
if (rateSelection == "FiatText")
|
||||
if (rateSelection == "FiatTextRadio")
|
||||
Assert.Contains("$5,500.00", s.Driver.PageSource);
|
||||
if (rateSelection == "CurrentRateText")
|
||||
if (rateSelection == "CurrentRateTextRadio")
|
||||
Assert.Contains("2.20000000 ₿", s.Driver.PageSource);
|
||||
if (rateSelection == "RateThenText")
|
||||
if (rateSelection == "RateThenTextRadio")
|
||||
Assert.Contains("1.10000000 ₿", s.Driver.PageSource);
|
||||
s.GoToInvoice(invoice.Id);
|
||||
s.Driver.FindElement(By.Id("refundlink")).Click();
|
||||
@ -475,11 +475,11 @@ namespace BTCPayServer.Tests
|
||||
tester.ActivateLTC();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
await user.GrantAccessAsync();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
// First we try payment with a merchant having only BTC
|
||||
var invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
{
|
||||
Price = 5000.0m,
|
||||
Currency = "USD",
|
||||
@ -490,10 +490,10 @@ namespace BTCPayServer.Tests
|
||||
}, Facade.Merchant);
|
||||
|
||||
var cashCow = tester.ExplorerNode;
|
||||
cashCow.Generate(2); // get some money in case
|
||||
await cashCow.GenerateAsync(2); // get some money in case
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||
var firstPayment = Money.Coins(0.04m);
|
||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||
await cashCow.SendToAddressAsync(invoiceAddress, firstPayment);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
@ -523,8 +523,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// Retry now with LTC enabled
|
||||
user.RegisterDerivationScheme("LTC");
|
||||
invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
{
|
||||
Price = 5000.0m,
|
||||
Currency = "USD",
|
||||
@ -537,7 +537,7 @@ namespace BTCPayServer.Tests
|
||||
cashCow = tester.ExplorerNode;
|
||||
invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||
firstPayment = Money.Coins(0.04m);
|
||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||
await cashCow.SendToAddressAsync(invoiceAddress, firstPayment);
|
||||
TestLogs.LogInformation("First payment sent to " + invoiceAddress);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
@ -550,8 +550,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.NotNull(ltcCryptoInfo);
|
||||
invoiceAddress = BitcoinAddress.Create(ltcCryptoInfo.Address, cashCow.Network);
|
||||
var secondPayment = Money.Coins(decimal.Parse(ltcCryptoInfo.Due, CultureInfo.InvariantCulture));
|
||||
cashCow.Generate(4); // LTC is not worth a lot, so just to make sure we have money...
|
||||
cashCow.SendToAddress(invoiceAddress, secondPayment);
|
||||
await cashCow.GenerateAsync(4); // LTC is not worth a lot, so just to make sure we have money...
|
||||
await cashCow.SendToAddressAsync(invoiceAddress, secondPayment);
|
||||
TestLogs.LogInformation("Second payment sent to " + invoiceAddress);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
@ -570,7 +570,6 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(2, checkout.AvailableCryptos.Count);
|
||||
Assert.Equal("LTC", checkout.CryptoCode);
|
||||
|
||||
|
||||
Assert.Equal(2, invoice.PaymentCodes.Count());
|
||||
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count());
|
||||
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count());
|
||||
@ -581,11 +580,10 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(invoice.SupportedTransactionCurrencies["LTC"].Enabled);
|
||||
Assert.True(invoice.PaymentSubtotals.ContainsKey("LTC"));
|
||||
Assert.True(invoice.PaymentTotals.ContainsKey("LTC"));
|
||||
|
||||
|
||||
|
||||
// Check if we can disable LTC
|
||||
invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
{
|
||||
Price = 5000.0m,
|
||||
Currency = "USD",
|
||||
|
@ -7,11 +7,11 @@ using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Security.Greenfield;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Views.Manage;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Support.Extensions;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
@ -20,7 +20,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
public class ApiKeysTests : UnitTestBase
|
||||
{
|
||||
public const int TestTimeout = TestUtils.TestTimeout;
|
||||
public const int TestTimeout = 120_000;
|
||||
|
||||
public const string TestApiPath = "api/test/apikey";
|
||||
public ApiKeysTests(ITestOutputHelper helper) : base(helper)
|
||||
@ -91,7 +91,6 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("button[value='btcpay.store.canmodifystoresettings:change-store-mode']")).Click();
|
||||
//there should be a store already by default in the dropdown
|
||||
var src = s.Driver.PageSource;
|
||||
var getPermissionValueIndex =
|
||||
s.Driver.FindElement(By.CssSelector("input[value='btcpay.store.canmodifystoresettings']"))
|
||||
.GetAttribute("name")
|
||||
@ -100,7 +99,7 @@ namespace BTCPayServer.Tests
|
||||
var option = dropdown.FindElement(By.TagName("option"));
|
||||
var storeId = option.GetAttribute("value");
|
||||
option.Click();
|
||||
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||
s.Driver.WaitForAndClick(By.Id("Generate"));
|
||||
var selectiveStoreApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
|
||||
|
||||
TestLogs.LogInformation("Checking CanModifyStoreSettings with StoreId permissions");
|
||||
@ -108,12 +107,13 @@ namespace BTCPayServer.Tests
|
||||
await TestApiAgainstAccessToken(selectiveStoreApiKey, tester, user,
|
||||
Permission.Create(Policies.CanModifyStoreSettings, storeId).ToString());
|
||||
|
||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||
TestLogs.LogInformation("Adding API key for no permissions");
|
||||
s.Driver.WaitForAndClick(By.Id("AddApiKey"));
|
||||
TestLogs.LogInformation("Generating API key for no permissions");
|
||||
s.Driver.WaitForAndClick(By.Id("Generate"));
|
||||
var noPermissionsApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
|
||||
|
||||
TestLogs.LogInformation("Checking no permissions");
|
||||
|
||||
TestLogs.LogInformation($"Checking no permissions: {noPermissionsApiKey}");
|
||||
await TestApiAgainstAccessToken(noPermissionsApiKey, tester, user);
|
||||
|
||||
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||
@ -137,26 +137,35 @@ namespace BTCPayServer.Tests
|
||||
var callbackUrl = s.ServerUri + "postredirect-callback-test";
|
||||
var authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri,
|
||||
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, applicationDetails: (appidentifier, new Uri(callbackUrl))).ToString();
|
||||
s.Driver.Navigate().GoToUrl(authUrl);
|
||||
TestLogs.LogInformation($"Going to auth URL {authUrl}");
|
||||
s.GoToUrl(authUrl);
|
||||
Assert.Contains(appidentifier, s.Driver.PageSource);
|
||||
Assert.Equal("hidden", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("type").ToLowerInvariant());
|
||||
Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("value").ToLowerInvariant());
|
||||
Assert.Equal("hidden", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("type").ToLowerInvariant());
|
||||
Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("value").ToLowerInvariant());
|
||||
Assert.DoesNotContain("change-store-mode", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Id("consent-yes")).Click();
|
||||
|
||||
TestLogs.LogInformation("Going to callback URL");
|
||||
|
||||
s.Driver.WaitForAndClick(By.Id("consent-yes"));
|
||||
Assert.Equal(callbackUrl, s.Driver.Url);
|
||||
TestLogs.LogInformation("On callback URL");
|
||||
|
||||
var apiKeyRepo = s.Server.PayTester.GetService<APIKeyRepository>();
|
||||
var accessToken = GetAccessTokenFromCallbackResult(s.Driver);
|
||||
|
||||
TestLogs.LogInformation($"Access token: {accessToken}");
|
||||
|
||||
await TestApiAgainstAccessToken(accessToken, tester, user,
|
||||
(await apiKeyRepo.GetKey(accessToken)).GetBlob().Permissions);
|
||||
|
||||
authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri,
|
||||
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, false, true, applicationDetails: (null, new Uri(callbackUrl))).ToString();
|
||||
|
||||
s.Driver.Navigate().GoToUrl(authUrl);
|
||||
TestLogs.LogInformation($"Going to auth URL 2 {authUrl}");
|
||||
s.GoToUrl(authUrl);
|
||||
TestLogs.LogInformation("On auth URL 2");
|
||||
Assert.DoesNotContain("kukksappname", s.Driver.PageSource);
|
||||
|
||||
Assert.Equal("checkbox", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("type").ToLowerInvariant());
|
||||
@ -166,50 +175,62 @@ namespace BTCPayServer.Tests
|
||||
|
||||
s.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), false);
|
||||
Assert.Contains("change-store-mode", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Id("consent-yes")).Click();
|
||||
|
||||
TestLogs.LogInformation("Going to callback URL 2");
|
||||
s.Driver.WaitForAndClick(By.Id("consent-yes"));
|
||||
Assert.Equal(callbackUrl, s.Driver.Url);
|
||||
TestLogs.LogInformation("On callback URL 2");
|
||||
|
||||
accessToken = GetAccessTokenFromCallbackResult(s.Driver);
|
||||
|
||||
TestLogs.LogInformation($"Access token: {accessToken}");
|
||||
TestLogs.LogInformation("Checking authorized permissions");
|
||||
|
||||
await TestApiAgainstAccessToken(accessToken, tester, user,
|
||||
(await apiKeyRepo.GetKey(accessToken)).GetBlob().Permissions);
|
||||
|
||||
//let's test the app identifier system
|
||||
TestLogs.LogInformation("Checking app identifier system");
|
||||
authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri,
|
||||
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, false, true, (appidentifier, new Uri(callbackUrl))).ToString();
|
||||
|
||||
//if it's the same, go to the confirm page
|
||||
s.Driver.Navigate().GoToUrl(authUrl);
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
TestLogs.LogInformation($"Going to auth URL 3 {authUrl}");
|
||||
s.GoToUrl(authUrl);
|
||||
TestLogs.LogInformation("On auth URL 3");
|
||||
s.Driver.WaitForAndClick(By.Id("continue"));
|
||||
TestLogs.LogInformation("Going to callback URL 3");
|
||||
Assert.Equal(callbackUrl, s.Driver.Url);
|
||||
TestLogs.LogInformation("On callback URL 3");
|
||||
|
||||
//same app but different redirect = nono
|
||||
authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri,
|
||||
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, false, true, (appidentifier, new Uri("https://international.local/callback"))).ToString();
|
||||
|
||||
s.Driver.Navigate().GoToUrl(authUrl);
|
||||
TestLogs.LogInformation($"Going to auth URL 4 {authUrl}");
|
||||
s.GoToUrl(authUrl);
|
||||
TestLogs.LogInformation("On auth URL 4");
|
||||
Assert.False(s.Driver.Url.StartsWith("https://international.com/callback"));
|
||||
|
||||
// Make sure we can check all permissions when not an admin
|
||||
TestLogs.LogInformation("Make sure we can check all permissions when not an admin");
|
||||
await user.MakeAdmin(false);
|
||||
s.Logout();
|
||||
s.GoToLogin();
|
||||
s.LogIn(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
||||
s.GoToProfile(ManageNavPages.APIKeys);
|
||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||
int checkedPermissionCount = 0;
|
||||
foreach (var checkbox in s.Driver.FindElements(By.ClassName("form-check-input")))
|
||||
{
|
||||
checkedPermissionCount++;
|
||||
checkbox.Click();
|
||||
}
|
||||
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||
TestLogs.LogInformation("Go to API Keys page");
|
||||
s.GoToUrl("/account/apikeys");
|
||||
TestLogs.LogInformation("On API Keys page");
|
||||
s.Driver.WaitForAndClick(By.Id("AddApiKey"));
|
||||
int checkedPermissionCount = s.Driver.FindElements(By.ClassName("form-check-input")).Count;
|
||||
TestLogs.LogInformation($"Adding API key: {checkedPermissionCount} permissions");
|
||||
s.Driver.ExecuteJavaScript("document.querySelectorAll('#Permissions .form-check-input').forEach(i => i.click())");
|
||||
TestLogs.LogInformation($"Clicked {checkedPermissionCount}");
|
||||
|
||||
TestLogs.LogInformation("Generating API key");
|
||||
s.Driver.WaitForAndClick(By.Id("Generate"));
|
||||
var allAPIKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
|
||||
|
||||
TestLogs.LogInformation("Checking API key permissions");
|
||||
|
||||
TestLogs.LogInformation($"Checking API key permissions: {allAPIKey}");
|
||||
var apikeydata = await TestApiAgainstAccessToken<ApiKeyData>(allAPIKey, "api/v1/api-keys/current", tester.PayTester.HttpClient);
|
||||
Assert.Equal(checkedPermissionCount, apikeydata.Permissions.Length);
|
||||
}
|
||||
@ -221,12 +242,14 @@ namespace BTCPayServer.Tests
|
||||
expectedPermissions ??= new Permission[0];
|
||||
var apikeydata = await TestApiAgainstAccessToken<ApiKeyData>(accessToken, $"api/v1/api-keys/current", tester.PayTester.HttpClient);
|
||||
var permissions = apikeydata.Permissions;
|
||||
TestLogs.LogInformation($"TestApiAgainstAccessToken: Permissions {permissions.Length}");
|
||||
Assert.Equal(expectedPermissions.Length, permissions.Length);
|
||||
foreach (var expectPermission in expectedPermissions)
|
||||
{
|
||||
Assert.True(permissions.Any(p => p == expectPermission), $"Missing expected permission {expectPermission}");
|
||||
}
|
||||
|
||||
TestLogs.LogInformation("Testing CanViewProfile");
|
||||
if (permissions.Contains(Permission.Create(Policies.CanViewProfile)))
|
||||
{
|
||||
var resultUser = await TestApiAgainstAccessToken<string>(accessToken, $"{TestApiPath}/me/id", tester.PayTester.HttpClient);
|
||||
@ -239,14 +262,17 @@ namespace BTCPayServer.Tests
|
||||
await TestApiAgainstAccessToken<string>(accessToken, $"{TestApiPath}/me/id", tester.PayTester.HttpClient);
|
||||
});
|
||||
}
|
||||
//create a second user to see if any of its data gets messed upin our results.
|
||||
//create a second user to see if any of its data gets messed up in our results.
|
||||
TestLogs.LogInformation("Testing second user");
|
||||
var secondUser = tester.NewAccount();
|
||||
secondUser.GrantAccess();
|
||||
await secondUser.GrantAccessAsync();
|
||||
|
||||
var canModifyAllStores = Permission.Create(Policies.CanModifyStoreSettings, null);
|
||||
var canModifyServer = Permission.Create(Policies.CanModifyServerSettings, null);
|
||||
var unrestricted = Permission.Create(Policies.Unrestricted, null);
|
||||
var selectiveStorePermissions = permissions.Where(p => p.Scope != null && p.Policy == Policies.CanModifyStoreSettings);
|
||||
|
||||
TestLogs.LogInformation("Testing can edit store for first user");
|
||||
if (permissions.Contains(canModifyAllStores) || selectiveStorePermissions.Any())
|
||||
{
|
||||
var resultStores =
|
||||
@ -306,7 +332,6 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
else if (!permissions.Contains(unrestricted))
|
||||
{
|
||||
|
||||
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||
{
|
||||
await TestApiAgainstAccessToken<bool>(accessToken,
|
||||
@ -321,6 +346,7 @@ namespace BTCPayServer.Tests
|
||||
tester.PayTester.HttpClient);
|
||||
}
|
||||
|
||||
TestLogs.LogInformation("Testing can edit store for second user");
|
||||
if (!permissions.Contains(unrestricted))
|
||||
{
|
||||
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||
@ -334,7 +360,9 @@ namespace BTCPayServer.Tests
|
||||
await TestApiAgainstAccessToken<bool>(accessToken, $"{TestApiPath}/me/stores/{secondUser.StoreId}/can-edit",
|
||||
tester.PayTester.HttpClient);
|
||||
}
|
||||
TestLogs.LogInformation("Testing can edit store for second user expectation met");
|
||||
|
||||
TestLogs.LogInformation($"Testing CanModifyServer with {permissions.Contains(canModifyServer)}");
|
||||
if (permissions.Contains(canModifyServer))
|
||||
{
|
||||
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
|
||||
@ -350,17 +378,21 @@ namespace BTCPayServer.Tests
|
||||
tester.PayTester.HttpClient);
|
||||
});
|
||||
}
|
||||
TestLogs.LogInformation("Testing CanModifyServer expectation met");
|
||||
}
|
||||
|
||||
public async Task<T> TestApiAgainstAccessToken<T>(string apikey, string url, HttpClient client)
|
||||
{
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Get,
|
||||
new Uri(client.BaseAddress, url));
|
||||
var uri = new Uri(client.BaseAddress, url);
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("token", apikey);
|
||||
TestLogs.LogInformation($"Testing {uri}");
|
||||
var result = await client.SendAsync(httpRequest);
|
||||
TestLogs.LogInformation($"Testing {uri} status: {result.StatusCode}");
|
||||
result.EnsureSuccessStatusCode();
|
||||
|
||||
var rawJson = await result.Content.ReadAsStringAsync();
|
||||
TestLogs.LogInformation($"Testing {uri} result: {rawJson}");
|
||||
if (typeof(T).IsPrimitive || typeof(T) == typeof(string))
|
||||
{
|
||||
return (T)Convert.ChangeType(rawJson, typeof(T));
|
||||
|
@ -106,8 +106,6 @@ retry:
|
||||
driver.ExecuteJavaScript($"document.getElementById('{collapseId}').classList.add('show')");
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void SetAttribute(this IWebDriver driver, string element, string attribute, string value)
|
||||
{
|
||||
driver.ExecuteJavaScript($"document.getElementById('{element}').setAttribute('{attribute}', '{value}')");
|
||||
@ -127,12 +125,17 @@ retry:
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
public static void ScrollTo(this IWebDriver driver, IWebElement element)
|
||||
{
|
||||
driver.ExecuteJavaScript("arguments[0].scrollIntoView();", element);
|
||||
}
|
||||
|
||||
public static void ScrollTo(this IWebDriver driver, By selector)
|
||||
{
|
||||
var element = driver.FindElement(selector);
|
||||
driver.ExecuteJavaScript("arguments[0].scrollIntoView();", element);
|
||||
ScrollTo(driver, driver.FindElement(selector));
|
||||
}
|
||||
|
||||
public static void WaitForAndClick(this IWebDriver driver, By selector)
|
||||
{
|
||||
// Try fast path
|
||||
|
@ -3,14 +3,13 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
@ -40,7 +39,6 @@ using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -1765,5 +1763,18 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(btc.CryptoCode, paymentMethod.CryptoCode);
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllPoliciesShowInUI()
|
||||
{
|
||||
foreach (var policy in Policies.AllPolicies)
|
||||
{
|
||||
Assert.True( UIManageController.AddApiKeyViewModel.PermissionValueItem.PermissionDescriptions.ContainsKey(policy));
|
||||
if (Policies.IsStorePolicy(policy))
|
||||
{
|
||||
Assert.True( UIManageController.AddApiKeyViewModel.PermissionValueItem.PermissionDescriptions.ContainsKey($"{policy}:"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1193,12 +1193,13 @@ namespace BTCPayServer.Tests
|
||||
new CreateInvoiceRequest() { Currency = "helloinvalid", Amount = 1 });
|
||||
});
|
||||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
string origOrderId = "testOrder";
|
||||
var newInvoice = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest()
|
||||
{
|
||||
Currency = "USD",
|
||||
Amount = 1,
|
||||
Metadata = JObject.Parse("{\"itemCode\": \"testitem\", \"orderId\": \"testOrder\"}"),
|
||||
Metadata = JObject.Parse($"{{\"itemCode\": \"testitem\", \"orderId\": \"{origOrderId}\"}}"),
|
||||
Checkout = new CreateInvoiceRequest.CheckoutOptions()
|
||||
{
|
||||
RedirectAutomatically = true,
|
||||
@ -1319,6 +1320,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
newInvoice = await client.GetInvoice(user.StoreId, newInvoice.Id);
|
||||
|
||||
const string newOrderId = "UPDATED-ORDER-ID";
|
||||
JObject metadataForUpdate = JObject.Parse($"{{\"orderId\": \"{newOrderId}\", \"itemCode\": \"updated\", newstuff: [1,2,3,4,5]}}");
|
||||
Assert.Contains(InvoiceStatus.Settled, newInvoice.AvailableStatusesForManualMarking);
|
||||
Assert.DoesNotContain(InvoiceStatus.Invalid, newInvoice.AvailableStatusesForManualMarking);
|
||||
await AssertHttpError(403, async () =>
|
||||
@ -1326,23 +1329,36 @@ namespace BTCPayServer.Tests
|
||||
await viewOnly.UpdateInvoice(user.StoreId, invoice.Id,
|
||||
new UpdateInvoiceRequest()
|
||||
{
|
||||
Metadata = JObject.Parse("{\"itemCode\": \"updated\", newstuff: [1,2,3,4,5]}")
|
||||
Metadata = metadataForUpdate
|
||||
});
|
||||
});
|
||||
invoice = await client.UpdateInvoice(user.StoreId, invoice.Id,
|
||||
new UpdateInvoiceRequest()
|
||||
{
|
||||
Metadata = JObject.Parse("{\"itemCode\": \"updated\", newstuff: [1,2,3,4,5]}")
|
||||
Metadata = metadataForUpdate
|
||||
});
|
||||
|
||||
Assert.Equal(newOrderId, invoice.Metadata["orderId"].Value<string>());
|
||||
Assert.Equal("updated", invoice.Metadata["itemCode"].Value<string>());
|
||||
Assert.Equal(15, ((JArray)invoice.Metadata["newstuff"]).Values<int>().Sum());
|
||||
|
||||
//also test the the metadata actually got saved
|
||||
invoice = await client.GetInvoice(user.StoreId, invoice.Id);
|
||||
Assert.Equal(newOrderId, invoice.Metadata["orderId"].Value<string>());
|
||||
Assert.Equal("updated", invoice.Metadata["itemCode"].Value<string>());
|
||||
Assert.Equal(15, ((JArray)invoice.Metadata["newstuff"]).Values<int>().Sum());
|
||||
|
||||
// test if we can find the updated invoice using the new orderId
|
||||
var invoicesWithOrderId = await client.GetInvoices(user.StoreId, new[] { newOrderId });
|
||||
Assert.NotNull(invoicesWithOrderId);
|
||||
Assert.Single(invoicesWithOrderId);
|
||||
Assert.Equal(invoice.Id, invoicesWithOrderId.First().Id);
|
||||
|
||||
// test if the old orderId does not yield any results anymore
|
||||
var invoicesWithOldOrderId = await client.GetInvoices(user.StoreId, new[] { origOrderId });
|
||||
Assert.NotNull(invoicesWithOldOrderId);
|
||||
Assert.Empty(invoicesWithOldOrderId);
|
||||
|
||||
//archive
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
@ -1531,7 +1547,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Single(info.NodeURIs);
|
||||
Assert.NotEqual(0, info.BlockHeight);
|
||||
|
||||
await AssertAPIError("ligthning-node-unavailable", () => client.GetLightningNodeChannels("BTC"));
|
||||
await AssertAPIError("lightning-node-unavailable", () => client.GetLightningNodeChannels("BTC"));
|
||||
// Not permission for the store!
|
||||
await AssertAPIError("missing-permission", () => client.GetLightningNodeChannels(user.StoreId, "BTC"));
|
||||
var invoiceData = await client.CreateLightningInvoice("BTC", new CreateLightningInvoiceRequest()
|
||||
@ -1687,7 +1703,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(firstAddress, (await viewOnlyClient.PreviewProposedStoreOnChainPaymentMethodAddresses(store.Id, "BTC",
|
||||
new UpdateOnChainPaymentMethodRequest() { Enabled = true, DerivationScheme = xpub })).Addresses.First().Address);
|
||||
|
||||
await AssertValidationError(new[] { "accountKeyPath" }, () => viewOnlyClient.SendHttpRequest<GreenfieldValidationError[]>(path: $"api/v1/stores/{store.Id}/payment-methods/Onchain/BTC/preview", method: HttpMethod.Post,
|
||||
await AssertValidationError(new[] { "accountKeyPath" }, () => viewOnlyClient.SendHttpRequest<GreenfieldValidationError[]>(path: $"api/v1/stores/{store.Id}/payment-methods/onchain/BTC/preview", method: HttpMethod.Post,
|
||||
bodyPayload: JObject.Parse("{\"accountKeyPath\": \"0/1\"}")));
|
||||
|
||||
var method = await client.UpdateStoreOnChainPaymentMethod(store.Id, "BTC",
|
||||
@ -2110,7 +2126,30 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await viewOnlyClient.GetOnChainWalletTransaction(walletId.StoreId, walletId.CryptoCode, txdata.TransactionHash.ToString());
|
||||
});
|
||||
await client.GetOnChainWalletTransaction(walletId.StoreId, walletId.CryptoCode, txdata.TransactionHash.ToString());
|
||||
var transaction = await client.GetOnChainWalletTransaction(walletId.StoreId, walletId.CryptoCode, txdata.TransactionHash.ToString());
|
||||
|
||||
Assert.Equal(transaction.TransactionHash, txdata.TransactionHash);
|
||||
Assert.Equal(String.Empty, transaction.Comment);
|
||||
Assert.Equal(new Dictionary<string, LabelData>(), transaction.Labels);
|
||||
|
||||
// transaction patch tests
|
||||
var patchedTransaction = await client.PatchOnChainWalletTransaction(
|
||||
walletId.StoreId, walletId.CryptoCode, txdata.TransactionHash.ToString(),
|
||||
new PatchOnChainTransactionRequest() {
|
||||
Comment = "test comment",
|
||||
Labels = new List<string>
|
||||
{
|
||||
"test label"
|
||||
}
|
||||
});
|
||||
Assert.Equal("test comment", patchedTransaction.Comment);
|
||||
Assert.Equal(
|
||||
new Dictionary<string, LabelData>()
|
||||
{
|
||||
{ "test label", new LabelData(){ Type = "raw", Text = "test label" } }
|
||||
}.ToJson(),
|
||||
patchedTransaction.Labels.ToJson()
|
||||
);
|
||||
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
@ -2265,6 +2304,38 @@ namespace BTCPayServer.Tests
|
||||
await AssertAPIError("store-user-role-orphaned", async () => await user2Client.RemoveStoreUser(user.StoreId, user2.UserId));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task StoreEmailTests()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
var admin = tester.NewAccount();
|
||||
await admin.GrantAccessAsync(true);
|
||||
var adminClient = await admin.CreateClient(Policies.Unrestricted);
|
||||
await adminClient.UpdateStoreEmailSettings(admin.StoreId,
|
||||
new EmailSettingsData());
|
||||
|
||||
var data = new EmailSettingsData()
|
||||
{
|
||||
From = "admin@admin.com",
|
||||
Login = "admin@admin.com",
|
||||
Password = "admin@admin.com",
|
||||
Port = 1234,
|
||||
Server = "admin.com",
|
||||
};
|
||||
await adminClient.UpdateStoreEmailSettings(admin.StoreId, data);
|
||||
var s = await adminClient.GetStoreEmailSettings(admin.StoreId);
|
||||
Assert.Equal(JsonConvert.SerializeObject(s), JsonConvert.SerializeObject(data));
|
||||
await AssertValidationError(new[] { nameof(EmailSettingsData.From) },
|
||||
async () => await adminClient.UpdateStoreEmailSettings(admin.StoreId,
|
||||
new EmailSettingsData() { From = "ass" }));
|
||||
|
||||
|
||||
await adminClient.SendEmail(admin.StoreId,
|
||||
new SendEmailRequest() { Body = "lol", Subject = "subj", Email = "sdasdas" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Tooling
|
||||
|
||||
This README describe some useful tooling that you may need during development and testing.
|
||||
To learn how to get started with your local development environment, read [our documentation](https://docs.btcpayserver.org/LocalDevelopment/).
|
||||
To learn how to get started with your local development environment, read [our documentation](https://docs.btcpayserver.org/Development/LocalDevelopment/).
|
||||
|
||||
## How to manually test payments
|
||||
|
||||
|
@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.BIP78.Sender;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Views.Manage;
|
||||
@ -460,11 +458,11 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public void GoToProfile(ManageNavPages navPages = ManageNavPages.Index)
|
||||
{
|
||||
Driver.FindElement(By.Id("Nav-Account")).Click();
|
||||
Driver.FindElement(By.Id("Nav-ManageAccount")).Click();
|
||||
Driver.WaitForAndClick(By.Id("Nav-Account"));
|
||||
Driver.WaitForAndClick(By.Id("Nav-ManageAccount"));
|
||||
if (navPages != ManageNavPages.Index)
|
||||
{
|
||||
Driver.FindElement(By.Id($"SectionNav-{navPages.ToString()}")).Click();
|
||||
Driver.WaitForAndClick(By.Id($"SectionNav-{navPages.ToString()}"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -453,6 +453,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
s.GoToStore();
|
||||
Assert.Contains(storeName, s.Driver.PageSource);
|
||||
Assert.DoesNotContain("id=\"Dashboard\"", s.Driver.PageSource);
|
||||
|
||||
// verify steps for wallet setup are displayed correctly
|
||||
s.GoToStore(StoreNavPages.Dashboard);
|
||||
@ -466,10 +467,11 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.AssertNoError();
|
||||
|
||||
s.GoToStore(StoreNavPages.Dashboard);
|
||||
Assert.True(s.Driver.FindElement(By.Id("SetupGuide-WalletDone")).Displayed);
|
||||
Assert.DoesNotContain("id=\"SetupGuide\"", s.Driver.PageSource);
|
||||
Assert.True(s.Driver.FindElement(By.Id("Dashboard")).Displayed);
|
||||
|
||||
// setup offchain wallet
|
||||
s.Driver.FindElement(By.Id("SetupGuide-Lightning")).Click();
|
||||
s.Driver.FindElement(By.Id("StoreNav-LightningBTC")).Click();
|
||||
s.AddLightningNode();
|
||||
s.Driver.AssertNoError();
|
||||
var successAlert = s.FindAlertMessage();
|
||||
@ -477,9 +479,6 @@ namespace BTCPayServer.Tests
|
||||
|
||||
s.ClickOnAllSectionLinks();
|
||||
|
||||
s.GoToStore(StoreNavPages.Dashboard);
|
||||
Assert.True(s.Driver.FindElement(By.Id("SetupGuide-LightningDone")).Displayed);
|
||||
|
||||
s.GoToInvoices();
|
||||
Assert.Contains("There are no invoices matching your criteria.", s.Driver.PageSource);
|
||||
var invoiceId = s.CreateInvoice();
|
||||
@ -674,7 +673,8 @@ namespace BTCPayServer.Tests
|
||||
s.LogIn(userId);
|
||||
// Make sure after login, we are not redirected to the PoS
|
||||
Assert.DoesNotContain("Tea shop", s.Driver.PageSource);
|
||||
// We are only if explicitely going to /
|
||||
|
||||
// We are only if explicitly going to /
|
||||
s.GoToUrl("/");
|
||||
Assert.Contains("Tea shop", s.Driver.PageSource);
|
||||
s.Driver.Navigate().Back();
|
||||
@ -696,7 +696,8 @@ namespace BTCPayServer.Tests
|
||||
s.LogIn(userId);
|
||||
// Make sure after login, we are not redirected to the PoS
|
||||
Assert.DoesNotContain("Tea shop", s.Driver.PageSource);
|
||||
// We are only if explicitely going to /
|
||||
|
||||
// We are only if explicitly going to /
|
||||
s.GoToUrl("/");
|
||||
Assert.Contains("Tea shop", s.Driver.PageSource);
|
||||
}
|
||||
@ -749,7 +750,12 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("CreatePaymentRequest")).Click();
|
||||
s.Driver.FindElement(By.Id("Title")).SendKeys("Pay123");
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("700");
|
||||
s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC");
|
||||
|
||||
var currencyInput = s.Driver.FindElement(By.Id("Currency"));
|
||||
Assert.Equal("USD", currencyInput.GetAttribute("value"));
|
||||
currencyInput.Clear();
|
||||
currencyInput.SendKeys("BTC");
|
||||
|
||||
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
||||
s.Driver.FindElement(By.Id("ViewPaymentRequest")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
@ -1011,13 +1017,13 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("WalletNav-Receive")).Click();
|
||||
//generate a receiving address
|
||||
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
|
||||
Assert.True(s.Driver.FindElement(By.ClassName("qr-container")).Displayed);
|
||||
Assert.True(s.Driver.FindElement(By.CssSelector("#address-tab .qr-container")).Displayed);
|
||||
var receiveAddr = s.Driver.FindElement(By.Id("address")).GetAttribute("value");
|
||||
//unreserve
|
||||
s.Driver.FindElement(By.CssSelector("button[value=unreserve-current-address]")).Click();
|
||||
//generate it again, should be the same one as before as nothing got used in the meantime
|
||||
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
|
||||
Assert.True(s.Driver.FindElement(By.ClassName("qr-container")).Displayed);
|
||||
Assert.True(s.Driver.FindElement(By.CssSelector("#address-tab .qr-container")).Displayed);
|
||||
Assert.Equal(receiveAddr, s.Driver.FindElement(By.Id("address")).GetAttribute("value"));
|
||||
|
||||
//send money to addr and ensure it changed
|
||||
@ -1613,6 +1619,12 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
|
||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("0.0000001");
|
||||
|
||||
var currencyInput = s.Driver.FindElement(By.Id("Currency"));
|
||||
Assert.Equal("USD", currencyInput.GetAttribute("value"));
|
||||
currencyInput.Clear();
|
||||
currencyInput.SendKeys("BTC");
|
||||
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(lnurl);
|
||||
|
@ -5,6 +5,7 @@ using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.BIP78.Sender;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
@ -74,6 +74,8 @@ namespace BTCPayServer.Tests
|
||||
[Fact]
|
||||
public void CanQueryDirectProviders()
|
||||
{
|
||||
// TODO: Check once in a while whether or not they are working again
|
||||
string[] brokenShitcoinCasinos = { "okex" };
|
||||
var factory = FastTests.CreateBTCPayRateFactory();
|
||||
var directlySupported = factory.GetSupportedExchanges().Where(s => s.Source == RateSource.Direct)
|
||||
.Select(s => s.Id).ToHashSet();
|
||||
@ -86,47 +88,48 @@ namespace BTCPayServer.Tests
|
||||
Fetcher: (BackgroundFetcherRateProvider)p.Value))
|
||||
.ToList())
|
||||
{
|
||||
TestLogs.LogInformation($"Testing {result.ExpectedName}");
|
||||
var name = result.ExpectedName;
|
||||
if (brokenShitcoinCasinos.Contains(name))
|
||||
{
|
||||
TestLogs.LogInformation($"Skipping {name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
TestLogs.LogInformation($"Testing {name}");
|
||||
|
||||
result.Fetcher.InvalidateCache();
|
||||
var exchangeRates = new ExchangeRates(result.ExpectedName, result.ResultAsync.Result);
|
||||
var exchangeRates = new ExchangeRates(name, result.ResultAsync.Result);
|
||||
result.Fetcher.InvalidateCache();
|
||||
Assert.NotNull(exchangeRates);
|
||||
Assert.NotEmpty(exchangeRates);
|
||||
Assert.NotEmpty(exchangeRates.ByExchange[result.ExpectedName]);
|
||||
if (result.ExpectedName == "bitbank" || result.ExpectedName == "bitflyer")
|
||||
Assert.NotEmpty(exchangeRates.ByExchange[name]);
|
||||
if (name == "bitbank" || name == "bitflyer")
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
Assert.Contains(exchangeRates.ByExchange[name],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "JPY") &&
|
||||
e.BidAsk.Bid > 100m); // 1BTC will always be more than 100JPY
|
||||
}
|
||||
else if (result.ExpectedName == "polispay")
|
||||
else if (name == "argoneum")
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "POLIS") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1BTC will always be more than 1 POLIS
|
||||
}
|
||||
else if (result.ExpectedName == "argoneum")
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
Assert.Contains(exchangeRates.ByExchange[name],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "AGM") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1 BTC will always be more than 1 AGM
|
||||
}
|
||||
else if (result.ExpectedName == "ripio")
|
||||
else if (name == "ripio")
|
||||
{
|
||||
// Ripio keeps changing their pair, so anything is fine...
|
||||
Assert.NotEmpty(exchangeRates.ByExchange[result.ExpectedName]);
|
||||
Assert.NotEmpty(exchangeRates.ByExchange[name]);
|
||||
}
|
||||
else if (result.ExpectedName == "cryptomarket")
|
||||
else if (name == "cryptomarket")
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
Assert.Contains(exchangeRates.ByExchange[name],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "CLP") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1 BTC will always be more than 1 CLP
|
||||
}
|
||||
else
|
||||
{
|
||||
// This check if the currency pair is using right currency pair
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
Assert.Contains(exchangeRates.ByExchange[name],
|
||||
e => (e.CurrencyPair == new CurrencyPair("BTC", "USD") ||
|
||||
e.CurrencyPair == new CurrencyPair("BTC", "EUR") ||
|
||||
e.CurrencyPair == new CurrencyPair("BTC", "USDT") ||
|
||||
@ -139,8 +142,8 @@ namespace BTCPayServer.Tests
|
||||
// we need to modify the AvailableRateProvider
|
||||
|
||||
// There are some exception we stopped supporting but don't want to break backward compat
|
||||
if (result.ExpectedName != "coinaverage" && result.ExpectedName != "gdax")
|
||||
Assert.Contains(result.ExpectedName, directlySupported);
|
||||
if (name != "coinaverage" && name != "gdax")
|
||||
Assert.Contains(name, directlySupported);
|
||||
}
|
||||
|
||||
// Kraken emit one request only after first GetRates
|
||||
@ -251,6 +254,7 @@ namespace BTCPayServer.Tests
|
||||
[Fact]
|
||||
public void CanGetRateCryptoCurrenciesByDefault()
|
||||
{
|
||||
string[] brokenShitcoins = { "BTX_USD", "CHC_USD" };
|
||||
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
|
||||
var factory = FastTests.CreateBTCPayRateFactory();
|
||||
var fetcher = new RateFetcher(factory);
|
||||
@ -261,11 +265,11 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var rules = new StoreBlob().GetDefaultRateRules(provider);
|
||||
var result = fetcher.FetchRates(pairs, rules, default);
|
||||
foreach (var value in result)
|
||||
foreach ((CurrencyPair key, Task<RateResult> value) in result)
|
||||
{
|
||||
var rateResult = value.Value.GetAwaiter().GetResult();
|
||||
TestLogs.LogInformation($"Testing {value.Key.ToString()}");
|
||||
if (value.Key.ToString() == "BTX_USD") // Broken shitcoin
|
||||
var rateResult = value.GetAwaiter().GetResult();
|
||||
TestLogs.LogInformation($"Testing {key}");
|
||||
if (brokenShitcoins.Contains(key.ToString()))
|
||||
continue;
|
||||
Assert.True(rateResult.BidAsk != null, $"Impossible to get the rate {rateResult.EvaluatedRule}");
|
||||
}
|
||||
|
@ -11,7 +11,9 @@ using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
@ -140,7 +142,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var sresp = Assert
|
||||
.IsType<JsonResult>(await tester.PayTester.GetController<UIHomeController>(acc.UserId, acc.StoreId)
|
||||
.Swagger()).Value.ToJson();
|
||||
.Swagger(tester.PayTester.GetService<IEnumerable<ISwaggerProvider>>())).Value.ToJson();
|
||||
JObject swagger = JObject.Parse(sresp);
|
||||
var schema = JSchema.Parse(File.ReadAllText(TestUtils.GetTestDataFullPath("OpenAPI-Specification-schema.json")));
|
||||
IList<ValidationError> errors;
|
||||
@ -189,7 +191,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var sresp = Assert
|
||||
.IsType<JsonResult>(await tester.PayTester.GetController<UIHomeController>(acc.UserId, acc.StoreId)
|
||||
.Swagger()).Value.ToJson();
|
||||
.Swagger(tester.PayTester.GetService<IEnumerable<ISwaggerProvider>>())).Value.ToJson();
|
||||
|
||||
JObject json = JObject.Parse(sresp);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: "3"
|
||||
version: "3"
|
||||
|
||||
# Run `docker-compose up dev` for bootstrapping your development environment
|
||||
# Doing so will expose NBXplorer, Bitcoind RPC and postgres port to the host so that tests can Run,
|
||||
@ -35,7 +35,7 @@ services:
|
||||
links:
|
||||
- dev
|
||||
- selenium
|
||||
extra_hosts:
|
||||
extra_hosts:
|
||||
- "tests:127.0.0.1"
|
||||
volumes:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
@ -43,7 +43,7 @@ services:
|
||||
- "merchant_lightningd_datadir:/etc/merchant_lightningd_datadir"
|
||||
|
||||
# The dev container is not actually used, it is just handy to run `docker-compose up dev` to start all services
|
||||
dev:
|
||||
dev:
|
||||
image: alpine:3.7
|
||||
command: [ "/bin/sh", "-c", "trap : TERM INT; while :; do echo Ready to code and debug like a rockstar!!!; sleep 2073600; done & wait" ]
|
||||
links:
|
||||
@ -69,7 +69,7 @@ services:
|
||||
volumes:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
|
||||
devlnd:
|
||||
devlnd:
|
||||
image: btcpayserver/bitcoin:22.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
@ -89,11 +89,11 @@ services:
|
||||
expose:
|
||||
- "4444"
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.2.18
|
||||
image: nicolasdorier/nbxplorer:2.3.14
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
expose:
|
||||
expose:
|
||||
- "32838"
|
||||
environment:
|
||||
NBXPLORER_NETWORK: regtest
|
||||
@ -114,6 +114,7 @@ services:
|
||||
NBXPLORER_MINGAPSIZE: 5
|
||||
NBXPLORER_MAXGAPSIZE: 10
|
||||
NBXPLORER_VERBOSE: 1
|
||||
NBXPLORER_POSTGRES: User ID=postgres;Include Error Detail=true;Host=postgres;Port=5432;Database=nbxplorer
|
||||
NBXPLORER_NOAUTH: 1
|
||||
NBXPLORER_EXPOSERPC: 1
|
||||
links:
|
||||
@ -140,7 +141,7 @@ services:
|
||||
zmqpubrawtx=tcp://0.0.0.0:28333
|
||||
deprecatedrpc=signrawtransaction
|
||||
fallbackfee=0.0002
|
||||
ports:
|
||||
ports:
|
||||
- "43782:43782"
|
||||
- "39388:39388"
|
||||
expose:
|
||||
@ -155,7 +156,7 @@ services:
|
||||
image: btcpayserver/lightning:v0.10.1-1-dev
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
LIGHTNINGD_CHAIN: "btc"
|
||||
LIGHTNINGD_NETWORK: "regtest"
|
||||
@ -203,7 +204,7 @@ services:
|
||||
merchant_lightningd:
|
||||
image: btcpayserver/lightning:v0.10.1-1-dev
|
||||
stop_signal: SIGKILL
|
||||
environment:
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
LIGHTNINGD_CHAIN: "btc"
|
||||
LIGHTNINGD_NETWORK: "regtest"
|
||||
@ -336,24 +337,26 @@ services:
|
||||
volumes:
|
||||
- "./monero_wallet:/wallet"
|
||||
depends_on:
|
||||
- monerod
|
||||
- monerod
|
||||
litecoind:
|
||||
restart: unless-stopped
|
||||
image: nicolasdorier/docker-litecoin:0.16.3
|
||||
image: btcpayserver/litecoin:0.18.1
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_EXTRA_ARGS: |-
|
||||
rpcuser=ceiwHEbqWI83
|
||||
rpcpassword=DwubwWsoo3
|
||||
regtest=1
|
||||
rpcport=43782
|
||||
rpcbind=0.0.0.0:43782
|
||||
port=39388
|
||||
whitelist=0.0.0.0/0
|
||||
ports:
|
||||
ports:
|
||||
- "43783:43782"
|
||||
expose:
|
||||
- "43782" # RPC
|
||||
- "39388" # P2P
|
||||
|
||||
|
||||
elementsd-liquid:
|
||||
restart: always
|
||||
container_name: btcpayserver_elementsd_liquid
|
||||
|
@ -86,7 +86,7 @@ services:
|
||||
expose:
|
||||
- "4444"
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.2.18
|
||||
image: nicolasdorier/nbxplorer:2.3.14
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
@ -103,6 +103,7 @@ services:
|
||||
NBXPLORER_MINGAPSIZE: 5
|
||||
NBXPLORER_MAXGAPSIZE: 10
|
||||
NBXPLORER_VERBOSE: 1
|
||||
NBXPLORER_POSTGRES: User ID=postgres;Include Error Detail=true;Host=postgres;Port=5432;Database=nbxplorer
|
||||
NBXPLORER_EXPOSERPC: 1
|
||||
NBXPLORER_NOAUTH: 1
|
||||
links:
|
||||
|
@ -48,11 +48,12 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BIP78.Sender" Version="0.2.2" />
|
||||
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.2" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.3.2" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.3.4" />
|
||||
<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.1" />
|
||||
<PackageReference Include="Fido2.AspNet" Version="2.0.1" />
|
||||
<PackageReference Include="HtmlSanitizer" Version="5.0.372" />
|
||||
@ -231,10 +232,5 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_ContentIncludedByDefault Remove="Views\Authorization\Authorize.cshtml" />
|
||||
<_ContentIncludedByDefault Remove="Views\Components\NotificationsDropdown\Default.cshtml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ProjectExtensions><VisualStudio><UserProperties wwwroot_4swagger_4v1_4swagger_1template_1invoices_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1misc_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1pull-payments_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1serverinfo_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1stores_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1users_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1webhooks_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" /></VisualStudio></ProjectExtensions>
|
||||
</Project>
|
||||
|
34
BTCPayServer/Components/AppSales/AppSales.cs
Normal file
34
BTCPayServer/Components/AppSales/AppSales.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Components.AppSales;
|
||||
|
||||
public class AppSales : ViewComponent
|
||||
{
|
||||
private readonly AppService _appService;
|
||||
private readonly StoreRepository _storeRepo;
|
||||
|
||||
public AppSales(AppService appService, StoreRepository storeRepo)
|
||||
{
|
||||
_appService = appService;
|
||||
_storeRepo = storeRepo;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(AppData app)
|
||||
{
|
||||
var stats = await _appService.GetSalesStats(app);
|
||||
var vm = new AppSalesViewModel
|
||||
{
|
||||
App = app,
|
||||
SalesCount = stats.SalesCount,
|
||||
Series = stats.Series
|
||||
};
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
}
|
13
BTCPayServer/Components/AppSales/AppSalesViewModel.cs
Normal file
13
BTCPayServer/Components/AppSales/AppSalesViewModel.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Apps;
|
||||
|
||||
namespace BTCPayServer.Components.AppSales;
|
||||
|
||||
public class AppSalesViewModel
|
||||
{
|
||||
public AppData App { get; set; }
|
||||
public int SalesCount { get; set; }
|
||||
public IEnumerable<SalesStatsItem> Series { get; set; }
|
||||
}
|
30
BTCPayServer/Components/AppSales/Default.cshtml
Normal file
30
BTCPayServer/Components/AppSales/Default.cshtml
Normal file
@ -0,0 +1,30 @@
|
||||
@model BTCPayServer.Components.AppSales.AppSalesViewModel
|
||||
|
||||
@{
|
||||
var action = $"Update{Model.App.AppType}";
|
||||
}
|
||||
|
||||
<div id="AppSales-@Model.App.Id" class="widget app-sales">
|
||||
<header class="mb-3">
|
||||
<h3>@Model.App.Name Contributions</h3>
|
||||
<a asp-controller="UIApps" asp-action="@action" asp-route-appId="@Model.App.Id">Manage</a>
|
||||
</header>
|
||||
<p>@Model.SalesCount Total Contributions</p>
|
||||
<div class="ct-chart ct-major-octave"></div>
|
||||
<script>
|
||||
(function () {
|
||||
const id = 'AppSales-@Model.App.Id';
|
||||
const labels = @Safe.Json(Model.Series.Select(i => i.Label));
|
||||
const series = @Safe.Json(Model.Series.Select(i => i.SalesCount));
|
||||
const min = Math.min(...series);
|
||||
const max = Math.max(...series);
|
||||
const low = min === max ? 0 : Math.max(min - ((max - min) / 5), 0);
|
||||
new Chartist.Bar(`#${id} .ct-chart`, {
|
||||
labels,
|
||||
series: [series]
|
||||
}, {
|
||||
low,
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</div>
|
32
BTCPayServer/Components/AppTopItems/AppTopItems.cs
Normal file
32
BTCPayServer/Components/AppTopItems/AppTopItems.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Components.AppTopItems;
|
||||
|
||||
public class AppTopItems : ViewComponent
|
||||
{
|
||||
private readonly AppService _appService;
|
||||
private readonly StoreRepository _storeRepo;
|
||||
|
||||
public AppTopItems(AppService appService, StoreRepository storeRepo)
|
||||
{
|
||||
_appService = appService;
|
||||
_storeRepo = storeRepo;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(AppData app)
|
||||
{
|
||||
var entries = await _appService.GetPerkStats(app);
|
||||
var vm = new AppTopItemsViewModel
|
||||
{
|
||||
App = app,
|
||||
Entries = entries
|
||||
};
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
}
|
11
BTCPayServer/Components/AppTopItems/AppTopItemsViewModel.cs
Normal file
11
BTCPayServer/Components/AppTopItems/AppTopItemsViewModel.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Apps;
|
||||
|
||||
namespace BTCPayServer.Components.AppTopItems;
|
||||
|
||||
public class AppTopItemsViewModel
|
||||
{
|
||||
public AppData App { get; set; }
|
||||
public IEnumerable<ItemStats> Entries { get; set; }
|
||||
}
|
33
BTCPayServer/Components/AppTopItems/Default.cshtml
Normal file
33
BTCPayServer/Components/AppTopItems/Default.cshtml
Normal file
@ -0,0 +1,33 @@
|
||||
@model BTCPayServer.Components.AppTopItems.AppTopItemsViewModel
|
||||
|
||||
@{
|
||||
var action = $"Update{Model.App.AppType}";
|
||||
}
|
||||
|
||||
<div class="widget app-top-items">
|
||||
<header class="mb-3">
|
||||
<h3>Top Perks</h3>
|
||||
<a asp-controller="UIApps" asp-action="@action" asp-route-appId="@Model.App.Id">View All</a>
|
||||
</header>
|
||||
@if (Model.Entries.Any())
|
||||
{
|
||||
<div class="app-items">
|
||||
@foreach (var entry in Model.Entries)
|
||||
{
|
||||
<div class="app-item">
|
||||
<span class="app-item-name">@entry.Title</span>
|
||||
<span class="app-item-value">
|
||||
@entry.SalesCount sale@(entry.SalesCount == 1 ? "" : "s"),
|
||||
@entry.TotalFormatted total
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="text-secondary mt-3">
|
||||
No contributions have been made yet.
|
||||
</p>
|
||||
}
|
||||
</div>
|
@ -60,14 +60,14 @@
|
||||
{
|
||||
<a asp-area="" asp-controller="UIWallets" asp-action="WalletTransactions" asp-route-walletId="@scheme.WalletId" class="nav-link js-scroll-trigger @ViewData.IsActiveCategory(typeof(WalletsNavPages), scheme.WalletId.ToString()) @ViewData.IsActivePage(StoreNavPages.OnchainSettings)" id="@($"StoreNav-Wallet{scheme.Crypto}")">
|
||||
<span class="me-2 btcpay-status btcpay-status--@(scheme.Enabled ? "enabled" : "pending")"></span>
|
||||
<span>@(Model.AltcoinsBuild ? $"{scheme.Crypto} " : "")Wallet</span>
|
||||
<span>@(Model.AltcoinsBuild ? $"{scheme.Crypto} Wallet" : "Bitcoin")</span>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a asp-area="" asp-controller="UIStores" asp-action="SetupWallet" asp-route-cryptoCode="@scheme.Crypto" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.OnchainSettings)" id="@($"StoreNav-Wallet{scheme.Crypto}")">
|
||||
<span class="me-2 btcpay-status btcpay-status--@(scheme.Enabled ? "enabled" : "pending")"></span>
|
||||
<span>@(Model.AltcoinsBuild ? $"{scheme.Crypto} " : "")Wallet</span>
|
||||
<span>@(Model.AltcoinsBuild ? $"{scheme.Crypto} Wallet" : "Bitcoin")</span>
|
||||
</a>
|
||||
}
|
||||
</li>
|
||||
@ -187,9 +187,9 @@
|
||||
<vc:ui-extension-point location="store-integrations-nav" model="@Model"/>
|
||||
}
|
||||
<li class="nav-item" permission="@Policies.CanModifyServerSettings">
|
||||
<a asp-area="" asp-controller="UIServer" asp-action="ListPlugins" class="nav-link js-scroll-trigger @ViewData.IsActivePage(ServerNavPages.Plugins)" id="Nav-AddPlugin">
|
||||
<a asp-area="" asp-controller="UIServer" asp-action="ListPlugins" class="nav-link js-scroll-trigger @ViewData.IsActivePage(ServerNavPages.Plugins)" id="Nav-ManagePlugins">
|
||||
<vc:icon symbol="new"/>
|
||||
<span>Add Plugin</span>
|
||||
<span>Manage Plugins</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
@if (Model.Total > pageSizeOptions.Min())
|
||||
{
|
||||
<nav aria-label="..." class="w-100">
|
||||
<nav aria-label="..." class="w-100 clearfix">
|
||||
@if (Model.Total > Model.Count)
|
||||
{
|
||||
<ul class="pagination float-start">
|
||||
|
27
BTCPayServer/Components/StoreNumbers/Default.cshtml
Normal file
27
BTCPayServer/Components/StoreNumbers/Default.cshtml
Normal file
@ -0,0 +1,27 @@
|
||||
@model BTCPayServer.Components.StoreNumbers.StoreNumbersViewModel
|
||||
|
||||
<div class="widget store-numbers">
|
||||
<div class="store-number">
|
||||
<header>
|
||||
<h6>Payouts Pending</h6>
|
||||
<a asp-controller="UIStorePullPayments" asp-action="Payouts" asp-route-storeId="@Model.Store.Id">Manage</a>
|
||||
</header>
|
||||
<div class="h3">@Model.PayoutsPending</div>
|
||||
</div>
|
||||
<div class="store-number">
|
||||
<header>
|
||||
<h6>TXs in the last @Model.TransactionDays days</h6>
|
||||
@if (Model.Transactions > 0)
|
||||
{
|
||||
<a asp-controller="UIWallets" asp-action="WalletTransactions" asp-route-walletId="@Model.WalletId">View All</a>
|
||||
}
|
||||
</header>
|
||||
<div class="h3">@Model.Transactions</div>
|
||||
</div>
|
||||
<div class="store-number">
|
||||
<header>
|
||||
<h6>Refunds Issued</h6>
|
||||
</header>
|
||||
<div class="h3">@Model.RefundsIssued</div>
|
||||
</div>
|
||||
</div>
|
77
BTCPayServer/Components/StoreNumbers/StoreNumbers.cs
Normal file
77
BTCPayServer/Components/StoreNumbers/StoreNumbers.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Components.StoreRecentTransactions;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Npgsql;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Components.StoreNumbers;
|
||||
|
||||
public class StoreNumbers : ViewComponent
|
||||
{
|
||||
private const string CryptoCode = "BTC";
|
||||
private const int TransactionDays = 7;
|
||||
|
||||
private readonly StoreRepository _storeRepo;
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
private readonly BTCPayWalletProvider _walletProvider;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
|
||||
public StoreNumbers(
|
||||
StoreRepository storeRepo,
|
||||
ApplicationDbContextFactory dbContextFactory,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
BTCPayWalletProvider walletProvider)
|
||||
{
|
||||
_storeRepo = storeRepo;
|
||||
_walletProvider = walletProvider;
|
||||
_networkProvider = networkProvider;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(StoreData store)
|
||||
{
|
||||
|
||||
await using var ctx = _dbContextFactory.CreateContext();
|
||||
var payoutsCount = await ctx.Payouts
|
||||
.Where(p => p.PullPaymentData.StoreId == store.Id && !p.PullPaymentData.Archived && p.State == PayoutState.AwaitingApproval)
|
||||
.CountAsync();
|
||||
var refundsCount = await ctx.Invoices
|
||||
.Where(i => i.StoreData.Id == store.Id && !i.Archived && i.CurrentRefundId != null)
|
||||
.CountAsync();
|
||||
|
||||
var walletId = new WalletId(store.Id, CryptoCode);
|
||||
var derivation = store.GetDerivationSchemeSettings(_networkProvider, walletId.CryptoCode);
|
||||
var transactionsCount = 0;
|
||||
if (derivation != null)
|
||||
{
|
||||
var network = derivation.Network;
|
||||
var wallet = _walletProvider.GetWallet(network);
|
||||
var allTransactions = await wallet.FetchTransactions(derivation.AccountDerivation);
|
||||
var afterDate = DateTimeOffset.UtcNow - TimeSpan.FromDays(TransactionDays);
|
||||
transactionsCount = allTransactions.UnconfirmedTransactions.Transactions
|
||||
.Concat(allTransactions.ConfirmedTransactions.Transactions)
|
||||
.Count(t => t.Timestamp > afterDate);
|
||||
}
|
||||
|
||||
var vm = new StoreNumbersViewModel
|
||||
{
|
||||
Store = store,
|
||||
WalletId = walletId,
|
||||
PayoutsPending = payoutsCount,
|
||||
Transactions = transactionsCount,
|
||||
TransactionDays = TransactionDays,
|
||||
RefundsIssued = refundsCount
|
||||
};
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using System.Collections;
|
||||
using BTCPayServer.Data;
|
||||
|
||||
namespace BTCPayServer.Components.StoreNumbers;
|
||||
|
||||
public class StoreNumbersViewModel
|
||||
{
|
||||
public StoreData Store { get; set; }
|
||||
public WalletId WalletId { get; set; }
|
||||
public int PayoutsPending { get; set; }
|
||||
public int Transactions { get; set; }
|
||||
public int RefundsIssued { get; set; }
|
||||
public int TransactionDays { get; set; }
|
||||
}
|
57
BTCPayServer/Components/StoreRecentInvoices/Default.cshtml
Normal file
57
BTCPayServer/Components/StoreRecentInvoices/Default.cshtml
Normal file
@ -0,0 +1,57 @@
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@using BTCPayServer.Client.Models
|
||||
@using BTCPayServer.Services.Invoices
|
||||
@model BTCPayServer.Components.StoreRecentInvoices.StoreRecentInvoicesViewModel
|
||||
|
||||
<div class="widget store-recent-transactions">
|
||||
<header>
|
||||
<h3>Recent Invoices</h3>
|
||||
@if (Model.Invoices.Any())
|
||||
{
|
||||
<a asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@Model.Store.Id">View All</a>
|
||||
}
|
||||
</header>
|
||||
@if (Model.Invoices.Any())
|
||||
{
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-125px">Date</th>
|
||||
<th class="text-nowrap">Invoice Id</th>
|
||||
<th>Status</th>
|
||||
<th class="text-end">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var invoice in Model.Invoices)
|
||||
{
|
||||
<tr>
|
||||
<td>@invoice.Date.ToTimeAgo()</td>
|
||||
<td>
|
||||
<a asp-controller="UIInvoice" asp-action="Invoice" asp-route-invoiceId="@invoice.InvoiceId" class="text-break">@invoice.InvoiceId</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-@invoice.Status.Status.ToModernStatus().ToString().ToLower()">
|
||||
@invoice.Status.Status.ToModernStatus().ToString()
|
||||
@if (invoice.Status.ExceptionStatus != InvoiceExceptionStatus.None)
|
||||
{
|
||||
@($"({invoice.Status.ExceptionStatus.ToString()})")
|
||||
}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-end">@invoice.AmountCurrency</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="text-secondary my-3">
|
||||
There are no recent invoices.
|
||||
</p>
|
||||
<a asp-controller="UIInvoice" asp-action="CreateInvoice" asp-route-storeId="@Model.Store.Id" class="fw-semibold">
|
||||
Create Invoice
|
||||
</a>
|
||||
}
|
||||
</div>
|
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
|
||||
namespace BTCPayServer.Components.StoreRecentInvoices;
|
||||
|
||||
public class StoreRecentInvoiceViewModel
|
||||
{
|
||||
public string InvoiceId { get; set; }
|
||||
public string OrderId { get; set; }
|
||||
public string AmountCurrency { get; set; }
|
||||
public InvoiceState Status { get; set; }
|
||||
public DateTimeOffset Date { get; set; }
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Components.StoreRecentInvoices;
|
||||
|
||||
public class StoreRecentInvoices : ViewComponent
|
||||
{
|
||||
private readonly StoreRepository _storeRepo;
|
||||
private readonly InvoiceRepository _invoiceRepo;
|
||||
private readonly CurrencyNameTable _currencyNameTable;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
|
||||
public StoreRecentInvoices(
|
||||
StoreRepository storeRepo,
|
||||
InvoiceRepository invoiceRepo,
|
||||
CurrencyNameTable currencyNameTable,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
ApplicationDbContextFactory dbContextFactory)
|
||||
{
|
||||
_storeRepo = storeRepo;
|
||||
_invoiceRepo = invoiceRepo;
|
||||
_userManager = userManager;
|
||||
_currencyNameTable = currencyNameTable;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(StoreData store)
|
||||
{
|
||||
var userId = _userManager.GetUserId(UserClaimsPrincipal);
|
||||
var invoiceEntities = await _invoiceRepo.GetInvoices(new InvoiceQuery
|
||||
{
|
||||
UserId = userId,
|
||||
StoreId = new [] { store.Id },
|
||||
Take = 5
|
||||
});
|
||||
var invoices = new List<StoreRecentInvoiceViewModel>();
|
||||
foreach (var invoice in invoiceEntities)
|
||||
{
|
||||
var state = invoice.GetInvoiceState();
|
||||
invoices.Add(new StoreRecentInvoiceViewModel
|
||||
{
|
||||
Date = invoice.InvoiceTime,
|
||||
Status = state,
|
||||
InvoiceId = invoice.Id,
|
||||
OrderId = invoice.Metadata.OrderId ?? string.Empty,
|
||||
AmountCurrency = _currencyNameTable.DisplayFormatCurrency(invoice.Price, invoice.Currency),
|
||||
});
|
||||
}
|
||||
var vm = new StoreRecentInvoicesViewModel
|
||||
{
|
||||
Store = store,
|
||||
Invoices = invoices
|
||||
};
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Data;
|
||||
|
||||
namespace BTCPayServer.Components.StoreRecentInvoices;
|
||||
|
||||
public class StoreRecentInvoicesViewModel
|
||||
{
|
||||
public StoreData Store { get; set; }
|
||||
public IEnumerable<StoreRecentInvoiceViewModel> Invoices { get; set; }
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@model BTCPayServer.Components.StoreRecentTransactions.StoreRecentTransactionsViewModel
|
||||
|
||||
<div class="widget store-recent-transactions">
|
||||
<header>
|
||||
<h3>Recent Transactions</h3>
|
||||
@if (Model.Transactions.Any())
|
||||
{
|
||||
<a asp-controller="UIWallets" asp-action="WalletTransactions" asp-route-walletId="@Model.WalletId">View All</a>
|
||||
}
|
||||
</header>
|
||||
@if (Model.Transactions.Any())
|
||||
{
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-125px">Date</th>
|
||||
<th>Transaction</th>
|
||||
<th class="text-end">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var tx in Model.Transactions)
|
||||
{
|
||||
<tr>
|
||||
<td>@tx.Timestamp.ToTimeAgo()</td>
|
||||
<td>
|
||||
<a href="@tx.Link" target="_blank" rel="noreferrer noopener" class="text-break">
|
||||
@tx.Id
|
||||
</a>
|
||||
</td>
|
||||
@if (tx.Positive)
|
||||
{
|
||||
<td class="text-end text-success">@tx.Balance</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td class="text-end text-danger">@tx.Balance</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="text-secondary mt-3 mb-0">
|
||||
There are no recent transactions.
|
||||
</p>
|
||||
}
|
||||
</div>
|
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer.Components.StoreRecentTransactions;
|
||||
|
||||
public class StoreRecentTransactionViewModel
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Balance { get; set; }
|
||||
public bool Positive { get; set; }
|
||||
public bool IsConfirmed { get; set; }
|
||||
public string Link { get; set; }
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Dapper;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBXplorer.Client;
|
||||
using static BTCPayServer.Components.StoreRecentTransactions.StoreRecentTransactionsViewModel;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Components.StoreRecentTransactions;
|
||||
|
||||
public class StoreRecentTransactions : ViewComponent
|
||||
{
|
||||
private const string CryptoCode = "BTC";
|
||||
private readonly StoreRepository _storeRepo;
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
private readonly BTCPayWalletProvider _walletProvider;
|
||||
|
||||
public BTCPayNetworkProvider NetworkProvider { get; }
|
||||
public NBXplorerConnectionFactory ConnectionFactory { get; }
|
||||
|
||||
public StoreRecentTransactions(
|
||||
StoreRepository storeRepo,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
NBXplorerConnectionFactory connectionFactory,
|
||||
BTCPayWalletProvider walletProvider,
|
||||
ApplicationDbContextFactory dbContextFactory)
|
||||
{
|
||||
_storeRepo = storeRepo;
|
||||
NetworkProvider = networkProvider;
|
||||
ConnectionFactory = connectionFactory;
|
||||
_walletProvider = walletProvider;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
}
|
||||
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(StoreData store)
|
||||
{
|
||||
var walletId = new WalletId(store.Id, CryptoCode);
|
||||
var derivationSettings = store.GetDerivationSchemeSettings(NetworkProvider, walletId.CryptoCode);
|
||||
var transactions = new List<StoreRecentTransactionViewModel>();
|
||||
if (derivationSettings?.AccountDerivation is not null)
|
||||
{
|
||||
if (ConnectionFactory.Available)
|
||||
{
|
||||
var wallet_id = derivationSettings.GetNBXWalletId();
|
||||
await using var conn = await ConnectionFactory.OpenConnection();
|
||||
var rows = await conn.QueryAsync(
|
||||
"SELECT t.tx_id, t.seen_at, to_btc(balance_change::NUMERIC) balance_change, (t.blk_id IS NOT NULL) confirmed " +
|
||||
"FROM get_wallets_recent(@wallet_id, @code, @interval, 5, 0) " +
|
||||
"JOIN txs t USING (code, tx_id) " +
|
||||
"ORDER BY seen_at DESC;",
|
||||
new
|
||||
{
|
||||
wallet_id,
|
||||
code = CryptoCode,
|
||||
interval = TimeSpan.FromDays(31)
|
||||
});
|
||||
var network = derivationSettings.Network;
|
||||
foreach (var r in rows)
|
||||
{
|
||||
var seenAt = new DateTimeOffset(((DateTime)r.seen_at));
|
||||
var balanceChange = new Money((decimal)r.balance_change, MoneyUnit.BTC);
|
||||
transactions.Add(new StoreRecentTransactionViewModel()
|
||||
{
|
||||
Timestamp = seenAt,
|
||||
Id = r.tx_id,
|
||||
Balance = balanceChange.ShowMoney(network),
|
||||
IsConfirmed = r.confirmed,
|
||||
Link = string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, r.tx_id),
|
||||
Positive = balanceChange.GetValue(network) >= 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var network = derivationSettings.Network;
|
||||
var wallet = _walletProvider.GetWallet(network);
|
||||
var allTransactions = await wallet.FetchTransactions(derivationSettings.AccountDerivation);
|
||||
transactions = allTransactions.UnconfirmedTransactions.Transactions
|
||||
.Concat(allTransactions.ConfirmedTransactions.Transactions).ToArray()
|
||||
.OrderByDescending(t => t.Timestamp)
|
||||
.Take(5)
|
||||
.Select(tx => new StoreRecentTransactionViewModel
|
||||
{
|
||||
Id = tx.TransactionId.ToString(),
|
||||
Positive = tx.BalanceChange.GetValue(network) >= 0,
|
||||
Balance = tx.BalanceChange.ShowMoney(network),
|
||||
IsConfirmed = tx.Confirmations != 0,
|
||||
Link = string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, tx.TransactionId.ToString()),
|
||||
Timestamp = tx.Timestamp
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var vm = new StoreRecentTransactionsViewModel
|
||||
{
|
||||
Store = store,
|
||||
WalletId = walletId,
|
||||
Transactions = transactions
|
||||
};
|
||||
return View(vm);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Data;
|
||||
|
||||
namespace BTCPayServer.Components.StoreRecentTransactions;
|
||||
|
||||
public class StoreRecentTransactionsViewModel
|
||||
{
|
||||
public StoreData Store { get; set; }
|
||||
public IList<StoreRecentTransactionViewModel> Transactions { get; set; } = new List<StoreRecentTransactionViewModel>();
|
||||
public WalletId WalletId { get; set; }
|
||||
}
|
@ -47,11 +47,7 @@ else
|
||||
@foreach (var option in Model.Options)
|
||||
{
|
||||
<li>
|
||||
@if (option.IsOwner && option.WalletId != null)
|
||||
{
|
||||
<a asp-controller="UIWallets" asp-action="WalletTransactions" asp-route-walletId="@option.WalletId" class="dropdown-item@(option.Selected ? " active" : "")" id="StoreSelectorMenuItem-@option.Value">@StoreName(option.Text)</a>
|
||||
}
|
||||
else if (option.IsOwner)
|
||||
@if (option.IsOwner)
|
||||
{
|
||||
<a asp-controller="UIStores" asp-action="Dashboard" asp-route-storeId="@option.Value" class="dropdown-item@(option.Selected ? " active" : "")" id="StoreSelectorMenuItem-@option.Value">@StoreName(option.Text)</a>
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ namespace BTCPayServer.Components.StoreSelector
|
||||
WalletId = walletId
|
||||
};
|
||||
})
|
||||
.OrderBy(s => s.Text)
|
||||
.ToList();
|
||||
|
||||
var vm = new StoreSelectorViewModel
|
||||
|
57
BTCPayServer/Components/StoreWalletBalance/Default.cshtml
Normal file
57
BTCPayServer/Components/StoreWalletBalance/Default.cshtml
Normal file
@ -0,0 +1,57 @@
|
||||
@using BTCPayServer.Services.Wallets
|
||||
@model BTCPayServer.Components.StoreWalletBalance.StoreWalletBalanceViewModel
|
||||
|
||||
<div id="StoreWalletBalance-@Model.Store.Id" class="widget store-wallet-balance">
|
||||
<h6 class="mb-2">Wallet Balance</h6>
|
||||
<header class="mb-3">
|
||||
<div class="balance">
|
||||
<h3 class="d-inline-block me-1">@Model.Balance</h3>
|
||||
<span class="text-secondary fw-semibold">@Model.CryptoCode</span>
|
||||
</div>
|
||||
<div class="btn-group mt-1" role="group" aria-label="Filter">
|
||||
<input type="radio" class="btn-check" name="filter" id="filter-week" value="week" @(Model.Type == WalletHistogramType.Week ? "checked" : "")>
|
||||
<label class="btn btn-link" for="filter-week">1W</label>
|
||||
<input type="radio" class="btn-check" name="filter" id="filter-month" value="month" @(Model.Type == WalletHistogramType.Month ? "checked" : "")>
|
||||
<label class="btn btn-link" for="filter-month">1M</label>
|
||||
<input type="radio" class="btn-check" name="filter" id="filter-year" value="year" @(Model.Type == WalletHistogramType.Year ? "checked" : "")>
|
||||
<label class="btn btn-link" for="filter-year">1Y</label>
|
||||
</div>
|
||||
</header>
|
||||
<div class="ct-chart ct-major-eleventh"></div>
|
||||
<script>
|
||||
(function () {
|
||||
const id = 'StoreWalletBalance-@Model.Store.Id';
|
||||
const baseUrl = @Safe.Json(Url.Action("WalletHistogram", "UIWallets", new { walletId = Model.WalletId, type = WalletHistogramType.Week }));
|
||||
const render = data => {
|
||||
const { series, labels, balance } = data;
|
||||
document.querySelector(`#${id} h3`).innerText = balance;
|
||||
const min = Math.min(...series);
|
||||
const max = Math.max(...series);
|
||||
const low = Math.max(min - ((max - min) / 5), 0);
|
||||
new Chartist.Line(`#${id} .ct-chart`, {
|
||||
labels,
|
||||
series: [series]
|
||||
}, {
|
||||
low,
|
||||
fullWidth: true,
|
||||
showArea: true
|
||||
});
|
||||
};
|
||||
const update = async type => {
|
||||
const url = baseUrl.replace(/\/week$/gi, `/${type}`);
|
||||
const response = await fetch(url);
|
||||
if (response.ok) {
|
||||
const json = await response.json();
|
||||
render(json);
|
||||
}
|
||||
};
|
||||
render({ series: @Safe.Json(Model.Series), labels: @Safe.Json(Model.Labels), balance: @Safe.Json(Model.Balance) });
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
delegate('change', `#${id} [name="filter"]`, async e => {
|
||||
const type = e.target.value;
|
||||
await update(type);
|
||||
})
|
||||
})
|
||||
})();
|
||||
</script>
|
||||
</div>
|
@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Dapper;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBXplorer;
|
||||
using NBXplorer.Client;
|
||||
|
||||
namespace BTCPayServer.Components.StoreWalletBalance;
|
||||
|
||||
public class StoreWalletBalance : ViewComponent
|
||||
{
|
||||
private const string CryptoCode = "BTC";
|
||||
private const WalletHistogramType DefaultType = WalletHistogramType.Week;
|
||||
|
||||
private readonly StoreRepository _storeRepo;
|
||||
private readonly WalletHistogramService _walletHistogramService;
|
||||
|
||||
public StoreWalletBalance(StoreRepository storeRepo, WalletHistogramService walletHistogramService)
|
||||
{
|
||||
_storeRepo = storeRepo;
|
||||
_walletHistogramService = walletHistogramService;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(StoreData store)
|
||||
{
|
||||
var walletId = new WalletId(store.Id, CryptoCode);
|
||||
var data = await _walletHistogramService.GetHistogram(store, walletId, DefaultType);
|
||||
|
||||
var vm = new StoreWalletBalanceViewModel
|
||||
{
|
||||
Store = store,
|
||||
CryptoCode = CryptoCode,
|
||||
WalletId = walletId,
|
||||
Series = data?.Series,
|
||||
Labels = data?.Labels,
|
||||
Balance = data?.Balance ?? 0,
|
||||
Type = DefaultType
|
||||
};
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
|
||||
namespace BTCPayServer.Components.StoreWalletBalance;
|
||||
|
||||
public class StoreWalletBalanceViewModel
|
||||
{
|
||||
public decimal Balance { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public StoreData Store { get; set; }
|
||||
public WalletId WalletId { get; set; }
|
||||
public WalletHistogramType Type { get; set; }
|
||||
public IList<string> Labels { get; set; } = new List<string>();
|
||||
public IList<decimal> Series { get; set; } = new List<decimal>();
|
||||
}
|
@ -50,6 +50,8 @@ namespace BTCPayServer.Configuration
|
||||
app.Option("--recommended-plugins", "Plugins which would be marked as recommended to be installed. Separated by newline or space", CommandOptionType.MultipleValue);
|
||||
app.Option("--xforwardedproto", "If specified, set X-Forwarded-Proto to the specified value, this may be useful if your reverse proxy handle https but is not configured to add X-Forwarded-Proto (example: --xforwardedproto https)", CommandOptionType.SingleValue);
|
||||
app.Option("--cheatmode", "Add elements in the UI to facilitate dev-time testing (Default false)", CommandOptionType.BoolValue);
|
||||
|
||||
app.Option("--explorerpostgres", $"Connection string to the postgres database of NBXplorer. (optional, used for dashboard and reporting features)", CommandOptionType.SingleValue);
|
||||
foreach (var network in provider.GetAll().OfType<BTCPayNetwork>())
|
||||
{
|
||||
var crypto = network.CryptoCode.ToLowerInvariant();
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Controllers;
|
||||
using NBitcoin;
|
||||
|
||||
@ -81,26 +82,28 @@ namespace BTCPayServer.Configuration
|
||||
// Read access key from cookie file
|
||||
if (connectionString.CookieFilePath != null)
|
||||
{
|
||||
string cookieFileContent = null;
|
||||
bool isFake = false;
|
||||
try
|
||||
bool isFake = connectionString.CookieFilePath == "fake"; // Hacks for testing
|
||||
string cookieFileContent = isFake ? "fake" : null;
|
||||
if (!isFake)
|
||||
{
|
||||
cookieFileContent = await System.IO.File.ReadAllTextAsync(connectionString.CookieFilePath);
|
||||
isFake = connectionString.CookieFilePath == "fake";
|
||||
connectionString.CookieFilePath = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new System.IO.FileNotFoundException("Cookie file path not found", ex);
|
||||
try
|
||||
{
|
||||
cookieFileContent = await System.IO.File.ReadAllTextAsync(connectionString.CookieFilePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new System.IO.FileNotFoundException("Cookie file path not found", ex);
|
||||
}
|
||||
}
|
||||
connectionString.CookieFilePath = null;
|
||||
|
||||
if (serviceType == ExternalServiceTypes.RTL || serviceType == ExternalServiceTypes.Configurator || serviceType == ExternalServiceTypes.ThunderHub)
|
||||
{
|
||||
connectionString.AccessKey = cookieFileContent;
|
||||
}
|
||||
else if (serviceType == ExternalServiceTypes.Spark)
|
||||
{
|
||||
var cookie = (isFake ? "fake:fake:fake" // Hacks for testing
|
||||
: cookieFileContent).Split(':');
|
||||
var cookie = (isFake ? "fake:fake:fake" : cookieFileContent).Split(':');
|
||||
if (cookie.Length >= 3)
|
||||
{
|
||||
connectionString.AccessKey = cookie[2];
|
||||
|
@ -9,5 +9,6 @@ namespace BTCPayServer.Configuration
|
||||
get;
|
||||
set;
|
||||
} = new List<NBXplorerConnectionSetting>();
|
||||
public string ConnectionString { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.ModelBinders;
|
||||
|
@ -1,42 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Client.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
public static class GreenfieldUtils
|
||||
{
|
||||
public static IActionResult CreateValidationError(this ControllerBase controller, ModelStateDictionary modelState)
|
||||
{
|
||||
return controller.UnprocessableEntity(modelState.ToGreenfieldValidationError());
|
||||
}
|
||||
|
||||
public static List<GreenfieldValidationError> ToGreenfieldValidationError(this ModelStateDictionary modelState)
|
||||
{
|
||||
List<GreenfieldValidationError> errors = new List<GreenfieldValidationError>();
|
||||
foreach (var error in modelState)
|
||||
{
|
||||
foreach (var errorMessage in error.Value.Errors)
|
||||
{
|
||||
errors.Add(new GreenfieldValidationError(error.Key, errorMessage.ErrorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
public static IActionResult CreateAPIError(this ControllerBase controller, string errorCode, string errorMessage)
|
||||
{
|
||||
return controller.BadRequest(new GreenfieldAPIError(errorCode, errorMessage));
|
||||
}
|
||||
public static IActionResult CreateAPIError(this ControllerBase controller, int httpCode, string errorCode, string errorMessage)
|
||||
{
|
||||
return controller.StatusCode(httpCode, new GreenfieldAPIError(errorCode, errorMessage));
|
||||
}
|
||||
public static IActionResult CreateAPIPermissionError(this ControllerBase controller, string missingPermission, string message = null)
|
||||
{
|
||||
return controller.StatusCode(403, new GreenfieldPermissionAPIError(missingPermission, message));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Payments;
|
||||
|
@ -77,6 +77,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return base.GetDepositAddress(cryptoCode);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseInternalLightningNode,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/server/lightning/{cryptoCode}/payments/{paymentHash}")]
|
||||
public override Task<IActionResult> GetPayment(string cryptoCode, string paymentHash)
|
||||
{
|
||||
return base.GetPayment(cryptoCode, paymentHash);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseInternalLightningNode,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/server/lightning/{cryptoCode}/invoices/{id}")]
|
||||
|
@ -78,6 +78,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return base.GetDepositAddress(cryptoCode);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/payments/{paymentHash}")]
|
||||
public override Task<IActionResult> GetPayment(string cryptoCode, string paymentHash)
|
||||
{
|
||||
return base.GetPayment(cryptoCode, paymentHash);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices/pay")]
|
||||
|
@ -3,6 +3,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.HostedServices;
|
||||
@ -20,7 +21,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
public void OnException(ExceptionContext context)
|
||||
{
|
||||
context.Result = new ObjectResult(new GreenfieldAPIError("ligthning-node-unavailable", $"The lightning node is unavailable ({context.Exception.GetType().Name}: {context.Exception.Message})")) { StatusCode = 503 };
|
||||
context.Result = new ObjectResult(new GreenfieldAPIError("lightning-node-unavailable", $"The lightning node is unavailable ({context.Exception.GetType().Name}: {context.Exception.Message})")) { StatusCode = 503 };
|
||||
// Do not mark handled, it is possible filters above have better errors
|
||||
}
|
||||
}
|
||||
@ -163,6 +164,13 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return Ok(new JValue((await lightningClient.GetDepositAddress()).ToString()));
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> GetPayment(string cryptoCode, string paymentHash)
|
||||
{
|
||||
var lightningClient = await GetLightningClient(cryptoCode, false);
|
||||
var payment = await lightningClient.GetPayment(paymentHash);
|
||||
return payment == null ? this.CreateAPIError(404, "payment-not-found", "Impossible to find a lightning payment with this payment hash") : Ok(ToModel(payment));
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> PayInvoice(string cryptoCode, PayLightningInvoiceRequest lightningInvoice)
|
||||
{
|
||||
var lightningClient = await GetLightningClient(cryptoCode, true);
|
||||
@ -224,12 +232,16 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
|
||||
try
|
||||
{
|
||||
var invoice = await lightningClient.CreateInvoice(
|
||||
new CreateInvoiceParams(request.Amount, request.Description, request.Expiry)
|
||||
var param = request.DescriptionHash != null
|
||||
? new CreateInvoiceParams(request.Amount, request.DescriptionHash, request.Expiry)
|
||||
{
|
||||
PrivateRouteHints = request.PrivateRouteHints
|
||||
},
|
||||
CancellationToken.None);
|
||||
PrivateRouteHints = request.PrivateRouteHints, Description = request.Description
|
||||
}
|
||||
: new CreateInvoiceParams(request.Amount, request.Description, request.Expiry)
|
||||
{
|
||||
PrivateRouteHints = request.PrivateRouteHints, DescriptionHash = request.DescriptionHash
|
||||
};
|
||||
var invoice = await lightningClient.CreateInvoice(param, CancellationToken.None);
|
||||
return Ok(ToModel(invoice));
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -269,6 +281,21 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
};
|
||||
}
|
||||
|
||||
private LightningPaymentData ToModel(LightningPayment payment)
|
||||
{
|
||||
return new LightningPaymentData
|
||||
{
|
||||
TotalAmount = payment.AmountSent,
|
||||
FeeAmount = payment.Amount != null && payment.AmountSent != null ? payment.AmountSent - payment.Amount : null,
|
||||
Id = payment.Id,
|
||||
Status = payment.Status,
|
||||
CreatedAt = payment.CreatedAt,
|
||||
BOLT11 = payment.BOLT11,
|
||||
PaymentHash = payment.PaymentHash,
|
||||
Preimage = payment.Preimage
|
||||
};
|
||||
}
|
||||
|
||||
protected async Task<bool> CanUseInternalLightning(bool doingAdminThings)
|
||||
{
|
||||
|
||||
|
@ -3,6 +3,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
|
@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
|
@ -0,0 +1,95 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Mails;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Validation;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[EnableCors(CorsPolicies.All)]
|
||||
public class GreenfieldStoreEmailController : Controller
|
||||
{
|
||||
private readonly EmailSenderFactory _emailSenderFactory;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
|
||||
public GreenfieldStoreEmailController(EmailSenderFactory emailSenderFactory, StoreRepository storeRepository)
|
||||
{
|
||||
_emailSenderFactory = emailSenderFactory;
|
||||
_storeRepository = storeRepository;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/stores/{storeId}/email/send")]
|
||||
public async Task<IActionResult> SendEmailFromStore(string storeId,
|
||||
[FromBody] SendEmailRequest request)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
{
|
||||
return this.CreateAPIError(404, "store-not-found", "The store was not found");
|
||||
}
|
||||
var emailSender = await _emailSenderFactory.GetEmailSender(storeId);
|
||||
if (emailSender is null )
|
||||
{
|
||||
return this.CreateAPIError(404,"smtp-not-configured", "Store does not have an SMTP server configured.");
|
||||
}
|
||||
|
||||
emailSender.SendEmail(request.Email, request.Subject, request.Body);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/email")]
|
||||
public IActionResult GetStoreEmailSettings()
|
||||
{
|
||||
|
||||
var store = HttpContext.GetStoreData();
|
||||
return store == null ? StoreNotFound() : Ok(FromModel(store));
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPut("~/api/v1/stores/{storeId}/email")]
|
||||
public async Task<IActionResult> UpdateStoreEmailSettings(string storeId, EmailSettings request)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
{
|
||||
return StoreNotFound();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(request.From) && !EmailValidator.IsEmail(request.From))
|
||||
{
|
||||
request.AddModelError(e => e.From,
|
||||
"Invalid email address", this);
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
var blob = store.GetStoreBlob();
|
||||
blob.EmailSettings = request;
|
||||
if (store.SetStoreBlob(blob))
|
||||
{
|
||||
await _storeRepository.UpdateStore(store);
|
||||
}
|
||||
|
||||
return Ok(FromModel(store));
|
||||
}
|
||||
private EmailSettings FromModel(Data.StoreData data)
|
||||
{
|
||||
return data.GetStoreBlob().EmailSettings??new();
|
||||
}
|
||||
private IActionResult StoreNotFound()
|
||||
{
|
||||
return this.CreateAPIError(404, "store-not-found", "The store was not found");
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
|
@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
|
@ -3,6 +3,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
|
@ -8,6 +8,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.BIP78.Sender;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
@ -18,6 +19,7 @@ using BTCPayServer.Payments.PayJoin;
|
||||
using BTCPayServer.Payments.PayJoin.Sender;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Services.Labels;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@ -49,6 +51,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly WalletReceiveService _walletReceiveService;
|
||||
private readonly IFeeProviderFactory _feeProviderFactory;
|
||||
private readonly LabelFactory _labelFactory;
|
||||
|
||||
public GreenfieldStoreOnChainWalletsController(
|
||||
IAuthorizationService authorizationService,
|
||||
@ -63,7 +66,9 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
DelayedTransactionBroadcaster delayedTransactionBroadcaster,
|
||||
EventAggregator eventAggregator,
|
||||
WalletReceiveService walletReceiveService,
|
||||
IFeeProviderFactory feeProviderFactory)
|
||||
IFeeProviderFactory feeProviderFactory,
|
||||
LabelFactory labelFactory
|
||||
)
|
||||
{
|
||||
_authorizationService = authorizationService;
|
||||
_btcPayWalletProvider = btcPayWalletProvider;
|
||||
@ -78,6 +83,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
_eventAggregator = eventAggregator;
|
||||
_walletReceiveService = walletReceiveService;
|
||||
_feeProviderFactory = feeProviderFactory;
|
||||
_labelFactory = labelFactory;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
@ -228,6 +234,65 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return Ok(ToModel(walletTransactionsInfoAsync, tx, wallet));
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPatch("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions/{transactionId}")]
|
||||
public async Task<IActionResult> PatchOnChainWalletTransaction(
|
||||
string storeId,
|
||||
string cryptoCode,
|
||||
string transactionId,
|
||||
[FromBody] PatchOnChainTransactionRequest request
|
||||
)
|
||||
{
|
||||
if (IsInvalidWalletRequest(cryptoCode, out var network,
|
||||
out var derivationScheme, out var actionResult))
|
||||
return actionResult;
|
||||
|
||||
var wallet = _btcPayWalletProvider.GetWallet(network);
|
||||
var tx = await wallet.FetchTransaction(derivationScheme.AccountDerivation, uint256.Parse(transactionId));
|
||||
if (tx is null)
|
||||
{
|
||||
return this.CreateAPIError(404, "transaction-not-found", "The transaction was not found.");
|
||||
}
|
||||
|
||||
var walletId = new WalletId(storeId, cryptoCode);
|
||||
var walletTransactionsInfoAsync = _walletRepository.GetWalletTransactionsInfo(walletId);
|
||||
if (!(await walletTransactionsInfoAsync).TryGetValue(transactionId, out var walletTransactionInfo))
|
||||
{
|
||||
walletTransactionInfo = new WalletTransactionInfo();
|
||||
}
|
||||
|
||||
if (request.Comment != null)
|
||||
{
|
||||
walletTransactionInfo.Comment = request.Comment.Trim().Truncate(WalletTransactionDataExtensions.MaxCommentSize);
|
||||
}
|
||||
|
||||
if (request.Labels != null)
|
||||
{
|
||||
var walletBlobInfo = await _walletRepository.GetWalletInfo(walletId);
|
||||
|
||||
foreach (string label in request.Labels)
|
||||
{
|
||||
var rawLabel = await _labelFactory.BuildLabel(
|
||||
walletBlobInfo,
|
||||
Request,
|
||||
walletTransactionInfo,
|
||||
walletId,
|
||||
transactionId,
|
||||
label
|
||||
);
|
||||
walletTransactionInfo.Labels.TryAdd(rawLabel.Text, rawLabel);
|
||||
}
|
||||
}
|
||||
|
||||
await _walletRepository.SetWalletTransactionInfo(walletId, transactionId, walletTransactionInfo);
|
||||
var walletTransactionsInfo =
|
||||
(await _walletRepository.GetWalletTransactionsInfo(walletId, new[] { transactionId }))
|
||||
.Values
|
||||
.FirstOrDefault();
|
||||
|
||||
return Ok(ToModel(walletTransactionsInfo, tx, wallet));
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/utxos")]
|
||||
public async Task<IActionResult> GetOnChainWalletUTXOs(string storeId, string cryptoCode)
|
||||
@ -281,6 +346,16 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return this.CreateAPIError(503, "not-available", $"You need to allow non-admins to use hotwallets for their stores (in /server/policies)");
|
||||
}
|
||||
|
||||
if (request.Destinations == null || !request.Destinations.Any())
|
||||
{
|
||||
ModelState.AddModelError(
|
||||
nameof(request.Destinations),
|
||||
"At least one destination must be specified"
|
||||
);
|
||||
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
var explorerClient = _explorerClientProvider.GetExplorerClient(cryptoCode);
|
||||
var wallet = _btcPayWalletProvider.GetWallet(network);
|
||||
|
||||
|
@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
|
@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Amazon.Runtime;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
@ -72,6 +73,19 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
return UserNotFound();
|
||||
}
|
||||
[Authorize(Policy = Policies.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/users/{idOrEmail}/toggle")]
|
||||
public async Task<IActionResult> ToggleUser(string idOrEmail, bool enabled )
|
||||
{
|
||||
var user = (await _userManager.FindByIdAsync(idOrEmail) ) ?? await _userManager.FindByEmailAsync(idOrEmail);
|
||||
if (user is null)
|
||||
{
|
||||
return UserNotFound();
|
||||
}
|
||||
|
||||
await _userService.ToggleUser(user.Id, enabled ? null : DateTimeOffset.MaxValue);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewUsers, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/users/")]
|
||||
@ -218,7 +232,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
|
||||
// User shouldn't be deleted if it's the only admin
|
||||
if (await IsUserTheOnlyOneAdmin(user))
|
||||
if (await _userService.IsUserTheOnlyOneAdmin(user))
|
||||
{
|
||||
return Forbid(AuthenticationSchemes.GreenfieldBasic);
|
||||
}
|
||||
@ -235,21 +249,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return UserService.FromModel(data, roles);
|
||||
}
|
||||
|
||||
private async Task<bool> IsUserTheOnlyOneAdmin()
|
||||
{
|
||||
return await IsUserTheOnlyOneAdmin(await _userManager.GetUserAsync(User));
|
||||
}
|
||||
|
||||
private async Task<bool> IsUserTheOnlyOneAdmin(ApplicationUser user)
|
||||
{
|
||||
var isUserAdmin = await _userService.IsAdminUser(user);
|
||||
if (!isUserAdmin)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin)).Count == 1;
|
||||
}
|
||||
|
||||
|
||||
private IActionResult UserNotFound()
|
||||
{
|
||||
|
@ -9,8 +9,10 @@ using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers.GreenField;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Security.Greenfield;
|
||||
using BTCPayServer.Services.Mails;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
@ -21,6 +23,8 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using InvoiceData = BTCPayServer.Client.Models.InvoiceData;
|
||||
using Language = BTCPayServer.Client.Models.Language;
|
||||
using NotificationData = BTCPayServer.Client.Models.NotificationData;
|
||||
@ -39,7 +43,10 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
private readonly IOptionsMonitor<IdentityOptions> _identityOptions;
|
||||
private readonly GreenfieldStoreOnChainPaymentMethodsController _chainPaymentMethodsController;
|
||||
private readonly GreenfieldStoreOnChainWalletsController _storeOnChainWalletsController;
|
||||
private readonly GreenfieldStoreLightningNetworkPaymentMethodsController _storeLightningNetworkPaymentMethodsController;
|
||||
|
||||
private readonly GreenfieldStoreLightningNetworkPaymentMethodsController
|
||||
_storeLightningNetworkPaymentMethodsController;
|
||||
|
||||
private readonly GreenfieldStoreLNURLPayPaymentMethodsController _storeLnurlPayPaymentMethodsController;
|
||||
private readonly GreenfieldHealthController _healthController;
|
||||
private readonly GreenfieldPaymentRequestsController _paymentRequestController;
|
||||
@ -56,6 +63,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
private readonly GreenfieldPullPaymentController _greenfieldPullPaymentController;
|
||||
private readonly UIHomeController _homeController;
|
||||
private readonly GreenfieldStorePaymentMethodsController _storePaymentMethodsController;
|
||||
private readonly GreenfieldStoreEmailController _greenfieldStoreEmailController;
|
||||
private readonly GreenfieldStoreUsersController _greenfieldStoreUsersController;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public BTCPayServerClientFactory(StoreRepository storeRepository,
|
||||
@ -79,6 +88,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
GreenfieldPullPaymentController greenfieldPullPaymentController,
|
||||
UIHomeController homeController,
|
||||
GreenfieldStorePaymentMethodsController storePaymentMethodsController,
|
||||
GreenfieldStoreEmailController greenfieldStoreEmailController,
|
||||
GreenfieldStoreUsersController greenfieldStoreUsersController,
|
||||
IServiceProvider serviceProvider)
|
||||
{
|
||||
_storeRepository = storeRepository;
|
||||
@ -102,12 +113,27 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
_greenfieldPullPaymentController = greenfieldPullPaymentController;
|
||||
_homeController = homeController;
|
||||
_storePaymentMethodsController = storePaymentMethodsController;
|
||||
_greenfieldStoreEmailController = greenfieldStoreEmailController;
|
||||
_greenfieldStoreUsersController = greenfieldStoreUsersController;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public async Task<BTCPayServerClient> Create(string userId, params string[] storeIds)
|
||||
public Task<BTCPayServerClient> Create(string userId, params string[] storeIds)
|
||||
{
|
||||
return Create(userId, storeIds, new DefaultHttpContext()
|
||||
{
|
||||
Request =
|
||||
{
|
||||
Scheme = "https",
|
||||
Host = new HostString("dummy.com"),
|
||||
Path = new PathString(),
|
||||
PathBase = new PathString(),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<BTCPayServerClient> Create(string userId, string[] storeIds, HttpContext context)
|
||||
{
|
||||
var context = new DefaultHttpContext();
|
||||
if (!string.IsNullOrEmpty(userId))
|
||||
{
|
||||
var user = await _userManager.FindByIdAsync(userId);
|
||||
@ -120,12 +146,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
claims.AddRange((await _userManager.GetRolesAsync(user)).Select(s =>
|
||||
new Claim(_identityOptions.CurrentValue.ClaimsIdentity.RoleClaimType, s)));
|
||||
context.User =
|
||||
new ClaimsPrincipal(new ClaimsIdentity(claims, $"Local{GreenfieldConstants.AuthenticationType}WithUser"));
|
||||
new ClaimsPrincipal(new ClaimsIdentity(claims,
|
||||
$"Local{GreenfieldConstants.AuthenticationType}WithUser"));
|
||||
}
|
||||
else
|
||||
{
|
||||
context.User =
|
||||
new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>(), $"Local{GreenfieldConstants.AuthenticationType}"));
|
||||
new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>(),
|
||||
$"Local{GreenfieldConstants.AuthenticationType}"));
|
||||
}
|
||||
|
||||
if (storeIds?.Any() is true)
|
||||
@ -158,11 +186,18 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
_greenfieldPullPaymentController,
|
||||
_homeController,
|
||||
_storePaymentMethodsController,
|
||||
new HttpContextAccessor() { HttpContext = context }
|
||||
_greenfieldStoreEmailController,
|
||||
_greenfieldStoreUsersController,
|
||||
new LocalHttpContextAccessor() {HttpContext = context}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public class LocalHttpContextAccessor : IHttpContextAccessor
|
||||
{
|
||||
public HttpContext? HttpContext { get; set; }
|
||||
}
|
||||
|
||||
public class LocalBTCPayServerClient : BTCPayServerClient
|
||||
{
|
||||
private readonly GreenfieldStoreOnChainPaymentMethodsController _chainPaymentMethodsController;
|
||||
@ -175,7 +210,10 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
private readonly GreenfieldStoresController _storesController;
|
||||
private readonly GreenfieldStoreLightningNodeApiController _storeLightningNodeApiController;
|
||||
private readonly GreenfieldInternalLightningNodeApiController _lightningNodeApiController;
|
||||
private readonly GreenfieldStoreLightningNetworkPaymentMethodsController _storeLightningNetworkPaymentMethodsController;
|
||||
|
||||
private readonly GreenfieldStoreLightningNetworkPaymentMethodsController
|
||||
_storeLightningNetworkPaymentMethodsController;
|
||||
|
||||
private readonly GreenfieldStoreLNURLPayPaymentMethodsController _storeLnurlPayPaymentMethodsController;
|
||||
private readonly GreenfieldInvoiceController _greenFieldInvoiceController;
|
||||
private readonly GreenfieldServerInfoController _greenFieldServerInfoController;
|
||||
@ -183,6 +221,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
private readonly GreenfieldPullPaymentController _greenfieldPullPaymentController;
|
||||
private readonly UIHomeController _homeController;
|
||||
private readonly GreenfieldStorePaymentMethodsController _storePaymentMethodsController;
|
||||
private readonly GreenfieldStoreEmailController _greenfieldStoreEmailController;
|
||||
private readonly GreenfieldStoreUsersController _greenfieldStoreUsersController;
|
||||
|
||||
public LocalBTCPayServerClient(
|
||||
IServiceProvider serviceProvider,
|
||||
@ -204,6 +244,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
GreenfieldPullPaymentController greenfieldPullPaymentController,
|
||||
UIHomeController homeController,
|
||||
GreenfieldStorePaymentMethodsController storePaymentMethodsController,
|
||||
GreenfieldStoreEmailController greenfieldStoreEmailController,
|
||||
GreenfieldStoreUsersController greenfieldStoreUsersController,
|
||||
IHttpContextAccessor httpContextAccessor) : base(new Uri("https://dummy.local"), "", "")
|
||||
{
|
||||
_chainPaymentMethodsController = chainPaymentMethodsController;
|
||||
@ -224,6 +266,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
_greenfieldPullPaymentController = greenfieldPullPaymentController;
|
||||
_homeController = homeController;
|
||||
_storePaymentMethodsController = storePaymentMethodsController;
|
||||
_greenfieldStoreEmailController = greenfieldStoreEmailController;
|
||||
_greenfieldStoreUsersController = greenfieldStoreUsersController;
|
||||
|
||||
var controllers = new[]
|
||||
{
|
||||
@ -231,13 +275,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
paymentRequestController, apiKeysController, notificationsController, usersController,
|
||||
storeLightningNetworkPaymentMethodsController, greenFieldInvoiceController, storeWebhooksController,
|
||||
greenFieldServerInfoController, greenfieldPullPaymentController, storesController, homeController,
|
||||
lightningNodeApiController, storeLightningNodeApiController as ControllerBase, storePaymentMethodsController
|
||||
lightningNodeApiController, storeLightningNodeApiController as ControllerBase,
|
||||
storePaymentMethodsController, greenfieldStoreEmailController, greenfieldStoreUsersController
|
||||
};
|
||||
|
||||
var authoverride = new DefaultAuthorizationService(
|
||||
serviceProvider.GetRequiredService<IAuthorizationPolicyProvider>(),
|
||||
new AuthHandlerProvider(
|
||||
serviceProvider.GetRequiredService<StoreRepository>(),
|
||||
serviceProvider.GetRequiredService<StoreRepository>(),
|
||||
serviceProvider.GetRequiredService<UserManager<ApplicationUser>>(),
|
||||
httpContextAccessor
|
||||
),
|
||||
@ -245,16 +290,15 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
serviceProvider.GetRequiredService<IAuthorizationHandlerContextFactory>(),
|
||||
serviceProvider.GetRequiredService<IAuthorizationEvaluator>(),
|
||||
serviceProvider.GetRequiredService<IOptions<AuthorizationOptions>>()
|
||||
|
||||
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
foreach (var controller in controllers)
|
||||
{
|
||||
controller.ControllerContext.HttpContext = httpContextAccessor.HttpContext;
|
||||
var authInterface = typeof(IAuthorizationService);
|
||||
foreach (FieldInfo fieldInfo in controller.GetType().GetFields().Where(info => authInterface.IsAssignableFrom(info.FieldType)))
|
||||
foreach (FieldInfo fieldInfo in controller.GetType().GetFields()
|
||||
.Where(info => authInterface.IsAssignableFrom(info.FieldType)))
|
||||
{
|
||||
fieldInfo.SetValue(controller, authoverride);
|
||||
}
|
||||
@ -269,12 +313,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
|
||||
public AuthHandlerProvider(StoreRepository storeRepository, UserManager<ApplicationUser> userManager, IHttpContextAccessor httpContextAccessor)
|
||||
public AuthHandlerProvider(StoreRepository storeRepository, UserManager<ApplicationUser> userManager,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_storeRepository = storeRepository;
|
||||
_userManager = userManager;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<IAuthorizationHandler>> GetHandlersAsync(AuthorizationHandlerContext context)
|
||||
{
|
||||
return Task.FromResult<IEnumerable<IAuthorizationHandler>>(new IAuthorizationHandler[]
|
||||
@ -283,6 +329,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override HttpRequestMessage CreateHttpRequest(string path,
|
||||
Dictionary<string, object> queryPayload = null, HttpMethod method = null)
|
||||
{
|
||||
@ -497,7 +544,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
await _lightningNodeApiController.GetDepositAddress(cryptoCode));
|
||||
}
|
||||
|
||||
public override async Task<LightningPaymentData> PayLightningInvoice(string cryptoCode, PayLightningInvoiceRequest request,
|
||||
public override async Task<LightningPaymentData> PayLightningInvoice(string cryptoCode,
|
||||
PayLightningInvoiceRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LightningPaymentData>(
|
||||
@ -526,7 +574,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
case JsonResult jsonResult:
|
||||
return (T)jsonResult.Value;
|
||||
case OkObjectResult { Value: T res }:
|
||||
case OkObjectResult {Value: T res}:
|
||||
return res;
|
||||
default:
|
||||
return default;
|
||||
@ -537,9 +585,9 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case UnprocessableEntityObjectResult { Value: List<GreenfieldValidationError> validationErrors }:
|
||||
case UnprocessableEntityObjectResult {Value: List<GreenfieldValidationError> validationErrors}:
|
||||
throw new GreenfieldValidationException(validationErrors.ToArray());
|
||||
case BadRequestObjectResult { Value: GreenfieldAPIError error }:
|
||||
case BadRequestObjectResult {Value: GreenfieldAPIError error}:
|
||||
throw new GreenfieldAPIException(400, error);
|
||||
case NotFoundResult _:
|
||||
throw new GreenfieldAPIException(404, new GreenfieldAPIError("not-found", ""));
|
||||
@ -561,7 +609,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
public override Task<IEnumerable<OnChainPaymentMethodData>> GetStoreOnChainPaymentMethods(string storeId,
|
||||
bool? enabled, CancellationToken token)
|
||||
{
|
||||
return Task.FromResult(GetFromActionResult(_chainPaymentMethodsController.GetOnChainPaymentMethods(storeId, enabled)));
|
||||
return Task.FromResult(
|
||||
GetFromActionResult(_chainPaymentMethodsController.GetOnChainPaymentMethods(storeId, enabled)));
|
||||
}
|
||||
|
||||
public override Task<OnChainPaymentMethodData> GetStoreOnChainPaymentMethod(string storeId,
|
||||
@ -582,17 +631,19 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<OnChainPaymentMethodData>(
|
||||
await _chainPaymentMethodsController.UpdateOnChainPaymentMethod(storeId, cryptoCode, new UpdateOnChainPaymentMethodRequest(
|
||||
enabled: paymentMethod.Enabled,
|
||||
label: paymentMethod.Label,
|
||||
accountKeyPath: paymentMethod.AccountKeyPath,
|
||||
derivationScheme: paymentMethod.DerivationScheme
|
||||
)));
|
||||
await _chainPaymentMethodsController.UpdateOnChainPaymentMethod(storeId, cryptoCode,
|
||||
new UpdateOnChainPaymentMethodRequest(
|
||||
enabled: paymentMethod.Enabled,
|
||||
label: paymentMethod.Label,
|
||||
accountKeyPath: paymentMethod.AccountKeyPath,
|
||||
derivationScheme: paymentMethod.DerivationScheme
|
||||
)));
|
||||
}
|
||||
|
||||
public override Task<OnChainPaymentMethodPreviewResultData> PreviewProposedStoreOnChainPaymentMethodAddresses(
|
||||
string storeId, string cryptoCode,
|
||||
UpdateOnChainPaymentMethodRequest paymentMethod, int offset = 0, int amount = 10, CancellationToken token = default)
|
||||
UpdateOnChainPaymentMethodRequest paymentMethod, int offset = 0, int amount = 10,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(GetFromActionResult<OnChainPaymentMethodPreviewResultData>(
|
||||
_chainPaymentMethodsController.GetProposedOnChainPaymentMethodPreview(storeId, cryptoCode,
|
||||
@ -604,7 +655,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(GetFromActionResult<OnChainPaymentMethodPreviewResultData>(
|
||||
_chainPaymentMethodsController.GetOnChainPaymentMethodPreview(storeId, cryptoCode, offset,
|
||||
_chainPaymentMethodsController.GetOnChainPaymentMethodPreview(storeId, cryptoCode, offset,
|
||||
amount)));
|
||||
}
|
||||
|
||||
@ -622,7 +673,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
public override async Task<PaymentRequestData> GetPaymentRequest(string storeId, string paymentRequestId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<PaymentRequestData>(await _paymentRequestController.GetPaymentRequest(storeId, paymentRequestId));
|
||||
return GetFromActionResult<PaymentRequestData>(
|
||||
await _paymentRequestController.GetPaymentRequest(storeId, paymentRequestId));
|
||||
}
|
||||
|
||||
public override async Task ArchivePaymentRequest(string storeId, string paymentRequestId,
|
||||
@ -686,7 +738,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
return GetFromActionResult<NotificationData>(
|
||||
await _notificationsController.UpdateNotification(notificationId,
|
||||
new UpdateNotification() { Seen = seen }));
|
||||
new UpdateNotification() {Seen = seen}));
|
||||
}
|
||||
|
||||
public override async Task RemoveNotification(string notificationId, CancellationToken token = default)
|
||||
@ -868,7 +920,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
return GetFromActionResult<LightningNetworkPaymentMethodData>(await
|
||||
_storeLightningNetworkPaymentMethodsController.UpdateLightningNetworkPaymentMethod(storeId, cryptoCode,
|
||||
new UpdateLightningNetworkPaymentMethodRequest(paymentMethod.ConnectionString, paymentMethod.Enabled)));
|
||||
new UpdateLightningNetworkPaymentMethodRequest(paymentMethod.ConnectionString,
|
||||
paymentMethod.Enabled)));
|
||||
}
|
||||
|
||||
public override async Task<IEnumerable<InvoiceData>> GetInvoices(string storeId, string[] orderId = null,
|
||||
@ -880,8 +933,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
int? skip = null,
|
||||
int? take = null,
|
||||
CancellationToken token = default
|
||||
|
||||
)
|
||||
)
|
||||
{
|
||||
return GetFromActionResult<IEnumerable<InvoiceData>>(
|
||||
await _greenFieldInvoiceController.GetInvoices(storeId, orderId,
|
||||
@ -966,7 +1018,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
|
||||
public override Task<Language[]> GetAvailableLanguages(CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(_homeController.LanguageService.GetLanguages().Select(language => new Language(language.Code, language.DisplayName)).ToArray());
|
||||
return Task.FromResult(_homeController.LanguageService.GetLanguages()
|
||||
.Select(language => new Language(language.Code, language.DisplayName)).ToArray());
|
||||
}
|
||||
|
||||
public override Task<PermissionMetadata[]> GetPermissionMetadata(CancellationToken token = default)
|
||||
@ -974,25 +1027,99 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return Task.FromResult(GetFromActionResult<PermissionMetadata[]>(_homeController.Permissions()));
|
||||
}
|
||||
|
||||
public override async Task<Dictionary<string, GenericPaymentMethodData>> GetStorePaymentMethods(string storeId, bool? enabled = null, CancellationToken token = default)
|
||||
public override async Task<Dictionary<string, GenericPaymentMethodData>> GetStorePaymentMethods(string storeId,
|
||||
bool? enabled = null, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult(await _storePaymentMethodsController.GetStorePaymentMethods(storeId, enabled));
|
||||
}
|
||||
|
||||
public override async Task<OnChainPaymentMethodDataWithSensitiveData> GenerateOnChainWallet(string storeId, string cryptoCode, GenerateOnChainWalletRequest request,
|
||||
public override async Task<OnChainPaymentMethodDataWithSensitiveData> GenerateOnChainWallet(string storeId,
|
||||
string cryptoCode, GenerateOnChainWalletRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<OnChainPaymentMethodDataWithSensitiveData>(await _chainPaymentMethodsController.GenerateOnChainWallet(storeId, cryptoCode, new GenerateWalletRequest()
|
||||
{
|
||||
Passphrase = request.Passphrase,
|
||||
AccountNumber = request.AccountNumber,
|
||||
ExistingMnemonic = request.ExistingMnemonic?.ToString(),
|
||||
WordCount = request.WordCount,
|
||||
WordList = request.WordList,
|
||||
SavePrivateKeys = request.SavePrivateKeys,
|
||||
ScriptPubKeyType = request.ScriptPubKeyType,
|
||||
ImportKeysToRPC = request.ImportKeysToRPC
|
||||
}));
|
||||
return GetFromActionResult<OnChainPaymentMethodDataWithSensitiveData>(
|
||||
await _chainPaymentMethodsController.GenerateOnChainWallet(storeId, cryptoCode,
|
||||
new GenerateWalletRequest()
|
||||
{
|
||||
Passphrase = request.Passphrase,
|
||||
AccountNumber = request.AccountNumber,
|
||||
ExistingMnemonic = request.ExistingMnemonic?.ToString(),
|
||||
WordCount = request.WordCount,
|
||||
WordList = request.WordList,
|
||||
SavePrivateKeys = request.SavePrivateKeys,
|
||||
ScriptPubKeyType = request.ScriptPubKeyType,
|
||||
ImportKeysToRPC = request.ImportKeysToRPC
|
||||
}));
|
||||
}
|
||||
|
||||
public override async Task SendEmail(string storeId, SendEmailRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
HandleActionResult(await _greenfieldStoreEmailController.SendEmailFromStore(storeId, request));
|
||||
}
|
||||
|
||||
public override Task<EmailSettingsData> GetStoreEmailSettings(string storeId, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(
|
||||
GetFromActionResult<EmailSettingsData>(_greenfieldStoreEmailController.GetStoreEmailSettings()));
|
||||
}
|
||||
|
||||
public override async Task<EmailSettingsData> UpdateStoreEmailSettings(string storeId,
|
||||
EmailSettingsData request, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<EmailSettingsData>(
|
||||
await _greenfieldStoreEmailController.UpdateStoreEmailSettings(storeId,
|
||||
JObject.FromObject(request).ToObject<EmailSettings>()));
|
||||
}
|
||||
|
||||
public override async Task<ApplicationUserData[]> GetUsers(CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult(await _usersController.GetUsers());
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<StoreUserData>> GetStoreUsers(string storeId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(
|
||||
GetFromActionResult<IEnumerable<StoreUserData>>(_greenfieldStoreUsersController.GetStoreUsers()));
|
||||
}
|
||||
|
||||
public override async Task AddStoreUser(string storeId, StoreUserData request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
HandleActionResult(await _greenfieldStoreUsersController.AddStoreUser(storeId, request));
|
||||
}
|
||||
|
||||
public override async Task RemoveStoreUser(string storeId, string userId, CancellationToken token = default)
|
||||
{
|
||||
HandleActionResult(await _greenfieldStoreUsersController.RemoveStoreUser(storeId, userId));
|
||||
}
|
||||
|
||||
public override async Task<ApplicationUserData> GetUserByIdOrEmail(string idOrEmail,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<ApplicationUserData>(await _usersController.GetUser(idOrEmail));
|
||||
}
|
||||
|
||||
public override async Task ToggleUser(string idOrEmail, bool enabled, CancellationToken token = default)
|
||||
{
|
||||
HandleActionResult(await _usersController.ToggleUser(idOrEmail, enabled));
|
||||
}
|
||||
|
||||
public override async Task<OnChainWalletTransactionData> PatchOnChainWalletTransaction(string storeId, string cryptoCode, string transactionId,
|
||||
PatchOnChainTransactionRequest request, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<OnChainWalletTransactionData>(await _storeOnChainWalletsController.PatchOnChainWalletTransaction(storeId, cryptoCode, transactionId, request));
|
||||
}
|
||||
|
||||
public override async Task<LightningPaymentData> GetLightningPayment(string cryptoCode, string paymentHash, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LightningPaymentData>(await _lightningNodeApiController.GetPayment(cryptoCode, paymentHash));
|
||||
}
|
||||
|
||||
public override async Task<LightningPaymentData> GetLightningPayment(string storeId, string cryptoCode, string paymentHash, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LightningPaymentData>(await _storeLightningNodeApiController.GetPayment(cryptoCode, paymentHash));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user