Compare commits

..

97 Commits

Author SHA1 Message Date
597116bb26 Allow Users to be disabled/enabled 2022-04-14 19:16:55 +02:00
64534efe71 Fix broken "CanCreateRefunds" test (#3635) 2022-04-14 14:07:10 +09:00
1235ced355 htmlcoin altcoin updates (#3601)
* remove old logo

* add new logo

* switch name from Althash to Htmlcoin

* increment version NBXplorer.Client

* fix KeyPath BIP-0044, fix name

https://github.com/BEPAL/slips/blob/master/slip-0044.md

* revert increment version
2022-04-14 13:18:12 +09:00
23d383be67 Add transaction info PATCH endpoint (#3561)
* Add transaction info patch endpoint

* Add "#nullable enable" to LabelFactory

* Add Swagger docs

* Update OnChain to onchain

* update feeRate to feerate

* Add test

* replace "Onchain" with "onchain"
2022-04-14 13:17:22 +09:00
fb90ff2fbb updates (#3631) 2022-04-14 13:09:37 +09:00
4706aa95e6 Minor Dashboard Adjustments (#3629)
* improves border-radius

* adjusts CF widget wording
2022-04-14 13:08:43 +09:00
8981414705 API: Add Lightning Payment info endpoint (#3557)
* Upgrade Lightning lib

* API: Add Lightning Payment info endpoint
2022-04-12 18:01:58 +09:00
7ec978fcdb Dashboard (#3530)
* Add dashboard and chart basics

* More widgets

* Make widgets responsive

* Layout dashboard

* Prepare ExplorerClient

* Switch to Chartist

* Dynamic data for store numbers and recent transactions tiles

* Dynamic data for recent invoices tile

* Improvements

* Plug NBXPlorer DB

* Properly filter by code

* Reorder cheat mode button

* AJAX update for graph data

* Fix create invoice button

* Retry connection on transient issues

* App Top Items stats

* Design updates

* App Sales stats

* Add points for weekly histogram, set last point to current balance

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2022-04-12 16:55:10 +09:00
d58803a058 Specify PayJoin enabled in Payment Link heading (#3614)
* Specify PayJoin enabled in Payment Link heading

* Fix for non bitcoin payments
2022-04-12 11:05:09 +09:00
fe6b7dc1e3 Fix visual bug when user clicks on "Create refund" without selecting an option (#3624)
* Add missing refund option validation error element

* Add missing hidden inputs for text elements

* Move validation element above button

* Update validation error message
2022-04-11 17:53:52 +09:00
c9f0988b95 Handle possible error when bumping fee (#3608)
fix #3600
2022-04-11 17:53:10 +09:00
cd9a52706c Use the store's default currency when creating entities (#3585)
* Use default currency for new pull payments

Closes #3582.

* Pull payment: Improve create form

* Use default currency for new invoices

Closes  #3581.

* Clean up old invoice form code

* Use default currency for new payment requests

* Test fixes
2022-04-11 17:50:30 +09:00
bfdb1b4af9 Design updates (#3565)
* Design updates

* Improve table styles

* Form input color improvements

* Form input shadows

* Increase accordion button padding

* Hover transition for checkboxes and radio buttons

* Improve checkbox and radio button spacings

* Improve input styles

* Secondary button updates

* Clear pager floats

* Link improvements

* Don't display border for last table row
2022-04-11 17:49:57 +09:00
e5174b4a29 Lightning: Link to services directly (#3593)
* Allow to access fake LN services in dev mode

* Link directly to Lightning services

Closes #3552.

* Fix typo
2022-04-11 17:49:28 +09:00
8feb60c30d Add ability to set default payment method for pay button (#3606)
* Add ability to set default payment method for pay button

close #3604

* Add "#nullable enable" to UIStoresController

* Add PaymentMethodOptionViewModel

* Add explicit "Use the store’s default" option
2022-04-11 17:48:12 +09:00
6bd7fb64ab CI test fixes (#3609)
* Test fix


Logs

* Add test logs

* Test change

* Use async overloads in CanPayWithTwoCurrencies test

* Bump NBXplorer

* More test updates

* More logs

* More waiting

* More waiting

* Update GoToUrl calls

* Log request status

* More logs

* More logs, more waits, idk

* Click checkboxes using JS

* Go to url directly

* Double timeout
2022-04-08 18:58:01 +09:00
b9602243d3 Fix tests for litecoin 2022-04-08 13:03:51 +09:00
b7a930ef18 Adjust user search input width (#3577)
Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2022-04-05 07:34:56 -04:00
add206ae2d Fixes #3598 by adding overflow scrolling to StoreSelectorMenu (#3599)
Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2022-04-05 07:34:14 -04:00
28ce095fb4 Merge pull request #3602 from phershbe/master
Link to local development environment instructions corrected
2022-04-05 09:01:00 +02:00
13952a4b79 Bump NBX (#3607) 2022-04-05 14:46:42 +09:00
05ec398346 Update CoinGeckoRateProvider 2022-04-04 14:47:50 +09:00
dea2dd52be Link to local development environment instructions corrected
As noted here: https://github.com/btcpayserver/btcpayserver/issues/3590

The link to the local development environment instructions was broken and now is correct.
2022-04-01 22:18:43 -04:00
debe3cda4b fix typo 2022-04-01 13:45:30 +02:00
5b5aa2c721 bump btcpay 2022-04-01 13:22:24 +02:00
e201ddd74c Plugins: Fix plugin installer 2022-04-01 13:20:19 +02:00
4a1580169d Merge pull request #3595 from pavlenex/shopify 2022-03-31 15:15:26 +02:00
001ca7de60 Fix UI to match Shopify's 2022-03-31 14:51:16 +02:00
184be4e27b Merge pull request #3592 from Kukks/updaart 2022-03-31 13:36:11 +02:00
7652645dda 1.4.8 update 2022-03-31 12:48:10 +02:00
ef6016857b FileService: AddFile from URL (#3566) 2022-03-31 11:54:25 +02:00
e449ca2c95 Fix shopify 2022-03-31 10:13:07 +02:00
b0f00773d6 fix issues around local btcpay client and no request obj 2022-03-30 15:04:51 +02:00
451eee549b added additional inputmode attr to relevant form input fields (#3578)
* added inputmode attr to relevant input fields

* missed some numerical form inputs

* removed inputmode attribute from checkout appearnce invoice settings input field
2022-03-30 09:58:50 +02:00
77da261fea Allow plugins to extend swagger docs and fix pull payment test 2022-03-29 20:29:27 +02:00
e23c9ee608 Add missing policies in api key UI 2022-03-29 20:12:02 +02:00
37cb87a9c6 Update CircleCI config (#3586) 2022-03-29 16:35:02 +01:00
211db8e0f0 Adjust pull payment badge color (#3584)
Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2022-03-29 12:09:30 +02:00
d074d60dad Change "was confirmed paid" to "is settled"
As detailed here: https://github.com/btcpayserver/btcpayserver/issues/3572
2022-03-27 07:56:32 +01:00
0bff5e2236 Update BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-email.json
Co-authored-by: d11n <mail@dennisreimann.de>
2022-03-26 21:46:40 +00:00
dca986eb2e Add Greenfield Store Email API 2022-03-26 21:46:40 +00:00
326eb1135b added inputmode attr to relevant input fields 2022-03-26 18:27:22 +00:00
b2f7b4e6b9 Update BTCPayServer/Services/Invoices/InvoiceRepository.cs
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
2022-03-25 13:21:43 +00:00
5129d6aa6b Update BTCPayServer/Services/Invoices/InvoiceRepository.cs
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
2022-03-25 13:21:43 +00:00
a8cf334616 Update BTCPayServer/Services/Invoices/InvoiceRepository.cs
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
2022-03-25 13:21:43 +00:00
3b4d06a1e5 Bugfix: Could not find an order by OrderId after it's OrderId was changed through the API 2022-03-25 13:21:43 +00:00
c7969476b0 Apply suggestions from code review
Co-authored-by: d11n <mail@dennisreimann.de>
2022-03-25 10:45:56 +00:00
7bf24df03a Change payment method name from "Wallet" to "Bitcoin"
As discussed here: https://github.com/btcpayserver/btcpayserver/issues/3571
2022-03-25 10:45:56 +00:00
5ef41294e4 Optimize github plugin fetching 2022-03-23 15:03:39 +00:00
2eb68655c7 FileService: Add method to check availability 2022-03-21 12:38:25 +01:00
23049439c0 API: Add description hash to CreateLightningInvoiceRequest (#3559)
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
2022-03-17 10:15:27 +01:00
ce6cd40b92 Adjust wallet receive page layout (#3553)
Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2022-03-11 10:43:31 +01:00
c36b0c16b0 New API endpoint: Send email using store SMTP (#3181)
Co-authored-by: Kukks <evilkukka@gmail.com>
2022-03-11 10:17:40 +01:00
c15f182377 Streamline JS/CSS bundles (#3520) 2022-03-11 08:41:48 +01:00
165cb345b4 Merge pull request #3551 from dennisreimann/acinq
Remove ACINQ from README
2022-03-10 11:52:57 +03:00
9b6d2beb4d Remove ACINQ from README
Closes #3546.
2022-03-10 09:50:40 +01:00
722c39a6ff Exclude ChainCoin in rate provider test 2022-03-08 12:12:01 +01:00
e344749d2f Remove Polis
As per [this comment](https://github.com/btcpayserver/btcpayserver/pull/3514#issuecomment-1059583143)
2022-03-08 12:12:01 +01:00
fe782bc3b6 Merge pull request #3519 from dennisreimann/2fa-layout
Use simple layout for 2FA views
2022-03-08 13:42:28 +03:00
d372cbad74 Fix null reference error when "destinations" field is not specified or empty when creating a new wallet transaction
fix #3537
2022-03-08 11:14:34 +01:00
1e1198f4ec Remove Kraken from README 2022-03-08 11:13:19 +01:00
5effc96cff Remove Kraken as a supporter
Closes #3539.
2022-03-08 11:13:19 +01:00
b8d4a1be05 Delete Plugins/packed directory 2022-03-08 09:57:43 +01:00
36a25e6efa Merge pull request #3543 from dennisreimann/manage-plugins
Rename Add plugin to Manage plugins
2022-03-08 11:26:35 +03:00
1240e7914d Fixes 2022-03-08 08:17:39 +00:00
e4683b1ea1 Refactoring: Extract ITempDataDictionary extensions 2022-03-08 08:17:39 +00:00
749c22a0c3 Refactoring: Extract HttpRequest extensions 2022-03-08 08:17:39 +00:00
6867774627 Refactoring: Extract StringExtensions 2022-03-08 08:17:39 +00:00
80944972e9 Rename Add plugin to Manage plugins
Closes #3540.
2022-03-08 08:04:15 +01:00
c1f608c0d8 Upgrade Lightning lib (#3531) 2022-03-08 10:02:48 +09:00
4dfbb08db3 Try test fix 2022-03-04 10:49:36 +00:00
ead1dffd98 QR container display fixes
Consistently centers the container contents.
2022-03-04 10:49:36 +00:00
5a16e4d132 Use simple layout for 2FA views 2022-03-03 13:43:22 +01:00
a89491e343 Skip Polis and OKEx in rate fetching tests 2022-03-03 10:57:55 +01:00
b1b00ae886 Scroll to checkbox before click 2022-03-03 10:57:55 +01:00
cde5bd87d8 update 1.4.7 2022-03-02 12:37:28 +01:00
3231d5d179 Allow file service to be used in plugins 2022-03-02 12:22:46 +01:00
03e49ea2bf Allow access to global invoices list
The recent changes in 19eea3a6154e73f7fc9d62ab20480134e95be3d5 prevent it to access a global/unfiltered list of invoices across all stores. This removes the fallback to the current store, which brings the global list back at `/invoices`.
2022-03-02 11:00:25 +01:00
7d3eef092c Add missing generate wallet greenfield docs 2022-03-02 10:54:05 +01:00
30d0410b49 fix shopify settings 2022-03-01 09:19:28 +01:00
eb2a887f77 Fix missing store in export for invoices
fixes #3505
2022-03-01 09:18:46 +01:00
e77b8d29cf Remove policy, set store context manually 2022-03-01 09:18:23 +01:00
490ec299c5 Fix missing store context for Lightning payouts 2022-03-01 09:18:23 +01:00
e47c2aa24d Fix redirect 2022-03-01 09:18:23 +01:00
3eb9fdca6a Syntax improvements 2022-03-01 09:18:23 +01:00
a4173a93b7 Improve display on payout confirm page 2022-03-01 09:18:23 +01:00
ad762cf239 Fix back link on payout confirm page
Fixes #3490.
2022-03-01 09:18:23 +01:00
5a478607dc Fix "Copy Link" button on pull payment and payment request pages
close #3499
2022-03-01 09:15:15 +01:00
4abc6eb387 Refactoring: Allow GreenfieldExtensions to be used by plugins 2022-03-01 09:14:51 +01:00
c313bba288 Fix mobile content z-index
I think we added this before taking proper care of the main menu z-index. Now that that is fixed we can remove the z-index of the content area, which fixes #3504.
2022-02-26 13:51:59 +01:00
73eaf97afb Fix pos print view
fixes #3503
2022-02-26 13:22:27 +01:00
8d25df5d4e Fix Pay Button code copying
Fixes #3489.
2022-02-24 12:23:03 +01:00
4a05f16050 Refactoring: Move WellKnownTempData into Abstractions.Constants
This allows plugins to reuse the status message mechanism.
2022-02-21 16:39:08 +01:00
3ef1423263 Update Changelog.md 2022-02-21 13:06:13 +01:00
898652189b Changelog: Fix usernames 2022-02-21 13:06:13 +01:00
2976edf333 Sticky header JS fix
The old version lead to an error on pages that do not contain a sticky header.
2022-02-21 13:05:50 +01:00
248be11e4d fix local client http accessor overrider 2022-02-21 11:48:40 +01:00
243 changed files with 5338 additions and 1735 deletions

View File

@ -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:

View File

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

View File

@ -0,0 +1,7 @@
namespace BTCPayServer.Abstractions.Constants;
public class WellKnownTempData
{
public const string SuccessMessage = nameof(SuccessMessage);
public const string ErrorMessage = nameof(ErrorMessage);
}

View File

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

View 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);
}

View 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; }
}

View File

@ -0,0 +1,9 @@
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Abstractions.Contracts;
public interface ISwaggerProvider
{
Task<JObject> Fetch();
}

View File

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

View 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));
}
}

View 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);
}
}

View File

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

View 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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View 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);
}
}
}

View File

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

View File

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

View File

@ -35,5 +35,7 @@ namespace BTCPayServer.Client.Models
/// </summary>
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset? Created { get; set; }
public bool Disabled { get; set; }
}
}

View File

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

View 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;
}
}

View File

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

View File

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

View File

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

View 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;
}
}

View File

@ -0,0 +1,10 @@
namespace BTCPayServer.Client.Models
{
public class SendEmailRequest
{
public string Email;
public string Subject;
public string Body;
}
}

View File

@ -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:

View File

@ -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'")
});
}
}

View File

@ -56,7 +56,6 @@ namespace BTCPayServer
InitViacoin();
InitMonero();
InitZcash();
InitPolis();
InitChaincoin();
// InitArgoneum();//their rate source is down 9/15/20.
InitMonetaryUnit();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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",

View File

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

View File

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

View File

@ -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}:"));
}
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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:

View File

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

View 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);
}
}

View 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; }
}

View 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>

View 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);
}
}

View 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; }
}

View 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>

View File

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

View File

@ -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">

View 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>

View 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);
}
}

View File

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

View 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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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>
}

View File

@ -47,6 +47,7 @@ namespace BTCPayServer.Components.StoreSelector
WalletId = walletId
};
})
.OrderBy(s => s.Text)
.ToList();
var vm = new StoreSelectorViewModel

View 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>

View File

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

View File

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

View File

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

View File

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

View File

@ -9,5 +9,6 @@ namespace BTCPayServer.Configuration
get;
set;
} = new List<NBXplorerConnectionSetting>();
public string ConnectionString { get; set; }
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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}")]

View File

@ -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")]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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