Compare commits
1 Commits
v1.11.0-rc
...
pgownq
Author | SHA1 | Date | |
---|---|---|---|
ed61fb642d |
.github
.run
BTCPayServer.Abstractions
BTCPayServer.Client
BTCPayServer.Client.csprojBTCPayServerClient.ServerInfo.csBTCPayServerClient.StoreUsers.csBTCPayServerClient.cs
Models
CreatePayoutThroughStoreRequest.csInvoiceData.csLNURLPayPaymentMethodBaseData.csLNURLPayPaymentMethodData.csLightningAutomatedPayoutSettings.csLightningNetworkPaymentMethodBaseData.csLightningNetworkPaymentMethodData.csOnChainAutomatedPayoutSettings.csPayoutData.csRefundInvoiceRequest.csStoreBaseData.csStoreData.csStoreReportRequest.csStoreReportsResponse.csWebhookEvent.cs
Permissions.csBTCPayServer.Common
Altcoins
BTCPayNetworkProvider.BGold.csBTCPayNetworkProvider.Bitcore.csBTCPayNetworkProvider.Chaincoin.cs
BTCPayNetwork.csBTCPayNetworkProvider.csBTCPayServer.Common.csprojLiquid
Monero/RPC
BTCPayServer.Data
ApplicationDbContext.csApplicationDbContextFactory.cs
Data
LightingAddressData.csPayoutProcessorData.csStoreData.csStoreRole.csUserStore.csWebhookDeliveryData.cs
Migrations
BTCPayServer.Rating
BTCPayServer.Tests
AltcoinTests
BTCPayServer.Tests.csprojCheckoutUITests.csCheckoutv2Tests.csCrowdfundTests.csExtensions.csFastTests.csGreenfieldAPITests.csPOSTests.csSeleniumTester.csSeleniumTests.csServerTester.csTestAccount.csTestUtils.csThirdPartyTests.csUnitTest1.csdocker-compose.altcoins.ymldocker-compose.ymlBTCPayServer
BTCPayServer.csprojEventAggregator.csExtensions.csStorePolicies.csPosData.cshtml
Components
AppTopItems
MainNav
StoreLightningBalance
StoreRecentInvoices
StoreRecentTransactions
StoreSelector
StoreWalletBalance
TruncateCenter
WalletNav
Controllers
BitpayInvoiceController.cs
GreenField
GreenfieldAppsController.csGreenfieldInvoiceController.csGreenfieldLightningNodeApiController.csGreenfieldPaymentRequestsController.csGreenfieldPullPaymentController.csGreenfieldReportsController.csGreenfieldServerRolesController.csGreenfieldStoreAutomatedLightningPayoutProcessorsController.csGreenfieldStoreAutomatedOnChainPayoutProcessorsController.csGreenfieldStoreLNURLPayPaymentMethodsController.csGreenfieldStoreLightningNetworkPaymentMethodsController.csGreenfieldStoreOnChainWalletsController.csGreenfieldStoreRolesController.csGreenfieldStoreUsersController.csGreenfieldStoreWebhooksController.csGreenfieldStoresController.csLocalBTCPayServerClient.cs
UIAppsController.csUIHomeController.csUIInvoiceController.Testing.csUIInvoiceController.UI.csUIInvoiceController.csUILNURLController.csUIManageController.APIKeys.csUIPaymentRequestController.csUIPublicController.csUIPullPaymentController.csUIReportsController.CheatMode.csUIReportsController.csUIServerController.Roles.csUIServerController.csUIStorePullPaymentsController.PullPayments.csUIStoresController.LightningLike.csUIStoresController.Onchain.csUIStoresController.Roles.csUIStoresController.csUIUserStoresController.csUIWalletsController.PSBT.csUIWalletsController.csData
Payouts
StoreBlob.csStoreDataExtensions.csWebhookDataExtensions.csExtensions
FileTypeDetector.csForms
FieldValueMirror.csFormDataExtensions.csFormDataService.csHtmlInputFormProvider.csHtmlSelectFormProvider.csHtmlTextareaFormProvider.csUIFormsController.cs
HostedServices
AppInventoryUpdaterHostedService.csBaseAsyncService.csBitpayIPNSender.csCleanupWebhookDeliveriesTask.csDelayedTransactionBroadcasterHostedService.csDynamicDnsHostedService.csIPeriodicTask.csInvoiceWatcher.csNewVersionCheckerHostedService.csPeriodicTaskLauncherHostedService.csPullPaymentHostedService.csRatesHostedService.csScheduledTask.csTransactionLabelMarkerHostedService.csWebhookSender.cs
Hosting
Models
AppViewModels
InvoicingModels
PaymentRequestViewModels
ServerViewModels
StoreReportsViewModels
StoreViewModels
CheckoutAppearanceViewModel.csCreateStoreViewModel.csEditWebhookViewModel.csGeneralSettingsViewModel.csLightningSettingsViewModel.csStoreUsersViewModel.csWalletSetupRequest.cs
ViewPullPaymentModel.csWalletViewModels
PaymentRequest
Payments
Bitcoin
IPaymentMethodHandler.csLNURLPay
LNURLPayPaymentHandler.csLNURLPayPaymentMethodDetails.csLNURLPaySupportedPaymentMethod.csPaymentTypes.LNURL.cs
Lightning
LightningLikePaymentData.csLightningLikePaymentHandler.csLightningLikePaymentMethodDetails.csLightningListener.csLightningPendingPayoutListener.csLightningSupportedPaymentMethod.cs
PayJoin
PaymentMethodId.csPaymentTypes.Bitcoin.csPaymentTypes.Lightning.csPaymentTypes.csPayoutProcessors
AfterPayoutActionData.csAfterPayoutFilterData.csBaseAutomatedPayoutProcessor.csBeforePayoutActionData.csBeforePayoutFilterData.csIPayoutProcessorFactory.cs
Lightning
LightningAutomatedPayoutBlob.csLightningAutomatedPayoutProcessor.csLightningAutomatedPayoutSenderFactory.csUILightningAutomatedPayoutProcessorsController.cs
OnChain
OnChainAutomatedPayoutBlob.csOnChainAutomatedPayoutProcessor.csUIOnChainAutomatedPayoutProcessorsController.cs
PayoutProcessorService.csPlugins
Program.csProperties
SearchString.csSecurity
Services
Altcoins
Apps
BTCPayServerEnvironment.csInvoices
LanguageService.csMigrationSettings.csPoliciesSettings.csReportService.csReporting
OnChainWalletReportProvider.csPaymentsReportProvider.csProductsReportProvider.csQueryContext.csReportProvider.csViewDefinition.cs
Stores
TorServices.csUserService.csWalletRepository.csWallets
TagHelpers
Views
Shared
Bitcoin
CreateOrEditRole.cshtmlCrowdfund
Forms
LayoutHeadTheme.cshtmlLightning
LightningLikeMethodCheckout-v2.cshtmlLightningLikeMethodCheckout.cshtmlViewLightningLikePaymentData.cshtml
ListRoles.cshtmlNFC
PayButton
PointOfSale
Public
UpdatePointOfSale.cshtmlShopify
ShowQR.cshtmlTemplateEditor.cshtml_Layout.cshtml_StoreFooterLogo.cshtmlUIApps
UIForms
UIHome
UIInvoice
Checkout-Body.cshtmlCheckoutNoScript.cshtmlCheckoutV2.cshtmlInvoice.cshtmlInvoiceReceipt.cshtmlInvoiceReceiptPrint.cshtmlListInvoices.cshtmlListInvoicesPaymentsPartial.cshtml_RefundModal.cshtml
UILNURL
UILightningAutomatedPayoutProcessors
UIManage
UIOnChainAutomatedPayoutProcessors
UIPaymentRequest
UIPayoutProcessors
UIPullPayment
UIReports
UIServer
UIStorePullPayments
UIStores
CheckoutAppearance.cshtmlDashboard.cshtmlGeneralSettings.cshtml
ImportWallet
Lightning.cshtmlLightningSettings.cshtmlModifyWebhook.cshtmlRates.cshtmlStoreNavPages.csStoreUsers.cshtml_GenerateWalletForm.cshtml_LayoutWalletSetup.cshtml_Nav.cshtmlUIUserStores
UIWallets
wwwroot
cart
checkout-v2
crowdfund
img
js
light-pos
locales
main
modal
paybutton
pos
swagger/v1
swagger.template.apps.jsonswagger.template.invoices.jsonswagger.template.payout-processors.jsonswagger.template.pull-payments.jsonswagger.template.serverinfo.jsonswagger.template.stores-email.jsonswagger.template.stores-payment-methods.lightning-network.jsonswagger.template.stores-payment-methods.lnurl.jsonswagger.template.stores-users.jsonswagger.template.stores.jsonswagger.template.webhooks.json
vendor
FileSaver
bootstrap-vue
bootstrap-vue.cssbootstrap-vue.jsbootstrap-vue.min.cssbootstrap-vue.min.jsbootstrap-vue.min.min.css.mapbootstrap-vue.min.min.js.map
bootstrap
papaparse
vue-sanitize-directive
vue-sortable
Build
Changelog.mdPlugins/BTCPayServer.Plugins.Test
BTCPayServer.Plugins.Test.csproj
README.mdSECURITY.mdbtcpayserver.slnbtcpayserver.sln.DotSettingsControllers
Data
Migrations
Resources/img
Services
TestExtension.csTestPluginMigrationRunner.csViews
2
.github/codeql/codeql-config.yml
vendored
2
.github/codeql/codeql-config.yml
vendored
@ -1,2 +0,0 @@
|
||||
paths-ignore:
|
||||
- 'BTCPayServer/wwwroot/vendor/**/*.js'
|
80
.github/workflows/codeql.yml
vendored
80
.github/workflows/codeql.yml
vendored
@ -1,80 +0,0 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
# Allow running tests manually. Usefull if scan failure, or need to rescan before next scheduled date.
|
||||
workflow_dispatch:
|
||||
# We scan only on a schedule for now, can uncomment the following to scan on commit or PR merge later on if deemed appropriate.
|
||||
# push:
|
||||
# branches: [ "master" ]
|
||||
# pull_request:
|
||||
# branches: [ "master" ]
|
||||
schedule:
|
||||
# Scan every Monday 06:00 UTC.
|
||||
- cron: '0 6 * * 1'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript', 'csharp' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Use only 'java' to analyze code written in Java, Kotlin or both
|
||||
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
21
.run/Pack Test Extension.run.xml
Normal file
21
.run/Pack Test Extension.run.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Pack Test Plugin" type="DotNetProject" factoryName=".NET Project" singleton="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/BTCPayServer.PluginPacker/bin/Debug/netcoreapp3.1/BTCPayServer.PluginPacker.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="../../../../BTCPayServer.Plugins.Test\bin\Debug\netcoreapp3.1 BTCPayServer.Plugins.Test "../../../../Packed Plugins"" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/BTCPayServer.PluginPacker/bin/Debug/netcoreapp3.1" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/BTCPayServer.PluginPacker/BTCPayServer.PluginPacker.csproj" />
|
||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
|
||||
<method v="2">
|
||||
<option name="Build" default="false" projectName="BTCPayServer.Plugins.Test" projectPath="C:\Git\btcpayserver\Plugins\BTCPayServer.Plugins.Test\BTCPayServer.Plugins.Test.csproj" />
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
@ -22,6 +21,7 @@ namespace BTCPayServer.Abstractions.Contracts
|
||||
}
|
||||
|
||||
public abstract T CreateContext();
|
||||
|
||||
class CustomNpgsqlMigrationsSqlGenerator : NpgsqlMigrationsSqlGenerator
|
||||
{
|
||||
#pragma warning disable EF1001 // Internal EF Core API usage.
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Contracts
|
||||
@ -7,8 +6,5 @@ namespace BTCPayServer.Abstractions.Contracts
|
||||
{
|
||||
Task ApplyAction(string hook, object args);
|
||||
Task<object> ApplyFilter(string hook, object args);
|
||||
|
||||
event EventHandler<(string hook, object args)> ActionInvoked;
|
||||
event EventHandler<(string hook, object args)> FilterInvoked;
|
||||
}
|
||||
}
|
||||
|
@ -69,6 +69,7 @@ public class Form
|
||||
if (!nameReturned.Add(fullName))
|
||||
{
|
||||
errors.Add($"Form contains duplicate field names '{fullName}'");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return errors.Count == 0;
|
||||
@ -85,10 +86,15 @@ public class Form
|
||||
thisPath.Add(field.Name);
|
||||
yield return (thisPath, field);
|
||||
}
|
||||
foreach (var descendant in GetAllFieldsCore(thisPath, field.Fields))
|
||||
|
||||
foreach (var child in field.Fields)
|
||||
{
|
||||
descendant.Field.Constant = field.Constant || descendant.Field.Constant;
|
||||
yield return descendant;
|
||||
if (field.Constant)
|
||||
child.Constant = true;
|
||||
foreach (var descendant in GetAllFieldsCore(thisPath, field.Fields))
|
||||
{
|
||||
yield return descendant;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,33 +6,31 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BTCPayServer.Abstractions.TagHelpers;
|
||||
|
||||
[HtmlTargetElement(Attributes = "[permission]")]
|
||||
[HtmlTargetElement(Attributes = "[not-permission]" )]
|
||||
[HtmlTargetElement(Attributes = nameof(Permission))]
|
||||
public class PermissionTagHelper : TagHelper
|
||||
{
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly ILogger<PermissionTagHelper> _logger;
|
||||
|
||||
public PermissionTagHelper(IAuthorizationService authorizationService, IHttpContextAccessor httpContextAccessor)
|
||||
public PermissionTagHelper(IAuthorizationService authorizationService, IHttpContextAccessor httpContextAccessor, ILogger<PermissionTagHelper> logger)
|
||||
{
|
||||
_authorizationService = authorizationService;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string Permission { get; set; }
|
||||
public string NotPermission { get; set; }
|
||||
public string PermissionResource { get; set; }
|
||||
|
||||
|
||||
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Permission) && string.IsNullOrEmpty(NotPermission))
|
||||
if (string.IsNullOrEmpty(Permission))
|
||||
return;
|
||||
if (_httpContextAccessor.HttpContext is null)
|
||||
return;
|
||||
|
||||
var expectedResult = !string.IsNullOrEmpty(Permission);
|
||||
var key = $"{Permission??NotPermission}_{PermissionResource}";
|
||||
var key = $"{Permission}_{PermissionResource}";
|
||||
if (!_httpContextAccessor.HttpContext.Items.TryGetValue(key, out var o) ||
|
||||
o is not AuthorizationResult res)
|
||||
{
|
||||
@ -41,7 +39,7 @@ public class PermissionTagHelper : TagHelper
|
||||
Permission);
|
||||
_httpContextAccessor.HttpContext.Items.Add(key, res);
|
||||
}
|
||||
if (expectedResult != res.Succeeded)
|
||||
if (!res.Succeeded)
|
||||
{
|
||||
output.SuppressOutput();
|
||||
}
|
||||
|
@ -12,8 +12,6 @@
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<RepositoryUrl>https://github.com/btcpayserver/btcpayserver</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<Configurations>Debug;Release;Altcoins-Debug;Altcoins-Release</Configurations>
|
||||
<Platforms>AnyCPU</Platforms>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Version Condition=" '$(Version)' == '' ">1.7.2</Version>
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
@ -12,11 +11,5 @@ namespace BTCPayServer.Client
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/server/info"), token);
|
||||
return await HandleResponse<ServerInfoData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<List<RoleData>> GetServerRoles(CancellationToken token = default)
|
||||
{
|
||||
using var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/server/roles"), token);
|
||||
return await HandleResponse<List<RoleData>>(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,13 +9,6 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<List<RoleData>> GetStoreRoles(string storeId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/roles"), token);
|
||||
return await HandleResponse<List<RoleData>>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<IEnumerable<StoreUserData>> GetStoreUsers(string storeId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
|
@ -51,8 +51,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
if (message.StatusCode == System.Net.HttpStatusCode.UnprocessableEntity)
|
||||
{
|
||||
var aa = await message.Content.ReadAsStringAsync();
|
||||
var err = JsonConvert.DeserializeObject<Models.GreenfieldValidationError[]>(aa);
|
||||
var err = JsonConvert.DeserializeObject<Models.GreenfieldValidationError[]>(await message.Content.ReadAsStringAsync());
|
||||
throw new GreenfieldValidationException(err);
|
||||
}
|
||||
if (message.StatusCode == System.Net.HttpStatusCode.Forbidden)
|
||||
|
@ -1,11 +1,8 @@
|
||||
#nullable enable
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class CreatePayoutThroughStoreRequest : CreatePayoutRequest
|
||||
{
|
||||
public string? PullPaymentId { get; set; }
|
||||
public bool? Approved { get; set; }
|
||||
public JObject? Metadata { get; set; }
|
||||
}
|
||||
|
@ -86,7 +86,6 @@ namespace BTCPayServer.Client.Models
|
||||
public bool? RequiresRefundEmail { get; set; } = null;
|
||||
public string DefaultLanguage { get; set; }
|
||||
public CheckoutType? CheckoutType { get; set; }
|
||||
public bool? LazyPaymentMethods { get; set; }
|
||||
}
|
||||
}
|
||||
public class InvoiceData : InvoiceDataBase
|
||||
|
@ -3,6 +3,7 @@ namespace BTCPayServer.Client.Models
|
||||
public class LNURLPayPaymentMethodBaseData
|
||||
{
|
||||
public bool UseBech32Scheme { get; set; }
|
||||
public bool EnableForStandardInvoices { get; set; }
|
||||
public bool LUD12Enabled { get; set; }
|
||||
|
||||
public LNURLPayPaymentMethodBaseData()
|
||||
|
@ -16,11 +16,12 @@ namespace BTCPayServer.Client.Models
|
||||
{
|
||||
}
|
||||
|
||||
public LNURLPayPaymentMethodData(string cryptoCode, bool enabled, bool useBech32Scheme)
|
||||
public LNURLPayPaymentMethodData(string cryptoCode, bool enabled, bool useBech32Scheme, bool enableForStandardInvoices)
|
||||
{
|
||||
Enabled = enabled;
|
||||
CryptoCode = cryptoCode;
|
||||
UseBech32Scheme = useBech32Scheme;
|
||||
EnableForStandardInvoices = enableForStandardInvoices;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,9 +10,4 @@ public class LightningAutomatedPayoutSettings
|
||||
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
|
||||
public TimeSpan IntervalSeconds { get; set; }
|
||||
|
||||
public int? CancelPayoutAfterFailures { get; set; }
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
public bool ProcessNewPayoutsInstantly { get; set; }
|
||||
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace BTCPayServer.Client.Models
|
||||
{
|
||||
|
||||
public string ConnectionString { get; set; }
|
||||
public bool DisableBOLT11PaymentOption { get; set; }
|
||||
public LightningNetworkPaymentMethodBaseData()
|
||||
{
|
||||
|
||||
|
@ -16,12 +16,13 @@ namespace BTCPayServer.Client.Models
|
||||
{
|
||||
}
|
||||
|
||||
public LightningNetworkPaymentMethodData(string cryptoCode, string connectionString, bool enabled, string paymentMethod)
|
||||
public LightningNetworkPaymentMethodData(string cryptoCode, string connectionString, bool enabled, string paymentMethod, bool disableBOLT11PaymentOption)
|
||||
{
|
||||
Enabled = enabled;
|
||||
CryptoCode = cryptoCode;
|
||||
ConnectionString = connectionString;
|
||||
PaymentMethod = paymentMethod;
|
||||
DisableBOLT11PaymentOption = disableBOLT11PaymentOption;
|
||||
}
|
||||
|
||||
public string PaymentMethod { get; set; }
|
||||
|
@ -12,8 +12,4 @@ public class OnChainAutomatedPayoutSettings
|
||||
public TimeSpan IntervalSeconds { get; set; }
|
||||
|
||||
public int? FeeBlockTarget { get; set; }
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
public decimal Threshold { get; set; }
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
public bool ProcessNewPayoutsInstantly { get; set; }
|
||||
}
|
||||
|
@ -31,6 +31,5 @@ namespace BTCPayServer.Client.Models
|
||||
public PayoutState State { get; set; }
|
||||
public int Revision { get; set; }
|
||||
public JObject PaymentProof { get; set; }
|
||||
public JObject Metadata { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ namespace BTCPayServer.Client.Models
|
||||
{
|
||||
RateThen,
|
||||
CurrentRate,
|
||||
OverpaidAmount,
|
||||
Fiat,
|
||||
Custom
|
||||
}
|
||||
@ -19,13 +18,8 @@ namespace BTCPayServer.Client.Models
|
||||
public string? Name { get; set; } = null;
|
||||
public string? PaymentMethod { get; set; }
|
||||
public string? Description { get; set; } = null;
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public RefundVariant? RefundVariant { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal SubtractPercentage { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal? CustomAmount { get; set; }
|
||||
public string? CustomCurrency { get; set; }
|
||||
|
@ -16,8 +16,6 @@ namespace BTCPayServer.Client.Models
|
||||
|
||||
public string Website { get; set; }
|
||||
|
||||
public string SupportUrl { get; set; }
|
||||
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public TimeSpan InvoiceExpiration { get; set; } = TimeSpan.FromMinutes(15);
|
||||
|
@ -1,5 +1,3 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class StoreData : StoreBaseData
|
||||
@ -19,12 +17,4 @@ namespace BTCPayServer.Client.Models
|
||||
|
||||
public string Role { get; set; }
|
||||
}
|
||||
|
||||
public class RoleData
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public List<string> Permissions { get; set; }
|
||||
public string Role { get; set; }
|
||||
public bool IsServerRole { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,62 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class StoreReportRequest
|
||||
{
|
||||
public string ViewName { get; set; }
|
||||
public TimePeriod TimePeriod { get; set; }
|
||||
}
|
||||
public class StoreReportResponse
|
||||
{
|
||||
public class Field
|
||||
{
|
||||
public Field()
|
||||
{
|
||||
|
||||
}
|
||||
public Field(string name, string type)
|
||||
{
|
||||
Name = name;
|
||||
Type = type;
|
||||
}
|
||||
public string Name { get; set; }
|
||||
public string Type { get; set; }
|
||||
}
|
||||
public IList<Field> Fields { get; set; } = new List<Field>();
|
||||
public List<JArray> Data { get; set; }
|
||||
public DateTimeOffset From { get; set; }
|
||||
public DateTimeOffset To { get; set; }
|
||||
public List<ChartDefinition> Charts { get; set; }
|
||||
|
||||
public int GetIndex(string fieldName)
|
||||
{
|
||||
return Fields.ToList().FindIndex(f => f.Name == fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
public class ChartDefinition
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public List<string> Groups { get; set; } = new List<string>();
|
||||
public List<string> Totals { get; set; } = new List<string>();
|
||||
public bool HasGrandTotal { get; set; }
|
||||
public List<string> Aggregates { get; set; } = new List<string>();
|
||||
public List<string> Filters { get; set; } = new List<string>();
|
||||
}
|
||||
|
||||
public class TimePeriod
|
||||
{
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset? From { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset? To { get; set; }
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class StoreReportsResponse
|
||||
{
|
||||
public string ViewName { get; set; }
|
||||
public StoreReportResponse.Field[] Fields
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
@ -51,10 +51,6 @@ namespace BTCPayServer.Client.Models
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
[JsonExtensionData]
|
||||
public IDictionary<string, JToken> AdditionalData { get; set; }
|
||||
public bool IsPruned()
|
||||
{
|
||||
return DeliveryId is null;
|
||||
}
|
||||
public T ReadAs<T>()
|
||||
{
|
||||
var str = JsonConvert.SerializeObject(this, DefaultSerializerSettings);
|
||||
|
@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
@ -136,7 +134,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
static Permission()
|
||||
{
|
||||
PolicyMap = Init();
|
||||
Init();
|
||||
}
|
||||
|
||||
public static Permission Create(string policy, string scope = null)
|
||||
@ -237,13 +235,11 @@ namespace BTCPayServer.Client
|
||||
return subPolicies.Contains(subpolicy) || subPolicies.Any(s => ContainsPolicy(s, subpolicy));
|
||||
}
|
||||
|
||||
public static ReadOnlyDictionary<string, HashSet<string>> PolicyMap { get; private set; }
|
||||
|
||||
private static Dictionary<string, HashSet<string>> PolicyMap = new();
|
||||
|
||||
private static ReadOnlyDictionary<string, HashSet<string>> Init()
|
||||
private static void Init()
|
||||
{
|
||||
var policyMap = new Dictionary<string, HashSet<string>>();
|
||||
PolicyHasChild(policyMap, Policies.CanModifyStoreSettings,
|
||||
PolicyHasChild(Policies.CanModifyStoreSettings,
|
||||
Policies.CanManageCustodianAccounts,
|
||||
Policies.CanManagePullPayments,
|
||||
Policies.CanModifyInvoices,
|
||||
@ -252,42 +248,25 @@ namespace BTCPayServer.Client
|
||||
Policies.CanModifyPaymentRequests,
|
||||
Policies.CanUseLightningNodeInStore);
|
||||
|
||||
PolicyHasChild(policyMap,Policies.CanManageUsers, Policies.CanCreateUser);
|
||||
PolicyHasChild(policyMap,Policies.CanManagePullPayments, Policies.CanCreatePullPayments);
|
||||
PolicyHasChild(policyMap,Policies.CanCreatePullPayments, Policies.CanCreateNonApprovedPullPayments);
|
||||
PolicyHasChild(policyMap,Policies.CanModifyPaymentRequests, Policies.CanViewPaymentRequests);
|
||||
PolicyHasChild(policyMap,Policies.CanModifyProfile, Policies.CanViewProfile);
|
||||
PolicyHasChild(policyMap,Policies.CanUseLightningNodeInStore, Policies.CanViewLightningInvoiceInStore, Policies.CanCreateLightningInvoiceInStore);
|
||||
PolicyHasChild(policyMap,Policies.CanManageNotificationsForUser, Policies.CanViewNotificationsForUser);
|
||||
PolicyHasChild(policyMap,Policies.CanModifyServerSettings,
|
||||
PolicyHasChild(Policies.CanManageUsers, Policies.CanCreateUser);
|
||||
PolicyHasChild(Policies.CanManagePullPayments, Policies.CanCreatePullPayments);
|
||||
PolicyHasChild(Policies.CanCreatePullPayments, Policies.CanCreateNonApprovedPullPayments);
|
||||
PolicyHasChild(Policies.CanModifyPaymentRequests, Policies.CanViewPaymentRequests);
|
||||
PolicyHasChild(Policies.CanModifyProfile, Policies.CanViewProfile);
|
||||
PolicyHasChild(Policies.CanUseLightningNodeInStore, Policies.CanViewLightningInvoiceInStore, Policies.CanCreateLightningInvoiceInStore);
|
||||
PolicyHasChild(Policies.CanManageNotificationsForUser, Policies.CanViewNotificationsForUser);
|
||||
PolicyHasChild(Policies.CanModifyServerSettings,
|
||||
Policies.CanUseInternalLightningNode,
|
||||
Policies.CanManageUsers);
|
||||
PolicyHasChild(policyMap, Policies.CanUseInternalLightningNode, Policies.CanCreateLightningInvoiceInternalNode, Policies.CanViewLightningInvoiceInternalNode);
|
||||
PolicyHasChild(policyMap, Policies.CanManageCustodianAccounts, Policies.CanViewCustodianAccounts);
|
||||
PolicyHasChild(policyMap, Policies.CanModifyInvoices, Policies.CanViewInvoices, Policies.CanCreateInvoice, Policies.CanCreateLightningInvoiceInStore);
|
||||
PolicyHasChild(policyMap, Policies.CanViewStoreSettings, Policies.CanViewInvoices, Policies.CanViewPaymentRequests);
|
||||
|
||||
var missingPolicies = Policies.AllPolicies.ToHashSet();
|
||||
//recurse through the tree to see which policies are not included in the tree
|
||||
foreach (var policy in policyMap)
|
||||
{
|
||||
missingPolicies.Remove(policy.Key);
|
||||
foreach (var subPolicy in policy.Value)
|
||||
{
|
||||
missingPolicies.Remove(subPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var missingPolicy in missingPolicies)
|
||||
{
|
||||
policyMap.Add(missingPolicy, new HashSet<string>());
|
||||
}
|
||||
return new ReadOnlyDictionary<string, HashSet<string>>(policyMap);
|
||||
PolicyHasChild(Policies.CanUseInternalLightningNode, Policies.CanCreateLightningInvoiceInternalNode, Policies.CanViewLightningInvoiceInternalNode);
|
||||
PolicyHasChild(Policies.CanManageCustodianAccounts, Policies.CanViewCustodianAccounts);
|
||||
PolicyHasChild(Policies.CanModifyInvoices, Policies.CanViewInvoices, Policies.CanCreateInvoice, Policies.CanCreateLightningInvoiceInStore);
|
||||
PolicyHasChild(Policies.CanViewStoreSettings, Policies.CanViewInvoices, Policies.CanViewPaymentRequests);
|
||||
}
|
||||
|
||||
private static void PolicyHasChild(Dictionary<string, HashSet<string>>policyMap, string policy, params string[] subPolicies)
|
||||
private static void PolicyHasChild(string policy, params string[] subPolicies)
|
||||
{
|
||||
if (policyMap.TryGetValue(policy, out var existingSubPolicies))
|
||||
if (PolicyMap.TryGetValue(policy, out var existingSubPolicies))
|
||||
{
|
||||
foreach (string subPolicy in subPolicies)
|
||||
{
|
||||
@ -296,7 +275,7 @@ namespace BTCPayServer.Client
|
||||
}
|
||||
else
|
||||
{
|
||||
policyMap.Add(policy, subPolicies.ToHashSet());
|
||||
PolicyMap.Add(policy, subPolicies.ToHashSet());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ namespace BTCPayServer
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"BTG_X = BTG_BTC * BTC_X",
|
||||
"BTG_BTC = exmo(BTG_BTC)",
|
||||
"BTG_BTC = bitfinex(BTG_BTC)",
|
||||
},
|
||||
CryptoImagePath = "imlegacy/btg.svg",
|
||||
LightningImagePath = "imlegacy/btg-lightning.svg",
|
||||
|
@ -17,7 +17,7 @@ namespace BTCPayServer
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"BTX_X = BTX_BTC * BTC_X",
|
||||
"BTX_BTC = graviex(BTX_BTC)"
|
||||
"BTX_BTC = hitbtc(BTX_BTC)"
|
||||
},
|
||||
CryptoImagePath = "imlegacy/bitcore.svg",
|
||||
LightningImagePath = "imlegacy/bitcore-lightning.svg",
|
||||
|
@ -0,0 +1,32 @@
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public partial class BTCPayNetworkProvider
|
||||
{
|
||||
public void InitChaincoin()
|
||||
{
|
||||
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("CHC");
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Chaincoin",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet
|
||||
? "https://explorer.chaincoin.org/Explorer/Transaction/{0}"
|
||||
: "https://test.explorer.chaincoin.org/Explorer/Transaction/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"CHC_X = CHC_BTC * BTC_X",
|
||||
"CHC_BTC = txbit(CHC_X)"
|
||||
},
|
||||
CryptoImagePath = "imlegacy/chaincoin.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
//https://github.com/satoshilabs/slips/blob/master/slip-0044.md
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("711'")
|
||||
: new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,6 @@ namespace BTCPayServer
|
||||
"LCAD_CAD = 1",
|
||||
"LCAD_X = CAD_BTC * BTC_X",
|
||||
"LCAD_BTC = bylls(CAD_BTC)",
|
||||
"CAD_BTC = LCAD_BTC"
|
||||
},
|
||||
AssetId = new uint256("0e99c1a6da379d1f4151fb9df90449d40d0608f6cb33a5bcbfc8c265f42bab0a"),
|
||||
DisplayName = "Liquid CAD",
|
||||
|
@ -1,5 +1,4 @@
|
||||
#if ALTCOINS
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Common;
|
||||
@ -35,12 +34,12 @@ namespace BTCPayServer
|
||||
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId));
|
||||
}
|
||||
|
||||
public override PaymentUrlBuilder GenerateBIP21(string cryptoInfoAddress, decimal? cryptoInfoDue)
|
||||
public override PaymentUrlBuilder GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
|
||||
{
|
||||
//precision 0: 10 = 0.00000010
|
||||
//precision 2: 10 = 0.00001000
|
||||
//precision 8: 10 = 10
|
||||
var money = cryptoInfoDue / (decimal)Math.Pow(10, 8 - Divisibility);
|
||||
var money = cryptoInfoDue is null ? null : new Money(cryptoInfoDue.ToDecimal(MoneyUnit.BTC) / decimal.Parse("1".PadRight(1 + 8 - Divisibility, '0')), MoneyUnit.BTC);
|
||||
var builder = base.GenerateBIP21(cryptoInfoAddress, money);
|
||||
builder.QueryParams.Add("assetid", AssetId.ToString());
|
||||
return builder;
|
||||
|
@ -45,10 +45,10 @@ namespace BTCPayServer.Services.Altcoins.Monero.RPC
|
||||
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Basic",
|
||||
Convert.ToBase64String(Encoding.Default.GetBytes($"{_username}:{_password}")));
|
||||
|
||||
HttpResponseMessage rawResult = await _httpClient.SendAsync(httpRequest, cts);
|
||||
rawResult.EnsureSuccessStatusCode();
|
||||
var rawResult = await _httpClient.SendAsync(httpRequest, cts);
|
||||
|
||||
var rawJson = await rawResult.Content.ReadAsStringAsync();
|
||||
|
||||
rawResult.EnsureSuccessStatusCode();
|
||||
JsonRpcResult<TResponse> response;
|
||||
try
|
||||
{
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Common;
|
||||
@ -88,13 +87,13 @@ namespace BTCPayServer
|
||||
});
|
||||
}
|
||||
|
||||
public virtual PaymentUrlBuilder GenerateBIP21(string cryptoInfoAddress, decimal? cryptoInfoDue)
|
||||
public virtual PaymentUrlBuilder GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
|
||||
{
|
||||
var builder = new PaymentUrlBuilder(this.NBitcoinNetwork.UriScheme);
|
||||
builder.Host = cryptoInfoAddress;
|
||||
if (cryptoInfoDue is not null && cryptoInfoDue.Value != 0.0m)
|
||||
if (cryptoInfoDue != null && cryptoInfoDue != Money.Zero)
|
||||
{
|
||||
builder.QueryParams.Add("amount", cryptoInfoDue.Value.ToString(CultureInfo.InvariantCulture));
|
||||
builder.QueryParams.Add("amount", cryptoInfoDue.ToString(false, true));
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ namespace BTCPayServer
|
||||
InitViacoin();
|
||||
InitMonero();
|
||||
InitZcash();
|
||||
InitChaincoin();
|
||||
// InitArgoneum();//their rate source is down 9/15/20.
|
||||
// InitMonetaryUnit(); Not supported from Bittrex from 11/23/2022, dead shitcoin
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="4.2.5" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="4.2.3" />
|
||||
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Altcoins)' != 'true'">
|
||||
|
@ -64,7 +64,6 @@ namespace BTCPayServer.Data
|
||||
public DbSet<U2FDevice> U2FDevices { get; set; }
|
||||
public DbSet<Fido2Credential> Fido2Credentials { get; set; }
|
||||
public DbSet<UserStore> UserStore { get; set; }
|
||||
public DbSet<StoreRole> StoreRoles { get; set; }
|
||||
[Obsolete]
|
||||
public DbSet<WalletData> Wallets { get; set; }
|
||||
public DbSet<WalletObjectData> WalletObjects { get; set; }
|
||||
@ -130,7 +129,6 @@ namespace BTCPayServer.Data
|
||||
PayoutProcessorData.OnModelCreating(builder, Database);
|
||||
WebhookData.OnModelCreating(builder, Database);
|
||||
FormData.OnModelCreating(builder, Database);
|
||||
StoreRole.OnModelCreating(builder, Database);
|
||||
|
||||
|
||||
if (Database.IsSqlite() && !_designTime)
|
||||
|
@ -1,6 +1,3 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Data;
|
||||
@ -43,7 +41,4 @@ public class LightningAddressDataBlob
|
||||
public decimal? Max { get; set; }
|
||||
|
||||
public JObject InvoiceMetadata { get; set; }
|
||||
|
||||
[JsonExtensionData] public Dictionary<string, JToken> AdditionalData { get; set; }
|
||||
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ namespace BTCPayServer.Data;
|
||||
public class AutomatedPayoutBlob
|
||||
{
|
||||
public TimeSpan Interval { get; set; } = TimeSpan.FromHours(1);
|
||||
public bool ProcessNewPayoutsInstantly { get; set; }
|
||||
}
|
||||
public class PayoutProcessorData : IHasBlobUntyped
|
||||
{
|
||||
|
@ -1,10 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using PayoutProcessorData = BTCPayServer.Data.PayoutProcessorData;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
@ -34,6 +37,8 @@ namespace BTCPayServer.Data
|
||||
|
||||
public byte[] StoreCertificate { get; set; }
|
||||
|
||||
[NotMapped] public string Role { get; set; }
|
||||
|
||||
public string StoreBlob { get; set; }
|
||||
|
||||
[Obsolete("Use GetDefaultPaymentId instead")]
|
||||
@ -47,7 +52,6 @@ namespace BTCPayServer.Data
|
||||
public IEnumerable<CustodianAccountData> CustodianAccounts { get; set; }
|
||||
public IEnumerable<StoreSettingData> Settings { get; set; }
|
||||
public IEnumerable<FormData> Forms { get; set; }
|
||||
public IEnumerable<StoreRole> StoreRoles { get; set; }
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
|
||||
{
|
||||
|
@ -1,50 +0,0 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Data;
|
||||
|
||||
public class StoreRole
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string StoreDataId { get; set; }
|
||||
public string Role { get; set; }
|
||||
public List<string> Permissions { get; set; }
|
||||
public List<UserStore> Users { get; set; }
|
||||
public StoreData StoreData { get; set; }
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
|
||||
{
|
||||
builder.Entity<StoreRole>(entity =>
|
||||
{
|
||||
entity.HasOne(e => e.StoreData)
|
||||
.WithMany(s => s.StoreRoles)
|
||||
.HasForeignKey(e => e.StoreDataId)
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired(false);
|
||||
|
||||
entity.HasIndex(entity => new {entity.StoreDataId, entity.Role}).IsUnique();
|
||||
});
|
||||
|
||||
|
||||
|
||||
if (!databaseFacade.IsNpgsql())
|
||||
{
|
||||
builder.Entity<StoreRole>()
|
||||
.Property(o => o.Permissions)
|
||||
.HasConversion(
|
||||
v => JsonConvert.SerializeObject(v),
|
||||
v => JsonConvert.DeserializeObject<List<string>>(v)?? new List<string>(),
|
||||
new ValueComparer<List<string>>(
|
||||
(c1, c2) => c1 ==c2 || c1 != null && c2 != null && c1.SequenceEqual(c2),
|
||||
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
|
||||
c => c.ToList()));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
@ -10,10 +9,7 @@ namespace BTCPayServer.Data
|
||||
|
||||
public string StoreDataId { get; set; }
|
||||
public StoreData StoreData { get; set; }
|
||||
[Column("Role")]
|
||||
public string StoreRoleId { get; set; }
|
||||
public StoreRole StoreRole { get; set; }
|
||||
|
||||
public string Role { get; set; }
|
||||
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder)
|
||||
@ -36,10 +32,6 @@ namespace BTCPayServer.Data
|
||||
.HasOne(pt => pt.StoreData)
|
||||
.WithMany(t => t.UserStores)
|
||||
.HasForeignKey(pt => pt.StoreDataId);
|
||||
|
||||
builder.Entity<UserStore>().HasOne(e => e.StoreRole)
|
||||
.WithMany(role => role.Users)
|
||||
.HasForeignKey(e => e.StoreRoleId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class WebhookDeliveryData
|
||||
public class WebhookDeliveryData : IHasBlobUntyped
|
||||
{
|
||||
[Key]
|
||||
[MaxLength(25)]
|
||||
@ -17,8 +17,10 @@ namespace BTCPayServer.Data
|
||||
|
||||
[Required]
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
public string Blob { get; set; }
|
||||
public bool Pruned { get; set; }
|
||||
[Obsolete("Use Blob2 instead")]
|
||||
public byte[] Blob { get; set; }
|
||||
public string Blob2 { get; set; }
|
||||
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
|
||||
{
|
||||
@ -26,11 +28,11 @@ namespace BTCPayServer.Data
|
||||
.HasOne(o => o.Webhook)
|
||||
.WithMany(a => a.Deliveries).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.Entity<WebhookDeliveryData>().HasIndex(o => o.WebhookId);
|
||||
builder.Entity<WebhookDeliveryData>().HasIndex(o => o.Timestamp);
|
||||
|
||||
if (databaseFacade.IsNpgsql())
|
||||
{
|
||||
builder.Entity<WebhookDeliveryData>()
|
||||
.Property(o => o.Blob)
|
||||
.Property(o => o.Blob2)
|
||||
.HasColumnType("JSONB");
|
||||
}
|
||||
}
|
||||
|
@ -1,106 +0,0 @@
|
||||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20230504125505_StoreRoles")]
|
||||
public partial class StoreRoles : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var permissionsType = migrationBuilder.IsNpgsql() ? "TEXT[]" : "TEXT";
|
||||
migrationBuilder.CreateTable(
|
||||
name: "StoreRoles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
StoreDataId = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Role = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Permissions = table.Column<string>(type: permissionsType, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_StoreRoles", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_StoreRoles_Stores_StoreDataId",
|
||||
column: x => x.StoreDataId,
|
||||
principalTable: "Stores",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_StoreRoles_StoreDataId_Role",
|
||||
table: "StoreRoles",
|
||||
columns: new[] { "StoreDataId", "Role" },
|
||||
unique: true);
|
||||
|
||||
object GetPermissionsData(string[] permissions)
|
||||
{
|
||||
if (migrationBuilder.IsNpgsql())
|
||||
return permissions;
|
||||
return JsonConvert.SerializeObject(permissions);
|
||||
}
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
"StoreRoles",
|
||||
columns: new[] { "Id", "Role", "Permissions" },
|
||||
columnTypes: new[] { "TEXT", "TEXT", permissionsType },
|
||||
values: new object[,]
|
||||
{
|
||||
{
|
||||
"Owner", "Owner", GetPermissionsData(new[]
|
||||
{
|
||||
"btcpay.store.canmodifystoresettings",
|
||||
"btcpay.store.cantradecustodianaccount",
|
||||
"btcpay.store.canwithdrawfromcustodianaccount",
|
||||
"btcpay.store.candeposittocustodianaccount"
|
||||
})
|
||||
},
|
||||
{
|
||||
"Guest", "Guest", GetPermissionsData(new[]
|
||||
{
|
||||
"btcpay.store.canviewstoresettings",
|
||||
"btcpay.store.canmodifyinvoices",
|
||||
"btcpay.store.canviewcustodianaccounts",
|
||||
"btcpay.store.candeposittocustodianaccount"
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
if (this.SupportAddForeignKey(migrationBuilder.ActiveProvider))
|
||||
{
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_UserStore_StoreRoles_Role",
|
||||
table: "UserStore",
|
||||
column: "Role",
|
||||
principalTable: "StoreRoles",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
if (this.SupportDropForeignKey(migrationBuilder.ActiveProvider))
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_UserStore_StoreRoles_Role",
|
||||
table: "UserStore");
|
||||
}
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "StoreRoles");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20230529135505_WebhookDeliveriesCleanup")]
|
||||
public partial class WebhookDeliveriesCleanup : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (migrationBuilder.IsNpgsql())
|
||||
{
|
||||
migrationBuilder.Sql("DROP TABLE IF EXISTS \"InvoiceWebhookDeliveries\", \"WebhookDeliveries\";");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "WebhookDeliveries",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
WebhookId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Timestamp = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
||||
Pruned = table.Column<bool>(type: "BOOLEAN", nullable: false),
|
||||
Blob = table.Column<string>(type: "JSONB", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_WebhookDeliveries", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_WebhookDeliveries_Webhooks_WebhookId",
|
||||
column: x => x.WebhookId,
|
||||
principalTable: "Webhooks",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WebhookDeliveries_WebhookId",
|
||||
table: "WebhookDeliveries",
|
||||
column: "WebhookId");
|
||||
migrationBuilder.Sql("CREATE INDEX \"IX_WebhookDeliveries_Timestamp\" ON \"WebhookDeliveries\"(\"Timestamp\") WHERE \"Pruned\" IS FALSE");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "InvoiceWebhookDeliveries",
|
||||
columns: table => new
|
||||
{
|
||||
InvoiceId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
DeliveryId = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_InvoiceWebhookDeliveries", x => new { x.InvoiceId, x.DeliveryId });
|
||||
table.ForeignKey(
|
||||
name: "FK_InvoiceWebhookDeliveries_WebhookDeliveries_DeliveryId",
|
||||
column: x => x.DeliveryId,
|
||||
principalTable: "WebhookDeliveries",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_InvoiceWebhookDeliveries_Invoices_InvoiceId",
|
||||
column: x => x.InvoiceId,
|
||||
principalTable: "Invoices",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -214,6 +214,56 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("CustodianAccount");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.FormData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Config")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Public")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("StoreId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreId");
|
||||
|
||||
b.ToTable("Forms");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PayoutProcessorData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<string>("PaymentMethod")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Processor")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StoreId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreId");
|
||||
|
||||
b.ToTable("PayoutProcessors");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.Fido2Credential", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -242,31 +292,6 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("Fido2Credentials");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.FormData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Config")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Public")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("StoreId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreId");
|
||||
|
||||
b.ToTable("Forms");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -630,34 +655,6 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("Payouts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PayoutProcessorData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<string>("Blob2")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PaymentMethod")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Processor")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StoreId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreId");
|
||||
|
||||
b.ToTable("PayoutProcessors");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -805,28 +802,6 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("Files");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Permissions")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Role")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StoreDataId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreDataId", "Role")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("StoreRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreSettingData", b =>
|
||||
{
|
||||
b.Property<string>("StoreId")
|
||||
@ -903,16 +878,13 @@ namespace BTCPayServer.Migrations
|
||||
b.Property<string>("StoreDataId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StoreRoleId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("Role");
|
||||
b.Property<string>("Role")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("ApplicationUserId", "StoreDataId");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.HasIndex("StoreRoleId");
|
||||
|
||||
b.ToTable("UserStore");
|
||||
});
|
||||
|
||||
@ -1019,11 +991,11 @@ namespace BTCPayServer.Migrations
|
||||
.HasMaxLength(25)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Blob")
|
||||
.HasColumnType("TEXT");
|
||||
b.Property<byte[]>("Blob")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<bool>("Pruned")
|
||||
.HasColumnType("INTEGER");
|
||||
b.Property<string>("Blob2")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("Timestamp")
|
||||
.HasColumnType("TEXT");
|
||||
@ -1035,8 +1007,6 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Timestamp");
|
||||
|
||||
b.HasIndex("WebhookId");
|
||||
|
||||
b.ToTable("WebhookDeliveries");
|
||||
@ -1218,16 +1188,6 @@ namespace BTCPayServer.Migrations
|
||||
b.Navigation("StoreData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.Fido2Credential", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("Fido2Credentials")
|
||||
.HasForeignKey("ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("ApplicationUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.FormData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "Store")
|
||||
@ -1238,6 +1198,26 @@ namespace BTCPayServer.Migrations
|
||||
b.Navigation("Store");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PayoutProcessorData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "Store")
|
||||
.WithMany("PayoutProcessors")
|
||||
.HasForeignKey("StoreId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("Store");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.Fido2Credential", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("Fido2Credentials")
|
||||
.HasForeignKey("ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("ApplicationUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
@ -1363,16 +1343,6 @@ namespace BTCPayServer.Migrations
|
||||
b.Navigation("StoreData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PayoutProcessorData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "Store")
|
||||
.WithMany("PayoutProcessors")
|
||||
.HasForeignKey("StoreId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("Store");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
@ -1422,16 +1392,6 @@ namespace BTCPayServer.Migrations
|
||||
b.Navigation("ApplicationUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreRole", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("StoreRoles")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("StoreData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreSettingData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "Store")
|
||||
@ -1486,15 +1446,9 @@ namespace BTCPayServer.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BTCPayServer.Data.StoreRole", "StoreRole")
|
||||
.WithMany("Users")
|
||||
.HasForeignKey("StoreRoleId");
|
||||
|
||||
b.Navigation("ApplicationUser");
|
||||
|
||||
b.Navigation("StoreData");
|
||||
|
||||
b.Navigation("StoreRole");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WalletObjectLinkData", b =>
|
||||
@ -1652,16 +1606,9 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.Navigation("Settings");
|
||||
|
||||
b.Navigation("StoreRoles");
|
||||
|
||||
b.Navigation("UserStores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreRole", b =>
|
||||
{
|
||||
b.Navigation("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WalletData", b =>
|
||||
{
|
||||
b.Navigation("WalletTransactions");
|
||||
|
@ -15,7 +15,7 @@ namespace BTCPayServer.Rating
|
||||
while (true)
|
||||
{
|
||||
var rounded = decimal.Round(value, divisibility, MidpointRounding.AwayFromZero);
|
||||
if ((Math.Abs(rounded - value) / value) < 0.01m)
|
||||
if ((Math.Abs(rounded - value) / value) < 0.001m)
|
||||
{
|
||||
value = rounded;
|
||||
break;
|
||||
|
@ -1,40 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Rating;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Services.Rates;
|
||||
|
||||
|
||||
public class ExchangeRateHostRateProvider : IRateProvider
|
||||
{
|
||||
public RateSourceInfo RateSourceInfo => new("exchangeratehost", "Yadio", "https://api.exchangerate.host/latest?base=BTC");
|
||||
private readonly HttpClient _httpClient;
|
||||
public ExchangeRateHostRateProvider(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
}
|
||||
|
||||
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _httpClient.GetAsync(RateSourceInfo.Url, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
|
||||
if(jobj["success"].Value<bool>() is not true || !jobj["base"].Value<string>().Equals("BTC", StringComparison.InvariantCulture))
|
||||
throw new Exception("exchangerate.host returned a non success response or the base currency was not the requested one (BTC)");
|
||||
var results = (JObject) jobj["rates"] ;
|
||||
//key value is currency code to rate value
|
||||
var list = new List<PairRate>();
|
||||
foreach (var item in results)
|
||||
{
|
||||
string name = item.Key;
|
||||
var value = item.Value.Value<decimal>();
|
||||
list.Add(new PairRate(new CurrencyPair("BTC", name), new BidAsk(value)));
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Rating;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Services.Rates;
|
||||
|
||||
public class FreeCurrencyRatesRateProvider : IRateProvider
|
||||
{
|
||||
public RateSourceInfo RateSourceInfo => new("free-currency-rates", "Free Currency Rates", "https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@1/latest/currencies/btc.min.json");
|
||||
private readonly HttpClient _httpClient;
|
||||
public FreeCurrencyRatesRateProvider(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
}
|
||||
|
||||
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _httpClient.GetAsync(RateSourceInfo.Url, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
|
||||
var results = (JObject) jobj["btc"] ;
|
||||
//key value is currency code to rate value
|
||||
var list = new List<PairRate>();
|
||||
foreach (var item in results)
|
||||
{
|
||||
string name = item.Key;
|
||||
var value = item.Value.Value<decimal>();
|
||||
list.Add(new PairRate(new CurrencyPair("BTC", name), new BidAsk(value)));
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ using System.Threading.Tasks;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
@ -52,7 +51,6 @@ namespace BTCPayServer.Tests
|
||||
user.RegisterDerivationScheme(cryptoCode);
|
||||
user.RegisterDerivationScheme("LTC");
|
||||
user.RegisterLightningNode(cryptoCode, LightningConnectionType.CLightning);
|
||||
user.SetLNUrl("BTC", false);
|
||||
var btcNetwork = tester.PayTester.Networks.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
@ -246,7 +244,7 @@ namespace BTCPayServer.Tests
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess(true);
|
||||
user.RegisterLightningNode("BTC");
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.Charge);
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
user.RegisterDerivationScheme("LTC");
|
||||
|
||||
@ -652,7 +650,6 @@ donation:
|
||||
price: 1.02
|
||||
custom: true
|
||||
";
|
||||
vmpos.Template = AppService.SerializeTemplate(MigrationStartupTask.ParsePOSYML(vmpos.Template));
|
||||
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
Assert.Equal("hello", vmpos.Title);
|
||||
@ -664,12 +661,13 @@ donation:
|
||||
Assert.Equal("good apple", vmview.Items[0].Title);
|
||||
Assert.Equal("orange", vmview.Items[1].Title);
|
||||
Assert.Equal(10.0m, vmview.Items[1].Price.Value);
|
||||
Assert.Equal("$5.00", vmview.Items[0].Price.Formatted);
|
||||
Assert.Equal("{0} Purchase", vmview.ButtonText);
|
||||
Assert.Equal("Nicolas Sexy Hair", vmview.CustomButtonText);
|
||||
Assert.Equal("Wanna tip?", vmview.CustomTipText);
|
||||
Assert.Equal("15,18,20", string.Join(',', vmview.CustomTipPercentages));
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, choiceKey: "orange").Result);
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, null, null, null, null, "orange").Result);
|
||||
|
||||
//
|
||||
var invoices = await user.BitPay.GetInvoicesAsync();
|
||||
@ -678,7 +676,7 @@ donation:
|
||||
Assert.Equal("CAD", orangeInvoice.Currency);
|
||||
Assert.Equal("orange", orangeInvoice.ItemDesc);
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, choiceKey: "apple").Result);
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, null, null, null, null, "apple").Result);
|
||||
|
||||
invoices = user.BitPay.GetInvoices();
|
||||
var appleInvoice = invoices.SingleOrDefault(invoice => invoice.ItemCode.Equals("apple"));
|
||||
@ -687,7 +685,7 @@ donation:
|
||||
|
||||
// testing custom amount
|
||||
var action = Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 6.6m, choiceKey: "donation").Result);
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 6.6m, null, null, null, null, "donation").Result);
|
||||
Assert.Equal(nameof(UIInvoiceController.Checkout), action.ActionName);
|
||||
invoices = user.BitPay.GetInvoices();
|
||||
var donationInvoice = invoices.Single(i => i.Price == 6.6m);
|
||||
@ -724,7 +722,6 @@ donation:
|
||||
price: 1.02
|
||||
custom: true
|
||||
";
|
||||
vmpos.Template = AppService.SerializeTemplate(MigrationStartupTask.ParsePOSYML(vmpos.Template));
|
||||
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
publicApps = user.GetController<UIPointOfSaleController>();
|
||||
vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync<ViewPointOfSaleViewModel>();
|
||||
@ -752,28 +749,26 @@ inventoryitem:
|
||||
inventory: 1
|
||||
noninventoryitem:
|
||||
price: 10.0";
|
||||
|
||||
vmpos.Template = AppService.SerializeTemplate(MigrationStartupTask.ParsePOSYML(vmpos.Template));
|
||||
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
|
||||
//inventoryitem has 1 item available
|
||||
await tester.WaitForEvent<AppInventoryUpdaterHostedService.UpdateAppInventory>(() =>
|
||||
{
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "inventoryitem").Result);
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, null, null, null, null, "inventoryitem").Result);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
//we already bought all available stock so this should fail
|
||||
await Task.Delay(100);
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "inventoryitem").Result);
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, null, null, null, null, "inventoryitem").Result);
|
||||
|
||||
//inventoryitem has unlimited items available
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "noninventoryitem").Result);
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, null, null, null, null, "noninventoryitem").Result);
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "noninventoryitem").Result);
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, null, null, null, null, "noninventoryitem").Result);
|
||||
|
||||
//verify invoices where created
|
||||
invoices = user.BitPay.GetInvoices();
|
||||
@ -784,13 +779,15 @@ noninventoryitem:
|
||||
|
||||
//let's mark the inventoryitem invoice as invalid, this should return the item to back in stock
|
||||
var controller = tester.PayTester.GetController<UIInvoiceController>(user.UserId, user.StoreId);
|
||||
var appService = tester.PayTester.GetService<AppService>();
|
||||
var eventAggregator = tester.PayTester.GetService<EventAggregator>();
|
||||
Assert.IsType<JsonResult>(await controller.ChangeInvoiceState(inventoryItemInvoice.Id, "invalid"));
|
||||
//check that item is back in stock
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
Assert.Equal(1,
|
||||
AppService.Parse(vmpos.Template).Single(item => item.Id == "inventoryitem").Inventory);
|
||||
appService.Parse(vmpos.Template, "BTC").Single(item => item.Id == "inventoryitem").Inventory);
|
||||
}, 10000);
|
||||
|
||||
//test payment methods option
|
||||
@ -805,13 +802,11 @@ btconly:
|
||||
- BTC
|
||||
normal:
|
||||
price: 1.0";
|
||||
|
||||
vmpos.Template = AppService.SerializeTemplate(MigrationStartupTask.ParsePOSYML(vmpos.Template));
|
||||
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "btconly").Result);
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, null, null, null, null, "btconly").Result);
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "normal").Result);
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, null, null, null, null, "normal").Result);
|
||||
invoices = user.BitPay.GetInvoices();
|
||||
var normalInvoice = invoices.Single(invoice => invoice.ItemCode == "normal");
|
||||
var btcOnlyInvoice = invoices.Single(invoice => invoice.ItemCode == "btconly");
|
||||
@ -851,21 +846,20 @@ g:
|
||||
custom: topup
|
||||
";
|
||||
|
||||
vmpos.Template = AppService.SerializeTemplate(MigrationStartupTask.ParsePOSYML(vmpos.Template));
|
||||
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
Assert.DoesNotContain("custom", vmpos.Template);
|
||||
var items = AppService.Parse(vmpos.Template);
|
||||
Assert.Contains(items, item => item.Id == "a" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "b" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "c" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Minimum);
|
||||
Assert.Contains(items, item => item.Id == "d" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "e" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Minimum);
|
||||
Assert.Contains(items, item => item.Id == "f" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Topup);
|
||||
Assert.Contains(items, item => item.Id == "g" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Topup);
|
||||
|
||||
var items = appService.Parse(vmpos.Template, vmpos.Currency);
|
||||
Assert.Contains(items, item => item.Id == "a" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "b" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "c" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Minimum);
|
||||
Assert.Contains(items, item => item.Id == "d" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Fixed);
|
||||
Assert.Contains(items, item => item.Id == "e" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Minimum);
|
||||
Assert.Contains(items, item => item.Id == "f" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup);
|
||||
Assert.Contains(items, item => item.Id == "g" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup);
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Static, choiceKey: "g").Result);
|
||||
.ViewPointOfSale(app.Id, PosViewType.Static, null, null, null, null, null, "g").Result);
|
||||
invoices = user.BitPay.GetInvoices();
|
||||
var topupInvoice = invoices.Single(invoice => invoice.ItemCode == "g");
|
||||
Assert.Equal(0, topupInvoice.Price);
|
||||
|
@ -23,7 +23,7 @@
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
|
||||
<PackageReference Include="Selenium.Support" Version="4.1.1" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="4.1.1" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="114.0.5735.9000" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="110.0.5481.7700" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
@ -164,7 +164,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC_LightningLike");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
Assert.Equal("Bitcoin (Lightning)", s.Driver.FindElement(By.ClassName("payment__currencies")).Text);
|
||||
Assert.Equal("Bitcoin (Lightning) (BTC)", s.Driver.FindElement(By.ClassName("payment__currencies")).Text);
|
||||
s.Driver.Quit();
|
||||
}
|
||||
|
||||
@ -187,7 +187,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var invoiceId = s.CreateInvoice(10, "USD", "a@g.com");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
Assert.Contains("sats", s.Driver.FindElement(By.ClassName("buyerTotalLine")).Text);
|
||||
Assert.Contains("sats", s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Text);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
|
@ -1,9 +1,13 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Views.Stores;
|
||||
using NBitcoin;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Support.Extensions;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
@ -36,13 +40,19 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// Configure store url
|
||||
var storeUrl = "https://satoshisteaks.com/";
|
||||
var supportUrl = "https://support.satoshisteaks.com/{InvoiceId}/";
|
||||
s.GoToStore();
|
||||
s.Driver.FindElement(By.Id("StoreWebsite")).SendKeys(storeUrl);
|
||||
s.Driver.FindElement(By.Id("StoreSupportUrl")).SendKeys(supportUrl);
|
||||
s.Driver.FindElement(By.Id("Save")).Click();
|
||||
Assert.Contains("Store successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
// Enable LNURL, which we will need for (non-)presence checks throughout this test
|
||||
s.GoToHome();
|
||||
s.GoToLightningSettings();
|
||||
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
|
||||
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), true);
|
||||
s.Driver.FindElement(By.Id("save")).Click();
|
||||
Assert.Contains("BTC Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
s.GoToStore(StoreNavPages.CheckoutAppearance);
|
||||
s.Driver.WaitForAndClick(By.Id("Presets"));
|
||||
s.Driver.WaitForAndClick(By.Id("Presets_InStore"));
|
||||
@ -62,9 +72,9 @@ namespace BTCPayServer.Tests
|
||||
var qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
|
||||
var address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||
var payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||
var copyAddress = s.Driver.FindElement(By.CssSelector("#Address_BTC .truncate-center-start")).Text;
|
||||
var copyAddress = s.Driver.FindElement(By.Id("Address_BTC")).GetAttribute("value");
|
||||
Assert.Equal($"bitcoin:{address}", payUrl);
|
||||
Assert.StartsWith("bcrt", s.Driver.FindElement(By.CssSelector("#Address_BTC .truncate-center-start")).Text);
|
||||
Assert.StartsWith("bcrt", s.Driver.FindElement(By.Id("Address_BTC")).GetAttribute("value"));
|
||||
Assert.DoesNotContain("lightning=", payUrl);
|
||||
Assert.Equal(address, copyAddress);
|
||||
Assert.Equal($"bitcoin:{address.ToUpperInvariant()}", qrValue);
|
||||
@ -84,7 +94,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||
Assert.StartsWith("lightning:lnurl", payUrl);
|
||||
Assert.StartsWith("lnurl", s.Driver.WaitForElement(By.CssSelector("#Lightning_BTC .truncate-center-start")).Text);
|
||||
Assert.StartsWith("lnurl", s.Driver.WaitForElement(By.Id("Lightning_BTC")).GetAttribute("value"));
|
||||
s.Driver.ElementDoesNotExist(By.Id("Address_BTC"));
|
||||
});
|
||||
|
||||
@ -99,7 +109,7 @@ namespace BTCPayServer.Tests
|
||||
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
|
||||
address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||
copyAddress = s.Driver.FindElement(By.CssSelector("#Lightning_BTC_LightningLike .truncate-center-start")).Text;
|
||||
copyAddress = s.Driver.FindElement(By.Id("Lightning_BTC_LightningLike")).GetAttribute("value");
|
||||
Assert.Equal($"lightning:{address}", payUrl);
|
||||
Assert.Equal(address, copyAddress);
|
||||
Assert.Equal($"lightning:{address.ToUpperInvariant()}", qrValue);
|
||||
@ -138,47 +148,8 @@ namespace BTCPayServer.Tests
|
||||
var expiredSection = s.Driver.FindElement(By.Id("unpaid"));
|
||||
Assert.True(expiredSection.Displayed);
|
||||
Assert.Contains("Invoice Expired", expiredSection.Text);
|
||||
Assert.Contains("resubmit a payment", expiredSection.Text);
|
||||
Assert.DoesNotContain("This invoice expired with partial payment", expiredSection.Text);
|
||||
|
||||
});
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ContactLink")));
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ReceiptLink")));
|
||||
Assert.Equal(storeUrl, s.Driver.FindElement(By.Id("StoreLink")).GetAttribute("href"));
|
||||
|
||||
// Expire paid partial
|
||||
s.GoToHome();
|
||||
invoiceId = s.CreateInvoice(2100, "EUR");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||
|
||||
await Task.Delay(200);
|
||||
address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||
var amountFraction = "0.00001";
|
||||
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(address, Network.RegTest),
|
||||
Money.Parse(amountFraction));
|
||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||
|
||||
expirySeconds = s.Driver.FindElement(By.Id("ExpirySeconds"));
|
||||
expirySeconds.Clear();
|
||||
expirySeconds.SendKeys("3");
|
||||
s.Driver.FindElement(By.Id("Expire")).Click();
|
||||
|
||||
paymentInfo = s.Driver.WaitForElement(By.Id("PaymentInfo"));
|
||||
Assert.Contains("The invoice hasn't been paid in full.", paymentInfo.Text);
|
||||
Assert.Contains("Please send", paymentInfo.Text);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
var expiredSection = s.Driver.FindElement(By.Id("unpaid"));
|
||||
Assert.True(expiredSection.Displayed);
|
||||
Assert.Contains("Invoice Expired", expiredSection.Text);
|
||||
Assert.Contains("This invoice expired with partial payment", expiredSection.Text);
|
||||
Assert.DoesNotContain("resubmit a payment", expiredSection.Text);
|
||||
});
|
||||
var contactLink = s.Driver.FindElement(By.Id("ContactLink"));
|
||||
Assert.Equal("Contact us", contactLink.Text);
|
||||
Assert.Matches(supportUrl.Replace("{InvoiceId}", invoiceId), contactLink.GetAttribute("href"));
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ReceiptLink")));
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("receipt-btn")));
|
||||
Assert.Equal(storeUrl, s.Driver.FindElement(By.Id("StoreLink")).GetAttribute("href"));
|
||||
|
||||
// Test payment
|
||||
@ -203,7 +174,7 @@ namespace BTCPayServer.Tests
|
||||
// Pay partial amount
|
||||
await Task.Delay(200);
|
||||
address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||
amountFraction = "0.00001";
|
||||
var amountFraction = "0.00001";
|
||||
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(address, Network.RegTest),
|
||||
Money.Parse(amountFraction));
|
||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||
@ -217,10 +188,8 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
|
||||
s.Driver.Navigate().Refresh();
|
||||
|
||||
// Pay full amount
|
||||
s.PayInvoice();
|
||||
|
||||
// Processing
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
@ -228,9 +197,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(processingSection.Displayed);
|
||||
Assert.Contains("Payment Received", processingSection.Text);
|
||||
Assert.Contains("Your payment has been received and is now processing", processingSection.Text);
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("confetti")));
|
||||
});
|
||||
s.Driver.FindElement(By.Id("confetti"));
|
||||
|
||||
// Mine
|
||||
s.MineBlockOnInvoiceCheckout();
|
||||
TestUtils.Eventually(() =>
|
||||
@ -247,8 +215,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("Invoice Paid", settledSection.Text);
|
||||
});
|
||||
s.Driver.FindElement(By.Id("confetti"));
|
||||
s.Driver.FindElement(By.Id("ReceiptLink"));
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ContactLink")));
|
||||
s.Driver.FindElement(By.Id("receipt-btn"));
|
||||
Assert.Equal(storeUrl, s.Driver.FindElement(By.Id("StoreLink")).GetAttribute("href"));
|
||||
|
||||
// BIP21
|
||||
@ -267,8 +234,8 @@ namespace BTCPayServer.Tests
|
||||
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
|
||||
address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||
var copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_BTC .truncate-center-start")).Text;
|
||||
var copyAddressLightning = s.Driver.FindElement(By.CssSelector("#Lightning_BTC .truncate-center-start")).Text;
|
||||
var copyAddressOnchain = s.Driver.FindElement(By.Id("Address_BTC")).GetAttribute("value");
|
||||
var copyAddressLightning = s.Driver.FindElement(By.Id("Lightning_BTC")).GetAttribute("value");
|
||||
Assert.StartsWith($"bitcoin:{address}?amount=", payUrl);
|
||||
Assert.Contains("?amount=", payUrl);
|
||||
Assert.Contains("&lightning=", payUrl);
|
||||
@ -326,6 +293,7 @@ namespace BTCPayServer.Tests
|
||||
s.GoToHome();
|
||||
s.GoToLightningSettings();
|
||||
Assert.True(s.Driver.FindElement(By.Id("LNURLEnabled")).Selected);
|
||||
Assert.True(s.Driver.FindElement(By.Id("LNURLStandardInvoiceEnabled")).Selected);
|
||||
|
||||
// BIP21 with top-up invoice
|
||||
invoiceId = s.CreateInvoice(amount: null);
|
||||
@ -335,8 +303,8 @@ namespace BTCPayServer.Tests
|
||||
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
|
||||
address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||
copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_BTC .truncate-center-start")).Text;
|
||||
copyAddressLightning = s.Driver.FindElement(By.CssSelector("#Lightning_BTC .truncate-center-start")).Text;
|
||||
copyAddressOnchain = s.Driver.FindElement(By.Id("Address_BTC")).GetAttribute("value");
|
||||
copyAddressLightning = s.Driver.FindElement(By.Id("Lightning_BTC")).GetAttribute("value");
|
||||
Assert.StartsWith($"bitcoin:{address}", payUrl);
|
||||
Assert.Contains("?lightning=lnurl", payUrl);
|
||||
Assert.DoesNotContain("amount=", payUrl);
|
||||
@ -396,7 +364,6 @@ namespace BTCPayServer.Tests
|
||||
s.GoToHome();
|
||||
s.GoToLightningSettings();
|
||||
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), false);
|
||||
s.Driver.ScrollTo(By.Id("save"));
|
||||
s.Driver.FindElement(By.Id("save")).Click();
|
||||
Assert.Contains("BTC Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
@ -422,7 +389,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("lang=de", s.Driver.Url);
|
||||
|
||||
s.Driver.Navigate().Refresh();
|
||||
languageSelect = new SelectElement(s.Driver.WaitForElement(By.Id("DefaultLang")));
|
||||
languageSelect = new SelectElement(s.Driver.FindElement(By.Id("DefaultLang")));
|
||||
Assert.Equal("Deutsch", languageSelect.SelectedOption.Text);
|
||||
Assert.Equal("Details anzeigen", s.Driver.FindElement(By.Id("DetailsToggle")).Text);
|
||||
languageSelect.SelectByText("English");
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
@ -56,7 +55,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Empty(appList2.Apps);
|
||||
Assert.Equal("test", appList.Apps[0].AppName);
|
||||
Assert.Equal(apps.CreatedAppId, appList.Apps[0].Id);
|
||||
Assert.True(appList.Apps[0].Role.ToPermissionSet(appList.Apps[0].StoreId).Contains(Policies.CanModifyStoreSettings, appList.Apps[0].StoreId));
|
||||
Assert.True(appList.Apps[0].IsOwner);
|
||||
Assert.Equal(user.StoreId, appList.Apps[0].StoreId);
|
||||
Assert.IsType<NotFoundResult>(apps2.DeleteApp(appList.Apps[0].Id));
|
||||
Assert.IsType<ViewResult>(apps.DeleteApp(appList.Apps[0].Id));
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -123,13 +122,6 @@ retry:
|
||||
driver.ExecuteJavaScript($"document.getElementById('{element}').{funcName}()");
|
||||
}
|
||||
|
||||
public static void WaitWalletTransactionsLoaded(this IWebDriver driver)
|
||||
{
|
||||
var wait = new WebDriverWait(driver, SeleniumTester.ImplicitWait);
|
||||
wait.UntilJsIsReady();
|
||||
wait.Until(d => d.WaitForElement(By.CssSelector("#WalletTransactions[data-loaded='true']")));
|
||||
}
|
||||
|
||||
public static IWebElement WaitForElement(this IWebDriver driver, By selector)
|
||||
{
|
||||
var wait = new WebDriverWait(driver, SeleniumTester.ImplicitWait);
|
||||
@ -197,7 +189,6 @@ retry:
|
||||
driver.FindElement(selector).Click();
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
public static bool ElementDoesNotExist(this IWebDriver driver, By selector)
|
||||
{
|
||||
Assert.Throws<NoSuchElementException>(() =>
|
||||
|
@ -26,7 +26,6 @@ using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Labels;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Validation;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@ -346,213 +345,165 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(Torrc.TryParse(input, out torrc));
|
||||
Assert.Equal(expected, torrc.ToString());
|
||||
}
|
||||
[Fact]
|
||||
public void CanCalculateDust()
|
||||
{
|
||||
var entity = new InvoiceEntity() { Currency = "USD" };
|
||||
entity.Networks = new BTCPayNetworkProvider(ChainName.Regtest);
|
||||
#pragma warning disable CS0618
|
||||
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||
entity.SetPaymentMethod(new PaymentMethod()
|
||||
{
|
||||
Currency = "BTC",
|
||||
Rate = 34_000m
|
||||
});
|
||||
entity.Price = 4000;
|
||||
entity.UpdateTotals();
|
||||
var accounting = entity.GetPaymentMethods().First().Calculate();
|
||||
// Exact price should be 0.117647059..., but the payment method round up to one sat
|
||||
Assert.Equal(0.11764706m, accounting.Due);
|
||||
entity.Payments.Add(new PaymentEntity()
|
||||
{
|
||||
Currency = "BTC",
|
||||
Output = new TxOut(Money.Coins(0.11764706m), new Key()),
|
||||
Accounted = true
|
||||
});
|
||||
entity.UpdateTotals();
|
||||
Assert.Equal(0.0m, entity.NetDue);
|
||||
// The dust's value is below 1 sat
|
||||
Assert.True(entity.Dust > 0.0m);
|
||||
Assert.True(Money.Satoshis(1.0m).ToDecimal(MoneyUnit.BTC) * entity.Rates["BTC"] > entity.Dust);
|
||||
Assert.True(!entity.IsOverPaid);
|
||||
Assert.True(!entity.IsUnderPaid);
|
||||
|
||||
// Now, imagine there is litecoin. It might seem from its
|
||||
// perspecitve that there has been a slight over payment.
|
||||
// However, Calculate() should just cap it to 0.0m
|
||||
entity.SetPaymentMethod(new PaymentMethod()
|
||||
{
|
||||
Currency = "LTC",
|
||||
Rate = 3400m
|
||||
});
|
||||
entity.UpdateTotals();
|
||||
var method = entity.GetPaymentMethods().First(p => p.Currency == "LTC");
|
||||
accounting = method.Calculate();
|
||||
Assert.Equal(0.0m, accounting.DueUncapped);
|
||||
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
#if ALTCOINS
|
||||
[Fact]
|
||||
public void CanCalculateCryptoDue()
|
||||
{
|
||||
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
|
||||
var entity = new InvoiceEntity() { Currency = "USD" };
|
||||
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
|
||||
{
|
||||
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null, null, null),
|
||||
});
|
||||
var entity = new InvoiceEntity();
|
||||
entity.Networks = networkProvider;
|
||||
#pragma warning disable CS0618
|
||||
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||
entity.SetPaymentMethod(new PaymentMethod()
|
||||
{
|
||||
Currency = "BTC",
|
||||
CryptoCode = "BTC",
|
||||
Rate = 5000,
|
||||
NextNetworkFee = Money.Coins(0.1m)
|
||||
});
|
||||
entity.Price = 5000;
|
||||
entity.UpdateTotals();
|
||||
|
||||
var paymentMethod = entity.GetPaymentMethods().TryGet("BTC", PaymentTypes.BTCLike);
|
||||
var accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(1.0m, accounting.ToSmallestUnit(Money.Satoshis(1.0m).ToDecimal(MoneyUnit.BTC)));
|
||||
Assert.Equal(1.1m, accounting.Due);
|
||||
Assert.Equal(1.1m, accounting.TotalDue);
|
||||
Assert.Equal(Money.Coins(1.1m), accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.1m), accounting.TotalDue);
|
||||
|
||||
entity.Payments.Add(new PaymentEntity()
|
||||
{
|
||||
Currency = "BTC",
|
||||
Output = new TxOut(Money.Coins(0.5m), new Key()),
|
||||
Rate = 5000,
|
||||
Accounted = true,
|
||||
NetworkFee = 0.1m
|
||||
});
|
||||
entity.UpdateTotals();
|
||||
|
||||
accounting = paymentMethod.Calculate();
|
||||
//Since we need to spend one more txout, it should be 1.1 - 0,5 + 0.1
|
||||
Assert.Equal(0.7m, accounting.Due);
|
||||
Assert.Equal(1.2m, accounting.TotalDue);
|
||||
Assert.Equal(Money.Coins(0.7m), accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.2m), accounting.TotalDue);
|
||||
|
||||
entity.Payments.Add(new PaymentEntity()
|
||||
{
|
||||
Currency = "BTC",
|
||||
Output = new TxOut(Money.Coins(0.2m), new Key()),
|
||||
Accounted = true,
|
||||
NetworkFee = 0.1m
|
||||
});
|
||||
entity.UpdateTotals();
|
||||
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(0.6m, accounting.Due);
|
||||
Assert.Equal(1.3m, accounting.TotalDue);
|
||||
Assert.Equal(Money.Coins(0.6m), accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.3m), accounting.TotalDue);
|
||||
|
||||
entity.Payments.Add(new PaymentEntity()
|
||||
{
|
||||
Currency = "BTC",
|
||||
Output = new TxOut(Money.Coins(0.6m), new Key()),
|
||||
Accounted = true,
|
||||
NetworkFee = 0.1m
|
||||
});
|
||||
entity.UpdateTotals();
|
||||
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(0.0m, accounting.Due);
|
||||
Assert.Equal(1.3m, accounting.TotalDue);
|
||||
Assert.Equal(Money.Zero, accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.3m), accounting.TotalDue);
|
||||
|
||||
entity.Payments.Add(
|
||||
new PaymentEntity() { Currency = "BTC", Output = new TxOut(Money.Coins(0.2m), new Key()), Accounted = true });
|
||||
entity.UpdateTotals();
|
||||
new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()), Accounted = true });
|
||||
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(0.0m, accounting.Due);
|
||||
Assert.Equal(1.3m, accounting.TotalDue);
|
||||
Assert.Equal(Money.Zero, accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.3m), accounting.TotalDue);
|
||||
|
||||
entity = new InvoiceEntity();
|
||||
entity.Networks = networkProvider;
|
||||
entity.Price = 5000;
|
||||
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
|
||||
paymentMethods.Add(
|
||||
new PaymentMethod() { Currency = "BTC", Rate = 1000, NextNetworkFee = Money.Coins(0.1m) });
|
||||
new PaymentMethod() { CryptoCode = "BTC", Rate = 1000, NextNetworkFee = Money.Coins(0.1m) });
|
||||
paymentMethods.Add(
|
||||
new PaymentMethod() { Currency = "LTC", Rate = 500, NextNetworkFee = Money.Coins(0.01m) });
|
||||
new PaymentMethod() { CryptoCode = "LTC", Rate = 500, NextNetworkFee = Money.Coins(0.01m) });
|
||||
entity.SetPaymentMethods(paymentMethods);
|
||||
entity.Payments = new List<PaymentEntity>();
|
||||
entity.UpdateTotals();
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(5.1m, accounting.Due);
|
||||
Assert.Equal(Money.Coins(5.1m), accounting.Due);
|
||||
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||
accounting = paymentMethod.Calculate();
|
||||
|
||||
Assert.Equal(10.01m, accounting.TotalDue);
|
||||
Assert.Equal(Money.Coins(10.01m), accounting.TotalDue);
|
||||
|
||||
entity.Payments.Add(new PaymentEntity()
|
||||
{
|
||||
Currency = "BTC",
|
||||
CryptoCode = "BTC",
|
||||
Output = new TxOut(Money.Coins(1.0m), new Key()),
|
||||
Accounted = true,
|
||||
NetworkFee = 0.1m
|
||||
});
|
||||
entity.UpdateTotals();
|
||||
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(4.2m, accounting.Due);
|
||||
Assert.Equal(1.0m, accounting.CryptoPaid);
|
||||
Assert.Equal(1.0m, accounting.Paid);
|
||||
Assert.Equal(5.2m, accounting.TotalDue);
|
||||
Assert.Equal(Money.Coins(4.2m), accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
|
||||
Assert.Equal(Money.Coins(1.0m), accounting.Paid);
|
||||
Assert.Equal(Money.Coins(5.2m), accounting.TotalDue);
|
||||
Assert.Equal(2, accounting.TxRequired);
|
||||
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(10.01m + 0.1m * 2 - 2.0m /* 8.21m */, accounting.Due);
|
||||
Assert.Equal(0.0m, accounting.CryptoPaid);
|
||||
Assert.Equal(2.0m, accounting.Paid);
|
||||
Assert.Equal(10.01m + 0.1m * 2, accounting.TotalDue);
|
||||
Assert.Equal(Money.Coins(10.01m + 0.1m * 2 - 2.0m /* 8.21m */), accounting.Due);
|
||||
Assert.Equal(Money.Coins(0.0m), accounting.CryptoPaid);
|
||||
Assert.Equal(Money.Coins(2.0m), accounting.Paid);
|
||||
Assert.Equal(Money.Coins(10.01m + 0.1m * 2), accounting.TotalDue);
|
||||
|
||||
entity.Payments.Add(new PaymentEntity()
|
||||
{
|
||||
Currency = "LTC",
|
||||
CryptoCode = "LTC",
|
||||
Output = new TxOut(Money.Coins(1.0m), new Key()),
|
||||
Accounted = true,
|
||||
NetworkFee = 0.01m
|
||||
});
|
||||
entity.UpdateTotals();
|
||||
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(4.2m - 0.5m + 0.01m / 2, accounting.Due);
|
||||
Assert.Equal(1.0m, accounting.CryptoPaid);
|
||||
Assert.Equal(1.5m, accounting.Paid);
|
||||
Assert.Equal(5.2m + 0.01m / 2, accounting.TotalDue); // The fee for LTC added
|
||||
Assert.Equal(Money.Coins(4.2m - 0.5m + 0.01m / 2), accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
|
||||
Assert.Equal(Money.Coins(1.5m), accounting.Paid);
|
||||
Assert.Equal(Money.Coins(5.2m + 0.01m / 2), accounting.TotalDue); // The fee for LTC added
|
||||
Assert.Equal(2, accounting.TxRequired);
|
||||
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(8.21m - 1.0m + 0.01m, accounting.Due);
|
||||
Assert.Equal(1.0m, accounting.CryptoPaid);
|
||||
Assert.Equal(3.0m, accounting.Paid);
|
||||
Assert.Equal(10.01m + 0.1m * 2 + 0.01m, accounting.TotalDue);
|
||||
Assert.Equal(Money.Coins(8.21m - 1.0m + 0.01m), accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
|
||||
Assert.Equal(Money.Coins(3.0m), accounting.Paid);
|
||||
Assert.Equal(Money.Coins(10.01m + 0.1m * 2 + 0.01m), accounting.TotalDue);
|
||||
Assert.Equal(2, accounting.TxRequired);
|
||||
|
||||
var remaining = Money.Coins(4.2m - 0.5m + 0.01m / 2.0m).ToDecimal(MoneyUnit.BTC);
|
||||
var remaining = Money.Coins(4.2m - 0.5m + 0.01m / 2);
|
||||
entity.Payments.Add(new PaymentEntity()
|
||||
{
|
||||
Currency = "BTC",
|
||||
Output = new TxOut(Money.Coins(remaining), new Key()),
|
||||
CryptoCode = "BTC",
|
||||
Output = new TxOut(remaining, new Key()),
|
||||
Accounted = true,
|
||||
NetworkFee = 0.1m
|
||||
});
|
||||
entity.UpdateTotals();
|
||||
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(0.0m, accounting.Due);
|
||||
Assert.Equal(1.0m + remaining, accounting.CryptoPaid);
|
||||
Assert.Equal(1.5m + remaining, accounting.Paid);
|
||||
Assert.Equal(5.2m + 0.01m / 2, accounting.TotalDue);
|
||||
Assert.Equal(Money.Zero, accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.0m) + remaining, accounting.CryptoPaid);
|
||||
Assert.Equal(Money.Coins(1.5m) + remaining, accounting.Paid);
|
||||
Assert.Equal(Money.Coins(5.2m + 0.01m / 2), accounting.TotalDue);
|
||||
Assert.Equal(accounting.Paid, accounting.TotalDue);
|
||||
Assert.Equal(2, accounting.TxRequired);
|
||||
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(0.0m, accounting.Due);
|
||||
Assert.Equal(1.0m, accounting.CryptoPaid);
|
||||
Assert.Equal(3.0m + remaining * 2, accounting.Paid);
|
||||
Assert.Equal(Money.Zero, accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
|
||||
Assert.Equal(Money.Coins(3.0m) + remaining * 2, accounting.Paid);
|
||||
// Paying 2 BTC fee, LTC fee removed because fully paid
|
||||
Assert.Equal(10.01m + 0.1m * 2 + 0.1m * 2 /* + 0.01m no need to pay this fee anymore */,
|
||||
Assert.Equal(Money.Coins(10.01m + 0.1m * 2 + 0.1m * 2 /* + 0.01m no need to pay this fee anymore */),
|
||||
accounting.TotalDue);
|
||||
Assert.Equal(1, accounting.TxRequired);
|
||||
Assert.Equal(accounting.Paid, accounting.TotalDue);
|
||||
@ -596,29 +547,27 @@ namespace BTCPayServer.Tests
|
||||
entity.Payments = new List<PaymentEntity>();
|
||||
entity.SetPaymentMethod(new PaymentMethod()
|
||||
{
|
||||
Currency = "BTC",
|
||||
CryptoCode = "BTC",
|
||||
Rate = 5000,
|
||||
NextNetworkFee = Money.Coins(0.1m)
|
||||
});
|
||||
entity.Price = 5000;
|
||||
entity.PaymentTolerance = 0;
|
||||
entity.UpdateTotals();
|
||||
|
||||
|
||||
var paymentMethod = entity.GetPaymentMethods().TryGet("BTC", PaymentTypes.BTCLike);
|
||||
var accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(1.1m, accounting.Due);
|
||||
Assert.Equal(1.1m, accounting.TotalDue);
|
||||
Assert.Equal(1.1m, accounting.MinimumTotalDue);
|
||||
Assert.Equal(Money.Coins(1.1m), accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.1m), accounting.TotalDue);
|
||||
Assert.Equal(Money.Coins(1.1m), accounting.MinimumTotalDue);
|
||||
|
||||
entity.PaymentTolerance = 10;
|
||||
entity.UpdateTotals();
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(0.99m, accounting.MinimumTotalDue);
|
||||
Assert.Equal(Money.Coins(0.99m), accounting.MinimumTotalDue);
|
||||
|
||||
entity.PaymentTolerance = 100;
|
||||
entity.UpdateTotals();
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(0.0000_0001m, accounting.MinimumTotalDue);
|
||||
Assert.Equal(Money.Satoshis(1), accounting.MinimumTotalDue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -659,7 +608,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanDetectFileType()
|
||||
public void CanDetectImage()
|
||||
{
|
||||
Assert.True(FileTypeDetector.IsPicture(new byte[] { 0x42, 0x4D }, "test.bmp"));
|
||||
Assert.False(FileTypeDetector.IsPicture(new byte[] { 0x42, 0x4D }, ".bmp"));
|
||||
@ -672,15 +621,6 @@ namespace BTCPayServer.Tests
|
||||
Assert.False(FileTypeDetector.IsPicture(new byte[] { 0x3C, 0x73, 0x76, 0x67 }, "test.jpg"));
|
||||
Assert.False(FileTypeDetector.IsPicture(new byte[] { 0xFF }, "e.jpg"));
|
||||
Assert.False(FileTypeDetector.IsPicture(new byte[] { }, "empty.jpg"));
|
||||
|
||||
Assert.False(FileTypeDetector.IsPicture(new byte[] { 0x49, 0x44, 0x33, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23 }, "music.mp3"));
|
||||
Assert.True(FileTypeDetector.IsAudio(new byte[] { 0x49, 0x44, 0x33, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23 }, "music.mp3"));
|
||||
Assert.True(FileTypeDetector.IsAudio(new byte[] { 0x52, 0x49, 0x46, 0x46, 0x24, 0x9A, 0x08, 0x00, 0x57, 0x41 }, "music.wav"));
|
||||
Assert.True(FileTypeDetector.IsAudio(new byte[] { 0xFF, 0xF1, 0x50, 0x80, 0x1C, 0x3F, 0xFC, 0xDA, 0x00, 0x4C }, "music.aac"));
|
||||
Assert.True(FileTypeDetector.IsAudio(new byte[] { 0x66, 0x4C, 0x61, 0x43, 0x00, 0x00, 0x00, 0x22, 0x04, 0x80 }, "music.flac"));
|
||||
Assert.True(FileTypeDetector.IsAudio(new byte[] { 0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00 }, "music.ogg"));
|
||||
Assert.True(FileTypeDetector.IsAudio(new byte[] { 0x1A, 0x45, 0xDF, 0xA3, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }, "music.weba"));
|
||||
Assert.True(FileTypeDetector.IsAudio(new byte[] { 0xFF, 0xF3, 0xE4, 0x64, 0x00, 0x20, 0xAD, 0xBD, 0x04, 0x00 }, "music.mp3"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -1102,13 +1042,14 @@ namespace BTCPayServer.Tests
|
||||
[Fact]
|
||||
public void CanParseFilter()
|
||||
{
|
||||
var storeId = "6DehZnc9S7qC6TUTNWuzJ1pFsHTHvES6An21r3MjvLey";
|
||||
var filter = "storeid:abc, status:abed, blabhbalh ";
|
||||
var search = new SearchString(filter);
|
||||
Assert.Equal("storeid:abc, status:abed, blabhbalh", search.ToString());
|
||||
Assert.Equal("blabhbalh", search.TextSearch);
|
||||
Assert.Single(search.Filters["storeid"], "abc");
|
||||
Assert.Single(search.Filters["status"], "abed");
|
||||
Assert.Single(search.Filters["storeid"]);
|
||||
Assert.Single(search.Filters["status"]);
|
||||
Assert.Equal("abc", search.Filters["storeid"].First());
|
||||
Assert.Equal("abed", search.Filters["status"].First());
|
||||
|
||||
filter = "status:abed, status:abed2";
|
||||
search = new SearchString(filter);
|
||||
@ -1123,48 +1064,6 @@ namespace BTCPayServer.Tests
|
||||
search = new SearchString(filter);
|
||||
Assert.Equal("2019-04-25 01:00 AM", search.Filters["startdate"].First());
|
||||
Assert.Equal("hekki", search.TextSearch);
|
||||
|
||||
// modify search
|
||||
filter = $"status:settled,exceptionstatus:paidLate,unusual:true, fulltext searchterm, storeid:{storeId},startdate:2019-04-25 01:00:00";
|
||||
search = new SearchString(filter);
|
||||
Assert.Equal(filter, search.ToString());
|
||||
Assert.Equal("fulltext searchterm", search.TextSearch);
|
||||
Assert.Single(search.Filters["storeid"], storeId);
|
||||
Assert.Single(search.Filters["status"], "settled");
|
||||
Assert.Single(search.Filters["exceptionstatus"], "paidLate");
|
||||
Assert.Single(search.Filters["unusual"], "true");
|
||||
|
||||
// toggle off bool with same value
|
||||
var modified = new SearchString(search.Toggle("unusual", "true"));
|
||||
Assert.Null(modified.GetFilterBool("unusual"));
|
||||
|
||||
// add to array
|
||||
modified = new SearchString(modified.Toggle("status", "processing"));
|
||||
var statusArray = modified.GetFilterArray("status");
|
||||
Assert.Equal(2, statusArray.Length);
|
||||
Assert.Contains("processing", statusArray);
|
||||
Assert.Contains("settled", statusArray);
|
||||
|
||||
// toggle off array with same value
|
||||
modified = new SearchString(modified.Toggle("status", "settled"));
|
||||
statusArray = modified.GetFilterArray("status");
|
||||
Assert.Single(statusArray, "processing");
|
||||
|
||||
// toggle off array with null value
|
||||
modified = new SearchString(modified.Toggle("status", null));
|
||||
Assert.Null(modified.GetFilterArray("status"));
|
||||
|
||||
// toggle off date with null value
|
||||
modified = new SearchString(modified.Toggle("startdate", "-7d"));
|
||||
Assert.Single(modified.GetFilterArray("startdate"), "-7d");
|
||||
modified = new SearchString(modified.Toggle("startdate", null));
|
||||
Assert.Null(modified.GetFilterArray("startdate"));
|
||||
|
||||
// toggle off date with same value
|
||||
modified = new SearchString(modified.Toggle("enddate", "-7d"));
|
||||
Assert.Single(modified.GetFilterArray("enddate"), "-7d");
|
||||
modified = new SearchString(modified.Toggle("enddate", "-7d"));
|
||||
Assert.Null(modified.GetFilterArray("enddate"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -1204,45 +1103,6 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal("000000161", m.OrderId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanParseOldPosAppData()
|
||||
{
|
||||
var data = new JObject()
|
||||
{
|
||||
["price"] = 1.64m
|
||||
}.ToString();
|
||||
Assert.Equal(1.64m, JsonConvert.DeserializeObject<PosAppCartItem>(data).Price);
|
||||
|
||||
data = new JObject()
|
||||
{
|
||||
["price"] = new JObject()
|
||||
{
|
||||
["value"] = 1.65m
|
||||
}
|
||||
}.ToString();
|
||||
Assert.Equal(1.65m, JsonConvert.DeserializeObject<PosAppCartItem>(data).Price);
|
||||
data = new JObject()
|
||||
{
|
||||
["price"] = new JObject()
|
||||
{
|
||||
["value"] = "1.6305"
|
||||
}
|
||||
}.ToString();
|
||||
Assert.Equal(1.6305m, JsonConvert.DeserializeObject<PosAppCartItem>(data).Price);
|
||||
|
||||
data = new JObject()
|
||||
{
|
||||
["price"] = new JObject()
|
||||
{
|
||||
["value"] = null
|
||||
}
|
||||
}.ToString();
|
||||
Assert.Equal(0.0m, JsonConvert.DeserializeObject<PosAppCartItem>(data).Price);
|
||||
|
||||
var o = JObject.Parse(JsonConvert.SerializeObject(new PosAppCartItem() { Price = 1.356m }));
|
||||
Assert.Equal(1.356m, o["price"].Value<decimal>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanParseCurrencyValue()
|
||||
{
|
||||
@ -1481,24 +1341,6 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(cache.States[0].Rates[0].Pair, cache2.States[0].Rates[0].Pair);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanParseStoreRoleId()
|
||||
{
|
||||
var id = StoreRoleId.Parse("test::lol");
|
||||
Assert.Equal("test", id.StoreId);
|
||||
Assert.Equal("lol", id.Role);
|
||||
Assert.Equal("test::lol", id.ToString());
|
||||
Assert.Equal("test::lol", id.Id);
|
||||
Assert.False(id.IsServerRole);
|
||||
|
||||
id = StoreRoleId.Parse("lol");
|
||||
Assert.Null(id.StoreId);
|
||||
Assert.Equal("lol", id.Role);
|
||||
Assert.Equal("lol", id.ToString());
|
||||
Assert.Equal("lol", id.Id);
|
||||
Assert.True(id.IsServerRole);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KitchenSinkTest()
|
||||
{
|
||||
@ -1943,6 +1785,11 @@ namespace BTCPayServer.Tests
|
||||
#pragma warning disable CS0618
|
||||
var dummy = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest).ToString();
|
||||
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
|
||||
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
|
||||
{
|
||||
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null, null, null),
|
||||
});
|
||||
var networkBTC = networkProvider.GetNetwork("BTC");
|
||||
var networkLTC = networkProvider.GetNetwork("LTC");
|
||||
InvoiceEntity invoiceEntity = new InvoiceEntity();
|
||||
@ -1950,14 +1797,14 @@ namespace BTCPayServer.Tests
|
||||
invoiceEntity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||
invoiceEntity.Price = 100;
|
||||
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
|
||||
paymentMethods.Add(new PaymentMethod() { Network = networkBTC, Currency = "BTC", Rate = 10513.44m, }
|
||||
paymentMethods.Add(new PaymentMethod() { Network = networkBTC, CryptoCode = "BTC", Rate = 10513.44m, }
|
||||
.SetPaymentMethodDetails(
|
||||
new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod()
|
||||
{
|
||||
NextNetworkFee = Money.Coins(0.00000100m),
|
||||
DepositAddress = dummy
|
||||
}));
|
||||
paymentMethods.Add(new PaymentMethod() { Network = networkLTC, Currency = "LTC", Rate = 216.79m }
|
||||
paymentMethods.Add(new PaymentMethod() { Network = networkLTC, CryptoCode = "LTC", Rate = 216.79m }
|
||||
.SetPaymentMethodDetails(
|
||||
new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod()
|
||||
{
|
||||
@ -1973,7 +1820,7 @@ namespace BTCPayServer.Tests
|
||||
new PaymentEntity()
|
||||
{
|
||||
Accounted = true,
|
||||
Currency = "BTC",
|
||||
CryptoCode = "BTC",
|
||||
NetworkFee = 0.00000100m,
|
||||
Network = networkProvider.GetNetwork("BTC"),
|
||||
}
|
||||
@ -1982,33 +1829,34 @@ namespace BTCPayServer.Tests
|
||||
Network = networkProvider.GetNetwork("BTC"),
|
||||
Output = new TxOut() { Value = Money.Coins(0.00151263m) }
|
||||
}));
|
||||
invoiceEntity.UpdateTotals();
|
||||
accounting = btc.Calculate();
|
||||
invoiceEntity.Payments.Add(
|
||||
new PaymentEntity()
|
||||
{
|
||||
Accounted = true,
|
||||
Currency = "BTC",
|
||||
CryptoCode = "BTC",
|
||||
NetworkFee = 0.00000100m,
|
||||
Network = networkProvider.GetNetwork("BTC")
|
||||
}
|
||||
.SetCryptoPaymentData(new BitcoinLikePaymentData()
|
||||
{
|
||||
Network = networkProvider.GetNetwork("BTC"),
|
||||
Output = new TxOut() { Value = Money.Coins(accounting.Due) }
|
||||
Output = new TxOut() { Value = accounting.Due }
|
||||
}));
|
||||
invoiceEntity.UpdateTotals();
|
||||
accounting = btc.Calculate();
|
||||
Assert.Equal(0.0m, accounting.Due);
|
||||
Assert.Equal(0.0m, accounting.DueUncapped);
|
||||
Assert.Equal(Money.Zero, accounting.Due);
|
||||
Assert.Equal(Money.Zero, accounting.DueUncapped);
|
||||
|
||||
var ltc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||
accounting = ltc.Calculate();
|
||||
|
||||
Assert.Equal(0.0m, accounting.Due);
|
||||
// LTC might should be over paid due to BTC paying above what it should (round 1 satoshi up), but we handle this case
|
||||
// and set DueUncapped to zero.
|
||||
Assert.Equal(0.0m, accounting.DueUncapped);
|
||||
Assert.Equal(Money.Zero, accounting.Due);
|
||||
// LTC might have over paid due to BTC paying above what it should (round 1 satoshi up)
|
||||
Assert.True(accounting.DueUncapped < Money.Zero);
|
||||
|
||||
var paymentMethod = InvoiceWatcher.GetNearestClearedPayment(paymentMethods, out var accounting2);
|
||||
Assert.Equal(btc.CryptoCode, paymentMethod.CryptoCode);
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -15,17 +15,12 @@ using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.PayoutProcessors;
|
||||
using BTCPayServer.PayoutProcessors.OnChain;
|
||||
using BTCPayServer.Plugins;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Custodian.Client.MockCustodian;
|
||||
using BTCPayServer.Services.Notifications;
|
||||
using BTCPayServer.Services.Notifications.Blobs;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json;
|
||||
@ -230,7 +225,7 @@ namespace BTCPayServer.Tests
|
||||
await Assert.ThrowsAsync<GreenfieldAPIException>(() => newUserClient.GetInvoices(store.Id));
|
||||
|
||||
// if user is a guest or owner, then it should be ok
|
||||
await unrestricted.AddStoreUser(store.Id, new StoreUserData() { UserId = newUser.Id});
|
||||
await unrestricted.AddStoreUser(store.Id, new StoreUserData() { UserId = newUser.Id, Role = "Guest" });
|
||||
await newUserClient.GetInvoices(store.Id);
|
||||
}
|
||||
|
||||
@ -1075,22 +1070,6 @@ namespace BTCPayServer.Tests
|
||||
var lnrURLs = await unauthenticated.GetPullPaymentLNURL(test4.Id);
|
||||
Assert.IsType<string>(lnrURLs.LNURLBech32);
|
||||
Assert.IsType<string>(lnrURLs.LNURLUri);
|
||||
Assert.Equal(12.303228134m, test4.Amount);
|
||||
Assert.Equal("BTC", test4.Currency);
|
||||
|
||||
// Test with SATS denomination values
|
||||
var testSats = await client.CreatePullPayment(storeId, new Client.Models.CreatePullPaymentRequest()
|
||||
{
|
||||
Name = "Test SATS",
|
||||
Amount = 21000,
|
||||
Currency = "SATS",
|
||||
PaymentMethods = new[] { "BTC", "BTC-LightningNetwork", "BTC_LightningLike" }
|
||||
});
|
||||
lnrURLs = await unauthenticated.GetPullPaymentLNURL(testSats.Id);
|
||||
Assert.IsType<string>(lnrURLs.LNURLBech32);
|
||||
Assert.IsType<string>(lnrURLs.LNURLUri);
|
||||
Assert.Equal(21000, testSats.Amount);
|
||||
Assert.Equal("SATS", testSats.Currency);
|
||||
|
||||
//permission test around auto approved pps and payouts
|
||||
var nonApproved = await acc.CreateClient(Policies.CanCreateNonApprovedPullPayments);
|
||||
@ -1152,8 +1131,7 @@ namespace BTCPayServer.Tests
|
||||
Approved = false,
|
||||
PaymentMethod = "BTC",
|
||||
Amount = 0.0001m,
|
||||
Destination = address.ToString(),
|
||||
|
||||
Destination = address.ToString()
|
||||
});
|
||||
await AssertAPIError("invalid-state", async () =>
|
||||
{
|
||||
@ -1338,8 +1316,7 @@ namespace BTCPayServer.Tests
|
||||
// We strip the user's Owner right, so the key should not work
|
||||
using var ctx = tester.PayTester.GetService<Data.ApplicationDbContextFactory>().CreateContext();
|
||||
var storeEntity = await ctx.UserStore.SingleAsync(u => u.ApplicationUserId == user.UserId && u.StoreDataId == newStore.Id);
|
||||
var roleId = (await tester.PayTester.GetService<StoreRepository>().GetStoreRoles(null)).Single(r => r.Role == "Guest").Id;
|
||||
storeEntity.StoreRoleId = roleId;
|
||||
storeEntity.Role = "Guest";
|
||||
await ctx.SaveChangesAsync();
|
||||
await AssertHttpError(403, async () => await client.UpdateStore(newStore.Id, new UpdateStoreRequest() { Name = "B" }));
|
||||
|
||||
@ -1450,7 +1427,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.False(hook.AutomaticRedelivery);
|
||||
Assert.Equal(fakeServer.ServerUri.AbsoluteUri, hook.Url);
|
||||
}
|
||||
using var tester = CreateServerTester(newDb: true);
|
||||
using var tester = CreateServerTester();
|
||||
using var fakeServer = new FakeServer();
|
||||
await fakeServer.Start();
|
||||
await tester.StartAsync();
|
||||
@ -1497,7 +1474,6 @@ namespace BTCPayServer.Tests
|
||||
var newDeliveryId = await clientProfile.RedeliverWebhook(user.StoreId, hook.Id, delivery.Id);
|
||||
req = await fakeServer.GetNextRequest();
|
||||
req.Response.StatusCode = 404;
|
||||
Assert.StartsWith("BTCPayServer", Assert.Single(req.Request.Headers.UserAgent));
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
// Releasing semaphore several times may help making this test less flaky
|
||||
@ -1527,14 +1503,6 @@ namespace BTCPayServer.Tests
|
||||
clientProfile = await user.CreateClient(Policies.CanModifyStoreSettings, Policies.CanCreateInvoice);
|
||||
await clientProfile.GetWebhookDeliveryRequest(user.StoreId, hook.Id, newDeliveryId);
|
||||
|
||||
|
||||
TestLogs.LogInformation("Can prune deliveries");
|
||||
var cleanup = tester.PayTester.GetService<HostedServices.CleanupWebhookDeliveriesTask>();
|
||||
cleanup.BatchSize = 1;
|
||||
cleanup.PruneAfter = TimeSpan.Zero;
|
||||
await cleanup.Do(default);
|
||||
await AssertHttpError(409, () => clientProfile.RedeliverWebhook(user.StoreId, hook.Id, delivery.Id));
|
||||
|
||||
TestLogs.LogInformation("Testing corner cases");
|
||||
Assert.Null(await clientProfile.GetWebhookDeliveryRequest(user.StoreId, "lol", newDeliveryId));
|
||||
Assert.Null(await clientProfile.GetWebhookDeliveryRequest(user.StoreId, hook.Id, "lol"));
|
||||
@ -1978,82 +1946,6 @@ namespace BTCPayServer.Tests
|
||||
CustomCurrency = "BTC"
|
||||
});
|
||||
Assert.True(pp.AutoApproveClaims);
|
||||
|
||||
// test subtract percentage
|
||||
validationError = await AssertValidationError(new[] { "SubtractPercentage" }, async () =>
|
||||
{
|
||||
await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||
{
|
||||
PaymentMethod = method.PaymentMethod,
|
||||
RefundVariant = RefundVariant.RateThen,
|
||||
SubtractPercentage = 101
|
||||
});
|
||||
});
|
||||
Assert.Contains("SubtractPercentage: Percentage must be a numeric value between 0 and 100", validationError.Message);
|
||||
|
||||
// should auto-approve
|
||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||
{
|
||||
PaymentMethod = method.PaymentMethod,
|
||||
RefundVariant = RefundVariant.RateThen,
|
||||
SubtractPercentage = 6.15m
|
||||
});
|
||||
Assert.Equal("BTC", pp.Currency);
|
||||
Assert.True(pp.AutoApproveClaims);
|
||||
Assert.Equal(0.9385m, pp.Amount);
|
||||
|
||||
// test RefundVariant.OverpaidAmount
|
||||
validationError = await AssertValidationError(new[] { "RefundVariant" }, async () =>
|
||||
{
|
||||
await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||
{
|
||||
PaymentMethod = method.PaymentMethod,
|
||||
RefundVariant = RefundVariant.OverpaidAmount
|
||||
});
|
||||
});
|
||||
Assert.Contains("Invoice is not overpaid", validationError.Message);
|
||||
|
||||
// should auto-approve
|
||||
invoice = await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest { Amount = 5000.0m, Currency = "USD" });
|
||||
methods = await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id);
|
||||
method = methods.First();
|
||||
|
||||
await tester.WaitForEvent<NewOnChainTransactionEvent>(async () =>
|
||||
{
|
||||
await tester.ExplorerNode.SendToAddressAsync(
|
||||
BitcoinAddress.Create(method.Destination, tester.NetworkProvider.BTC.NBitcoinNetwork),
|
||||
Money.Coins(method.Due * 2)
|
||||
);
|
||||
});
|
||||
|
||||
await tester.ExplorerNode.GenerateAsync(5);
|
||||
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
invoice = await client.GetInvoice(user.StoreId, invoice.Id);
|
||||
Assert.True(invoice.Status == InvoiceStatus.Settled);
|
||||
Assert.True(invoice.AdditionalStatus == InvoiceExceptionStatus.PaidOver);
|
||||
});
|
||||
|
||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||
{
|
||||
PaymentMethod = method.PaymentMethod,
|
||||
RefundVariant = RefundVariant.OverpaidAmount
|
||||
});
|
||||
Assert.Equal("BTC", pp.Currency);
|
||||
Assert.True(pp.AutoApproveClaims);
|
||||
Assert.Equal(method.Due, pp.Amount);
|
||||
|
||||
// once more with subtract percentage
|
||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||
{
|
||||
PaymentMethod = method.PaymentMethod,
|
||||
RefundVariant = RefundVariant.OverpaidAmount,
|
||||
SubtractPercentage = 21m
|
||||
});
|
||||
Assert.Equal("BTC", pp.Currency);
|
||||
Assert.True(pp.AutoApproveClaims);
|
||||
Assert.Equal(0.79m, pp.Amount);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
@ -2361,7 +2253,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Single(paymentMethods);
|
||||
Assert.True(paymentMethods.First().Activated);
|
||||
|
||||
var invoiceWithDefaultPaymentMethodLN = await client.CreateInvoice(user.StoreId,
|
||||
var invoiceWithdefaultPaymentMethodLN = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest()
|
||||
{
|
||||
Currency = "USD",
|
||||
@ -2372,9 +2264,9 @@ namespace BTCPayServer.Tests
|
||||
DefaultPaymentMethod = "BTC_LightningLike"
|
||||
}
|
||||
});
|
||||
Assert.Equal("BTC_LightningLike", invoiceWithDefaultPaymentMethodLN.Checkout.DefaultPaymentMethod);
|
||||
Assert.Equal("BTC_LightningLike", invoiceWithdefaultPaymentMethodLN.Checkout.DefaultPaymentMethod);
|
||||
|
||||
var invoiceWithDefaultPaymentMethodOnChain = await client.CreateInvoice(user.StoreId,
|
||||
var invoiceWithdefaultPaymentMethodOnChain = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest()
|
||||
{
|
||||
Currency = "USD",
|
||||
@ -2385,35 +2277,13 @@ namespace BTCPayServer.Tests
|
||||
DefaultPaymentMethod = "BTC"
|
||||
}
|
||||
});
|
||||
Assert.Equal("BTC", invoiceWithDefaultPaymentMethodOnChain.Checkout.DefaultPaymentMethod);
|
||||
|
||||
// reset lazy payment methods
|
||||
Assert.Equal("BTC", invoiceWithdefaultPaymentMethodOnChain.Checkout.DefaultPaymentMethod);
|
||||
|
||||
store = await client.GetStore(user.StoreId);
|
||||
store.LazyPaymentMethods = false;
|
||||
store = await client.UpdateStore(store.Id,
|
||||
JObject.FromObject(store).ToObject<UpdateStoreRequest>());
|
||||
Assert.False(store.LazyPaymentMethods);
|
||||
|
||||
// use store default payment method
|
||||
store = await client.GetStore(user.StoreId);
|
||||
Assert.Null(store.DefaultPaymentMethod);
|
||||
var storeDefaultPaymentMethod = "BTC-LightningNetwork";
|
||||
store.DefaultPaymentMethod = storeDefaultPaymentMethod;
|
||||
store = await client.UpdateStore(store.Id,
|
||||
JObject.FromObject(store).ToObject<UpdateStoreRequest>());
|
||||
Assert.Equal(storeDefaultPaymentMethod, store.DefaultPaymentMethod);
|
||||
|
||||
var invoiceWithStoreDefaultPaymentMethod = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest()
|
||||
{
|
||||
Currency = "USD",
|
||||
Amount = 100,
|
||||
Checkout = new CreateInvoiceRequest.CheckoutOptions()
|
||||
{
|
||||
PaymentMethods = new[] { "BTC", "BTC-LightningNetwork", "BTC_LightningLike" }
|
||||
}
|
||||
});
|
||||
Assert.Equal(storeDefaultPaymentMethod, invoiceWithStoreDefaultPaymentMethod.Checkout.DefaultPaymentMethod);
|
||||
|
||||
//let's see the overdue amount
|
||||
invoice = await client.CreateInvoice(user.StoreId,
|
||||
@ -2470,10 +2340,27 @@ namespace BTCPayServer.Tests
|
||||
Assert.NotNull(merchantInvoice.PaymentHash);
|
||||
Assert.Equal(merchantInvoice.Id, merchantInvoice.PaymentHash);
|
||||
|
||||
var client = await user.CreateClient(Policies.CanUseInternalLightningNode);
|
||||
// The default client is using charge, so we should not be able to query channels
|
||||
var chargeClient = await user.CreateClient(Policies.CanUseInternalLightningNode);
|
||||
|
||||
var info = await chargeClient.GetLightningNodeInfo("BTC");
|
||||
Assert.Single(info.NodeURIs);
|
||||
Assert.NotEqual(0, info.BlockHeight);
|
||||
Assert.NotNull(info.Alias);
|
||||
Assert.NotNull(info.Color);
|
||||
Assert.NotNull(info.Version);
|
||||
Assert.NotNull(info.PeersCount);
|
||||
Assert.NotNull(info.ActiveChannelsCount);
|
||||
Assert.NotNull(info.InactiveChannelsCount);
|
||||
Assert.NotNull(info.PendingChannelsCount);
|
||||
|
||||
var gex = await AssertAPIError("lightning-node-unavailable", () => chargeClient.ConnectToLightningNode("BTC", new ConnectToNodeRequest(NodeInfo.Parse($"{new Key().PubKey.ToHex()}@localhost:3827"))));
|
||||
Assert.Contains("NotSupported", gex.Message);
|
||||
|
||||
await AssertAPIError("lightning-node-unavailable", () => chargeClient.GetLightningNodeChannels("BTC"));
|
||||
// Not permission for the store!
|
||||
await AssertAPIError("missing-permission", () => client.GetLightningNodeChannels(user.StoreId, "BTC"));
|
||||
var invoiceData = await client.CreateLightningInvoice("BTC", new CreateLightningInvoiceRequest()
|
||||
await AssertAPIError("missing-permission", () => chargeClient.GetLightningNodeChannels(user.StoreId, "BTC"));
|
||||
var invoiceData = await chargeClient.CreateLightningInvoice("BTC", new CreateLightningInvoiceRequest()
|
||||
{
|
||||
Amount = LightMoney.Satoshis(1000),
|
||||
Description = "lol",
|
||||
@ -2481,17 +2368,17 @@ namespace BTCPayServer.Tests
|
||||
PrivateRouteHints = false
|
||||
});
|
||||
var chargeInvoice = invoiceData;
|
||||
Assert.NotNull(await client.GetLightningInvoice("BTC", invoiceData.Id));
|
||||
Assert.NotNull(await chargeClient.GetLightningInvoice("BTC", invoiceData.Id));
|
||||
|
||||
// check list for internal node
|
||||
var invoices = await client.GetLightningInvoices("BTC");
|
||||
var pendingInvoices = await client.GetLightningInvoices("BTC", true);
|
||||
var invoices = await chargeClient.GetLightningInvoices("BTC");
|
||||
var pendingInvoices = await chargeClient.GetLightningInvoices("BTC", true);
|
||||
Assert.NotEmpty(invoices);
|
||||
Assert.Contains(invoices, i => i.Id == invoiceData.Id);
|
||||
Assert.NotEmpty(pendingInvoices);
|
||||
Assert.Contains(pendingInvoices, i => i.Id == invoiceData.Id);
|
||||
|
||||
client = await user.CreateClient($"{Policies.CanUseLightningNodeInStore}:{user.StoreId}");
|
||||
var client = await user.CreateClient($"{Policies.CanUseLightningNodeInStore}:{user.StoreId}");
|
||||
// Not permission for the server
|
||||
await AssertAPIError("missing-permission", () => client.GetLightningNodeChannels("BTC"));
|
||||
|
||||
@ -2570,7 +2457,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains(payments, i => i.BOLT11 == merchantInvoice.BOLT11);
|
||||
|
||||
// Node info
|
||||
var info = await client.GetLightningNodeInfo(user.StoreId, "BTC");
|
||||
info = await client.GetLightningNodeInfo(user.StoreId, "BTC");
|
||||
Assert.Single(info.NodeURIs);
|
||||
Assert.NotEqual(0, info.BlockHeight);
|
||||
|
||||
@ -2611,12 +2498,7 @@ namespace BTCPayServer.Tests
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.CLightning);
|
||||
|
||||
var client = await user.CreateClient(Policies.Unrestricted);
|
||||
var invoices = new Task<Client.Models.InvoiceData>[5];
|
||||
|
||||
// Create invoices
|
||||
for (int i = 0; i < invoices.Length; i++)
|
||||
{
|
||||
invoices[i] = client.CreateInvoice(user.StoreId,
|
||||
var invoice = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest
|
||||
{
|
||||
Currency = "USD",
|
||||
@ -2627,35 +2509,18 @@ namespace BTCPayServer.Tests
|
||||
DefaultPaymentMethod = "BTC_LightningLike"
|
||||
}
|
||||
});
|
||||
}
|
||||
var pm = Assert.Single(await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id));
|
||||
Assert.False(pm.AdditionalData.HasValues);
|
||||
|
||||
var pm = new InvoicePaymentMethodDataModel[invoices.Length];
|
||||
for (int i = 0; i < invoices.Length; i++)
|
||||
{
|
||||
pm[i] = Assert.Single(await client.GetInvoicePaymentMethods(user.StoreId, (await invoices[i]).Id));
|
||||
Assert.True(pm[i].AdditionalData.HasValues);
|
||||
}
|
||||
var resp = await tester.CustomerLightningD.Pay(pm.Destination);
|
||||
Assert.Equal(PayResult.Ok, resp.Result);
|
||||
Assert.NotNull(resp.Details.PaymentHash);
|
||||
Assert.NotNull(resp.Details.Preimage);
|
||||
|
||||
// Pay them all at once
|
||||
Task<PayResponse>[] payResponses = new Task<PayResponse>[invoices.Length];
|
||||
for (int i = 0; i < invoices.Length; i++)
|
||||
{
|
||||
payResponses[i] = tester.CustomerLightningD.Pay(pm[i].Destination);
|
||||
}
|
||||
|
||||
// Checking the results
|
||||
for (int i = 0; i < invoices.Length; i++)
|
||||
{
|
||||
var resp = await payResponses[i];
|
||||
Assert.Equal(PayResult.Ok, resp.Result);
|
||||
Assert.NotNull(resp.Details.PaymentHash);
|
||||
Assert.NotNull(resp.Details.Preimage);
|
||||
|
||||
pm[i] = Assert.Single(await client.GetInvoicePaymentMethods(user.StoreId, (await invoices[i]).Id));
|
||||
Assert.True(pm[i].AdditionalData.HasValues);
|
||||
Assert.Equal(resp.Details.PaymentHash.ToString(), pm[i].AdditionalData.GetValue("paymentHash"));
|
||||
Assert.Equal(resp.Details.Preimage.ToString(), pm[i].AdditionalData.GetValue("preimage"));
|
||||
}
|
||||
pm = Assert.Single(await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id));
|
||||
Assert.True(pm.AdditionalData.HasValues);
|
||||
Assert.Equal(resp.Details.PaymentHash.ToString(), pm.AdditionalData.GetValue("paymentHash"));
|
||||
Assert.Equal(resp.Details.Preimage.ToString(), pm.AdditionalData.GetValue("preimage"));
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 20 * 1000)]
|
||||
@ -3393,16 +3258,11 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var client = await user.CreateClient(Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings);
|
||||
|
||||
var roles = await client.GetServerRoles();
|
||||
Assert.Equal(2,roles.Count);
|
||||
#pragma warning disable CS0618
|
||||
var ownerRole = roles.Single(data => data.Role == StoreRoles.Owner);
|
||||
var guestRole = roles.Single(data => data.Role == StoreRoles.Guest);
|
||||
#pragma warning restore CS0618
|
||||
var users = await client.GetStoreUsers(user.StoreId);
|
||||
var storeuser = Assert.Single(users);
|
||||
Assert.Equal(user.UserId, storeuser.UserId);
|
||||
Assert.Equal(ownerRole.Id, storeuser.Role);
|
||||
Assert.Equal(StoreRoles.Owner, storeuser.Role);
|
||||
|
||||
var user2 = tester.NewAccount();
|
||||
await user2.GrantAccessAsync(false);
|
||||
|
||||
@ -3413,7 +3273,7 @@ namespace BTCPayServer.Tests
|
||||
await AssertPermissionError(Policies.CanModifyStoreSettings, async () => await user2Client.AddStoreUser(user.StoreId, new StoreUserData()));
|
||||
await AssertPermissionError(Policies.CanModifyStoreSettings, async () => await user2Client.RemoveStoreUser(user.StoreId, user.UserId));
|
||||
|
||||
await client.AddStoreUser(user.StoreId, new StoreUserData() { Role = guestRole.Id, UserId = user2.UserId });
|
||||
await client.AddStoreUser(user.StoreId, new StoreUserData() { Role = StoreRoles.Guest, UserId = user2.UserId });
|
||||
|
||||
//test no access to api when only a guest
|
||||
await AssertPermissionError(Policies.CanModifyStoreSettings, async () => await user2Client.GetStoreUsers(user.StoreId));
|
||||
@ -3427,10 +3287,10 @@ namespace BTCPayServer.Tests
|
||||
await user2Client.GetStore(user.StoreId));
|
||||
|
||||
|
||||
await client.AddStoreUser(user.StoreId, new StoreUserData() { Role = ownerRole.Id, UserId = user2.UserId });
|
||||
await client.AddStoreUser(user.StoreId, new StoreUserData() { Role = StoreRoles.Owner, UserId = user2.UserId });
|
||||
await AssertAPIError("duplicate-store-user-role", async () =>
|
||||
await client.AddStoreUser(user.StoreId,
|
||||
new StoreUserData() { Role = ownerRole.Id, UserId = user2.UserId }));
|
||||
new StoreUserData() { Role = StoreRoles.Owner, UserId = user2.UserId }));
|
||||
await user2Client.RemoveStoreUser(user.StoreId, user.UserId);
|
||||
|
||||
|
||||
@ -3546,57 +3406,16 @@ namespace BTCPayServer.Tests
|
||||
PaymentMethod = "BTC_LightningNetwork",
|
||||
Destination = customerInvoice.BOLT11
|
||||
});
|
||||
Assert.Equal(payout.Metadata.ToString(), new JObject().ToString()); //empty
|
||||
Assert.Empty(await adminClient.GetStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork"));
|
||||
await adminClient.UpdateStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork",
|
||||
new LightningAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(600) });
|
||||
Assert.Equal(600, Assert.Single(await adminClient.GetStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork")).IntervalSeconds.TotalSeconds);
|
||||
new LightningAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(2) });
|
||||
Assert.Equal(2, Assert.Single(await adminClient.GetStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork")).IntervalSeconds.TotalSeconds);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var payoutC =
|
||||
(await adminClient.GetStorePayouts(admin.StoreId, false)).Single(data => data.Id == payout.Id);
|
||||
Assert.Equal(PayoutState.Completed, payoutC.State);
|
||||
});
|
||||
|
||||
payout = await adminClient.CreatePayout(admin.StoreId,
|
||||
new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
Approved = true,
|
||||
PaymentMethod = "BTC",
|
||||
Destination = (await tester.ExplorerNode.GetNewAddressAsync()).ToString(),
|
||||
Amount = 0.0001m,
|
||||
Metadata = JObject.FromObject(new
|
||||
{
|
||||
source ="apitest",
|
||||
sourceLink = "https://chocolate.com"
|
||||
})
|
||||
});
|
||||
Assert.Equal(payout.Metadata.ToString(), JObject.FromObject(new
|
||||
{
|
||||
source = "apitest",
|
||||
sourceLink = "https://chocolate.com"
|
||||
}).ToString());
|
||||
|
||||
payout =
|
||||
(await adminClient.GetStorePayouts(admin.StoreId, false)).Single(data => data.Id == payout.Id);
|
||||
|
||||
Assert.Equal(payout.Metadata.ToString(), JObject.FromObject(new
|
||||
{
|
||||
source = "apitest",
|
||||
sourceLink = "https://chocolate.com"
|
||||
}).ToString());
|
||||
|
||||
customerInvoice = await tester.CustomerLightningD.CreateInvoice(LightMoney.FromUnit(10, LightMoneyUnit.Satoshi),
|
||||
Guid.NewGuid().ToString(), TimeSpan.FromDays(40));
|
||||
var payout2 = await adminClient.CreatePayout(admin.StoreId,
|
||||
new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
Approved = true,
|
||||
Amount = new Money(100, MoneyUnit.Satoshi).ToDecimal(MoneyUnit.BTC),
|
||||
PaymentMethod = "BTC_LightningNetwork",
|
||||
Destination = customerInvoice.BOLT11
|
||||
});
|
||||
Assert.Equal(payout2.Amount, new Money(100, MoneyUnit.Satoshi).ToDecimal(MoneyUnit.BTC));
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
@ -3675,8 +3494,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.Empty(await adminClient.GetPayoutProcessors(admin.StoreId));
|
||||
|
||||
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC",
|
||||
new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(3600) });
|
||||
Assert.Equal(3600, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
|
||||
new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(100000) });
|
||||
Assert.Equal(100000, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
|
||||
|
||||
var tpGen = Assert.Single(await adminClient.GetPayoutProcessors(admin.StoreId));
|
||||
Assert.Equal("BTC", Assert.Single(tpGen.PaymentMethods));
|
||||
@ -3689,10 +3508,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.Empty(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC"));
|
||||
Assert.Empty(await adminClient.GetPayoutProcessors(admin.StoreId));
|
||||
|
||||
// Send just enough money to cover the smallest of the payouts.
|
||||
var fee = (await tester.PayTester.GetService<IFeeProviderFactory>().CreateFeeProvider(tester.DefaultNetwork).GetFeeRateAsync(100)).GetFee(150);
|
||||
await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.00001m) + fee);
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.000012m));
|
||||
await tester.ExplorerNode.GenerateAsync(1);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
@ -3702,9 +3519,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(3, payouts.Length);
|
||||
});
|
||||
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC",
|
||||
new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(600), FeeBlockTarget = 1000 });
|
||||
Assert.Equal(600, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
|
||||
|
||||
new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(5) });
|
||||
Assert.Equal(5, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
Assert.Equal(2, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count());
|
||||
@ -3712,135 +3528,14 @@ namespace BTCPayServer.Tests
|
||||
Assert.Single(payouts.Where(data => data.State == PayoutState.InProgress));
|
||||
});
|
||||
|
||||
uint256 txid = null;
|
||||
await tester.WaitForEvent<NewOnChainTransactionEvent>(async () =>
|
||||
{
|
||||
txid = await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.01m) + fee);
|
||||
}, correctEvent: ev => ev.NewTransactionEvent.TransactionData.TransactionHash == txid);
|
||||
await tester.PayTester.GetService<PayoutProcessorService>().Restart(new PayoutProcessorService.PayoutProcessorQuery(admin.StoreId, "BTC"));
|
||||
await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.01m));
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
Assert.Equal(4, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count());
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
Assert.Empty(payouts.Where(data => data.State != PayoutState.InProgress));
|
||||
});
|
||||
|
||||
// settings that were added later
|
||||
var settings =
|
||||
Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC"));
|
||||
Assert.False( settings.ProcessNewPayoutsInstantly);
|
||||
Assert.Equal(0m, settings.Threshold);
|
||||
|
||||
//let's use the ProcessNewPayoutsInstantly so that it will trigger instantly
|
||||
|
||||
settings.IntervalSeconds = TimeSpan.FromDays(1);
|
||||
settings.ProcessNewPayoutsInstantly = true;
|
||||
|
||||
await tester.WaitForEvent<NewOnChainTransactionEvent>(async () =>
|
||||
{
|
||||
txid = await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(1m) + fee);
|
||||
}, correctEvent: ev => ev.NewTransactionEvent.TransactionData.TransactionHash == txid);
|
||||
|
||||
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC", settings);
|
||||
settings =
|
||||
Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC"));
|
||||
Assert.True( settings.ProcessNewPayoutsInstantly);
|
||||
|
||||
var pluginHookService = tester.PayTester.GetService<IPluginHookService>();
|
||||
var beforeHookTcs = new TaskCompletionSource();
|
||||
var afterHookTcs = new TaskCompletionSource();
|
||||
pluginHookService.ActionInvoked += (sender, tuple) =>
|
||||
{
|
||||
switch (tuple.hook)
|
||||
{
|
||||
case "before-automated-payout-processing":
|
||||
beforeHookTcs.TrySetResult();
|
||||
break;
|
||||
case "after-automated-payout-processing":
|
||||
afterHookTcs.TrySetResult();
|
||||
break;
|
||||
}
|
||||
};
|
||||
var payoutThatShouldBeProcessedStraightAway = await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
PullPaymentId = pullPayment.Id,
|
||||
Amount = 0.5m,
|
||||
Approved = true,
|
||||
PaymentMethod = "BTC",
|
||||
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
});
|
||||
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
Assert.Single(payouts.Where(data => data.State == PayoutState.InProgress && data.Id == payoutThatShouldBeProcessedStraightAway.Id));
|
||||
|
||||
beforeHookTcs = new TaskCompletionSource();
|
||||
afterHookTcs = new TaskCompletionSource();
|
||||
//let's test the threshold limiter
|
||||
settings.Threshold = 0.5m;
|
||||
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC", settings);
|
||||
|
||||
//quick test: when updating processor, it processes instantly
|
||||
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
settings =
|
||||
Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC"));
|
||||
Assert.Equal(0.5m, settings.Threshold);
|
||||
|
||||
//create a payout that should not be processed straight away due to threshold
|
||||
|
||||
beforeHookTcs = new TaskCompletionSource();
|
||||
afterHookTcs = new TaskCompletionSource();
|
||||
var payoutThatShouldNotBeProcessedStraightAway = await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
Amount = 0.1m,
|
||||
Approved = true,
|
||||
PaymentMethod = "BTC",
|
||||
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
});
|
||||
|
||||
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
Assert.Single(payouts.Where(data => data.State == PayoutState.AwaitingPayment && data.Id == payoutThatShouldNotBeProcessedStraightAway.Id));
|
||||
|
||||
beforeHookTcs = new TaskCompletionSource();
|
||||
afterHookTcs = new TaskCompletionSource();
|
||||
var payoutThatShouldNotBeProcessedStraightAway2 = await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
Amount = 0.3m,
|
||||
Approved = true,
|
||||
PaymentMethod = "BTC",
|
||||
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
});
|
||||
|
||||
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
Assert.Equal(2, payouts.Count(data => data.State == PayoutState.AwaitingPayment &&
|
||||
(data.Id == payoutThatShouldNotBeProcessedStraightAway.Id || data.Id == payoutThatShouldNotBeProcessedStraightAway2.Id)));
|
||||
|
||||
beforeHookTcs = new TaskCompletionSource();
|
||||
afterHookTcs = new TaskCompletionSource();
|
||||
var payoutThatShouldNotBeProcessedStraightAway3 = await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
Amount = 0.3m,
|
||||
Approved = true,
|
||||
PaymentMethod = "BTC",
|
||||
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
});
|
||||
|
||||
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
Assert.Empty(payouts.Where(data => data.State != PayoutState.InProgress));
|
||||
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
|
@ -1,12 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Plugins.PointOfSale;
|
||||
using BTCPayServer.Plugins.PointOfSale.Controllers;
|
||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
@ -21,74 +19,6 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void CanParseOldYmlCorrectly()
|
||||
{
|
||||
var testOriginalDefaultYmlTemplate = @"
|
||||
green tea:
|
||||
price: 1
|
||||
title: Green Tea
|
||||
description: Lovely, fresh and tender, Meng Ding Gan Lu ('sweet dew') is grown in the lush Meng Ding Mountains of the southwestern province of Sichuan where it has been cultivated for over a thousand years.
|
||||
image: ~/img/pos-sample/green-tea.jpg
|
||||
|
||||
black tea:
|
||||
price: 1
|
||||
title: Black Tea
|
||||
description: Tian Jian Tian Jian means 'heavenly tippy tea' in Chinese, and it describes the finest grade of dark tea. Our Tian Jian dark tea is from Hunan province which is famous for making some of the best dark teas available.
|
||||
image: ~/img/pos-sample/black-tea.jpg
|
||||
|
||||
rooibos:
|
||||
price: 1.2
|
||||
title: Rooibos
|
||||
description: Rooibos is a dramatic red tea made from a South African herb that contains polyphenols and flavonoids. Often called 'African redbush tea', Rooibos herbal tea delights the senses and delivers potential health benefits with each caffeine-free sip.
|
||||
image: ~/img/pos-sample/rooibos.jpg
|
||||
|
||||
pu erh:
|
||||
price: 2
|
||||
title: Pu Erh
|
||||
description: This loose pur-erh tea is produced in Yunnan Province, China. The process in a relatively high humidity environment has mellowed the elemental character of the tea when compared to young Pu-erh.
|
||||
image: ~/img/pos-sample/pu-erh.jpg
|
||||
|
||||
herbal tea:
|
||||
price: 1.8
|
||||
title: Herbal Tea
|
||||
description: Chamomile tea is made from the flower heads of the chamomile plant. The medicinal use of chamomile dates back to the ancient Egyptians, Romans and Greeks. Pay us what you want!
|
||||
image: ~/img/pos-sample/herbal-tea.jpg
|
||||
custom: true
|
||||
|
||||
fruit tea:
|
||||
price: 1.5
|
||||
title: Fruit Tea
|
||||
description: The Tibetan Himalayas, the land is majestic and beautiful—a spiritual place where, despite the perilous environment, many journey seeking enlightenment. Pay us what you want!
|
||||
image: ~/img/pos-sample/fruit-tea.jpg
|
||||
inventory: 5
|
||||
custom: true
|
||||
";
|
||||
var parsedDefault = MigrationStartupTask.ParsePOSYML(testOriginalDefaultYmlTemplate);
|
||||
Assert.Equal(6, parsedDefault.Length);
|
||||
Assert.Equal( "Green Tea" ,parsedDefault[0].Title);
|
||||
Assert.Equal( "green tea" ,parsedDefault[0].Id);
|
||||
Assert.Equal( "Lovely, fresh and tender, Meng Ding Gan Lu ('sweet dew') is grown in the lush Meng Ding Mountains of the southwestern province of Sichuan where it has been cultivated for over a thousand years." ,parsedDefault[0].Description);
|
||||
Assert.Null( parsedDefault[0].BuyButtonText);
|
||||
Assert.Equal( "~/img/pos-sample/green-tea.jpg" ,parsedDefault[0].Image);
|
||||
Assert.Equal( 1 ,parsedDefault[0].Price);
|
||||
Assert.Equal( ViewPointOfSaleViewModel.ItemPriceType.Fixed ,parsedDefault[0].PriceType);
|
||||
Assert.Null( parsedDefault[0].AdditionalData);
|
||||
Assert.Null( parsedDefault[0].PaymentMethods);
|
||||
|
||||
|
||||
Assert.Equal( "Herbal Tea" ,parsedDefault[4].Title);
|
||||
Assert.Equal( "herbal tea" ,parsedDefault[4].Id);
|
||||
Assert.Equal( "Chamomile tea is made from the flower heads of the chamomile plant. The medicinal use of chamomile dates back to the ancient Egyptians, Romans and Greeks. Pay us what you want!" ,parsedDefault[4].Description);
|
||||
Assert.Null( parsedDefault[4].BuyButtonText);
|
||||
Assert.Equal( "~/img/pos-sample/herbal-tea.jpg" ,parsedDefault[4].Image);
|
||||
Assert.Equal( 1.8m ,parsedDefault[4].Price);
|
||||
Assert.Equal( ViewPointOfSaleViewModel.ItemPriceType.Minimum ,parsedDefault[4].PriceType);
|
||||
Assert.Null( parsedDefault[4].AdditionalData);
|
||||
Assert.Null( parsedDefault[4].PaymentMethods);
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanUsePoSApp1()
|
||||
@ -123,7 +53,6 @@ donation:
|
||||
price: 1.02
|
||||
custom: true
|
||||
";
|
||||
vmpos.Template = AppService.SerializeTemplate(MigrationStartupTask.ParsePOSYML(vmpos.Template));
|
||||
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
var publicApps = user.GetController<UIPointOfSaleController>();
|
||||
@ -135,10 +64,10 @@ donation:
|
||||
Assert.Equal("donation", vmview.Items[1].Title);
|
||||
// orange is available
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, choiceKey: "orange").Result);
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, null, null, null, null, "orange").Result);
|
||||
// apple is not found
|
||||
Assert.IsType<NotFoundResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, choiceKey: "apple").Result);
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, null, null, null, null, "apple").Result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@ -65,6 +64,7 @@ namespace BTCPayServer.Tests
|
||||
var containerIp = File.ReadAllText("/etc/hosts").Split('\n', StringSplitOptions.RemoveEmptyEntries).Last()
|
||||
.Split('\t', StringSplitOptions.RemoveEmptyEntries)[0].Trim();
|
||||
TestLogs.LogInformation($"Selenium: Container's IP {containerIp}");
|
||||
ServerUri = new Uri(Server.PayTester.ServerUri.AbsoluteUri.Replace($"http://{Server.PayTester.HostName}", $"http://{containerIp}", StringComparison.OrdinalIgnoreCase), UriKind.Absolute);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -76,8 +76,8 @@ namespace BTCPayServer.Tests
|
||||
Driver = new ChromeDriver(cds, options,
|
||||
// A bit less than test timeout
|
||||
TimeSpan.FromSeconds(50));
|
||||
ServerUri = Server.PayTester.ServerUri;
|
||||
}
|
||||
ServerUri = Server.PayTester.ServerUri;
|
||||
Driver.Manage().Window.Maximize();
|
||||
|
||||
TestLogs.LogInformation($"Selenium: Using {Driver.GetType()}");
|
||||
@ -180,7 +180,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Driver.FindElement(By.Id("StoreSelectorToggle")).Click();
|
||||
}
|
||||
GoToUrl("/stores/create");
|
||||
Driver.WaitForElement(By.Id("StoreSelectorCreate")).Click();
|
||||
var name = "Store" + RandomUtils.GetUInt64();
|
||||
TestLogs.LogInformation($"Created store {name}");
|
||||
Driver.WaitForElement(By.Id("Name")).SendKeys(name);
|
||||
@ -313,6 +313,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var connectionString = connectionType switch
|
||||
{
|
||||
LightningConnectionType.Charge =>
|
||||
$"type=charge;server={Server.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true",
|
||||
LightningConnectionType.CLightning =>
|
||||
$"type=clightning;server={((CLightningClient)Server.MerchantLightningD).Address.AbsoluteUri}",
|
||||
LightningConnectionType.LndREST =>
|
||||
@ -393,10 +395,6 @@ namespace BTCPayServer.Tests
|
||||
public void GoToHome()
|
||||
{
|
||||
Driver.Navigate().GoToUrl(ServerUri);
|
||||
if (Driver.PageSource.Contains("id=\"SkipWizard\""))
|
||||
{
|
||||
Driver.FindElement(By.Id("SkipWizard")).Click();
|
||||
}
|
||||
}
|
||||
|
||||
public void Logout()
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
@ -13,10 +11,8 @@ using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
@ -57,11 +53,10 @@ namespace BTCPayServer.Tests
|
||||
using var s = CreateSeleniumTester();
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser(true);
|
||||
s.GoToHome();
|
||||
s.GoToServer();
|
||||
s.Driver.FindElement(By.Id("Nav-ServerSettings")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
s.ClickOnAllSectionLinks();
|
||||
s.GoToServer();
|
||||
s.Driver.FindElement(By.Id("Nav-ServerSettings")).Click();
|
||||
s.Driver.FindElement(By.LinkText("Services")).Click();
|
||||
|
||||
TestLogs.LogInformation("Let's check if we can access the logs");
|
||||
@ -121,31 +116,24 @@ namespace BTCPayServer.Tests
|
||||
|
||||
s.Driver.FindElement(By.Name("buyerEmail")).SendKeys("aa@aa.com");
|
||||
s.Driver.FindElement(By.CssSelector("input[type='submit']")).Click();
|
||||
invoiceId = s.Driver.Url.Split('/').Last();
|
||||
s.Driver.Navigate().GoToUrl(editUrl);
|
||||
Assert.Contains("aa@aa.com", s.Driver.PageSource);
|
||||
var invoice = await s.Server.PayTester.GetService<InvoiceRepository>().GetInvoice(invoiceId);
|
||||
Assert.Equal("aa@aa.com", invoice.Metadata.BuyerEmail);
|
||||
|
||||
//Custom Forms
|
||||
s.GoToStore(StoreNavPages.Forms);
|
||||
Assert.Contains("There are no forms yet.", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Id("CreateForm")).Click();
|
||||
s.Driver.FindElement(By.Name("Name")).SendKeys("Custom Form 1");
|
||||
s.Driver.FindElement(By.Id("ApplyEmailTemplate")).Click();
|
||||
|
||||
s.Driver.FindElement(By.Id("CodeTabButton")).Click();
|
||||
s.Driver.WaitForElement(By.Id("CodeTabPane"));
|
||||
|
||||
var config = s.Driver.FindElement(By.Name("FormConfig")).GetAttribute("value");
|
||||
Assert.Contains("buyerEmail", config);
|
||||
|
||||
s.Driver.FindElement((By.CssSelector("[data-form-template='email']"))).Click();
|
||||
var emailtemplate = s.Driver.FindElement(By.Name("FormConfig")).GetAttribute("value");
|
||||
Assert.Contains("buyerEmail", emailtemplate);
|
||||
s.Driver.FindElement(By.Name("FormConfig")).Clear();
|
||||
s.Driver.FindElement(By.Name("FormConfig"))
|
||||
.SendKeys(config.Replace("Enter your email", "CustomFormInputTest"));
|
||||
.SendKeys(emailtemplate.Replace("Enter your email", "CustomFormInputTest"));
|
||||
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
||||
s.Driver.FindElement(By.Id("ViewForm")).Click();
|
||||
|
||||
|
||||
var formurl = s.Driver.Url;
|
||||
Assert.Contains("CustomFormInputTest", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Name("buyerEmail")).SendKeys("aa@aa.com");
|
||||
@ -164,16 +152,12 @@ namespace BTCPayServer.Tests
|
||||
Assert.DoesNotContain("Custom Form 1", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Id("CreateForm")).Click();
|
||||
s.Driver.FindElement(By.Name("Name")).SendKeys("Custom Form 2");
|
||||
s.Driver.FindElement(By.Id("ApplyEmailTemplate")).Click();
|
||||
|
||||
s.Driver.FindElement(By.Id("CodeTabButton")).Click();
|
||||
s.Driver.WaitForElement(By.Id("CodeTabPane"));
|
||||
|
||||
s.Driver.FindElement((By.CssSelector("[data-form-template='email']"))).Click();
|
||||
s.Driver.SetCheckbox(By.Name("Public"), true);
|
||||
|
||||
s.Driver.FindElement(By.Name("FormConfig")).Clear();
|
||||
s.Driver.FindElement(By.Name("FormConfig"))
|
||||
.SendKeys(config.Replace("Enter your email", "CustomFormInputTest2"));
|
||||
.SendKeys(emailtemplate.Replace("Enter your email", "CustomFormInputTest2"));
|
||||
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
||||
s.Driver.FindElement(By.Id("ViewForm")).Click();
|
||||
formurl = s.Driver.Url;
|
||||
@ -248,8 +232,7 @@ namespace BTCPayServer.Tests
|
||||
s.Server.ActivateLightning();
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser(true);
|
||||
s.GoToHome();
|
||||
s.GoToServer();
|
||||
s.Driver.FindElement(By.Id("Nav-ServerSettings")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
s.Driver.FindElement(By.LinkText("Services")).Click();
|
||||
|
||||
@ -316,7 +299,6 @@ namespace BTCPayServer.Tests
|
||||
await s.StartAsync();
|
||||
//Register & Log Out
|
||||
var email = s.RegisterNewUser();
|
||||
s.GoToHome();
|
||||
s.Logout();
|
||||
s.Driver.AssertNoError();
|
||||
Assert.Contains("/login", s.Driver.Url);
|
||||
@ -352,7 +334,6 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Password")).SendKeys("abc???");
|
||||
s.Driver.FindElement(By.Id("LoginButton")).Click();
|
||||
|
||||
s.GoToHome();
|
||||
s.GoToProfile();
|
||||
s.ClickOnAllSectionLinks();
|
||||
|
||||
@ -360,7 +341,6 @@ namespace BTCPayServer.Tests
|
||||
s.Logout();
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser(true);
|
||||
s.GoToHome();
|
||||
s.GoToServer(ServerNavPages.Users);
|
||||
s.Driver.FindElement(By.Id("CreateUser")).Click();
|
||||
|
||||
@ -383,7 +363,6 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("LoginButton")).Click();
|
||||
|
||||
// We should be logged in now
|
||||
s.GoToHome();
|
||||
s.Driver.FindElement(By.Id("mainNav"));
|
||||
|
||||
//let's test delete user quickly while we're at it
|
||||
@ -616,7 +595,7 @@ namespace BTCPayServer.Tests
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
s.Driver.FindElement(By.Id("ReceiptLink")).Click();
|
||||
s.Driver.FindElement(By.Id("receipt-btn")).Click();
|
||||
});
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
@ -628,13 +607,14 @@ namespace BTCPayServer.Tests
|
||||
|
||||
await s.Server.PayTester.InvoiceRepository.MarkInvoiceStatus(i, InvoiceStatus.Settled);
|
||||
|
||||
TestUtils.Eventually(() => s.Driver.FindElement(By.Id("ReceiptLink")).Click());
|
||||
TestUtils.Eventually(() => s.Driver.FindElement(By.Id("receipt-btn")).Click());
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
Assert.DoesNotContain("invoice-unsettled", s.Driver.PageSource);
|
||||
Assert.DoesNotContain("invoice-processing", s.Driver.PageSource);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
@ -645,24 +625,21 @@ namespace BTCPayServer.Tests
|
||||
s.RegisterNewUser();
|
||||
s.GoToUrl("/");
|
||||
|
||||
// verify redirected to create store page
|
||||
Assert.EndsWith("/stores/create", s.Driver.Url);
|
||||
Assert.Contains("Create your first store", s.Driver.PageSource);
|
||||
Assert.Contains("Create a store to begin accepting payments", s.Driver.PageSource);
|
||||
Assert.False(s.Driver.PageSource.Contains("id=\"StoreSelectorDropdown\""), "Store selector dropdown should not be present");
|
||||
Assert.True(s.Driver.PageSource.Contains("id=\"StoreSelectorCreate\""), "Store selector create button should be present");
|
||||
|
||||
// verify steps for store creation are displayed correctly
|
||||
s.Driver.FindElement(By.Id("SetupGuide-Store")).Click();
|
||||
Assert.Contains("/stores/create", s.Driver.Url);
|
||||
|
||||
(_, string storeId) = s.CreateNewStore();
|
||||
|
||||
// should redirect to first store
|
||||
// should redirect to store
|
||||
s.GoToUrl("/");
|
||||
|
||||
Assert.Contains($"/stores/{storeId}", s.Driver.Url);
|
||||
Assert.True(s.Driver.PageSource.Contains("id=\"StoreSelectorDropdown\""), "Store selector dropdown should be present");
|
||||
Assert.True(s.Driver.PageSource.Contains("id=\"SetupGuide\""), "Store setup guide should be present");
|
||||
|
||||
s.GoToUrl("/stores/create");
|
||||
Assert.Contains("Create a new store", s.Driver.PageSource);
|
||||
Assert.DoesNotContain("Create your first store", s.Driver.PageSource);
|
||||
Assert.DoesNotContain("To start accepting payments, set up a store.", s.Driver.PageSource);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
@ -736,8 +713,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.DoesNotContain(invoiceId, s.Driver.PageSource);
|
||||
|
||||
// unarchive via list
|
||||
s.Driver.FindElement(By.Id("StatusOptionsToggle")).Click();
|
||||
s.Driver.FindElement(By.Id("StatusOptionsIncludeArchived")).Click();
|
||||
s.Driver.FindElement(By.Id("SearchOptionsToggle")).Click();
|
||||
s.Driver.FindElement(By.Id("SearchOptionsIncludeArchived")).Click();
|
||||
Assert.Contains(invoiceId, s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.CssSelector($".selector[value=\"{invoiceId}\"]")).Click();
|
||||
s.Driver.FindElement(By.Id("ActionsDropdownToggle")).Click();
|
||||
@ -968,13 +945,11 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.CssSelector("label[for='DefaultView_Cart']")).Click();
|
||||
s.Driver.FindElement(By.CssSelector(".template-item:nth-of-type(1) .btn-primary")).Click();
|
||||
s.Driver.FindElement(By.Id("BuyButtonText")).SendKeys("Take my money");
|
||||
s.Driver.FindElement(By.Id("EditorCategories-ts-control")).SendKeys("Drinks");
|
||||
s.Driver.FindElement(By.Id("SaveItemChanges")).Click();
|
||||
s.Driver.FindElement(By.Id("ToggleRawEditor")).Click();
|
||||
|
||||
var template = s.Driver.FindElement(By.Id("Template")).GetAttribute("value");
|
||||
Assert.Contains("\"buyButtonText\": \"Take my money\"", template);
|
||||
Assert.Matches("\"categories\": \\[\n\\s+\"Drinks\"\n\\s+\\]", template);
|
||||
Assert.Contains("buyButtonText: Take my money", template);
|
||||
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).Click();
|
||||
Assert.Contains("App updated", s.FindAlertMessage().Text);
|
||||
@ -988,14 +963,6 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(s.Driver.PageSource.Contains("Tea shop"), "Unable to create PoS");
|
||||
Assert.True(s.Driver.PageSource.Contains("Cart"), "PoS not showing correct default view");
|
||||
Assert.True(s.Driver.PageSource.Contains("Take my money"), "PoS not showing correct default view");
|
||||
Assert.Equal(5, s.Driver.FindElements(By.CssSelector(".posItem:not(.d-none)")).Count);
|
||||
|
||||
var drinks = s.Driver.FindElement(By.CssSelector("label[for='Category-Drinks']"));
|
||||
Assert.Equal("Drinks", drinks.Text);
|
||||
drinks.Click();
|
||||
Assert.Single(s.Driver.FindElements(By.CssSelector(".posItem:not(.d-none)")));
|
||||
s.Driver.FindElement(By.CssSelector("label[for='Category-*']")).Click();
|
||||
Assert.Equal(5, s.Driver.FindElements(By.CssSelector(".posItem:not(.d-none)")).Count);
|
||||
|
||||
s.Driver.Url = posBaseUrl + "/static";
|
||||
Assert.False(s.Driver.PageSource.Contains("Cart"), "Static PoS not showing correct view");
|
||||
@ -1162,13 +1129,12 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("ArchivePaymentRequest")).Click();
|
||||
Assert.Contains("The payment request has been archived", s.FindAlertMessage().Text);
|
||||
Assert.DoesNotContain("Pay123", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Id("StatusOptionsToggle")).Click();
|
||||
s.Driver.WaitForElement(By.Id("StatusOptionsIncludeArchived")).Click();
|
||||
s.Driver.FindElement(By.Id("SearchDropdownToggle")).Click();
|
||||
s.Driver.FindElement(By.Id("SearchIncludeArchived")).Click();
|
||||
Assert.Contains("Pay123", s.Driver.PageSource);
|
||||
|
||||
// unarchive (from list)
|
||||
s.Driver.FindElement(By.Id($"ToggleActions-{payReqId}")).Click();
|
||||
s.Driver.WaitForElement(By.Id($"ToggleArchival-{payReqId}")).Click();
|
||||
s.Driver.FindElement(By.Id($"ToggleArchival-{payReqId}")).Click();
|
||||
Assert.Contains("The payment request has been unarchived", s.FindAlertMessage().Text);
|
||||
Assert.Contains("Pay123", s.Driver.PageSource);
|
||||
}
|
||||
@ -1459,7 +1425,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("CancelWizard")).Click();
|
||||
|
||||
// Check the label is applied to the tx
|
||||
s.Driver.WaitWalletTransactionsLoaded();
|
||||
|
||||
Assert.Equal("label2", s.Driver.FindElement(By.XPath("//*[@id=\"WalletTransactionsList\"]//*[contains(@class, 'transaction-label')]")).Text);
|
||||
|
||||
//change the wallet and ensure old address is not there and generating a new one does not result in the prev one
|
||||
@ -1510,9 +1476,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// Check the tx sent earlier arrived
|
||||
s.Driver.FindElement(By.Id($"StoreNav-Wallet{cryptoCode}")).Click();
|
||||
s.Driver.WaitWalletTransactionsLoaded();
|
||||
s.Driver.FindElement(By.PartialLinkText(tx.ToString()));
|
||||
|
||||
Assert.Contains(tx.ToString(), s.Driver.PageSource);
|
||||
var walletTransactionUri = new Uri(s.Driver.Url);
|
||||
|
||||
// Send to bob
|
||||
@ -1638,8 +1602,9 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// Transactions list is empty
|
||||
s.Driver.FindElement(By.Id($"StoreNav-Wallet{cryptoCode}")).Click();
|
||||
s.Driver.WaitWalletTransactionsLoaded();
|
||||
Assert.Contains("There are no transactions yet", s.Driver.FindElement(By.Id("WalletTransactions")).Text);
|
||||
Assert.Contains("There are no transactions yet.", s.Driver.PageSource);
|
||||
s.Driver.AssertElementNotFound(By.Id("ExportDropdownToggle"));
|
||||
s.Driver.AssertElementNotFound(By.Id("ActionsDropdownToggle"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -1761,12 +1726,12 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
Assert.Contains("transaction-label", s.Driver.PageSource);
|
||||
var labels = s.Driver.FindElements(By.CssSelector("#WalletTransactionsList tr:first-child div.transaction-label"));
|
||||
Assert.Equal(2, labels.Count);
|
||||
Assert.Contains(labels, element => element.Text == "payout");
|
||||
Assert.Contains(labels, element => element.Text == "pull-payment");
|
||||
});
|
||||
|
||||
var labels = s.Driver.FindElements(By.CssSelector("#WalletTransactionsList tr:first-child div.transaction-label"));
|
||||
Assert.Equal(2, labels.Count);
|
||||
Assert.Contains(labels, element => element.Text == "payout");
|
||||
Assert.Contains(labels, element => element.Text == "pull-payment");
|
||||
|
||||
s.GoToStore(s.StoreId, StoreNavPages.Payouts);
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.InProgress}-view")).Click();
|
||||
ReadOnlyCollection<IWebElement> txs;
|
||||
@ -1950,7 +1915,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
Assert.Contains(PayoutState.AwaitingPayment.GetStateString(), s.Driver.PageSource);
|
||||
|
||||
// LNURL Withdraw support check with BTC denomination
|
||||
//lnurl-w support check
|
||||
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
|
||||
@ -2018,42 +1984,6 @@ namespace BTCPayServer.Tests
|
||||
|
||||
Assert.Contains(PayoutState.AwaitingApproval.GetStateString(), s.Driver.PageSource);
|
||||
});
|
||||
|
||||
// LNURL Withdraw support check with SATS denomination
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("PP SATS");
|
||||
s.Driver.SetCheckbox(By.Id("AutoApproveClaims"), true);
|
||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("21021");
|
||||
s.Driver.FindElement(By.Id("Currency")).Clear();
|
||||
s.Driver.FindElement(By.Id("Currency")).SendKeys("SATS" + Keys.Enter);
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("#lnurlwithdraw-button")).Click();
|
||||
lnurl = new Uri(LNURL.LNURL.Parse(s.Driver.FindElement(By.Id("qr-code-data-input")).GetAttribute("value"), out _).ToString().Replace("https", "http"));
|
||||
s.Driver.FindElement(By.CssSelector("button[data-bs-dismiss='modal']")).Click();
|
||||
var amount = new LightMoney(21021, LightMoneyUnit.Satoshi);
|
||||
info = Assert.IsType<LNURLWithdrawRequest>(await LNURL.LNURL.FetchInformation(lnurl, s.Server.PayTester.HttpClient));
|
||||
Assert.Equal(amount, info.MaxWithdrawable);
|
||||
Assert.Equal(amount, info.CurrentBalance);
|
||||
info = Assert.IsType<LNURLWithdrawRequest>(await LNURL.LNURL.FetchInformation(info.BalanceCheck, s.Server.PayTester.HttpClient));
|
||||
Assert.Equal(amount, info.MaxWithdrawable);
|
||||
Assert.Equal(amount, info.CurrentBalance);
|
||||
|
||||
bolt2 = (await s.Server.CustomerLightningD.CreateInvoice(
|
||||
amount,
|
||||
$"LNurl w payout test {DateTime.UtcNow.Ticks}",
|
||||
TimeSpan.FromHours(1), CancellationToken.None));
|
||||
response = await info.SendRequest(bolt2.BOLT11, s.Server.PayTester.HttpClient);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
Assert.Contains(bolt2.BOLT11, s.Driver.PageSource);
|
||||
|
||||
Assert.Contains(PayoutState.Completed.GetStateString(), s.Driver.PageSource);
|
||||
Assert.Equal(LightningInvoiceStatus.Paid, (await s.Server.CustomerLightningD.GetInvoice(bolt2.Id)).Status);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -2093,150 +2023,6 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanUsePOSKeypad()
|
||||
{
|
||||
using var s = CreateSeleniumTester();
|
||||
s.Server.ActivateLightning();
|
||||
await s.StartAsync();
|
||||
|
||||
await s.Server.EnsureChannelsSetup();
|
||||
|
||||
s.RegisterNewUser(true);
|
||||
s.CreateNewStore();
|
||||
s.GoToStore();
|
||||
s.AddLightningNode(LightningConnectionType.CLightning, false);
|
||||
s.Driver.FindElement(By.Id("StoreNav-CreatePointOfSale")).Click();
|
||||
s.Driver.FindElement(By.Id("AppName")).SendKeys(Guid.NewGuid().ToString());
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
TestUtils.Eventually(() => Assert.Contains("App successfully created", s.FindAlertMessage().Text));
|
||||
s.Driver.FindElement(By.CssSelector("label[for='DefaultView_Light']")).Click();
|
||||
s.Driver.FindElement(By.Id("Currency")).SendKeys("EUR");
|
||||
s.Driver.FindElement(By.Id("CustomTipPercentages")).Clear();
|
||||
s.Driver.FindElement(By.Id("CustomTipPercentages")).SendKeys("10,21");
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).Click();
|
||||
Assert.Contains("App updated", s.FindAlertMessage().Text);
|
||||
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
||||
var windows = s.Driver.WindowHandles;
|
||||
Assert.Equal(2, windows.Count);
|
||||
s.Driver.SwitchTo().Window(windows[1]);
|
||||
s.Driver.WaitForElement(By.ClassName("keypad"));
|
||||
|
||||
// basic checks
|
||||
Assert.Contains("EUR", s.Driver.FindElement(By.Id("Currency")).Text);
|
||||
Assert.Contains("0,00", s.Driver.FindElement(By.Id("Amount")).Text);
|
||||
Assert.Equal("", s.Driver.FindElement(By.Id("Calculation")).Text);
|
||||
Assert.True(s.Driver.FindElement(By.Id("ModeTablist-amount")).Selected);
|
||||
Assert.False(s.Driver.FindElement(By.Id("ModeTablist-discount")).Enabled);
|
||||
Assert.False(s.Driver.FindElement(By.Id("ModeTablist-tip")).Enabled);
|
||||
|
||||
// Amount: 1234,56
|
||||
s.Driver.FindElement(By.CssSelector(".keypad [data-key='1']")).Click();
|
||||
s.Driver.FindElement(By.CssSelector(".keypad [data-key='2']")).Click();
|
||||
s.Driver.FindElement(By.CssSelector(".keypad [data-key='3']")).Click();
|
||||
s.Driver.FindElement(By.CssSelector(".keypad [data-key='4']")).Click();
|
||||
s.Driver.FindElement(By.CssSelector(".keypad [data-key='.']")).Click();
|
||||
s.Driver.FindElement(By.CssSelector(".keypad [data-key='5']")).Click();
|
||||
s.Driver.FindElement(By.CssSelector(".keypad [data-key='6']")).Click();
|
||||
Assert.Equal("1.234,56", s.Driver.FindElement(By.Id("Amount")).Text);
|
||||
Assert.True(s.Driver.FindElement(By.Id("ModeTablist-discount")).Enabled);
|
||||
Assert.True(s.Driver.FindElement(By.Id("ModeTablist-tip")).Enabled);
|
||||
Assert.Equal("", s.Driver.FindElement(By.Id("Calculation")).Text);
|
||||
|
||||
// Discount: 10%
|
||||
s.Driver.FindElement(By.CssSelector("label[for='ModeTablist-discount']")).Click();
|
||||
s.Driver.FindElement(By.CssSelector(".keypad [data-key='1']")).Click();
|
||||
s.Driver.FindElement(By.CssSelector(".keypad [data-key='0']")).Click();
|
||||
Assert.Contains("1.111,10", s.Driver.FindElement(By.Id("Amount")).Text);
|
||||
Assert.Contains("10% discount", s.Driver.FindElement(By.Id("Discount")).Text);
|
||||
Assert.Contains("1.234,56 € - 123,46 € (10%)", s.Driver.FindElement(By.Id("Calculation")).Text);
|
||||
|
||||
// Tip: 10%
|
||||
s.Driver.FindElement(By.CssSelector("label[for='ModeTablist-tip']")).Click();
|
||||
s.Driver.WaitForElement(By.Id("Tip-Custom"));
|
||||
s.Driver.FindElement(By.Id("Tip-10")).Click();
|
||||
Assert.Contains("1.222,21", s.Driver.FindElement(By.Id("Amount")).Text);
|
||||
Assert.Contains("1.234,56 € - 123,46 € (10%) + 111,11 € (10%)", s.Driver.FindElement(By.Id("Calculation")).Text);
|
||||
|
||||
// Pay
|
||||
s.Driver.FindElement(By.Id("pay-button")).Click();
|
||||
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||
s.Driver.FindElement(By.Id("DetailsToggle")).Click();
|
||||
s.Driver.WaitForElement(By.Id("PaymentDetails-TotalFiat"));
|
||||
Assert.Contains("1 222,21 €", s.Driver.FindElement(By.Id("PaymentDetails-TotalFiat")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanUsePOSCart()
|
||||
{
|
||||
using var s = CreateSeleniumTester();
|
||||
s.Server.ActivateLightning();
|
||||
await s.StartAsync();
|
||||
|
||||
await s.Server.EnsureChannelsSetup();
|
||||
|
||||
s.RegisterNewUser(true);
|
||||
s.CreateNewStore();
|
||||
s.GoToStore();
|
||||
s.AddLightningNode(LightningConnectionType.CLightning, false);
|
||||
s.Driver.FindElement(By.Id("StoreNav-CreatePointOfSale")).Click();
|
||||
s.Driver.FindElement(By.Id("AppName")).SendKeys(Guid.NewGuid().ToString());
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
Assert.Contains("App successfully created", s.FindAlertMessage().Text);
|
||||
s.Driver.FindElement(By.CssSelector("label[for='DefaultView_Cart']")).Click();
|
||||
s.Driver.FindElement(By.Id("Currency")).SendKeys("EUR");
|
||||
s.Driver.FindElement(By.Id("CustomTipPercentages")).Clear();
|
||||
s.Driver.FindElement(By.Id("CustomTipPercentages")).SendKeys("10,21");
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).Click();
|
||||
Assert.Contains("App updated", s.FindAlertMessage().Text);
|
||||
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
||||
var windows = s.Driver.WindowHandles;
|
||||
Assert.Equal(2, windows.Count);
|
||||
s.Driver.SwitchTo().Window(windows[1]);
|
||||
s.Driver.WaitForElement(By.Id("PosItems"));
|
||||
Assert.Empty(s.Driver.FindElements(By.CssSelector("#CartItems tr")));
|
||||
|
||||
// Select and clear
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(1) .btn-primary")).Click();
|
||||
Assert.Single(s.Driver.FindElements(By.CssSelector("#CartItems tr")));
|
||||
s.Driver.FindElement(By.Id("CartClear")).Click();
|
||||
Thread.Sleep(250);
|
||||
Assert.Empty(s.Driver.FindElements(By.CssSelector("#CartItems tr")));
|
||||
|
||||
// Select items
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(2) .btn-primary")).Click();
|
||||
Thread.Sleep(250);
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(1) .btn-primary")).Click();
|
||||
Thread.Sleep(250);
|
||||
s.Driver.FindElement(By.CssSelector(".posItem:nth-child(2) .btn-primary")).Click();
|
||||
Thread.Sleep(250);
|
||||
Assert.Equal(2, s.Driver.FindElements(By.CssSelector("#CartItems tr")).Count);
|
||||
Assert.Equal("3,00 €", s.Driver.FindElement(By.Id("CartTotal")).Text);
|
||||
|
||||
// Discount: 10%
|
||||
s.Driver.ElementDoesNotExist(By.Id("CartDiscount"));
|
||||
s.Driver.FindElement(By.Id("Discount")).SendKeys("10");
|
||||
Assert.Contains("10% = 0,30 €", s.Driver.FindElement(By.Id("CartDiscount")).Text);
|
||||
Assert.Equal("2,70 €", s.Driver.FindElement(By.Id("CartTotal")).Text);
|
||||
|
||||
// Tip: 10%
|
||||
s.Driver.ElementDoesNotExist(By.Id("CartTip"));
|
||||
s.Driver.FindElement(By.Id("Tip-10")).Click();
|
||||
Assert.Contains("10% = 0,27 €", s.Driver.FindElement(By.Id("CartTip")).Text);
|
||||
Assert.Equal("2,97 €", s.Driver.FindElement(By.Id("CartTotal")).Text);
|
||||
|
||||
// Pay
|
||||
s.Driver.FindElement(By.Id("CartSubmit")).Click();
|
||||
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||
s.Driver.FindElement(By.Id("DetailsToggle")).Click();
|
||||
s.Driver.WaitForElement(By.Id("PaymentDetails-TotalFiat"));
|
||||
Assert.Contains("2,97 €", s.Driver.FindElement(By.Id("PaymentDetails-TotalFiat")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
@ -2299,23 +2085,22 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
// Standard invoice test
|
||||
s.GoToStore(storeId);
|
||||
s.GoToLightningSettings();
|
||||
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), true);
|
||||
SudoForceSaveLightningSettingsRightNowAndFast(s, cryptoCode);
|
||||
i = s.CreateInvoice(storeId, 0.0000001m, cryptoCode);
|
||||
s.GoToInvoiceCheckout(i);
|
||||
s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Click();
|
||||
// BOLT11 is also displayed for standard invoice (not LNURL, even if it is available)
|
||||
s.Driver.FindElement(By.ClassName("payment__currencies")).Click();
|
||||
// BOLT11 is also available for standard invoices
|
||||
Assert.Equal(2, s.Driver.FindElements(By.CssSelector(".vex.vex-theme-btcpay .vex-content .vexmenu li.vexmenuitem")).Count);
|
||||
s.Driver.FindElement(By.CssSelector(".vex.vex-theme-btcpay .vex-content .vexmenu li.vexmenuitem")).Click();
|
||||
s.Driver.FindElement(By.Id("copy-tab")).Click();
|
||||
var bolt11 = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
|
||||
var bolt11Parsed = Lightning.BOLT11PaymentRequest.Parse(bolt11, s.Server.ExplorerNode.Network);
|
||||
var invoiceId = s.Driver.Url.Split('/').Last();
|
||||
using (var resp = await s.Server.PayTester.HttpClient.GetAsync("BTC/lnurl/pay/i/" + invoiceId))
|
||||
{
|
||||
resp.EnsureSuccessStatusCode();
|
||||
fetchedReuqest = JsonConvert.DeserializeObject<LNURLPayRequest>(await resp.Content.ReadAsStringAsync());
|
||||
}
|
||||
lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
|
||||
parsed = LNURL.LNURL.Parse(lnurl, out tag);
|
||||
fetchedReuqest = Assert.IsType<LNURLPayRequest>(await LNURL.LNURL.FetchInformation(parsed, new HttpClient()));
|
||||
Assert.Equal(0.0000001m, fetchedReuqest.MaxSendable.ToDecimal(LightMoneyUnit.BTC));
|
||||
Assert.Equal(0.0000001m, fetchedReuqest.MinSendable.ToDecimal(LightMoneyUnit.BTC));
|
||||
|
||||
|
||||
await Assert.ThrowsAsync<LNUrlException>(async () =>
|
||||
{
|
||||
await fetchedReuqest.SendRequest(new LightMoney(0.0000002m, LightMoneyUnit.BTC),
|
||||
@ -2337,6 +2122,13 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(new LightMoney(0.0000001m, LightMoneyUnit.BTC),
|
||||
lnurlResponse2.GetPaymentRequest(network).MinimumAmount);
|
||||
s.GoToHome();
|
||||
s.GoToLightningSettings();
|
||||
// LNURL is enabled and settings are expanded
|
||||
Assert.True(s.Driver.FindElement(By.Id("LNURLEnabled")).Selected);
|
||||
Assert.Contains("show", s.Driver.FindElement(By.Id("LNURLSettings")).GetAttribute("class"));
|
||||
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), false);
|
||||
s.Driver.FindElement(By.Id("save")).Click();
|
||||
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
i = s.CreateInvoice(storeId, 0.000001m, cryptoCode);
|
||||
s.GoToInvoiceCheckout(i);
|
||||
@ -2350,12 +2142,23 @@ namespace BTCPayServer.Tests
|
||||
s.GoToHome();
|
||||
s.GoToLightningSettings();
|
||||
s.Driver.SetCheckbox(By.Id("LNURLBech32Mode"), false);
|
||||
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), false);
|
||||
s.Driver.SetCheckbox(By.Id("DisableBolt11PaymentMethod"), true);
|
||||
s.Driver.FindElement(By.Id("save")).Click();
|
||||
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
// Ensure the toggles are set correctly
|
||||
s.GoToLightningSettings();
|
||||
|
||||
//TODO: DisableBolt11PaymentMethod is actually disabled because LNURLStandardInvoiceEnabled is disabled
|
||||
// checkboxes is not good choice here, in next release we should have multi choice instead
|
||||
Assert.False(s.Driver.FindElement(By.Id("LNURLBech32Mode")).Selected);
|
||||
Assert.False(s.Driver.FindElement(By.Id("LNURLStandardInvoiceEnabled")).Selected);
|
||||
|
||||
//even though we set DisableBolt11PaymentMethod to true, logic when saving it turns it back off as otherwise no lightning option is available at all!
|
||||
Assert.False(s.Driver.FindElement(By.Id("DisableBolt11PaymentMethod")).Selected);
|
||||
// Invoice creation should fail, because it is a standard invoice with amount, but DisableBolt11PaymentMethod = true and LNURLStandardInvoiceEnabled = false
|
||||
s.CreateInvoice(storeId, 0.0000001m, cryptoCode, "", null, expectedSeverity: StatusMessageModel.StatusSeverity.Success);
|
||||
|
||||
i = s.CreateInvoice(storeId, null, cryptoCode);
|
||||
s.GoToInvoiceCheckout(i);
|
||||
@ -2371,13 +2174,15 @@ namespace BTCPayServer.Tests
|
||||
s.AddLightningNode(LightningConnectionType.LndREST, false);
|
||||
s.GoToLightningSettings();
|
||||
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
|
||||
s.Driver.SetCheckbox(By.Id("DisableBolt11PaymentMethod"), true);
|
||||
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), true);
|
||||
s.Driver.FindElement(By.Id("save")).Click();
|
||||
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||
var invForPP = s.CreateInvoice(null, cryptoCode);
|
||||
var invForPP = s.CreateInvoice(0.0000001m, cryptoCode);
|
||||
s.GoToInvoiceCheckout(invForPP);
|
||||
s.Driver.FindElement(By.Id("copy-tab")).Click();
|
||||
lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
|
||||
LNURL.LNURL.Parse(lnurl, out tag);
|
||||
parsed = LNURL.LNURL.Parse(lnurl, out tag);
|
||||
|
||||
// Check that pull payment has lightning option
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
@ -2466,7 +2271,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var addresses = s.Driver.FindElements(By.ClassName("lightning-address-value"));
|
||||
Assert.Equal(2, addresses.Count);
|
||||
var callbacks = new List<Uri>();
|
||||
|
||||
foreach (IWebElement webElement in addresses)
|
||||
{
|
||||
var value = webElement.GetAttribute("value");
|
||||
@ -2484,7 +2289,6 @@ namespace BTCPayServer.Tests
|
||||
lnaddress2 = m["text/identifier"];
|
||||
Assert.Equal(2, request.MinSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
||||
Assert.Equal(10, request.MaxSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
||||
callbacks.Add(request.Callback);
|
||||
break;
|
||||
|
||||
case { } v when v.StartsWith(lnaddress1):
|
||||
@ -2492,7 +2296,6 @@ namespace BTCPayServer.Tests
|
||||
lnaddress1 = m["text/identifier"];
|
||||
Assert.Equal(1, request.MinSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
||||
Assert.Equal(6.12m, request.MaxSendable.ToDecimal(LightMoneyUnit.BTC));
|
||||
callbacks.Add(request.Callback);
|
||||
break;
|
||||
default:
|
||||
Assert.False(true, "Should have matched");
|
||||
@ -2500,21 +2303,10 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
var repo = s.Server.PayTester.GetService<InvoiceRepository>();
|
||||
|
||||
var invoices = await repo.GetInvoices(new InvoiceQuery() { StoreId = new[] { s.StoreId } });
|
||||
// Resolving a ln address shouldn't create any btcpay invoice.
|
||||
// This must be done because some NOST clients resolve ln addresses preemptively without user interaction
|
||||
Assert.Empty(invoices);
|
||||
|
||||
// Calling the callbacks should create the invoices
|
||||
foreach (var callback in callbacks)
|
||||
{
|
||||
using var r = await s.Server.PayTester.HttpClient.GetAsync(callback);
|
||||
await r.Content.ReadAsStringAsync();
|
||||
}
|
||||
invoices = await repo.GetInvoices(new InvoiceQuery() { StoreId = new[] { s.StoreId } });
|
||||
Assert.Equal(2, invoices.Length);
|
||||
var emailSuffix = $"@{s.Server.PayTester.HostName}:{s.Server.PayTester.Port}";
|
||||
|
||||
foreach (var i in invoices)
|
||||
{
|
||||
var lightningPaymentMethod = i.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.LNURLPay));
|
||||
@ -2531,8 +2323,6 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
var lnUsername = lnaddress1.Split('@')[0];
|
||||
|
||||
|
||||
LNURLPayRequest req;
|
||||
using (var resp = await s.Server.PayTester.HttpClient.GetAsync($"/.well-known/lnurlp/{lnUsername}"))
|
||||
{
|
||||
@ -2594,7 +2384,6 @@ namespace BTCPayServer.Tests
|
||||
using var s = CreateSeleniumTester();
|
||||
await s.StartAsync();
|
||||
var user = s.RegisterNewUser();
|
||||
s.GoToHome();
|
||||
s.GoToProfile(ManageNavPages.LoginCodes);
|
||||
var code = s.Driver.FindElement(By.Id("logincode")).GetAttribute("value");
|
||||
s.Driver.FindElement(By.Id("regeneratecode")).Click();
|
||||
@ -2606,12 +2395,14 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.SetAttribute("LoginCode", "value", "bad code");
|
||||
s.Driver.InvokeJSFunction("logincode-form", "submit");
|
||||
|
||||
|
||||
s.Driver.SetAttribute("LoginCode", "value", code);
|
||||
s.Driver.InvokeJSFunction("logincode-form", "submit");
|
||||
s.GoToHome();
|
||||
s.GoToProfile();
|
||||
Assert.Contains(user, s.Driver.PageSource);
|
||||
}
|
||||
|
||||
|
||||
// For god know why, selenium have problems clicking on the save button, resulting in ultimate hacks
|
||||
// to make it works.
|
||||
private void SudoForceSaveLightningSettingsRightNowAndFast(SeleniumTester s, string cryptoCode)
|
||||
@ -2630,6 +2421,7 @@ retry:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
public async Task CanUseLNURLAuth()
|
||||
@ -2637,7 +2429,6 @@ retry:
|
||||
using var s = CreateSeleniumTester();
|
||||
await s.StartAsync();
|
||||
var user = s.RegisterNewUser(true);
|
||||
s.GoToHome();
|
||||
s.GoToProfile(ManageNavPages.TwoFactorAuthentication);
|
||||
s.Driver.FindElement(By.Name("Name")).SendKeys("ln wallet");
|
||||
s.Driver.FindElement(By.Name("type"))
|
||||
@ -2659,11 +2450,10 @@ retry:
|
||||
_ = await request.SendChallenge(linkingKey, new HttpClient());
|
||||
TestUtils.Eventually(() => s.FindAlertMessage());
|
||||
|
||||
s.CreateNewStore(); // create a store to prevent redirect after login
|
||||
s.Logout();
|
||||
s.LogIn(user, "123456");
|
||||
var section = s.Driver.FindElement(By.Id("lnurlauth-section"));
|
||||
links = section.FindElements(By.CssSelector(".tab-content a")).Select(element => element.GetAttribute("href")).ToList();
|
||||
links = section.FindElements(By.CssSelector(".tab-content a")).Select(element => element.GetAttribute("href"));
|
||||
Assert.Equal(2, links.Count());
|
||||
prevEndpoint = null;
|
||||
foreach (string link in links)
|
||||
@ -2677,149 +2467,9 @@ retry:
|
||||
_ = await request.SendChallenge(linkingKey, new HttpClient());
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
Assert.StartsWith(s.ServerUri.ToString(), s.Driver.Url);
|
||||
Assert.Equal(s.Driver.Url, s.ServerUri.ToString());
|
||||
});
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
public async Task CanUseRoleManager()
|
||||
{
|
||||
using var s = CreateSeleniumTester(newDb: true);
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser(true);
|
||||
s.GoToHome();
|
||||
s.GoToServer(ServerNavPages.Roles);
|
||||
var existingServerRoles = s.Driver.FindElement(By.CssSelector("table")).FindElements(By.CssSelector("tr"));
|
||||
Assert.Equal(3, existingServerRoles.Count);
|
||||
IWebElement ownerRow = null;
|
||||
IWebElement guestRow = null;
|
||||
foreach (var roleItem in existingServerRoles)
|
||||
{
|
||||
if (roleItem.Text.Contains("owner", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
ownerRow = roleItem;
|
||||
}
|
||||
else if (roleItem.Text.Contains("guest", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
guestRow = roleItem;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.NotNull(ownerRow);
|
||||
Assert.NotNull(guestRow);
|
||||
|
||||
var ownerBadges = ownerRow.FindElements(By.CssSelector(".badge"));
|
||||
Assert.Contains(ownerBadges, element => element.Text.Equals("Default", StringComparison.InvariantCultureIgnoreCase));
|
||||
Assert.Contains(ownerBadges, element => element.Text.Equals("Server-wide", StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
var guestBadges = guestRow.FindElements(By.CssSelector(".badge"));
|
||||
Assert.DoesNotContain(guestBadges, element => element.Text.Equals("Default", StringComparison.InvariantCultureIgnoreCase));
|
||||
Assert.Contains(guestBadges, element => element.Text.Equals("Server-wide", StringComparison.InvariantCultureIgnoreCase));
|
||||
guestRow.FindElement(By.Id("SetDefault")).Click();
|
||||
s.FindAlertMessage();
|
||||
|
||||
existingServerRoles = s.Driver.FindElement(By.CssSelector("table")).FindElements(By.CssSelector("tr"));
|
||||
foreach (var roleItem in existingServerRoles)
|
||||
{
|
||||
if (roleItem.Text.Contains("owner", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
ownerRow = roleItem;
|
||||
}
|
||||
else if (roleItem.Text.Contains("guest", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
guestRow = roleItem;
|
||||
}
|
||||
}
|
||||
guestBadges = guestRow.FindElements(By.CssSelector(".badge"));
|
||||
Assert.Contains(guestBadges, element => element.Text.Equals("Default", StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
ownerBadges = ownerRow.FindElements(By.CssSelector(".badge"));
|
||||
Assert.DoesNotContain(ownerBadges, element => element.Text.Equals("Default", StringComparison.InvariantCultureIgnoreCase));
|
||||
ownerRow.FindElement(By.Id("SetDefault")).Click();
|
||||
s.FindAlertMessage();
|
||||
|
||||
|
||||
|
||||
s.CreateNewStore();
|
||||
s.GoToStore(StoreNavPages.Roles);
|
||||
var existingStoreRoles = s.Driver.FindElement(By.CssSelector("table")).FindElements(By.CssSelector("tr"));
|
||||
Assert.Equal(3, existingStoreRoles.Count);
|
||||
Assert.Equal(2, existingStoreRoles.Count(element => element.Text.Contains("Server-wide", StringComparison.InvariantCultureIgnoreCase)));
|
||||
|
||||
foreach (var roleItem in existingStoreRoles)
|
||||
{
|
||||
if (roleItem.Text.Contains("owner", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
ownerRow = roleItem;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ownerRow.FindElement(By.LinkText("Remove")).Click();
|
||||
Assert.DoesNotContain("ConfirmContinue", s.Driver.PageSource);
|
||||
s.Driver.Navigate().Back();
|
||||
existingStoreRoles = s.Driver.FindElement(By.CssSelector("table")).FindElements(By.CssSelector("tr"));
|
||||
foreach (var roleItem in existingStoreRoles)
|
||||
{
|
||||
if (roleItem.Text.Contains("guest", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
guestRow = roleItem;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
guestRow.FindElement(By.LinkText("Remove")).Click();
|
||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
s.FindAlertMessage();
|
||||
|
||||
s.GoToStore(StoreNavPages.Roles);
|
||||
s.Driver.FindElement(By.Id("CreateRole")).Click();
|
||||
|
||||
Assert.Contains("Create role", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Id("Save")).Click();
|
||||
s.Driver.FindElement(By.Id("Role")).SendKeys("store role");
|
||||
s.Driver.FindElement(By.Id("Save")).Click();
|
||||
s.FindAlertMessage();
|
||||
|
||||
existingStoreRoles = s.Driver.FindElement(By.CssSelector("table")).FindElements(By.CssSelector("tr"));
|
||||
foreach (var roleItem in existingStoreRoles)
|
||||
{
|
||||
if (roleItem.Text.Contains("store role", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
guestRow = roleItem;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
guestBadges = guestRow.FindElements(By.CssSelector(".badge"));
|
||||
Assert.DoesNotContain(guestBadges, element => element.Text.Equals("server-wide", StringComparison.InvariantCultureIgnoreCase));
|
||||
s.GoToStore(StoreNavPages.Users);
|
||||
var options = s.Driver.FindElements(By.CssSelector("#Role option"));
|
||||
Assert.Equal(2, options.Count);
|
||||
Assert.Contains(options, element => element.Text.Equals("store role", StringComparison.InvariantCultureIgnoreCase));
|
||||
s.CreateNewStore();
|
||||
s.GoToStore(StoreNavPages.Roles);
|
||||
existingStoreRoles = s.Driver.FindElement(By.CssSelector("table")).FindElements(By.CssSelector("tr"));
|
||||
Assert.Equal(2, existingStoreRoles.Count);
|
||||
Assert.Equal(1, existingStoreRoles.Count(element => element.Text.Contains("Server-wide", StringComparison.InvariantCultureIgnoreCase)));
|
||||
Assert.Equal(0, existingStoreRoles.Count(element => element.Text.Contains("store role", StringComparison.InvariantCultureIgnoreCase)));
|
||||
s.GoToStore(StoreNavPages.Users);
|
||||
options = s.Driver.FindElements(By.CssSelector("#Role option"));
|
||||
Assert.Single(options);
|
||||
Assert.DoesNotContain(options, element => element.Text.Equals("store role", StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
|
||||
s.GoToStore(StoreNavPages.Roles);
|
||||
s.Driver.FindElement(By.Id("CreateRole")).Click();
|
||||
s.Driver.FindElement(By.Id("Role")).SendKeys("Malice");
|
||||
|
||||
s.Driver.ExecuteJavaScript($"document.getElementById('Policies')['{Policies.CanModifyServerSettings}']=new Option('{Policies.CanModifyServerSettings}', '{Policies.CanModifyServerSettings}', true,true);");
|
||||
|
||||
s.Driver.FindElement(By.Id("Save")).Click();
|
||||
s.FindAlertMessage();
|
||||
Assert.Contains("Malice",s.Driver.PageSource);
|
||||
Assert.DoesNotContain(Policies.CanModifyServerSettings,s.Driver.PageSource);
|
||||
}
|
||||
|
||||
private static void CanBrowseContent(SeleniumTester s)
|
||||
{
|
||||
|
@ -92,7 +92,7 @@ namespace BTCPayServer.Tests
|
||||
#endif
|
||||
public void ActivateLightning()
|
||||
{
|
||||
ActivateLightning(LightningConnectionType.CLightning);
|
||||
ActivateLightning(LightningConnectionType.Charge);
|
||||
}
|
||||
public void ActivateLightning(LightningConnectionType internalNode)
|
||||
{
|
||||
@ -109,7 +109,14 @@ namespace BTCPayServer.Tests
|
||||
string connectionString = null;
|
||||
if (connectionType is null)
|
||||
return LightningSupportedPaymentMethod.InternalNode;
|
||||
if (connectionType == LightningConnectionType.CLightning)
|
||||
if (connectionType == LightningConnectionType.Charge)
|
||||
{
|
||||
if (isMerchant)
|
||||
connectionString = $"type=charge;server={MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true";
|
||||
else
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
else if (connectionType == LightningConnectionType.CLightning)
|
||||
{
|
||||
if (isMerchant)
|
||||
connectionString = "type=clightning;server=" +
|
||||
@ -170,7 +177,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public async Task<PayResponse> SendLightningPaymentAsync(Invoice invoice)
|
||||
{
|
||||
var bolt11 = invoice.CryptoInfo.Where(o => o.PaymentUrls?.BOLT11 != null).First().PaymentUrls.BOLT11;
|
||||
var bolt11 = invoice.CryptoInfo.Where(o => o.PaymentUrls.BOLT11 != null).First().PaymentUrls.BOLT11;
|
||||
bolt11 = bolt11.Replace("lightning:", "", StringComparison.OrdinalIgnoreCase);
|
||||
return await CustomerLightningD.Pay(bolt11);
|
||||
}
|
||||
@ -187,8 +194,7 @@ namespace BTCPayServer.Tests
|
||||
tcs.TrySetResult(evt);
|
||||
}
|
||||
});
|
||||
if (action != null)
|
||||
await action.Invoke();
|
||||
await action.Invoke();
|
||||
var result = await tcs.Task;
|
||||
sub.Dispose();
|
||||
return result;
|
||||
@ -241,8 +247,6 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public List<string> Stores { get; internal set; } = new List<string>();
|
||||
public bool DeleteStore { get; set; } = true;
|
||||
public BTCPayNetworkBase DefaultNetwork => NetworkProvider.DefaultNetwork;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var r in this.Resources)
|
||||
|
@ -40,7 +40,6 @@ namespace BTCPayServer.Tests
|
||||
public class TestAccount
|
||||
{
|
||||
readonly ServerTester parent;
|
||||
public string LNAddress;
|
||||
|
||||
public TestAccount(ServerTester parent)
|
||||
{
|
||||
@ -215,13 +214,6 @@ namespace BTCPayServer.Tests
|
||||
get => GenerateWalletResponseV.DerivationScheme;
|
||||
}
|
||||
|
||||
public void SetLNUrl(string cryptoCode, bool activated)
|
||||
{
|
||||
var lnSettingsVm = GetController<UIStoresController>().LightningSettings(StoreId, cryptoCode).AssertViewModel<LightningSettingsViewModel>();
|
||||
lnSettingsVm.LNURLEnabled = activated;
|
||||
Assert.IsType<RedirectToActionResult>(GetController<UIStoresController>().LightningSettings(lnSettingsVm).Result);
|
||||
}
|
||||
|
||||
private async Task RegisterAsync(bool isAdmin = false)
|
||||
{
|
||||
var account = parent.PayTester.GetController<UIAccountController>();
|
||||
@ -243,7 +235,7 @@ namespace BTCPayServer.Tests
|
||||
policies.LockSubscription = false;
|
||||
await account.Register(RegisterDetails);
|
||||
}
|
||||
TestLogs.LogInformation($"UserId: {account.RegisteredUserId} Password: {Password}");
|
||||
|
||||
UserId = account.RegisteredUserId;
|
||||
Email = RegisterDetails.Email;
|
||||
IsAdmin = account.RegisteredAdmin;
|
||||
@ -278,7 +270,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public bool IsAdmin { get; internal set; }
|
||||
|
||||
public void RegisterLightningNode(string cryptoCode, LightningConnectionType? connectionType = null, bool isMerchant = true)
|
||||
public void RegisterLightningNode(string cryptoCode, LightningConnectionType connectionType, bool isMerchant = true)
|
||||
{
|
||||
RegisterLightningNodeAsync(cryptoCode, connectionType, isMerchant).GetAwaiter().GetResult();
|
||||
}
|
||||
@ -310,9 +302,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
|
||||
}
|
||||
|
||||
public async Task<Coin> ReceiveUTXO(Money value, BTCPayNetwork network = null)
|
||||
public async Task<Coin> ReceiveUTXO(Money value, BTCPayNetwork network)
|
||||
{
|
||||
network ??= SupportedNetwork;
|
||||
var cashCow = parent.ExplorerNode;
|
||||
var btcPayWallet = parent.PayTester.GetService<BTCPayWalletProvider>().GetWallet(network);
|
||||
var address = (await btcPayWallet.ReserveAddressAsync(this.DerivationScheme)).Address;
|
||||
@ -472,10 +463,7 @@ namespace BTCPayServer.Tests
|
||||
var req = await _server.GetNextRequest(cancellation);
|
||||
var bytes = await req.Request.Body.ReadBytesAsync((int)req.Request.Headers.ContentLength);
|
||||
var callback = Encoding.UTF8.GetString(bytes);
|
||||
lock (_webhookEvents)
|
||||
{
|
||||
_webhookEvents.Add(JsonConvert.DeserializeObject<WebhookInvoiceEvent>(callback));
|
||||
}
|
||||
_webhookEvents.Add(JsonConvert.DeserializeObject<WebhookInvoiceEvent>(callback));
|
||||
req.Response.StatusCode = 200;
|
||||
_server.Done();
|
||||
}
|
||||
@ -492,21 +480,18 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
int retry = 0;
|
||||
retry:
|
||||
lock (WebhookEvents)
|
||||
foreach (var evt in WebhookEvents)
|
||||
{
|
||||
foreach (var evt in WebhookEvents)
|
||||
if (evt.Type == eventType)
|
||||
{
|
||||
if (evt.Type == eventType)
|
||||
var typedEvt = evt.ReadAs<TEvent>();
|
||||
try
|
||||
{
|
||||
assert(typedEvt);
|
||||
return typedEvt;
|
||||
}
|
||||
catch (XunitException)
|
||||
{
|
||||
var typedEvt = evt.ReadAs<TEvent>();
|
||||
try
|
||||
{
|
||||
assert(typedEvt);
|
||||
return typedEvt;
|
||||
}
|
||||
catch (XunitException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -548,101 +533,12 @@ retry:
|
||||
public async Task AddGuest(string userId)
|
||||
{
|
||||
var repo = this.parent.PayTester.GetService<StoreRepository>();
|
||||
await repo.AddStoreUser(StoreId, userId, StoreRoleId.Guest);
|
||||
await repo.AddStoreUser(StoreId, userId, "Guest");
|
||||
}
|
||||
public async Task AddOwner(string userId)
|
||||
{
|
||||
var repo = this.parent.PayTester.GetService<StoreRepository>();
|
||||
await repo.AddStoreUser(StoreId, userId, StoreRoleId.Owner);
|
||||
}
|
||||
|
||||
public async Task<uint256> PayOnChain(string invoiceId)
|
||||
{
|
||||
var cryptoCode = "BTC";
|
||||
var client = await CreateClient();
|
||||
var methods = await client.GetInvoicePaymentMethods(StoreId, invoiceId);
|
||||
var method = methods.First(m => m.PaymentMethod == cryptoCode);
|
||||
var address = method.Destination;
|
||||
var tx = await client.CreateOnChainTransaction(StoreId, cryptoCode, new CreateOnChainTransactionRequest()
|
||||
{
|
||||
Destinations = new List<CreateOnChainTransactionRequest.CreateOnChainTransactionRequestDestination>()
|
||||
{
|
||||
new ()
|
||||
{
|
||||
Destination = address,
|
||||
Amount = method.Due
|
||||
}
|
||||
},
|
||||
FeeRate = new FeeRate(1.0m)
|
||||
});
|
||||
await WaitInvoicePaid(invoiceId);
|
||||
return tx.TransactionHash;
|
||||
}
|
||||
|
||||
public async Task PayOnBOLT11(string invoiceId)
|
||||
{
|
||||
var cryptoCode = "BTC";
|
||||
var client = await CreateClient();
|
||||
var methods = await client.GetInvoicePaymentMethods(StoreId, invoiceId);
|
||||
var method = methods.First(m => m.PaymentMethod == $"{cryptoCode}-LightningNetwork");
|
||||
var bolt11 = method.Destination;
|
||||
TestLogs.LogInformation("PAYING");
|
||||
await parent.CustomerLightningD.Pay(bolt11);
|
||||
TestLogs.LogInformation("PAID");
|
||||
await WaitInvoicePaid(invoiceId);
|
||||
}
|
||||
|
||||
public async Task PayOnLNUrl(string invoiceId)
|
||||
{
|
||||
var cryptoCode = "BTC";
|
||||
var network = SupportedNetwork.NBitcoinNetwork;
|
||||
var client = await CreateClient();
|
||||
var methods = await client.GetInvoicePaymentMethods(StoreId, invoiceId);
|
||||
var method = methods.First(m => m.PaymentMethod == $"{cryptoCode}-LNURLPAY");
|
||||
var lnurL = LNURL.LNURL.Parse(method.PaymentLink, out var tag);
|
||||
var http = new HttpClient();
|
||||
var payreq = (LNURL.LNURLPayRequest)await LNURL.LNURL.FetchInformation(lnurL, tag, http);
|
||||
var resp = await payreq.SendRequest(payreq.MinSendable, network, http);
|
||||
var bolt11 = resp.Pr;
|
||||
await parent.CustomerLightningD.Pay(bolt11);
|
||||
await WaitInvoicePaid(invoiceId);
|
||||
}
|
||||
|
||||
public Task WaitInvoicePaid(string invoiceId)
|
||||
{
|
||||
return TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var client = await CreateClient();
|
||||
var invoice = await client.GetInvoice(StoreId, invoiceId);
|
||||
if (invoice.Status == InvoiceStatus.Settled)
|
||||
return;
|
||||
Assert.Equal(InvoiceStatus.Processing, invoice.Status);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task PayOnLNAddress(string lnAddrUser = null)
|
||||
{
|
||||
lnAddrUser ??= LNAddress;
|
||||
var network = SupportedNetwork.NBitcoinNetwork;
|
||||
var payReqStr = await (await parent.PayTester.HttpClient.GetAsync($".well-known/lnurlp/{lnAddrUser}")).Content.ReadAsStringAsync();
|
||||
var payreq = JsonConvert.DeserializeObject<LNURL.LNURLPayRequest>(payReqStr);
|
||||
var resp = await payreq.SendRequest(payreq.MinSendable, network, parent.PayTester.HttpClient);
|
||||
var bolt11 = resp.Pr;
|
||||
await parent.CustomerLightningD.Pay(bolt11);
|
||||
}
|
||||
|
||||
public async Task<string> CreateLNAddress()
|
||||
{
|
||||
var lnAddrUser = Guid.NewGuid().ToString();
|
||||
var ctx = parent.PayTester.GetService<ApplicationDbContextFactory>().CreateContext();
|
||||
ctx.LightningAddresses.Add(new()
|
||||
{
|
||||
StoreDataId = StoreId,
|
||||
Username = lnAddrUser
|
||||
});
|
||||
await ctx.SaveChangesAsync();
|
||||
LNAddress = lnAddrUser;
|
||||
return lnAddrUser;
|
||||
await repo.AddStoreUser(StoreId, userId, "Owner");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,14 +112,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
catch (XunitException) when (!cts.Token.IsCancellationRequested)
|
||||
{
|
||||
bool timeout =false;
|
||||
try
|
||||
{
|
||||
await Task.Delay(500, cts.Token);
|
||||
}
|
||||
catch { timeout = true; }
|
||||
if (timeout)
|
||||
throw;
|
||||
await Task.Delay(500, cts.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -290,9 +290,9 @@ retry:
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanGetRateCryptoCurrenciesByDefault()
|
||||
public void CanGetRateCryptoCurrenciesByDefault()
|
||||
{
|
||||
string[] brokenShitcoins = { };
|
||||
string[] brokenShitcoins = { "BTX_USD", "CHC_USD" };
|
||||
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
|
||||
var factory = FastTests.CreateBTCPayRateFactory();
|
||||
var fetcher = new RateFetcher(factory);
|
||||
@ -305,37 +305,15 @@ retry:
|
||||
var result = fetcher.FetchRates(pairs, rules, default);
|
||||
foreach ((CurrencyPair key, Task<RateResult> value) in result)
|
||||
{
|
||||
var rateResult = await value;
|
||||
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}");
|
||||
}
|
||||
|
||||
var b = new StoreBlob();
|
||||
foreach (var k in StoreBlob.RecommendedExchanges)
|
||||
{
|
||||
b.DefaultCurrency = k.Key;
|
||||
rules = b.GetDefaultRateRules(provider);
|
||||
pairs =
|
||||
provider.GetAll()
|
||||
.Select(c => new CurrencyPair(c.CryptoCode, k.Key))
|
||||
.ToHashSet();
|
||||
result = fetcher.FetchRates(pairs, rules, default);
|
||||
foreach ((CurrencyPair key, Task<RateResult> value) in result)
|
||||
{
|
||||
var rateResult = await value;
|
||||
TestLogs.LogInformation($"Testing {key} when default currency is {k.Key}");
|
||||
if (brokenShitcoins.Contains(key.ToString()))
|
||||
continue;
|
||||
Assert.True(rateResult.BidAsk != null, $"Impossible to get the rate {rateResult.EvaluatedRule}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Fast", "Fast")]
|
||||
public async Task CheckJsContent()
|
||||
{
|
||||
// This test verify that no malicious js is added in the minified files.
|
||||
@ -344,71 +322,42 @@ retry:
|
||||
var actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "bootstrap", "bootstrap.bundle.min.js").Trim();
|
||||
var version = Regex.Match(actual, "Bootstrap v([0-9]+.[0-9]+.[0-9]+)").Groups[1].Value;
|
||||
var expected = (await (await client.GetAsync($"https://cdn.jsdelivr.net/npm/bootstrap@{version}/dist/js/bootstrap.bundle.min.js")).Content.ReadAsStringAsync()).Trim();
|
||||
EqualJsContent(expected, actual);
|
||||
Assert.Equal(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "clipboard.js", "clipboard.js");
|
||||
expected = (await (await client.GetAsync("https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.8/clipboard.js")).Content.ReadAsStringAsync()).Trim();
|
||||
EqualJsContent(expected, actual);
|
||||
Assert.Equal(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "vuejs", "vue.min.js").Trim();
|
||||
version = Regex.Match(actual, "Vue\\.js v([0-9]+.[0-9]+.[0-9]+)").Groups[1].Value;
|
||||
expected = (await (await client.GetAsync($"https://cdnjs.cloudflare.com/ajax/libs/vue/{version}/vue.min.js")).Content.ReadAsStringAsync()).Trim();
|
||||
EqualJsContent(expected, actual);
|
||||
Assert.Equal(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "i18next", "i18next.min.js").Trim();
|
||||
expected = (await (await client.GetAsync("https://cdnjs.cloudflare.com/ajax/libs/i18next/22.0.6/i18next.min.js")).Content.ReadAsStringAsync()).Trim();
|
||||
EqualJsContent(expected, actual);
|
||||
Assert.Equal(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "i18next", "i18nextHttpBackend.min.js").Trim();
|
||||
expected = (await (await client.GetAsync("https://cdnjs.cloudflare.com/ajax/libs/i18next-http-backend/2.0.1/i18nextHttpBackend.min.js")).Content.ReadAsStringAsync()).Trim();
|
||||
EqualJsContent(expected, actual);
|
||||
Assert.Equal(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "i18next", "vue-i18next.js").Trim();
|
||||
expected = (await (await client.GetAsync("https://unpkg.com/@panter/vue-i18next@0.15.2/dist/vue-i18next.js")).Content.ReadAsStringAsync()).Trim();
|
||||
EqualJsContent(expected, actual);
|
||||
Assert.Equal(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "vue-qrcode", "vue-qrcode.min.js").Trim();
|
||||
version = Regex.Match(actual, "vue-qrcode v([0-9]+.[0-9]+.[0-9]+)").Groups[1].Value;
|
||||
expected = (await (await client.GetAsync($"https://unpkg.com/@chenfengyuan/vue-qrcode@{version}/dist/vue-qrcode.min.js")).Content.ReadAsStringAsync()).Trim();
|
||||
EqualJsContent(expected, actual);
|
||||
Assert.Equal(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "tom-select", "tom-select.complete.min.js").Trim();
|
||||
expected = (await (await client.GetAsync($"https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/js/tom-select.complete.min.js")).Content.ReadAsStringAsync()).Trim();
|
||||
EqualJsContent(expected, actual);
|
||||
Assert.Equal(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "dom-confetti", "dom-confetti.min.js").Trim();
|
||||
version = Regex.Match(actual, "Original file: /npm/dom-confetti@([0-9]+.[0-9]+.[0-9]+)/lib/main.js").Groups[1].Value;
|
||||
expected = (await (await client.GetAsync($"https://cdn.jsdelivr.net/npm/dom-confetti@{version}/lib/main.min.js")).Content.ReadAsStringAsync()).Trim();
|
||||
EqualJsContent(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "vue-sortable", "sortable.min.js").Trim();
|
||||
version = Regex.Match(actual, "Sortable ([0-9]+.[0-9]+.[0-9]+) ").Groups[1].Value;
|
||||
expected = (await (await client.GetAsync($"https://unpkg.com/sortablejs@{version}/Sortable.min.js")).Content.ReadAsStringAsync()).Trim();
|
||||
EqualJsContent(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "bootstrap-vue", "bootstrap-vue.min.js").Trim();
|
||||
version = Regex.Match(actual, "BootstrapVue ([0-9]+.[0-9]+.[0-9]+)").Groups[1].Value;
|
||||
expected = (await (await client.GetAsync($"https://cdnjs.cloudflare.com/ajax/libs/bootstrap-vue/{version}/bootstrap-vue.min.js")).Content.ReadAsStringAsync()).Trim();
|
||||
EqualJsContent(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "FileSaver", "FileSaver.min.js").Trim();
|
||||
expected = (await (await client.GetAsync($"https://raw.githubusercontent.com/eligrey/FileSaver.js/43bbd2f0ae6794f8d452cd360e9d33aef6071234/dist/FileSaver.min.js")).Content.ReadAsStringAsync()).Trim();
|
||||
EqualJsContent(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "papaparse", "papaparse.min.js").Trim();
|
||||
expected = (await (await client.GetAsync($"https://raw.githubusercontent.com/mholt/PapaParse/5.4.1/papaparse.min.js")).Content.ReadAsStringAsync()).Trim();
|
||||
EqualJsContent(expected, actual);
|
||||
|
||||
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "vue-sanitize-directive", "vue-sanitize-directive.umd.min.js").Trim();
|
||||
version = Regex.Match(actual, "Original file: /npm/vue-sanitize-directive@([0-9]+.[0-9]+.[0-9]+)").Groups[1].Value;
|
||||
expected = (await (await client.GetAsync($"https://cdn.jsdelivr.net/npm/vue-sanitize-directive@{version}/dist/vue-sanitize-directive.umd.min.js")).Content.ReadAsStringAsync()).Trim();
|
||||
EqualJsContent(expected, actual);
|
||||
}
|
||||
|
||||
private void EqualJsContent(string expected, string actual)
|
||||
{
|
||||
if (expected != actual)
|
||||
Assert.Equal(expected, actual.ReplaceLineEndings("\n"));
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
string GetFileContent(params string[] path)
|
||||
|
@ -39,7 +39,6 @@ using BTCPayServer.Plugins.PayButton;
|
||||
using BTCPayServer.Plugins.PointOfSale;
|
||||
using BTCPayServer.Plugins.PointOfSale.Controllers;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
using BTCPayServer.Security.Greenfield;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
@ -467,6 +466,14 @@ namespace BTCPayServer.Tests
|
||||
await ProcessLightningPayment(LightningConnectionType.CLightning);
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanSendLightningPaymentCharge()
|
||||
{
|
||||
await ProcessLightningPayment(LightningConnectionType.Charge);
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
@ -719,7 +726,7 @@ namespace BTCPayServer.Tests
|
||||
btcDerivationScheme.GetDerivation(new KeyPath("0/90")).ScriptPubKey, Money.Coins(1.0m));
|
||||
tester.ExplorerNode.Generate(1);
|
||||
var transactions = Assert.IsType<ListTransactionsViewModel>(Assert
|
||||
.IsType<ViewResult>(walletController.WalletTransactions(walletId, loadTransactions: true).Result).Model);
|
||||
.IsType<ViewResult>(walletController.WalletTransactions(walletId).Result).Model);
|
||||
Assert.Empty(transactions.Transactions);
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(walletController.WalletRescan(walletId, rescan).Result);
|
||||
@ -748,7 +755,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.NotNull(rescan.TimeOfScan);
|
||||
Assert.Equal(1, rescan.LastSuccess.Found);
|
||||
transactions = Assert.IsType<ListTransactionsViewModel>(Assert
|
||||
.IsType<ViewResult>(walletController.WalletTransactions(walletId, loadTransactions: true).Result).Model);
|
||||
.IsType<ViewResult>(walletController.WalletTransactions(walletId).Result).Model);
|
||||
var tx = Assert.Single(transactions.Transactions);
|
||||
Assert.Equal(tx.Id, txId.ToString());
|
||||
|
||||
@ -763,7 +770,7 @@ namespace BTCPayServer.Tests
|
||||
await walletController.ModifyTransaction(walletId, tx.Id, addcomment: "hello"));
|
||||
|
||||
transactions = Assert.IsType<ListTransactionsViewModel>(Assert
|
||||
.IsType<ViewResult>(walletController.WalletTransactions(walletId, loadTransactions: true).Result).Model);
|
||||
.IsType<ViewResult>(walletController.WalletTransactions(walletId).Result).Model);
|
||||
tx = Assert.Single(transactions.Transactions);
|
||||
|
||||
Assert.Equal("hello", tx.Comment);
|
||||
@ -775,7 +782,7 @@ namespace BTCPayServer.Tests
|
||||
await walletController.ModifyTransaction(walletId, tx.Id, removelabel: "test2"));
|
||||
|
||||
transactions = Assert.IsType<ListTransactionsViewModel>(Assert
|
||||
.IsType<ViewResult>(walletController.WalletTransactions(walletId, loadTransactions: true).Result).Model);
|
||||
.IsType<ViewResult>(walletController.WalletTransactions(walletId).Result).Model);
|
||||
tx = Assert.Single(transactions.Transactions);
|
||||
|
||||
Assert.Equal("hello", tx.Comment);
|
||||
@ -1627,8 +1634,7 @@ namespace BTCPayServer.Tests
|
||||
var user = tester.NewAccount();
|
||||
var cryptoCode = "BTC";
|
||||
user.GrantAccess(true);
|
||||
user.RegisterLightningNode(cryptoCode);
|
||||
user.SetLNUrl(cryptoCode, false);
|
||||
user.RegisterLightningNode(cryptoCode, LightningConnectionType.Charge);
|
||||
var vm = user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModel<CheckoutAppearanceViewModel>();
|
||||
var criteria = Assert.Single(vm.PaymentMethodCriteria);
|
||||
Assert.Equal(new PaymentMethodId(cryptoCode, LightningPaymentType.Instance).ToString(), criteria.PaymentMethod);
|
||||
@ -1643,12 +1649,16 @@ namespace BTCPayServer.Tests
|
||||
Price = 1.5m,
|
||||
Currency = "USD"
|
||||
}, Facade.Merchant);
|
||||
|
||||
Assert.Single(invoice.CryptoInfo);
|
||||
Assert.Equal(PaymentTypes.LightningLike.ToString(), invoice.CryptoInfo[0].PaymentType);
|
||||
|
||||
// Activating LNUrl, we should still have only 1 payment criteria that can be set.
|
||||
user.RegisterLightningNode(cryptoCode);
|
||||
user.SetLNUrl(cryptoCode, true);
|
||||
user.RegisterLightningNode(cryptoCode, LightningConnectionType.Charge);
|
||||
var lnSettingsVm = user.GetController<UIStoresController>().LightningSettings(user.StoreId, cryptoCode).AssertViewModel<LightningSettingsViewModel>();
|
||||
lnSettingsVm.LNURLEnabled = true;
|
||||
lnSettingsVm.LNURLStandardInvoiceEnabled = true;
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>().LightningSettings(lnSettingsVm).Result);
|
||||
vm = user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModel<CheckoutAppearanceViewModel>();
|
||||
criteria = Assert.Single(vm.PaymentMethodCriteria);
|
||||
Assert.Equal(new PaymentMethodId(cryptoCode, LightningPaymentType.Instance).ToString(), criteria.PaymentMethod);
|
||||
@ -1761,7 +1771,7 @@ namespace BTCPayServer.Tests
|
||||
var parsedJson = await GetExport(user);
|
||||
Assert.Equal(3, parsedJson.Length);
|
||||
|
||||
var invoiceDueAfterFirstPayment = 3 * networkFee.ToDecimal(MoneyUnit.BTC) * invoice.Rate;
|
||||
var invoiceDueAfterFirstPayment = (3 * networkFee).ToDecimal(MoneyUnit.BTC) * invoice.Rate;
|
||||
var pay1str = parsedJson[0].ToString();
|
||||
Assert.Contains("\"InvoiceItemDesc\": \"Some \\\", description\"", pay1str);
|
||||
Assert.Equal(invoiceDueAfterFirstPayment, GetFieldValue(pay1str, "InvoiceDue"));
|
||||
@ -1963,8 +1973,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Empty(appList2.Apps);
|
||||
Assert.Equal("test", appList.Apps[0].AppName);
|
||||
Assert.Equal(apps.CreatedAppId, appList.Apps[0].Id);
|
||||
|
||||
Assert.True(app.Role.ToPermissionSet(app.StoreId).Contains(Policies.CanModifyStoreSettings, app.StoreId));
|
||||
Assert.True(app.IsOwner);
|
||||
Assert.Equal(user.StoreId, appList.Apps[0].StoreId);
|
||||
Assert.IsType<NotFoundResult>(apps2.DeleteApp(appList.Apps[0].Id));
|
||||
Assert.IsType<ViewResult>(apps.DeleteApp(appList.Apps[0].Id));
|
||||
@ -1985,7 +1994,6 @@ namespace BTCPayServer.Tests
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess(true);
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
var btcpayClient = await user.CreateClient();
|
||||
|
||||
DateTimeOffset expiration = DateTimeOffset.UtcNow + TimeSpan.FromMinutes(21);
|
||||
|
||||
@ -2066,20 +2074,6 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var zeroInvoicePM = await greenfield.GetInvoicePaymentMethods(user.StoreId, zeroInvoice.Id);
|
||||
Assert.Empty(zeroInvoicePM);
|
||||
|
||||
var invoice6 = await btcpayClient.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest()
|
||||
{
|
||||
Amount = GreenfieldConstants.MaxAmount,
|
||||
Currency = "USD"
|
||||
});
|
||||
var repo = tester.PayTester.GetService<InvoiceRepository>();
|
||||
var entity = (await repo.GetInvoice(invoice6.Id));
|
||||
Assert.Equal((decimal)ulong.MaxValue, entity.Price);
|
||||
entity.GetPaymentMethods().First().Calculate();
|
||||
// Shouldn't be possible as we clamp the value, but existing invoice may have that
|
||||
entity.Price = decimal.MaxValue;
|
||||
entity.GetPaymentMethods().First().Calculate();
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
@ -2163,7 +2157,7 @@ namespace BTCPayServer.Tests
|
||||
txFee = localInvoice.BtcDue - invoice.BtcDue;
|
||||
Assert.Equal("paidPartial", localInvoice.ExceptionStatus.ToString());
|
||||
Assert.Equal(1, localInvoice.CryptoInfo[0].TxCount);
|
||||
Assert.Equal(localInvoice.BitcoinAddress, invoice.BitcoinAddress); //Same address
|
||||
Assert.NotEqual(localInvoice.BitcoinAddress, invoice.BitcoinAddress); //New address
|
||||
Assert.True(IsMapped(invoice, ctx));
|
||||
Assert.True(IsMapped(localInvoice, ctx));
|
||||
|
||||
@ -2451,31 +2445,6 @@ namespace BTCPayServer.Tests
|
||||
Assert.False(fn.Seen);
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanFixMappedDomainAppType()
|
||||
{
|
||||
using var tester = CreateServerTester(newDb: true);
|
||||
await tester.StartAsync();
|
||||
var f = tester.PayTester.GetService<ApplicationDbContextFactory>();
|
||||
using (var ctx = f.CreateContext())
|
||||
{
|
||||
var setting = new SettingData() { Id = "BTCPayServer.Services.PoliciesSettings" };
|
||||
setting.Value = JObject.Parse("{\"RootAppId\": null, \"RootAppType\": 1, \"Experimental\": false, \"PluginSource\": null, \"LockSubscription\": false, \"DisableSSHService\": false, \"PluginPreReleases\": false, \"BlockExplorerLinks\": [],\"DomainToAppMapping\": [{\"AppId\": \"87kj5yKay8mB4UUZcJhZH5TqDKMD3CznjwLjiu1oYZXe\", \"Domain\": \"donate.nicolas-dorier.com\", \"AppType\": 0}], \"CheckForNewVersions\": false, \"AllowHotWalletForAll\": false, \"RequiresConfirmedEmail\": false, \"DiscourageSearchEngines\": false, \"DisableInstantNotifications\": false, \"DisableNonAdminCreateUserApi\": false, \"AllowHotWalletRPCImportForAll\": false, \"AllowLightningInternalNodeForAll\": false, \"DisableStoresToUseServerEmailSettings\": false}").ToString();
|
||||
ctx.Settings.Add(setting);
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
await RestartMigration(tester);
|
||||
using (var ctx = f.CreateContext())
|
||||
{
|
||||
var setting = await ctx.Settings.FirstOrDefaultAsync(c => c.Id == "BTCPayServer.Services.PoliciesSettings");
|
||||
var o = JObject.Parse(setting.Value);
|
||||
Assert.Equal("Crowdfund", o["RootAppType"].Value<string>());
|
||||
o = (JObject)((JArray)o["DomainToAppMapping"])[0];
|
||||
Assert.Equal("PointOfSale", o["AppType"].Value<string>());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanDoLightningInternalNodeMigration()
|
||||
@ -2936,124 +2905,5 @@ namespace BTCPayServer.Tests
|
||||
Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files(new string[] { fileId })).Model);
|
||||
Assert.Null(viewFilesViewModel.DirectUrlByFiles);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
public async Task CanCreateReports()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
tester.ActivateLightning();
|
||||
tester.DeleteStore = false;
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var acc = tester.NewAccount();
|
||||
await acc.GrantAccessAsync();
|
||||
await acc.MakeAdmin();
|
||||
acc.RegisterDerivationScheme("BTC", importKeysToNBX: true);
|
||||
acc.RegisterLightningNode("BTC");
|
||||
await acc.ReceiveUTXO(Money.Coins(1.0m));
|
||||
|
||||
var client = await acc.CreateClient();
|
||||
var posController = acc.GetController<UIPointOfSaleController>();
|
||||
|
||||
var app = await client.CreatePointOfSaleApp(acc.StoreId, new CreatePointOfSaleAppRequest()
|
||||
{
|
||||
AppName = "Static",
|
||||
DefaultView = Client.Models.PosViewType.Static,
|
||||
Template = new PointOfSaleSettings().Template
|
||||
});
|
||||
var resp = await posController.ViewPointOfSale(app.Id, choiceKey: "green-tea");
|
||||
var invoiceId = GetInvoiceId(resp);
|
||||
await acc.PayOnChain(invoiceId);
|
||||
|
||||
app = await client.CreatePointOfSaleApp(acc.StoreId, new CreatePointOfSaleAppRequest()
|
||||
{
|
||||
AppName = "Cart",
|
||||
DefaultView = Client.Models.PosViewType.Cart,
|
||||
Template = new PointOfSaleSettings().Template
|
||||
});
|
||||
resp = await posController.ViewPointOfSale(app.Id, posData: new JObject()
|
||||
{
|
||||
["cart"] = new JArray()
|
||||
{
|
||||
new JObject()
|
||||
{
|
||||
["id"] = "green-tea",
|
||||
["count"] = 2
|
||||
},
|
||||
new JObject()
|
||||
{
|
||||
["id"] = "black-tea",
|
||||
["count"] = 1
|
||||
},
|
||||
}
|
||||
}.ToString());
|
||||
invoiceId = GetInvoiceId(resp);
|
||||
await acc.PayOnBOLT11(invoiceId);
|
||||
|
||||
resp = await posController.ViewPointOfSale(app.Id, posData: new JObject()
|
||||
{
|
||||
["cart"] = new JArray()
|
||||
{
|
||||
new JObject()
|
||||
{
|
||||
["id"] = "green-tea",
|
||||
["count"] = 5
|
||||
}
|
||||
}
|
||||
}.ToString());
|
||||
invoiceId = GetInvoiceId(resp);
|
||||
await acc.PayOnLNUrl(invoiceId);
|
||||
|
||||
await acc.CreateLNAddress();
|
||||
await acc.PayOnLNAddress();
|
||||
|
||||
var report = await GetReport(acc, new() { ViewName = "Payments" });
|
||||
// 1 payment on LN Address
|
||||
// 1 payment on LNURL
|
||||
// 1 payment on BOLT11
|
||||
// 1 payment on chain
|
||||
Assert.Equal(4, report.Data.Count);
|
||||
var lnAddressIndex = report.GetIndex("LightningAddress");
|
||||
var paymentTypeIndex = report.GetIndex("PaymentType");
|
||||
Assert.Contains(report.Data, d => d[lnAddressIndex]?.Value<string>()?.Contains(acc.LNAddress) is true);
|
||||
var paymentTypes = report.Data
|
||||
.GroupBy(d => d[paymentTypeIndex].Value<string>())
|
||||
.ToDictionary(d => d.Key);
|
||||
Assert.Equal(3, paymentTypes["Lightning"].Count());
|
||||
Assert.Single(paymentTypes["On-Chain"]);
|
||||
|
||||
// 2 on-chain transactions: It received from the cashcow, then paid its own invoice
|
||||
report = await GetReport(acc, new() { ViewName = "On-Chain Wallets" });
|
||||
var txIdIndex = report.GetIndex("TransactionId");
|
||||
var balanceIndex = report.GetIndex("BalanceChange");
|
||||
Assert.Equal(2, report.Data.Count);
|
||||
Assert.Equal(64, report.Data[0][txIdIndex].Value<string>().Length);
|
||||
Assert.Contains(report.Data, d => d[balanceIndex].Value<decimal>() == 1.0m);
|
||||
|
||||
// Items sold
|
||||
report = await GetReport(acc, new() { ViewName = "Products sold" });
|
||||
var itemIndex = report.GetIndex("Product");
|
||||
var countIndex = report.GetIndex("Quantity");
|
||||
var itemsCount = report.Data.GroupBy(d => d[itemIndex].Value<string>())
|
||||
.ToDictionary(d => d.Key, r => r.Sum(d => d[countIndex].Value<int>()));
|
||||
Assert.Equal(8, itemsCount["green-tea"]);
|
||||
Assert.Equal(1, itemsCount["black-tea"]);
|
||||
}
|
||||
|
||||
private async Task<StoreReportResponse> GetReport(TestAccount acc, StoreReportRequest req)
|
||||
{
|
||||
var controller = acc.GetController<UIReportsController>();
|
||||
return (await controller.StoreReportsJson(acc.StoreId, req)).AssertType<JsonResult>()
|
||||
.Value
|
||||
.AssertType<StoreReportResponse>();
|
||||
}
|
||||
|
||||
private static string GetInvoiceId(IActionResult resp)
|
||||
{
|
||||
var redirect = resp.AssertType<RedirectToActionResult>();
|
||||
Assert.Equal("Checkout", redirect.ActionName);
|
||||
return (string)redirect.RouteValues["invoiceId"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ services:
|
||||
TESTS_AzureBlobStorageConnectionString: ${TESTS_AzureBlobStorageConnectionString:-none}
|
||||
TEST_MERCHANTLIGHTNINGD: "type=clightning;server=unix://etc/merchant_lightningd_datadir/lightning-rpc"
|
||||
TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=unix://etc/customer_lightningd_datadir/lightning-rpc"
|
||||
TEST_MERCHANTCHARGE: "type=charge;server=http://lightning-charged:9112/;api-token=foiewnccewuify;allowinsecure=true"
|
||||
TEST_MERCHANTLND: "http://lnd:lnd@merchant_lnd:8080/"
|
||||
TESTS_INCONTAINER: "true"
|
||||
TESTS_SSHCONNECTION: "root@sshd:22"
|
||||
@ -37,10 +38,6 @@ services:
|
||||
- selenium
|
||||
extra_hosts:
|
||||
- "tests:127.0.0.1"
|
||||
networks:
|
||||
default:
|
||||
custom:
|
||||
ipv4_address: 172.23.0.18
|
||||
volumes:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
- "customer_lightningd_datadir:/etc/customer_lightningd_datadir"
|
||||
@ -55,6 +52,7 @@ services:
|
||||
- postgres
|
||||
- customer_lightningd
|
||||
- merchant_lightningd
|
||||
- lightning-charged
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
- sshd
|
||||
@ -73,7 +71,7 @@ services:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
|
||||
devlnd:
|
||||
image: btcpayserver/bitcoin:25.0
|
||||
image: btcpayserver/bitcoin:24.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
@ -87,19 +85,12 @@ services:
|
||||
- postgres
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
|
||||
selenium:
|
||||
image: selenium/standalone-chrome:101.0
|
||||
extra_hosts:
|
||||
- "tests:172.23.0.18"
|
||||
expose:
|
||||
- "4444"
|
||||
networks:
|
||||
default:
|
||||
custom:
|
||||
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.3.63
|
||||
image: nicolasdorier/nbxplorer:2.3.58
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
@ -135,7 +126,7 @@ services:
|
||||
|
||||
bitcoind:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/bitcoin:25.0
|
||||
image: btcpayserver/bitcoin:24.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
@ -163,7 +154,7 @@ services:
|
||||
- "bitcoin_datadir:/data"
|
||||
|
||||
customer_lightningd:
|
||||
image: btcpayserver/lightning:v23.05-dev
|
||||
image: btcpayserver/lightning:v23.02-1-dev
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
@ -189,8 +180,30 @@ services:
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
lightning-charged:
|
||||
image: shesek/lightning-charge:0.4.23-1-standalone
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NETWORK: regtest
|
||||
API_TOKEN: foiewnccewuify
|
||||
BITCOIND_RPCCONNECT: bitcoind
|
||||
LN_NET_PATH: /etc/lightning
|
||||
LN_NET: /etc/lightning
|
||||
volumes:
|
||||
- "bitcoin_datadir:/etc/bitcoin"
|
||||
- "lightning_charge_datadir:/data"
|
||||
- "merchant_lightningd_datadir:/etc/lightning"
|
||||
expose:
|
||||
- "9112" # Charge
|
||||
- "9735" # Lightning
|
||||
ports:
|
||||
- "54938:9112" # Charge
|
||||
depends_on:
|
||||
- bitcoind
|
||||
- merchant_lightningd
|
||||
|
||||
merchant_lightningd:
|
||||
image: btcpayserver/lightning:v23.05-dev
|
||||
image: btcpayserver/lightning:v23.02-1-dev
|
||||
stop_signal: SIGKILL
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
@ -224,7 +237,7 @@ services:
|
||||
- "5432"
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.16.4-beta
|
||||
image: btcpayserver/lnd:v0.15.4-beta-1
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -259,7 +272,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.16.4-beta
|
||||
image: btcpayserver/lnd:v0.15.4-beta-1
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -391,12 +404,3 @@ volumes:
|
||||
torrcdir:
|
||||
tor_servicesdir:
|
||||
monero_data:
|
||||
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
custom:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.23.0.0/16
|
||||
|
@ -22,6 +22,7 @@ services:
|
||||
TESTS_AzureBlobStorageConnectionString: ${TESTS_AzureBlobStorageConnectionString:-none}
|
||||
TEST_MERCHANTLIGHTNINGD: "type=clightning;server=unix://etc/merchant_lightningd_datadir/lightning-rpc"
|
||||
TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=unix://etc/customer_lightningd_datadir/lightning-rpc"
|
||||
TEST_MERCHANTCHARGE: "type=charge;server=http://lightning-charged:9112/;api-token=foiewnccewuify;allowinsecure=true"
|
||||
TEST_MERCHANTLND: "http://lnd:lnd@merchant_lnd:8080/"
|
||||
TESTS_INCONTAINER: "true"
|
||||
TESTS_SSHCONNECTION: "root@sshd:22"
|
||||
@ -35,10 +36,6 @@ services:
|
||||
- selenium
|
||||
extra_hosts:
|
||||
- "tests:127.0.0.1"
|
||||
networks:
|
||||
default:
|
||||
custom:
|
||||
ipv4_address: 172.23.0.18
|
||||
volumes:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
- "customer_lightningd_datadir:/etc/customer_lightningd_datadir"
|
||||
@ -53,6 +50,7 @@ services:
|
||||
- postgres
|
||||
- customer_lightningd
|
||||
- merchant_lightningd
|
||||
- lightning-charged
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
- sshd
|
||||
@ -70,33 +68,26 @@ services:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
|
||||
devlnd:
|
||||
image: btcpayserver/bitcoin:25.0
|
||||
image: btcpayserver/bitcoin:24.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
BITCOIN_EXTRA_ARGS: |
|
||||
deprecatedrpc=signrawtransaction
|
||||
connect=bitcoind:39388
|
||||
fallbackfee=0.0002
|
||||
rpcallowip=0.0.0.0/0
|
||||
fallbackfee=0.0002
|
||||
depends_on:
|
||||
- nbxplorer
|
||||
- postgres
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
|
||||
selenium:
|
||||
image: selenium/standalone-chrome:101.0
|
||||
extra_hosts:
|
||||
- "tests:172.23.0.18"
|
||||
expose:
|
||||
- "4444"
|
||||
networks:
|
||||
default:
|
||||
custom:
|
||||
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.3.63
|
||||
image: nicolasdorier/nbxplorer:2.3.58
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
@ -119,9 +110,10 @@ services:
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
|
||||
bitcoind:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/bitcoin:25.0
|
||||
image: btcpayserver/bitcoin:24.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
@ -149,7 +141,7 @@ services:
|
||||
- "bitcoin_datadir:/data"
|
||||
|
||||
customer_lightningd:
|
||||
image: btcpayserver/lightning:v23.05-dev
|
||||
image: btcpayserver/lightning:v23.02-1-dev
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
@ -175,8 +167,30 @@ services:
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
lightning-charged:
|
||||
image: shesek/lightning-charge:0.4.23-1-standalone
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NETWORK: regtest
|
||||
API_TOKEN: foiewnccewuify
|
||||
BITCOIND_RPCCONNECT: bitcoind
|
||||
LN_NET_PATH: /etc/lightning
|
||||
LN_NET: /etc/lightning
|
||||
volumes:
|
||||
- "bitcoin_datadir:/etc/bitcoin"
|
||||
- "lightning_charge_datadir:/data"
|
||||
- "merchant_lightningd_datadir:/etc/lightning"
|
||||
expose:
|
||||
- "9112" # Charge
|
||||
- "9735" # Lightning
|
||||
ports:
|
||||
- "54938:9112" # Charge
|
||||
depends_on:
|
||||
- bitcoind
|
||||
- merchant_lightningd
|
||||
|
||||
merchant_lightningd:
|
||||
image: btcpayserver/lightning:v23.05-dev
|
||||
image: btcpayserver/lightning:v23.02-1-dev
|
||||
stop_signal: SIGKILL
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
@ -211,7 +225,7 @@ services:
|
||||
- "5432"
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.16.4-beta
|
||||
image: btcpayserver/lnd:v0.15.4-beta-1
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -248,7 +262,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.16.4-beta
|
||||
image: btcpayserver/lnd:v0.15.4-beta-1
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -309,12 +323,3 @@ volumes:
|
||||
tor_datadir:
|
||||
torrcdir:
|
||||
tor_servicesdir:
|
||||
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
custom:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.23.0.0/16
|
||||
|
@ -45,10 +45,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="YamlDotNet" Version="8.0.0" />
|
||||
<PackageReference Include="BIP78.Sender" Version="0.2.2" />
|
||||
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.2" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.4.29" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.4.22" />
|
||||
<PackageReference Include="CsvHelper" Version="15.0.5" />
|
||||
<PackageReference Include="Dapper" Version="2.0.123" />
|
||||
<PackageReference Include="Fido2" Version="2.0.2" />
|
||||
@ -76,12 +75,12 @@
|
||||
<PackageReference Include="TwentyTwenty.Storage.Azure" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Google" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Local" Version="2.12.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.9" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.9" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Views\UIReports\StoreReports.cshtml" />
|
||||
<None Include="wwwroot\vendor\font-awesome\fonts\fontawesome-webfont.svg" />
|
||||
<None Include="wwwroot\vendor\font-awesome\fonts\fontawesome-webfont.woff2" />
|
||||
<None Include="wwwroot\vendor\font-awesome\less\animated.less" />
|
||||
@ -112,6 +111,9 @@
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_screen-reader.scss" />
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_stacked.scss" />
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_variables.scss" />
|
||||
<None Include="wwwroot\vendor\jquery-easing\jquery.easing.compatibility.js" />
|
||||
<None Include="wwwroot\vendor\jquery-easing\jquery.easing.js" />
|
||||
<None Include="wwwroot\vendor\jquery-easing\jquery.easing.min.js" />
|
||||
<None Include="wwwroot\vendor\jquery\jquery.js" />
|
||||
<None Include="wwwroot\vendor\jquery\jquery.min.js" />
|
||||
</ItemGroup>
|
||||
@ -120,7 +122,6 @@
|
||||
<Folder Include="wwwroot\vendor\bootstrap" />
|
||||
<Folder Include="wwwroot\vendor\clipboard.js\" />
|
||||
<Folder Include="wwwroot\vendor\highlightjs\" />
|
||||
<Folder Include="wwwroot\vendor\pivottable\" />
|
||||
<Folder Include="wwwroot\vendor\summernote" />
|
||||
<Folder Include="wwwroot\vendor\tom-select" />
|
||||
<Folder Include="wwwroot\vendor\ur-registry" />
|
||||
@ -138,7 +139,6 @@
|
||||
<ItemGroup>
|
||||
<Watch Include="Views\**\*.*"></Watch>
|
||||
<Watch Remove="Views\UIAccount\CheatPermissions.cshtml" />
|
||||
<Watch Remove="Views\UIReports\StoreReports.cshtml" />
|
||||
<Content Update="Views\UIApps\_ViewImports.cshtml">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
|
@ -24,7 +24,7 @@ public class AppTopItems : ViewComponent
|
||||
public async Task<IViewComponentResult> InvokeAsync(string appId, string appType)
|
||||
{
|
||||
var type = _appService.GetAppType(appType);
|
||||
if (type is not (IHasItemStatsAppType and AppBaseType appBaseType))
|
||||
if (type is not IHasItemStatsAppType salesAppType || type is not AppBaseType appBaseType)
|
||||
return new HtmlContentViewComponentResult(new StringHtmlContent(string.Empty));
|
||||
|
||||
var vm = new AppTopItemsViewModel
|
||||
@ -40,7 +40,7 @@ public class AppTopItems : ViewComponent
|
||||
var app = HttpContext.GetAppData();
|
||||
var entries = await _appService.GetItemStats(app);
|
||||
vm.SalesCount = entries.Select(e => e.SalesCount).ToList();
|
||||
vm.Entries = entries.Take(5).ToList();
|
||||
vm.Entries = entries.ToList();
|
||||
vm.AppType = app.AppType;
|
||||
vm.AppUrl = await appBaseType.ConfigureLink(app);
|
||||
vm.Name = app.Name;
|
||||
|
@ -48,7 +48,7 @@
|
||||
<span class="app-item-point ct-point"></span>
|
||||
@entry.Title
|
||||
</span>
|
||||
<span class="app-item-value" data-sensitive>
|
||||
<span class="app-item-value">
|
||||
<span class="text-muted">@entry.SalesCount @($"{label}{(entry.SalesCount == 1 ? "" : "s")}"),</span>
|
||||
@entry.TotalFormatted
|
||||
</span>
|
||||
|
@ -1,5 +1,6 @@
|
||||
@using BTCPayServer.Views.Server
|
||||
@using BTCPayServer.Views.Stores
|
||||
@using BTCPayServer.Views.Apps
|
||||
@using BTCPayServer.Views.Invoice
|
||||
@using BTCPayServer.Views.Manage
|
||||
@using BTCPayServer.Views.PaymentRequest
|
||||
@ -131,12 +132,6 @@
|
||||
<span>Invoices</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" permission="@Policies.CanViewInvoices">
|
||||
<a asp-area="" asp-controller="UIReports" asp-action="StoreReports" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.Reporting)" id="SectionNav-Reporting">
|
||||
<vc:icon symbol="invoice" />
|
||||
<span>Reporting</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
|
||||
<a asp-area="" asp-controller="UIPaymentRequest" asp-action="GetPaymentRequests" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActiveCategory(typeof(PaymentRequestsNavPages))" id="StoreNav-PaymentRequests">
|
||||
<vc:icon symbol="payment-requests"/>
|
||||
@ -150,10 +145,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
|
||||
<a asp-area=""
|
||||
asp-controller="UIStorePullPayments" asp-action="Payouts"
|
||||
asp-route-pullPaymentId=""
|
||||
asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.Payouts)" id="StoreNav-Payouts">
|
||||
<a asp-area="" asp-controller="UIStorePullPayments" asp-action="Payouts" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.Payouts)" id="StoreNav-Payouts">
|
||||
<vc:icon symbol="payouts"/>
|
||||
<span>Payouts</span>
|
||||
</a>
|
||||
@ -183,7 +175,7 @@
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item" permission="@Policies.CanModifyServerSettings">
|
||||
<a asp-area="" asp-controller="UIServer" asp-action="ListPlugins" class="nav-link @ViewData.IsActivePage(ServerNavPages.Plugins)" id="Nav-ManagePlugins">
|
||||
<vc:icon symbol="manage-plugins"/>
|
||||
<vc:icon symbol="plugin"/>
|
||||
<span>Manage Plugins</span>
|
||||
</a>
|
||||
</li>
|
||||
@ -244,7 +236,7 @@
|
||||
<span>Account</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu py-0 w-100" aria-labelledby="Nav-Account">
|
||||
<li class="p-3 border-bottom">
|
||||
<li class="p-3">
|
||||
<strong class="d-block text-truncate" style="max-width:195px">@User.Identity.Name</strong>
|
||||
@if (User.IsInRole(Roles.ServerAdmin))
|
||||
{
|
||||
@ -253,19 +245,10 @@
|
||||
</li>
|
||||
@if (!Theme.CustomTheme)
|
||||
{
|
||||
<li class="py-1 px-3">
|
||||
<vc:theme-switch css-class="nav-link pb-0"/>
|
||||
<li class="border-top py-1 px-3">
|
||||
<vc:theme-switch css-class="nav-link"/>
|
||||
</li>
|
||||
}
|
||||
<li class="py-1 px-3">
|
||||
<label class="d-flex align-items-center justify-content-between gap-3 nav-link">
|
||||
<span class="fw-semibold">Hide Sensitive Info</span>
|
||||
<input id="HideSensitiveInfo" name="HideSensitiveInfo" type="checkbox" class="btcpay-toggle" />
|
||||
</label>
|
||||
<script>
|
||||
document.getElementById('HideSensitiveInfo').checked = window.localStorage.getItem('btcpay-hide-sensitive-info') === 'true';
|
||||
</script>
|
||||
</li>
|
||||
<li class="border-top py-1 px-3">
|
||||
<a asp-area="" asp-controller="UIManage" asp-action="Index" class="nav-link @ViewData.IsActiveCategory(typeof(ManageNavPages))" id="Nav-ManageAccount">
|
||||
<span>Manage Account</span>
|
||||
|
@ -72,6 +72,7 @@ namespace BTCPayServer.Components.MainNav
|
||||
vm.Apps = apps.Select(a => new StoreApp
|
||||
{
|
||||
Id = a.Id,
|
||||
IsOwner = a.IsOwner,
|
||||
AppName = a.AppName,
|
||||
AppType = a.AppType
|
||||
}).ToList();
|
||||
|
@ -20,5 +20,6 @@ namespace BTCPayServer.Components.MainNav
|
||||
public string Id { get; set; }
|
||||
public string AppName { get; set; }
|
||||
public string AppType { get; set; }
|
||||
public bool IsOwner { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -23,18 +23,16 @@
|
||||
@if (Model.Balance.OffchainBalance != null)
|
||||
{
|
||||
<div class="balance">
|
||||
<div class="d-flex align-items-baseline gap-1">
|
||||
<h3 class="d-inline-block me-1" data-balance="@Model.TotalOffchain" data-sensitive>@Model.TotalOffchain</h3>
|
||||
<span class="text-secondary fw-semibold text-nowrap">
|
||||
<span class="currency">@Model.CryptoCode</span> in channels
|
||||
</span>
|
||||
</div>
|
||||
<h3 class="d-inline-block me-1" data-balance="@Model.TotalOffchain">@Model.TotalOffchain</h3>
|
||||
<span class="text-secondary fw-semibold text-nowrap">
|
||||
<span class="currency">@Model.CryptoCode</span> in channels
|
||||
</span>
|
||||
|
||||
<div class="balance-details collapse" id="balanceDetailsOffchain">
|
||||
@if (Model.Balance.OffchainBalance.Opening != null)
|
||||
{
|
||||
<div class="mt-2 d-flex align-items-baseline gap-1">
|
||||
<span class="fw-semibold" data-balance="@Model.Balance.OffchainBalance.Opening" data-sensitive>
|
||||
<div class="mt-2">
|
||||
<span class="fw-semibold" data-balance="@Model.Balance.OffchainBalance.Opening">
|
||||
@Model.Balance.OffchainBalance.Opening
|
||||
</span>
|
||||
<span class="text-secondary text-nowrap">
|
||||
@ -44,8 +42,8 @@
|
||||
}
|
||||
@if (Model.Balance.OffchainBalance.Local != null)
|
||||
{
|
||||
<div class="mt-2 d-flex align-items-baseline gap-1">
|
||||
<span class="fw-semibold" data-balance="@Model.Balance.OffchainBalance.Local" data-sensitive>
|
||||
<div class="mt-2">
|
||||
<span class="fw-semibold" data-balance="@Model.Balance.OffchainBalance.Local">
|
||||
@Model.Balance.OffchainBalance.Local
|
||||
</span>
|
||||
<span class="text-secondary text-nowrap">
|
||||
@ -55,8 +53,8 @@
|
||||
}
|
||||
@if (Model.Balance.OffchainBalance.Remote != null)
|
||||
{
|
||||
<div class="mt-2 d-flex align-items-baseline gap-1">
|
||||
<span class="fw-semibold" data-balance="@Model.Balance.OffchainBalance.Remote" data-sensitive>
|
||||
<div class="mt-2">
|
||||
<span class="fw-semibold" data-balance="@Model.Balance.OffchainBalance.Remote">
|
||||
@Model.Balance.OffchainBalance.Remote
|
||||
</span>
|
||||
<span class="text-secondary text-nowrap">
|
||||
@ -66,8 +64,8 @@
|
||||
}
|
||||
@if (Model.Balance.OffchainBalance.Closing != null)
|
||||
{
|
||||
<div class="mt-2 d-flex align-items-baseline gap-1">
|
||||
<span class="fw-semibold" data-balance="@Model.Balance.OffchainBalance.Closing" data-sensitive>
|
||||
<div class="mt-2">
|
||||
<span class="fw-semibold" data-balance="@Model.Balance.OffchainBalance.Closing">
|
||||
@Model.Balance.OffchainBalance.Closing
|
||||
</span>
|
||||
<span class="text-secondary text-nowrap">
|
||||
@ -81,17 +79,15 @@
|
||||
@if (Model.Balance.OnchainBalance != null)
|
||||
{
|
||||
<div class="balance">
|
||||
<div class="d-flex align-items-baseline gap-1">
|
||||
<h3 class="d-inline-block me-1" data-balance="@Model.TotalOnchain" data-sensitive>@Model.TotalOnchain</h3>
|
||||
<span class="text-secondary fw-semibold text-nowrap">
|
||||
<span class="currency">@Model.CryptoCode</span> on-chain
|
||||
</span>
|
||||
</div>
|
||||
<h3 class="d-inline-block me-1" data-balance="@Model.TotalOnchain">@Model.TotalOnchain</h3>
|
||||
<span class="text-secondary fw-semibold text-nowrap">
|
||||
<span class="currency">@Model.CryptoCode</span> on-chain
|
||||
</span>
|
||||
<div class="balance-details collapse" id="balanceDetailsOnchain">
|
||||
@if (Model.Balance.OnchainBalance.Confirmed != null)
|
||||
{
|
||||
<div class="mt-2 d-flex align-items-baseline gap-1">
|
||||
<span class="fw-semibold" data-balance="@Model.Balance.OnchainBalance.Confirmed" data-sensitive>
|
||||
<div class="mt-2">
|
||||
<span class="fw-semibold" data-balance="@Model.Balance.OnchainBalance.Confirmed">
|
||||
@Model.Balance.OnchainBalance.Confirmed
|
||||
</span>
|
||||
<span class="text-secondary text-nowrap">
|
||||
@ -101,8 +97,8 @@
|
||||
}
|
||||
@if (Model.Balance.OnchainBalance.Unconfirmed != null)
|
||||
{
|
||||
<div class="mt-2 d-flex align-items-baseline gap-1">
|
||||
<span class="fw-semibold" data-balance="@Model.Balance.OnchainBalance.Unconfirmed" data-sensitive>
|
||||
<div class="mt-2">
|
||||
<span class="fw-semibold" data-balance="@Model.Balance.OnchainBalance.Unconfirmed">
|
||||
@Model.Balance.OnchainBalance.Unconfirmed
|
||||
</span>
|
||||
<span class="text-secondary text-nowrap">
|
||||
@ -112,8 +108,8 @@
|
||||
}
|
||||
@if (Model.Balance.OnchainBalance.Reserved != null)
|
||||
{
|
||||
<div class="mt-2 d-flex align-items-baseline gap-1">
|
||||
<span class="fw-semibold" data-balance="@Model.Balance.OnchainBalance.Reserved" data-sensitive>
|
||||
<div class="mt-2">
|
||||
<span class="fw-semibold" data-balance="@Model.Balance.OnchainBalance.Reserved">
|
||||
@Model.Balance.OnchainBalance.Reserved
|
||||
</span>
|
||||
<span class="text-secondary text-nowrap">
|
||||
|
@ -3,7 +3,6 @@
|
||||
@using BTCPayServer.Services
|
||||
@using BTCPayServer.Services.Invoices
|
||||
@inject DisplayFormatter DisplayFormatter
|
||||
@inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
|
||||
@model BTCPayServer.Components.StoreRecentInvoices.StoreRecentInvoicesViewModel
|
||||
|
||||
<div class="widget store-recent-invoices" id="StoreRecentInvoices-@Model.Store.Id">
|
||||
@ -52,45 +51,21 @@
|
||||
<a asp-controller="UIInvoice" asp-action="Invoice" asp-route-invoiceId="@invoice.InvoiceId" class="text-break">@invoice.InvoiceId</a>
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
@if (invoice.Details.Archived)
|
||||
<span class="badge badge-@invoice.Status.Status.ToModernStatus().ToString().ToLower()">
|
||||
@invoice.Status.Status.ToModernStatus().ToString()
|
||||
@if (invoice.Status.ExceptionStatus != InvoiceExceptionStatus.None)
|
||||
{
|
||||
<span class="badge bg-warning">archived</span>
|
||||
@($"({invoice.Status.ExceptionStatus.ToString()})")
|
||||
}
|
||||
<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>
|
||||
@if (invoice.HasRefund)
|
||||
{
|
||||
<span class="badge bg-warning">
|
||||
Refund
|
||||
</span>
|
||||
@foreach (var paymentMethodId in invoice.Details.Payments.Select(payment => payment.GetPaymentMethodId()).Distinct())
|
||||
{
|
||||
var image = PaymentMethodHandlerDictionary[paymentMethodId]?.GetCryptoImage(paymentMethodId);
|
||||
var badge = paymentMethodId.PaymentType.GetBadge();
|
||||
if (!string.IsNullOrEmpty(image) || !string.IsNullOrEmpty(badge))
|
||||
{
|
||||
<span class="d-inline-flex align-items-center gap-1">
|
||||
@if (!string.IsNullOrEmpty(image))
|
||||
{
|
||||
<img src="@Context.Request.GetRelativePathOrAbsolute(image)" alt="@paymentMethodId.PaymentType.ToString()" style="height:1.5em" />
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(badge))
|
||||
{
|
||||
@badge
|
||||
}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
@if (invoice.HasRefund)
|
||||
{
|
||||
<span class="badge bg-warning">Refund</span>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span data-sensitive>@DisplayFormatter.Currency(invoice.Amount, invoice.Currency)</span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end">@DisplayFormatter.Currency(invoice.Amount, invoice.Currency)</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
|
||||
namespace BTCPayServer.Components.StoreRecentInvoices;
|
||||
@ -12,7 +11,5 @@ public class StoreRecentInvoiceViewModel
|
||||
public string Currency { get; set; }
|
||||
public InvoiceState Status { get; set; }
|
||||
public DateTimeOffset Date { get; set; }
|
||||
|
||||
public InvoiceDetailsModel Details { get; set; }
|
||||
public bool HasRefund { get; set; }
|
||||
}
|
||||
|
@ -3,13 +3,11 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Components.StoreRecentInvoices;
|
||||
|
||||
@ -55,22 +53,17 @@ public class StoreRecentInvoices : ViewComponent
|
||||
});
|
||||
|
||||
vm.Invoices = (from invoice in invoiceEntities
|
||||
let state = invoice.GetInvoiceState()
|
||||
select new StoreRecentInvoiceViewModel
|
||||
{
|
||||
Date = invoice.InvoiceTime,
|
||||
Status = state,
|
||||
HasRefund = invoice.Refunds.Any(),
|
||||
InvoiceId = invoice.Id,
|
||||
OrderId = invoice.Metadata.OrderId ?? string.Empty,
|
||||
Amount = invoice.Price,
|
||||
Currency = invoice.Currency,
|
||||
Details = new InvoiceDetailsModel
|
||||
{
|
||||
Archived = invoice.Archived,
|
||||
Payments = invoice.GetPayments(false)
|
||||
}
|
||||
}).ToList();
|
||||
let state = invoice.GetInvoiceState()
|
||||
select new StoreRecentInvoiceViewModel
|
||||
{
|
||||
Date = invoice.InvoiceTime,
|
||||
Status = state,
|
||||
HasRefund = invoice.Refunds.Any(),
|
||||
InvoiceId = invoice.Id,
|
||||
OrderId = invoice.Metadata.OrderId ?? string.Empty,
|
||||
Amount = invoice.Price,
|
||||
Currency = invoice.Currency
|
||||
}).ToList();
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
@ -72,15 +72,11 @@
|
||||
</td>
|
||||
@if (tx.Positive)
|
||||
{
|
||||
<td class="text-end text-success">
|
||||
<span data-sensitive>@DisplayFormatter.Currency(tx.Balance, tx.Currency)</span>
|
||||
</td>
|
||||
<td class="text-end text-success">@DisplayFormatter.Currency(tx.Balance, tx.Currency)</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td class="text-end text-danger">
|
||||
<span data-sensitive>@DisplayFormatter.Currency(tx.Balance, tx.Currency)</span>
|
||||
</td>
|
||||
<td class="text-end text-danger">@DisplayFormatter.Currency(tx.Balance, tx.Currency)</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ public class StoreRecentTransactions : ViewComponent
|
||||
{
|
||||
var network = derivationSettings.Network;
|
||||
var wallet = _walletProvider.GetWallet(network);
|
||||
var allTransactions = await wallet.FetchTransactionHistory(derivationSettings.AccountDerivation, 0, 5, TimeSpan.FromDays(31.0), cancellationToken: this.HttpContext.RequestAborted);
|
||||
var allTransactions = await wallet.FetchTransactionHistory(derivationSettings.AccountDerivation, 0, 5, TimeSpan.FromDays(31.0));
|
||||
var walletTransactionsInfo = await _walletRepository.GetWalletTransactionsInfo(vm.WalletId, allTransactions.Select(t => t.TransactionId.ToString()).ToArray());
|
||||
|
||||
transactions = allTransactions
|
||||
|
@ -1,8 +1,8 @@
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@using BTCPayServer.Abstractions.Contracts
|
||||
@using BTCPayServer.Client
|
||||
@using BTCPayServer.Services
|
||||
@inject SignInManager<ApplicationUser> SignInManager
|
||||
@inject BTCPayServerEnvironment Env
|
||||
@inject IFileService FileService
|
||||
@model BTCPayServer.Components.StoreSelector.StoreSelectorViewModel
|
||||
@ -29,14 +29,18 @@
|
||||
{
|
||||
<a asp-controller="UIHome" asp-action="Index" id="StoreSelectorHome" class="navbar-brand py-2">@{await LogoContent();}</a>
|
||||
}
|
||||
else if (Model.CurrentStoreIsOwner)
|
||||
{
|
||||
<a asp-controller="UIStores" asp-action="Dashboard" asp-route-storeId="@Model.CurrentStoreId" id="StoreSelectorHome" class="navbar-brand py-2">@{await LogoContent();}</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a asp-controller="UIStores" asp-action="Dashboard" permission="@Policies.CanModifyStoreSettings" asp-route-storeId="@Model.CurrentStoreId" id="StoreSelectorHome" class="navbar-brand py-2">@{await LogoContent();}</a>
|
||||
<a asp-controller="UIInvoice" asp-action="ListInvoices" not-permission="@Policies.CanModifyStoreSettings" asp-route-storeId="@Model.CurrentStoreId" id="StoreSelectorHome" class="navbar-brand py-2">@{await LogoContent();}</a>
|
||||
<a asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@Model.CurrentStoreId" id="StoreSelectorHome" class="navbar-brand py-2">@{await LogoContent();}</a>
|
||||
}
|
||||
@if (Model.Options.Any())
|
||||
{
|
||||
<div id="StoreSelector">
|
||||
|
||||
<div id="StoreSelector">
|
||||
@if (Model.Options.Any())
|
||||
{
|
||||
<div id="StoreSelectorDropdown" class="dropdown only-for-js">
|
||||
<button id="StoreSelectorToggle" class="btn btn-secondary dropdown-toggle rounded-pill px-3 @(Model.CurrentStoreId == null ? "empty-state" : "")" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
@if (!string.IsNullOrEmpty(Model.CurrentStoreLogoFileId))
|
||||
@ -68,5 +72,9 @@ else
|
||||
<li><a asp-controller="UIUserStores" asp-action="CreateStore" class="dropdown-item" id="StoreSelectorCreate">Create Store</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else if (SignInManager.IsSignedIn(User))
|
||||
{
|
||||
<a asp-controller="UIUserStores" asp-action="CreateStore" class="btn btn-primary w-100 rounded-pill text-nowrap" id="StoreSelectorCreate">Create Store</a>
|
||||
}
|
||||
</div>
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
@ -39,14 +38,13 @@ namespace BTCPayServer.Components.StoreSelector
|
||||
.FirstOrDefault()?
|
||||
.Network.CryptoCode;
|
||||
var walletId = cryptoCode != null ? new WalletId(store.Id, cryptoCode) : null;
|
||||
var role = store.GetStoreRoleOfUser(userId);
|
||||
return new StoreSelectorOption
|
||||
{
|
||||
Text = store.StoreName,
|
||||
Value = store.Id,
|
||||
Selected = store.Id == currentStore?.Id,
|
||||
WalletId = walletId,
|
||||
IsOwner = role != null && role.Permissions.Contains(Policies.CanModifyStoreSettings)
|
||||
IsOwner = store.Role == StoreRoles.Owner,
|
||||
WalletId = walletId
|
||||
};
|
||||
})
|
||||
.OrderBy(s => s.Text)
|
||||
@ -59,6 +57,7 @@ namespace BTCPayServer.Components.StoreSelector
|
||||
Options = options,
|
||||
CurrentStoreId = currentStore?.Id,
|
||||
CurrentDisplayName = currentStore?.StoreName,
|
||||
CurrentStoreIsOwner = currentStore?.Role == StoreRoles.Owner,
|
||||
CurrentStoreLogoFileId = blob?.LogoFileId
|
||||
};
|
||||
|
||||
|
@ -8,6 +8,7 @@ namespace BTCPayServer.Components.StoreSelector
|
||||
public string CurrentStoreId { get; set; }
|
||||
public string CurrentStoreLogoFileId { get; set; }
|
||||
public string CurrentDisplayName { get; set; }
|
||||
public bool CurrentStoreIsOwner { get; set; }
|
||||
}
|
||||
|
||||
public class StoreSelectorOption
|
||||
|
@ -17,8 +17,8 @@
|
||||
<header class="mb-3">
|
||||
@if (Model.Balance != null)
|
||||
{
|
||||
<div class="balance d-flex align-items-baseline gap-1">
|
||||
<h3 class="d-inline-block me-1" data-balance="@Model.Balance" data-sensitive>@Model.Balance</h3>
|
||||
<div class="balance">
|
||||
<h3 class="d-inline-block me-1" data-balance="@Model.Balance">@Model.Balance</h3>
|
||||
<span class="text-secondary fw-semibold currency">@Model.CryptoCode</span>
|
||||
</div>
|
||||
}
|
||||
@ -92,7 +92,8 @@
|
||||
window.setTimeout(() => {
|
||||
const yLabels = [...document.querySelectorAll('.ct-label.ct-vertical.ct-start')];
|
||||
if (yLabels) {
|
||||
const width = Math.max(...(yLabels.map(l => l.innerText.length * 7.5)));
|
||||
const factor = rate ? 6 : 8;
|
||||
const width = Math.max(...(yLabels.map(l => l.innerText.length * factor)));
|
||||
const opts = Object.assign({}, renderOpts, {
|
||||
axisY: Object.assign({}, renderOpts.axisY, { offset: width })
|
||||
});
|
||||
|
@ -1,33 +1,10 @@
|
||||
@model BTCPayServer.Components.TruncateCenter.TruncateCenterViewModel
|
||||
@{
|
||||
var classes = string.IsNullOrEmpty(Model.Classes) ? string.Empty : Model.Classes.Trim();
|
||||
var isTruncated = !string.IsNullOrEmpty(Model.Start) && !string.IsNullOrEmpty(Model.End);
|
||||
@if (Model.Copy) classes += " truncate-center--copy";
|
||||
@if (Model.Elastic) classes += " truncate-center--elastic";
|
||||
}
|
||||
<span class="truncate-center @classes">
|
||||
@if (Model.IsVue)
|
||||
{
|
||||
<span class="truncate-center-truncated" data-bs-toggle="tooltip" :title=@Safe.Json(Model.Text)>
|
||||
<span class="truncate-center-start" v-text=@Safe.Json(Model.Text)></span>
|
||||
<span class="truncate-center-end" v-text=@Safe.Json($"{Model.Text}.slice(-{Model.Padding})")></span>
|
||||
</span>
|
||||
<span class="truncate-center-text" v-text=@Safe.Json(Model.Text)></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="truncate-center-truncated" @(isTruncated ? $"data-bs-toggle=tooltip title={Model.Text}" : "")>
|
||||
<span class="truncate-center-start">@(Model.Elastic || !isTruncated ? Model.Text : $"{Model.Start}…")</span>
|
||||
@if (isTruncated)
|
||||
{
|
||||
<span class="truncate-center-end">@Model.End</span>
|
||||
}
|
||||
</span>
|
||||
<span class="truncate-center-text">@Model.Text</span>
|
||||
}
|
||||
<span class="truncate-center @Model.Classes">
|
||||
<span class="truncate-center-truncated" @(Model.Truncated != Model.Text ? $"data-bs-toggle=tooltip title={Model.Text}" : "")>@Model.Truncated</span>
|
||||
<span class="truncate-center-text">@Model.Text</span>
|
||||
@if (Model.Copy)
|
||||
{
|
||||
<button type="button" class="btn btn-link p-0" @(Model.IsVue ? ":" : string.Empty)data-clipboard=@Safe.Json(Model.Text)>
|
||||
<button type="button" class="btn btn-link p-0" data-clipboard="@Model.Text">
|
||||
<vc:icon symbol="copy" />
|
||||
</button>
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ namespace BTCPayServer.Components.TruncateCenter;
|
||||
/// <returns>HTML with truncated string</returns>
|
||||
public class TruncateCenter : ViewComponent
|
||||
{
|
||||
public IViewComponentResult Invoke(string text, string link = null, string classes = null, int padding = 7, bool copy = true, bool elastic = false, bool isVue = false)
|
||||
public IViewComponentResult Invoke(string text, string link = null, string classes = null, int padding = 7, bool copy = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return new HtmlContentViewComponentResult(new StringHtmlContent(string.Empty));
|
||||
@ -23,17 +23,11 @@ public class TruncateCenter : ViewComponent
|
||||
{
|
||||
Classes = classes,
|
||||
Padding = padding,
|
||||
Elastic = elastic,
|
||||
IsVue = isVue,
|
||||
Copy = copy,
|
||||
Text = text,
|
||||
Link = link
|
||||
Link = link,
|
||||
Truncated = text.Length > 2 * padding ? $"{text[..padding]}…{text[^padding..]}" : text
|
||||
};
|
||||
if (!isVue && text.Length > 2 * padding)
|
||||
{
|
||||
vm.Start = text[..padding];
|
||||
vm.End = text[^padding..];
|
||||
}
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
|
@ -3,13 +3,10 @@ namespace BTCPayServer.Components.TruncateCenter
|
||||
public class TruncateCenterViewModel
|
||||
{
|
||||
public string Text { get; set; }
|
||||
public string Start { get; set; }
|
||||
public string End { get; set; }
|
||||
public string Truncated { get; set; }
|
||||
public string Classes { get; set; }
|
||||
public string Link { get; set; }
|
||||
public int Padding { get; set; }
|
||||
public bool Copy { get; set; }
|
||||
public bool Elastic { get; set; }
|
||||
public bool IsVue { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
<div class="d-sm-flex align-items-center justify-content-between">
|
||||
<a asp-controller="UIWallets" asp-action="WalletTransactions" asp-route-walletId="@Model.WalletId" class="unobtrusive-link">
|
||||
<h2 class="mb-1">@Model.Label</h2>
|
||||
<div class="text-muted fw-semibold" data-sensitive>
|
||||
<div class="text-muted fw-semibold">
|
||||
@Model.Balance @Model.Network.CryptoCode
|
||||
</div>
|
||||
</a>
|
||||
|
@ -1,23 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.ModelBinders;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Security.Greenfield;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitpayClient;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -42,7 +36,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (invoice == null)
|
||||
throw new BitpayHttpException(400, "Invalid invoice");
|
||||
return await CreateInvoiceCore(invoice, HttpContext.GetStoreData(), HttpContext.Request.GetAbsoluteRoot(), cancellationToken: cancellationToken);
|
||||
return await _InvoiceController.CreateInvoiceCore(invoice, HttpContext.GetStoreData(), HttpContext.Request.GetAbsoluteRoot(), cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@ -72,7 +66,7 @@ namespace BTCPayServer.Controllers
|
||||
int? limit = null,
|
||||
int? offset = null)
|
||||
{
|
||||
if (User.Identity?.AuthenticationType == Security.Bitpay.BitpayAuthenticationTypes.Anonymous)
|
||||
if (User.Identity.AuthenticationType == Security.Bitpay.BitpayAuthenticationTypes.Anonymous)
|
||||
return Forbid(Security.Bitpay.BitpayAuthenticationTypes.Anonymous);
|
||||
if (dateEnd != null)
|
||||
dateEnd = dateEnd.Value + TimeSpan.FromDays(1); //Should include the end day
|
||||
@ -94,133 +88,5 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
return Json(DataWrapper.Create(entities));
|
||||
}
|
||||
|
||||
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(BitpayCreateInvoiceRequest invoice,
|
||||
StoreData store, string serverUrl, List<string> additionalTags = null,
|
||||
CancellationToken cancellationToken = default, Action<InvoiceEntity> entityManipulator = null)
|
||||
{
|
||||
var entity = await CreateInvoiceCoreRaw(invoice, store, serverUrl, additionalTags, cancellationToken, entityManipulator);
|
||||
var resp = entity.EntityToDTO();
|
||||
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
|
||||
}
|
||||
|
||||
internal async Task<InvoiceEntity> CreateInvoiceCoreRaw(BitpayCreateInvoiceRequest invoice, StoreData store, string serverUrl, List<string> additionalTags = null, CancellationToken cancellationToken = default, Action<InvoiceEntity> entityManipulator = null)
|
||||
{
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var entity = _InvoiceRepository.CreateNewInvoice(store.Id);
|
||||
entity.ExpirationTime = invoice.ExpirationTime is { } v ? v : entity.InvoiceTime + storeBlob.InvoiceExpiration;
|
||||
entity.MonitoringExpiration = entity.ExpirationTime + storeBlob.MonitoringExpiration;
|
||||
if (entity.ExpirationTime - TimeSpan.FromSeconds(30.0) < entity.InvoiceTime)
|
||||
{
|
||||
throw new BitpayHttpException(400, "The expirationTime is set too soon");
|
||||
}
|
||||
if (entity.Price < 0.0m)
|
||||
{
|
||||
throw new BitpayHttpException(400, "The price should be 0 or more.");
|
||||
}
|
||||
if (entity.Price > GreenfieldConstants.MaxAmount)
|
||||
{
|
||||
throw new BitpayHttpException(400, $"The price should less than {GreenfieldConstants.MaxAmount}.");
|
||||
}
|
||||
entity.Metadata.OrderId = invoice.OrderId;
|
||||
entity.Metadata.PosDataLegacy = invoice.PosData;
|
||||
entity.ServerUrl = serverUrl;
|
||||
entity.FullNotifications = invoice.FullNotifications || invoice.ExtendedNotifications;
|
||||
entity.ExtendedNotifications = invoice.ExtendedNotifications;
|
||||
entity.NotificationURLTemplate = invoice.NotificationURL;
|
||||
entity.NotificationEmail = invoice.NotificationEmail;
|
||||
if (additionalTags != null)
|
||||
entity.InternalTags.AddRange(additionalTags);
|
||||
FillBuyerInfo(invoice, entity);
|
||||
|
||||
var price = invoice.Price;
|
||||
entity.Metadata.ItemCode = invoice.ItemCode;
|
||||
entity.Metadata.ItemDesc = invoice.ItemDesc;
|
||||
entity.Metadata.Physical = invoice.Physical;
|
||||
entity.Metadata.TaxIncluded = invoice.TaxIncluded;
|
||||
entity.Currency = invoice.Currency;
|
||||
if (price is { } vv)
|
||||
{
|
||||
entity.Price = vv;
|
||||
entity.Type = InvoiceType.Standard;
|
||||
}
|
||||
else
|
||||
{
|
||||
entity.Price = 0m;
|
||||
entity.Type = InvoiceType.TopUp;
|
||||
}
|
||||
|
||||
entity.StoreSupportUrl = storeBlob.StoreSupportUrl;
|
||||
entity.RedirectURLTemplate = invoice.RedirectURL ?? store.StoreWebsite;
|
||||
entity.RedirectAutomatically =
|
||||
invoice.RedirectAutomatically.GetValueOrDefault(storeBlob.RedirectAutomatically);
|
||||
entity.RequiresRefundEmail = invoice.RequiresRefundEmail;
|
||||
entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);
|
||||
|
||||
IPaymentFilter excludeFilter = null;
|
||||
if (invoice.PaymentCurrencies?.Any() is true)
|
||||
{
|
||||
invoice.SupportedTransactionCurrencies ??=
|
||||
new Dictionary<string, InvoiceSupportedTransactionCurrency>();
|
||||
foreach (string paymentCurrency in invoice.PaymentCurrencies)
|
||||
{
|
||||
invoice.SupportedTransactionCurrencies.TryAdd(paymentCurrency,
|
||||
new InvoiceSupportedTransactionCurrency() { Enabled = true });
|
||||
}
|
||||
}
|
||||
if (invoice.SupportedTransactionCurrencies != null && invoice.SupportedTransactionCurrencies.Count != 0)
|
||||
{
|
||||
var supportedTransactionCurrencies = invoice.SupportedTransactionCurrencies
|
||||
.Where(c => c.Value.Enabled)
|
||||
.Select(c => PaymentMethodId.TryParse(c.Key, out var p) ? p : null)
|
||||
.Where(c => c != null)
|
||||
.ToHashSet();
|
||||
excludeFilter = PaymentFilter.Where(p => !supportedTransactionCurrencies.Contains(p));
|
||||
}
|
||||
entity.PaymentTolerance = storeBlob.PaymentTolerance;
|
||||
entity.DefaultPaymentMethod = invoice.DefaultPaymentMethod;
|
||||
entity.RequiresRefundEmail = invoice.RequiresRefundEmail;
|
||||
|
||||
return await _InvoiceController.CreateInvoiceCoreRaw(entity, store, excludeFilter, null, cancellationToken, entityManipulator);
|
||||
}
|
||||
|
||||
private void FillBuyerInfo(BitpayCreateInvoiceRequest req, InvoiceEntity invoiceEntity)
|
||||
{
|
||||
var buyerInformation = invoiceEntity.Metadata;
|
||||
buyerInformation.BuyerAddress1 = req.BuyerAddress1;
|
||||
buyerInformation.BuyerAddress2 = req.BuyerAddress2;
|
||||
buyerInformation.BuyerCity = req.BuyerCity;
|
||||
buyerInformation.BuyerCountry = req.BuyerCountry;
|
||||
buyerInformation.BuyerEmail = req.BuyerEmail;
|
||||
buyerInformation.BuyerName = req.BuyerName;
|
||||
buyerInformation.BuyerPhone = req.BuyerPhone;
|
||||
buyerInformation.BuyerState = req.BuyerState;
|
||||
buyerInformation.BuyerZip = req.BuyerZip;
|
||||
var buyer = req.Buyer;
|
||||
if (buyer == null)
|
||||
return;
|
||||
buyerInformation.BuyerAddress1 ??= buyer.Address1;
|
||||
buyerInformation.BuyerAddress2 ??= buyer.Address2;
|
||||
buyerInformation.BuyerCity ??= buyer.City;
|
||||
buyerInformation.BuyerCountry ??= buyer.country;
|
||||
buyerInformation.BuyerEmail ??= buyer.email;
|
||||
buyerInformation.BuyerName ??= buyer.Name;
|
||||
buyerInformation.BuyerPhone ??= buyer.phone;
|
||||
buyerInformation.BuyerState ??= buyer.State;
|
||||
buyerInformation.BuyerZip ??= buyer.zip;
|
||||
}
|
||||
private SpeedPolicy ParseSpeedPolicy(string transactionSpeed, SpeedPolicy defaultPolicy)
|
||||
{
|
||||
if (transactionSpeed == null)
|
||||
return defaultPolicy;
|
||||
var mappings = new Dictionary<string, SpeedPolicy>();
|
||||
mappings.Add("low", SpeedPolicy.LowSpeed);
|
||||
mappings.Add("low-medium", SpeedPolicy.LowMediumSpeed);
|
||||
mappings.Add("medium", SpeedPolicy.MediumSpeed);
|
||||
mappings.Add("high", SpeedPolicy.HighSpeed);
|
||||
if (!mappings.TryGetValue(transactionSpeed, out SpeedPolicy policy))
|
||||
policy = defaultPolicy;
|
||||
return policy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -245,7 +245,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
EmbeddedCSS = request.EmbeddedCSS?.Trim(),
|
||||
NotificationUrl = request.NotificationUrl?.Trim(),
|
||||
Tagline = request.Tagline?.Trim(),
|
||||
PerksTemplate = request.PerksTemplate is not null ? AppService.SerializeTemplate(AppService.Parse(request.PerksTemplate.Trim())) : null,
|
||||
PerksTemplate = request.PerksTemplate is not null ? _appService.SerializeTemplate(_appService.Parse(request.PerksTemplate.Trim(), request.TargetCurrency!)) : null,
|
||||
// If Disqus shortname is not null or empty we assume that Disqus should be enabled
|
||||
DisqusEnabled = !string.IsNullOrEmpty(request.DisqusShortname?.Trim()),
|
||||
DisqusShortname = request.DisqusShortname?.Trim(),
|
||||
@ -272,7 +272,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
ShowDiscount = request.ShowDiscount,
|
||||
EnableTips = request.EnableTips,
|
||||
Currency = request.Currency,
|
||||
Template = request.Template != null ? AppService.SerializeTemplate(AppService.Parse(request.Template)) : null,
|
||||
Template = request.Template != null ? _appService.SerializeTemplate(_appService.Parse(request.Template, request.Currency)) : null,
|
||||
ButtonText = request.FixedAmountPayButtonText ?? PointOfSaleSettings.BUTTON_TEXT_DEF,
|
||||
CustomButtonText = request.CustomAmountPayButtonText ?? PointOfSaleSettings.CUSTOM_BUTTON_TEXT_DEF,
|
||||
CustomTipText = request.TipText ?? PointOfSaleSettings.CUSTOM_TIP_TEXT_DEF,
|
||||
@ -331,7 +331,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
Currency = settings.Currency,
|
||||
Items = JsonConvert.DeserializeObject(
|
||||
JsonConvert.SerializeObject(
|
||||
AppService.Parse(settings.Template),
|
||||
_appService.Parse(settings.Template, settings.Currency),
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
|
||||
@ -363,8 +363,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
try
|
||||
{
|
||||
// Just checking if we can serialize
|
||||
AppService.SerializeTemplate(AppService.Parse(request.Template));
|
||||
// Just checking if we can serialize, we don't care about the currency
|
||||
_appService.SerializeTemplate(_appService.Parse(request.Template, "USD"));
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -406,7 +406,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
Tagline = settings.Tagline,
|
||||
Perks = JsonConvert.DeserializeObject(
|
||||
JsonConvert.SerializeObject(
|
||||
AppService.Parse(settings.PerksTemplate),
|
||||
_appService.Parse(settings.PerksTemplate, settings.TargetCurrency),
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
|
||||
@ -453,8 +453,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
|
||||
try
|
||||
{
|
||||
// Just checking if we can serialize
|
||||
AppService.SerializeTemplate(AppService.Parse(request.PerksTemplate));
|
||||
// Just checking if we can serialize, we don't care about the currency
|
||||
_appService.SerializeTemplate(_appService.Parse(request.PerksTemplate, "USD"));
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@ -12,7 +12,6 @@ using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Security.Greenfield;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
@ -184,11 +183,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Amount), "The amount should be 0 or more.");
|
||||
}
|
||||
if (request.Amount > GreenfieldConstants.MaxAmount)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Amount), $"The amount should less than {GreenfieldConstants.MaxAmount}.");
|
||||
}
|
||||
request.Checkout ??= new CreateInvoiceRequest.CheckoutOptions();
|
||||
request.Checkout = request.Checkout ?? new CreateInvoiceRequest.CheckoutOptions();
|
||||
if (request.Checkout.PaymentMethods?.Any() is true)
|
||||
{
|
||||
for (int i = 0; i < request.Checkout.PaymentMethods.Length; i++)
|
||||
@ -231,7 +226,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var invoice = await _invoiceController.CreateInvoiceCoreRaw(request, store,
|
||||
@ -388,15 +383,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
if (invoicePaymentMethod is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.PaymentMethod), "Please select one of the payment methods which were available for the original invoice");
|
||||
this.ModelState.AddModelError(nameof(request.PaymentMethod), "Please select one of the payment methods which were available for the original invoice");
|
||||
}
|
||||
if (request.RefundVariant is null)
|
||||
ModelState.AddModelError(nameof(request.RefundVariant), "`refundVariant` is mandatory");
|
||||
this.ModelState.AddModelError(nameof(request.RefundVariant), "`refundVariant` is mandatory");
|
||||
if (!ModelState.IsValid || invoicePaymentMethod is null || paymentMethodId is null)
|
||||
return this.CreateValidationError(ModelState);
|
||||
|
||||
var accounting = invoicePaymentMethod.Calculate();
|
||||
var cryptoPaid = accounting.Paid;
|
||||
var cryptoPaid = invoicePaymentMethod.Calculate().Paid.ToDecimal(MoneyUnit.BTC);
|
||||
var cdCurrency = _currencyNameTable.GetCurrencyData(invoice.Currency, true);
|
||||
var paidCurrency = Math.Round(cryptoPaid * invoicePaymentMethod.Rate, cdCurrency.Divisibility);
|
||||
var rateResult = await _rateProvider.FetchRate(
|
||||
@ -404,10 +398,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
store.GetStoreBlob().GetRateRules(_networkProvider),
|
||||
cancellationToken
|
||||
);
|
||||
var cryptoCode = invoicePaymentMethod.GetId().CryptoCode;
|
||||
var paymentMethodDivisibility = _currencyNameTable.GetCurrencyData(paymentMethodId.CryptoCode, false)?.Divisibility ?? 8;
|
||||
var paidAmount = cryptoPaid.RoundToSignificant(paymentMethodDivisibility);
|
||||
var createPullPayment = new CreatePullPayment
|
||||
var createPullPayment = new HostedServices.CreatePullPayment()
|
||||
{
|
||||
BOLT11Expiration = store.GetStoreBlob().RefundBOLT11Expiration,
|
||||
Name = request.Name ?? $"Refund {invoice.Id}",
|
||||
@ -419,61 +411,37 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
if (request.RefundVariant != RefundVariant.Custom)
|
||||
{
|
||||
if (request.CustomAmount is not null)
|
||||
ModelState.AddModelError(nameof(request.CustomAmount), "CustomAmount should only be set if the refundVariant is Custom");
|
||||
this.ModelState.AddModelError(nameof(request.CustomAmount), "CustomAmount should only be set if the refundVariant is Custom");
|
||||
if (request.CustomCurrency is not null)
|
||||
ModelState.AddModelError(nameof(request.CustomCurrency), "CustomCurrency should only be set if the refundVariant is Custom");
|
||||
}
|
||||
if (request.SubtractPercentage is < 0 or > 100)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.SubtractPercentage), "Percentage must be a numeric value between 0 and 100");
|
||||
}
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return this.CreateValidationError(ModelState);
|
||||
this.ModelState.AddModelError(nameof(request.CustomCurrency), "CustomCurrency should only be set if the refundVariant is Custom");
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
var appliedDivisibility = paymentMethodDivisibility;
|
||||
switch (request.RefundVariant)
|
||||
{
|
||||
case RefundVariant.RateThen:
|
||||
createPullPayment.Currency = cryptoCode;
|
||||
createPullPayment.Amount = paidAmount;
|
||||
createPullPayment.Currency = invoicePaymentMethod.GetId().CryptoCode;
|
||||
createPullPayment.Amount = cryptoPaid.RoundToSignificant(paymentMethodDivisibility);
|
||||
createPullPayment.AutoApproveClaims = true;
|
||||
break;
|
||||
|
||||
case RefundVariant.CurrentRate:
|
||||
createPullPayment.Currency = cryptoCode;
|
||||
createPullPayment.Amount = Math.Round(paidCurrency / rateResult.BidAsk.Bid, appliedDivisibility);
|
||||
createPullPayment.Currency = invoicePaymentMethod.GetId().CryptoCode;
|
||||
createPullPayment.Amount = Math.Round(paidCurrency / rateResult.BidAsk.Bid, paymentMethodDivisibility);
|
||||
createPullPayment.AutoApproveClaims = true;
|
||||
break;
|
||||
|
||||
case RefundVariant.Fiat:
|
||||
appliedDivisibility = cdCurrency.Divisibility;
|
||||
createPullPayment.Currency = invoice.Currency;
|
||||
createPullPayment.Amount = paidCurrency;
|
||||
createPullPayment.AutoApproveClaims = false;
|
||||
break;
|
||||
|
||||
case RefundVariant.OverpaidAmount:
|
||||
if (invoice.ExceptionStatus != InvoiceExceptionStatus.PaidOver)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.RefundVariant), "Invoice is not overpaid");
|
||||
}
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
var dueAmount = accounting.TotalDue;
|
||||
createPullPayment.Currency = cryptoCode;
|
||||
createPullPayment.Amount = Math.Round(paidAmount - dueAmount, appliedDivisibility);
|
||||
createPullPayment.AutoApproveClaims = true;
|
||||
break;
|
||||
|
||||
case RefundVariant.Custom:
|
||||
if (request.CustomAmount is null || (request.CustomAmount is decimal v && v <= 0))
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.CustomAmount), "Amount must be greater than 0");
|
||||
this.ModelState.AddModelError(nameof(request.CustomAmount), "Amount must be greater than 0");
|
||||
}
|
||||
|
||||
if (
|
||||
@ -504,13 +472,6 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
ModelState.AddModelError(nameof(request.RefundVariant), "Please select a valid refund option");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
// reduce by percentage
|
||||
if (request.SubtractPercentage is > 0 and <= 100)
|
||||
{
|
||||
var reduceByAmount = createPullPayment.Amount * (request.SubtractPercentage / 100);
|
||||
createPullPayment.Amount = Math.Round(createPullPayment.Amount - reduceByAmount, appliedDivisibility);
|
||||
}
|
||||
|
||||
var ppId = await _pullPaymentService.CreatePullPayment(createPullPayment);
|
||||
|
||||
@ -580,13 +541,13 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
CryptoCode = method.GetId().CryptoCode,
|
||||
Destination = details.GetPaymentDestination(),
|
||||
Rate = method.Rate,
|
||||
Due = accounting.DueUncapped,
|
||||
TotalPaid = accounting.Paid,
|
||||
PaymentMethodPaid = accounting.CryptoPaid,
|
||||
Amount = accounting.TotalDue,
|
||||
NetworkFee = accounting.NetworkFee,
|
||||
Due = accounting.DueUncapped.ToDecimal(MoneyUnit.BTC),
|
||||
TotalPaid = accounting.Paid.ToDecimal(MoneyUnit.BTC),
|
||||
PaymentMethodPaid = accounting.CryptoPaid.ToDecimal(MoneyUnit.BTC),
|
||||
Amount = accounting.TotalDue.ToDecimal(MoneyUnit.BTC),
|
||||
NetworkFee = accounting.NetworkFee.ToDecimal(MoneyUnit.BTC),
|
||||
PaymentLink =
|
||||
method.GetId().PaymentType.GetPaymentLink(method.Network, entity, details, accounting.Due,
|
||||
method.GetId().PaymentType.GetPaymentLink(method.Network, details, accounting.Due,
|
||||
Request.GetAbsoluteRoot()),
|
||||
Payments = payments.Select(paymentEntity => ToPaymentModel(entity, paymentEntity)).ToList(),
|
||||
AdditionalData = details.GetAdditionalData()
|
||||
|
@ -133,7 +133,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
"A valid node info was not provided to open a channel with");
|
||||
}
|
||||
|
||||
if (request?.ChannelAmount is null)
|
||||
if (request.ChannelAmount == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.ChannelAmount), "ChannelAmount is missing");
|
||||
}
|
||||
@ -142,7 +142,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
ModelState.AddModelError(nameof(request.ChannelAmount), "ChannelAmount must be more than 0");
|
||||
}
|
||||
|
||||
if (request?.FeeRate is null)
|
||||
if (request.FeeRate == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.FeeRate), "FeeRate is missing");
|
||||
}
|
||||
|
@ -124,8 +124,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
|
||||
try
|
||||
{
|
||||
var prData = await _paymentRequestRepository.FindPaymentRequest(pr.Id, null);
|
||||
var invoice = await _invoiceController.CreatePaymentRequestInvoice(prData, amount, pr.AmountDue, this.StoreData, Request, cancellationToken);
|
||||
var invoice = await _invoiceController.CreatePaymentRequestInvoice(pr, amount, this.StoreData, Request, cancellationToken);
|
||||
return Ok(GreenfieldInvoiceController.ToModel(invoice, _linkGenerator, Request));
|
||||
}
|
||||
catch (BitpayHttpException e)
|
||||
@ -255,9 +254,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
Email = blob.Email,
|
||||
AllowCustomPaymentAmounts = blob.AllowCustomPaymentAmounts,
|
||||
EmbeddedCSS = blob.EmbeddedCSS,
|
||||
CustomCSSLink = blob.CustomCSSLink,
|
||||
FormResponse = blob.FormResponse,
|
||||
FormId = blob.FormId
|
||||
CustomCSSLink = blob.CustomCSSLink
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,6 @@ using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using MarkPayoutRequest = BTCPayServer.HostedServices.MarkPayoutRequest;
|
||||
|
||||
namespace BTCPayServer.Controllers.Greenfield
|
||||
@ -256,15 +255,16 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return PullPaymentNotFound();
|
||||
|
||||
var blob = pp.GetBlob();
|
||||
if (_pullPaymentService.SupportsLNURL(blob))
|
||||
var pms = blob.SupportedPaymentMethods.FirstOrDefault(id => id.PaymentType == LightningPaymentType.Instance && _networkProvider.DefaultNetwork.CryptoCode == id.CryptoCode);
|
||||
if (pms is not null && blob.Currency.Equals(pms.CryptoCode, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var lnurlEndpoint = new Uri(Url.Action("GetLNURLForPullPayment", "UILNURL", new
|
||||
{
|
||||
cryptoCode = _networkProvider.DefaultNetwork.CryptoCode,
|
||||
pullPaymentId
|
||||
pullPaymentId = pullPaymentId
|
||||
}, Request.Scheme, Request.Host.ToString())!);
|
||||
|
||||
return base.Ok(new PullPaymentLNURL
|
||||
return base.Ok(new PullPaymentLNURL()
|
||||
{
|
||||
LNURLBech32 = LNURL.LNURL.EncodeUri(lnurlEndpoint, "withdrawRequest", true).ToString(),
|
||||
LNURLUri = LNURL.LNURL.EncodeUri(lnurlEndpoint, "withdrawRequest", false).ToString()
|
||||
@ -285,8 +285,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
Amount = blob.Amount,
|
||||
PaymentMethodAmount = blob.CryptoAmount,
|
||||
Revision = blob.Revision,
|
||||
State = p.State,
|
||||
Metadata = blob.Metadata?? new JObject(),
|
||||
State = p.State
|
||||
};
|
||||
model.Destination = blob.Destination;
|
||||
model.PaymentMethod = p.PaymentMethodId;
|
||||
@ -323,20 +322,27 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
ModelState.AddModelError(nameof(request.Destination), destination.error ?? "The destination is invalid for the payment specified");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
var amtError = ClaimRequest.IsPayoutAmountOk(destination.destination, request.Amount, paymentMethodId.CryptoCode, ppBlob.Currency);
|
||||
if (amtError.error is not null)
|
||||
|
||||
if (request.Amount is null && destination.destination.Amount != null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Amount), amtError.error );
|
||||
request.Amount = destination.destination.Amount;
|
||||
}
|
||||
else if (request.Amount != null && destination.destination.Amount != null && request.Amount != destination.destination.Amount)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Amount), $"Amount is implied in destination ({destination.destination.Amount}) that does not match the payout amount provided {request.Amount})");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
if (request.Amount is { } v && (v < ppBlob.MinimumClaim || v == 0.0m))
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Amount), $"Amount too small (should be at least {ppBlob.MinimumClaim})");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
request.Amount = amtError.amount;
|
||||
var result = await _pullPaymentService.Claim(new ClaimRequest()
|
||||
{
|
||||
Destination = destination.destination,
|
||||
PullPaymentId = pullPaymentId,
|
||||
Value = request.Amount,
|
||||
PaymentMethodId = paymentMethodId
|
||||
PaymentMethodId = paymentMethodId,
|
||||
});
|
||||
|
||||
return HandleClaimResult(result);
|
||||
@ -346,7 +352,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
[Authorize(Policy = Policies.CanCreateNonApprovedPullPayments, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> CreatePayoutThroughStore(string storeId, CreatePayoutThroughStoreRequest request)
|
||||
{
|
||||
if (request?.Approved is true)
|
||||
if (request.Approved is true)
|
||||
{
|
||||
if (!(await _authorizationService.AuthorizeAsync(User, null,
|
||||
new PolicyRequirement(Policies.CanCreatePullPayments))).Succeeded)
|
||||
@ -388,13 +394,15 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
var amtError = ClaimRequest.IsPayoutAmountOk(destination.destination, request.Amount);
|
||||
if (amtError.error is not null)
|
||||
if (request.Amount is null && destination.destination.Amount != null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Amount), amtError.error );
|
||||
request.Amount = destination.destination.Amount;
|
||||
}
|
||||
else if (request.Amount != null && destination.destination.Amount != null && request.Amount != destination.destination.Amount)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Amount), $"Amount is implied in destination ({destination.destination.Amount}) that does not match the payout amount provided {request.Amount})");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
request.Amount = amtError.amount;
|
||||
if (request.Amount is { } v && (v < ppBlob?.MinimumClaim || v == 0.0m))
|
||||
{
|
||||
var minimumClaim = ppBlob?.MinimumClaim is decimal val ? val : 0.0m;
|
||||
@ -408,8 +416,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
PreApprove = request.Approved,
|
||||
Value = request.Amount,
|
||||
PaymentMethodId = paymentMethodId,
|
||||
StoreId = storeId,
|
||||
Metadata = request.Metadata
|
||||
StoreId = storeId
|
||||
});
|
||||
return HandleClaimResult(result);
|
||||
}
|
||||
|
@ -1,80 +0,0 @@
|
||||
#nullable enable
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore.Internal;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Dapper;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Services;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace BTCPayServer.Controllers.GreenField;
|
||||
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[EnableCors(CorsPolicies.All)]
|
||||
public class GreenfieldReportsController : Controller
|
||||
{
|
||||
public GreenfieldReportsController(
|
||||
ApplicationDbContextFactory dbContextFactory,
|
||||
ReportService reportService)
|
||||
{
|
||||
DBContextFactory = dbContextFactory;
|
||||
ReportService = reportService;
|
||||
}
|
||||
public ApplicationDbContextFactory DBContextFactory { get; }
|
||||
public ReportService ReportService { get; }
|
||||
|
||||
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/stores/{storeId}/reports")]
|
||||
[NonAction] // Disabling this endpoint as we still need to figure out the request/response model
|
||||
public async Task<IActionResult> StoreReports(string storeId, [FromBody] StoreReportRequest? vm = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
vm ??= new StoreReportRequest();
|
||||
vm.ViewName ??= "Payments";
|
||||
vm.TimePeriod ??= new TimePeriod();
|
||||
vm.TimePeriod.To ??= DateTime.UtcNow;
|
||||
vm.TimePeriod.From ??= vm.TimePeriod.To.Value.AddMonths(-1);
|
||||
var from = vm.TimePeriod.From.Value;
|
||||
var to = vm.TimePeriod.To.Value;
|
||||
|
||||
if (ReportService.ReportProviders.TryGetValue(vm.ViewName, out var report))
|
||||
{
|
||||
if (!report.IsAvailable())
|
||||
return this.CreateAPIError(503, "view-unavailable", $"This view is unavailable at this moment");
|
||||
|
||||
var ctx = new Services.Reporting.QueryContext(storeId, from, to);
|
||||
await report.Query(ctx, cancellationToken);
|
||||
var result = new StoreReportResponse()
|
||||
{
|
||||
Fields = ctx.ViewDefinition?.Fields ?? new List<StoreReportResponse.Field>(),
|
||||
Charts = ctx.ViewDefinition?.Charts ?? new List<ChartDefinition>(),
|
||||
Data = ctx.Data.Select(d => new JArray(d)).ToList(),
|
||||
From = from,
|
||||
To = to
|
||||
};
|
||||
return Json(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.ViewName), "View doesn't exist");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,42 +0,0 @@
|
||||
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.Services.Stores;
|
||||
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 GreenfieldServerRolesController : ControllerBase
|
||||
{
|
||||
private readonly StoreRepository _storeRepository;
|
||||
|
||||
public GreenfieldServerRolesController(StoreRepository storeRepository)
|
||||
{
|
||||
_storeRepository = storeRepository;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/server/roles")]
|
||||
public async Task<IActionResult> GetServerRoles()
|
||||
{
|
||||
return Ok(FromModel(await _storeRepository.GetStoreRoles(null, false, false)));
|
||||
}
|
||||
private List<RoleData> FromModel(StoreRepository.StoreRole[] data)
|
||||
{
|
||||
return data.Select(r => new RoleData() {Role = r.Role, Id = r.Id, Permissions = r.Permissions, IsServerRole = true}).ToList();
|
||||
}
|
||||
|
||||
private IActionResult StoreNotFound()
|
||||
{
|
||||
return this.CreateAPIError(404, "store-not-found", "The store was not found");
|
||||
}
|
||||
}
|
19
BTCPayServer/Controllers/GreenField/GreenfieldStoreAutomatedLightningPayoutProcessorsController.cs
19
BTCPayServer/Controllers/GreenField/GreenfieldStoreAutomatedLightningPayoutProcessorsController.cs
@ -2,7 +2,6 @@
|
||||
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;
|
||||
@ -53,23 +52,16 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
|
||||
private static LightningAutomatedPayoutSettings ToModel(PayoutProcessorData data)
|
||||
{
|
||||
var blob = data.HasTypedBlob<LightningAutomatedPayoutBlob>().GetBlob();
|
||||
return new LightningAutomatedPayoutSettings()
|
||||
{
|
||||
PaymentMethod = data.PaymentMethod,
|
||||
IntervalSeconds = blob.Interval,
|
||||
CancelPayoutAfterFailures = blob.CancelPayoutAfterFailures,
|
||||
ProcessNewPayoutsInstantly = blob.ProcessNewPayoutsInstantly
|
||||
IntervalSeconds = data.HasTypedBlob<AutomatedPayoutBlob>().GetBlob()!.Interval
|
||||
};
|
||||
}
|
||||
|
||||
private static LightningAutomatedPayoutBlob FromModel(LightningAutomatedPayoutSettings data)
|
||||
private static AutomatedPayoutBlob FromModel(LightningAutomatedPayoutSettings data)
|
||||
{
|
||||
return new LightningAutomatedPayoutBlob() {
|
||||
Interval = data.IntervalSeconds,
|
||||
CancelPayoutAfterFailures = data.CancelPayoutAfterFailures,
|
||||
ProcessNewPayoutsInstantly = data.ProcessNewPayoutsInstantly
|
||||
};
|
||||
return new AutomatedPayoutBlob() { Interval = data.IntervalSeconds };
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
@ -77,9 +69,6 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
public async Task<IActionResult> UpdateStoreLightningAutomatedPayoutProcessor(
|
||||
string storeId, string paymentMethod, LightningAutomatedPayoutSettings request)
|
||||
{
|
||||
AutomatedPayoutConstants.ValidateInterval(ModelState, request.IntervalSeconds, nameof(request.IntervalSeconds));
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
paymentMethod = PaymentMethodId.Parse(paymentMethod).ToString();
|
||||
var activeProcessor =
|
||||
(await _payoutProcessorService.GetProcessors(
|
||||
@ -91,7 +80,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}))
|
||||
.FirstOrDefault();
|
||||
activeProcessor ??= new PayoutProcessorData();
|
||||
activeProcessor.HasTypedBlob<LightningAutomatedPayoutBlob>().SetBlob(FromModel(request));
|
||||
activeProcessor.HasTypedBlob<AutomatedPayoutBlob>().SetBlob(FromModel(request));
|
||||
activeProcessor.StoreId = storeId;
|
||||
activeProcessor.PaymentMethod = paymentMethod;
|
||||
activeProcessor.Processor = LightningAutomatedPayoutSenderFactory.ProcessorName;
|
||||
|
@ -1,9 +1,7 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
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;
|
||||
@ -59,9 +57,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
FeeBlockTarget = blob.FeeTargetBlock,
|
||||
PaymentMethod = data.PaymentMethod,
|
||||
IntervalSeconds = blob.Interval,
|
||||
Threshold = blob.Threshold,
|
||||
ProcessNewPayoutsInstantly = blob.ProcessNewPayoutsInstantly
|
||||
IntervalSeconds = blob.Interval
|
||||
};
|
||||
}
|
||||
|
||||
@ -70,9 +66,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return new OnChainAutomatedPayoutBlob()
|
||||
{
|
||||
FeeTargetBlock = data.FeeBlockTarget ?? 1,
|
||||
Interval = data.IntervalSeconds,
|
||||
Threshold = data.Threshold,
|
||||
ProcessNewPayoutsInstantly = data.ProcessNewPayoutsInstantly
|
||||
Interval = data.IntervalSeconds
|
||||
};
|
||||
}
|
||||
|
||||
@ -81,11 +75,6 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
public async Task<IActionResult> UpdateStoreOnchainAutomatedPayoutProcessor(
|
||||
string storeId, string paymentMethod, OnChainAutomatedPayoutSettings request)
|
||||
{
|
||||
AutomatedPayoutConstants.ValidateInterval(ModelState, request.IntervalSeconds, nameof(request.IntervalSeconds));
|
||||
if (request.FeeBlockTarget is int t && (t < 1 || t > 1000))
|
||||
ModelState.AddModelError(nameof(request.FeeBlockTarget), "The feeBlockTarget should be between 1 and 1000");
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
paymentMethod = PaymentMethodId.Parse(paymentMethod).ToString();
|
||||
var activeProcessor =
|
||||
(await _payoutProcessorService.GetProcessors(
|
||||
|
@ -54,7 +54,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
new LNURLPayPaymentMethodData(
|
||||
paymentMethod.PaymentId.CryptoCode,
|
||||
!excludedPaymentMethods.Match(paymentMethod.PaymentId),
|
||||
paymentMethod.UseBech32Scheme
|
||||
paymentMethod.UseBech32Scheme, paymentMethod.EnableForStandardInvoices
|
||||
)
|
||||
)
|
||||
.Where((result) => enabled is null || enabled == result.Enabled)
|
||||
@ -124,7 +124,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
LNURLPaySupportedPaymentMethod? paymentMethod = new LNURLPaySupportedPaymentMethod()
|
||||
{
|
||||
CryptoCode = cryptoCode,
|
||||
UseBech32Scheme = paymentMethodData.UseBech32Scheme
|
||||
UseBech32Scheme = paymentMethodData.UseBech32Scheme,
|
||||
EnableForStandardInvoices = paymentMethodData.EnableForStandardInvoices
|
||||
};
|
||||
|
||||
var store = Store;
|
||||
@ -153,7 +154,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
: new LNURLPayPaymentMethodData(
|
||||
paymentMethod.PaymentId.CryptoCode,
|
||||
!excluded,
|
||||
paymentMethod.UseBech32Scheme
|
||||
paymentMethod.UseBech32Scheme, paymentMethod.EnableForStandardInvoices
|
||||
);
|
||||
}
|
||||
private void AssertCryptoCodeWallet(string cryptoCode, out BTCPayNetwork network)
|
||||
|
@ -66,7 +66,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
paymentMethod.GetExternalLightningUrl()?.ToString() ??
|
||||
paymentMethod.GetDisplayableConnectionString(),
|
||||
!excludedPaymentMethods.Match(paymentMethod.PaymentId),
|
||||
paymentMethod.PaymentId.ToStringNormalized()
|
||||
paymentMethod.PaymentId.ToStringNormalized(),
|
||||
paymentMethod.DisableBOLT11PaymentOption
|
||||
)
|
||||
)
|
||||
.Where((result) => enabled is null || enabled == result.Enabled)
|
||||
@ -206,7 +207,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
? null
|
||||
: new LightningNetworkPaymentMethodData(paymentMethod.PaymentId.CryptoCode,
|
||||
paymentMethod.GetDisplayableConnectionString(), !excluded,
|
||||
paymentMethod.PaymentId.ToStringNormalized());
|
||||
paymentMethod.PaymentId.ToStringNormalized(), paymentMethod.DisableBOLT11PaymentOption);
|
||||
}
|
||||
|
||||
private BTCPayNetwork AssertSupportLightning(string cryptoCode)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user