Compare commits
77 Commits
v1.0.5.7
...
extensions
Author | SHA1 | Date | |
---|---|---|---|
85a3dc4848 | |||
493b10393b | |||
b406f52670 | |||
ef3f314754 | |||
d2910686cd | |||
793b1b56d9 | |||
d8f9075e2a | |||
1199d4ead9 | |||
4520e1bb3e | |||
afece8193e | |||
3b14585d1a | |||
9cd32a908f | |||
b9ca02088d | |||
440ce0221a | |||
caff7eda9f | |||
aef1cefc18 | |||
1385c7cc9b | |||
8d0260b644 | |||
5d9827fb60 | |||
2083954aa5 | |||
66af258876 | |||
4ba04031ef | |||
a30456a92d | |||
c8dd13577e | |||
fac35b46bb | |||
748cb778e0 | |||
bbcca24bcc | |||
b8c52b1120 | |||
b1b3ce48ee | |||
da864f4c6c | |||
758f627e12 | |||
20322c6ab8 | |||
7a711f0690 | |||
067b977ec8 | |||
58f0ca3d8a | |||
4176f3659b | |||
5979fe5eef | |||
362ba21567 | |||
71894ba245 | |||
2b19e0fbc6 | |||
4d0b402e8b | |||
933e0c30bf | |||
b430afe3e1 | |||
2f25f1790e | |||
d90fcf15bd | |||
be9cc41957 | |||
ef99eeb300 | |||
1646241dfb | |||
543e628a8b | |||
36269cbed6 | |||
75dcdc72d2 | |||
f5c90bebdc | |||
15af66de55 | |||
17c8ac8248 | |||
e5c6b9a979 | |||
753849cedd | |||
b81e4c01ec | |||
2a32b05df1 | |||
e3a0fe88c1 | |||
ee3aa49eee | |||
dd27ad79bd | |||
cfd0f556a5 | |||
347e70f4ea | |||
828aae35ce | |||
2f56783b7e | |||
dc4ecdaa38 | |||
1440e8c55d | |||
51a072808f | |||
0a8c2926ea | |||
5e79f567a7 | |||
e2eb26eb93 | |||
740a50a26d | |||
be3f248a5a | |||
f2870caed2 | |||
ad22d3fd91 | |||
9dcd8b6edf | |||
9607e0e55d |
8
.github/ISSUE_TEMPLATE/bug-report.md
vendored
8
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@ -24,15 +24,15 @@ A clear and concise description of what you expected to happen.
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Your BTCPay Environment (please complete the following information):**
|
||||
- BTCPay Server Version [available in the right bottom corner of footer]
|
||||
- Deployment Method: [e.g. Docker, Manual, Third-Party-hoist]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- BTCPay Server Version: [available in the right bottom corner of footer]
|
||||
- Deployment Method: [e.g. Docker, Manual, Third-Party-host]
|
||||
- Browser: [e.g. Chrome, Safari]
|
||||
|
||||
**Logs (if applicable)**
|
||||
Basic logs can be found in Server Settings > Logs. More logs https://docs.btcpayserver.org/Troubleshooting/#2-looking-through-the-logs
|
||||
|
||||
**Setup Parameters**
|
||||
If you're reporting a deployment issue run `. btcpay-setup.sh -i` and paste your the parameters by obscuring private information.
|
||||
If you're reporting a deployment issue run `. btcpay-setup.sh -i` and paste the setup parameters here with your private information removed or obscured.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -298,3 +298,4 @@ BTCPayServer/wwwroot/bundles/*
|
||||
!.vscode/extensions.json
|
||||
BTCPayServer/testpwd
|
||||
.DS_Store
|
||||
Packed Plugins
|
||||
|
6
.run/Build and pack extensions.run.xml
Normal file
6
.run/Build and pack extensions.run.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Build and pack plugins" type="CompoundRunConfigurationType">
|
||||
<toRun name="Pack Test Plugin" type="DotNetProject" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
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\BTCPayServer.Plugins.Test\BTCPayServer.Plugins.Test.csproj" />
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
33
BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj
Normal file
33
BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj
Normal file
@ -0,0 +1,33 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Company>BTCPay Server</Company>
|
||||
<Copyright>Copyright © BTCPay Server 2020</Copyright>
|
||||
<Description>A library for BTCPay Server plugin development</Description>
|
||||
<PackageIcon>icon.png</PackageIcon>
|
||||
<PackageTags>btcpay,btcpayserver</PackageTags>
|
||||
<PackageProjectUrl>https://github.com/btcpayserver/btcpayserver/tree/master/BTCPayServer.Abstractions</PackageProjectUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<RepositoryUrl>https://github.com/btcpayserver/btcpayserver</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<DebugType>portable</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>1591;1573;1572;1584;1570;3021</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="icon.png" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
21
BTCPayServer.Abstractions/Contracts/IBTCPayServerPlugin.cs
Normal file
21
BTCPayServer.Abstractions/Contracts/IBTCPayServerPlugin.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using BTCPayServer.Abstractions.Converters;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BTCPayServer.Contracts
|
||||
{
|
||||
public interface IBTCPayServerPlugin
|
||||
{
|
||||
public string Identifier { get; }
|
||||
string Name { get; }
|
||||
[JsonConverter(typeof(VersionConverter))]
|
||||
Version Version { get; }
|
||||
string Description { get; }
|
||||
bool SystemPlugin { get; set; }
|
||||
string[] Dependencies { get; }
|
||||
void Execute(IApplicationBuilder applicationBuilder, IServiceProvider applicationBuilderApplicationServices);
|
||||
void Execute(IServiceCollection applicationBuilder);
|
||||
}
|
||||
}
|
27
BTCPayServer.Abstractions/Contracts/INotificationHandler.cs
Normal file
27
BTCPayServer.Abstractions/Contracts/INotificationHandler.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer.Contracts
|
||||
{
|
||||
public abstract class BaseNotification
|
||||
{
|
||||
public abstract string Identifier { get; }
|
||||
public abstract string NotificationType { get; }
|
||||
}
|
||||
|
||||
public interface INotificationHandler
|
||||
{
|
||||
string NotificationType { get; }
|
||||
Type NotificationBlobType { get; }
|
||||
public (string identifier, string name)[] Meta { get; }
|
||||
void FillViewModel(object notification, NotificationViewModel vm);
|
||||
}
|
||||
|
||||
public class NotificationViewModel
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
public string Body { get; set; }
|
||||
public string ActionLink { get; set; }
|
||||
public bool Seen { get; set; }
|
||||
}
|
||||
}
|
12
BTCPayServer.Abstractions/Contracts/ISettingsRepository.cs
Normal file
12
BTCPayServer.Abstractions/Contracts/ISettingsRepository.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Services
|
||||
{
|
||||
public interface ISettingsRepository
|
||||
{
|
||||
Task<T> GetSettingAsync<T>(string name = null);
|
||||
Task UpdateSetting<T>(T obj, string name = null);
|
||||
Task<T> WaitSettingsChanged<T>(CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
21
BTCPayServer.Abstractions/Contracts/IStoreNavExtension.cs
Normal file
21
BTCPayServer.Abstractions/Contracts/IStoreNavExtension.cs
Normal file
@ -0,0 +1,21 @@
|
||||
namespace BTCPayServer.Contracts
|
||||
{
|
||||
public interface IUIExtension
|
||||
{
|
||||
string Partial { get; }
|
||||
|
||||
string Location { get; }
|
||||
}
|
||||
|
||||
public class UIExtension: IUIExtension
|
||||
{
|
||||
public UIExtension(string partial, string location)
|
||||
{
|
||||
Partial = partial;
|
||||
Location = location;
|
||||
}
|
||||
|
||||
public string Partial { get; }
|
||||
public string Location { get; }
|
||||
}
|
||||
}
|
@ -6,5 +6,4 @@ namespace BTCPayServer.Contracts
|
||||
|
||||
string Partial { get; }
|
||||
}
|
||||
|
||||
}
|
19
BTCPayServer.Abstractions/Converters/VersionConverter.cs
Normal file
19
BTCPayServer.Abstractions/Converters/VersionConverter.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Converters
|
||||
{
|
||||
public class VersionConverter : JsonConverter<Version>
|
||||
{
|
||||
public override Version Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return Version.Parse(reader.GetString());
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
20
BTCPayServer.Abstractions/Extensions/Extensions.cs
Normal file
20
BTCPayServer.Abstractions/Extensions/Extensions.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System.Text.Json;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public static class SetStatusMessageModelExtensions
|
||||
{
|
||||
public static void SetStatusMessageModel(this ITempDataDictionary tempData, StatusMessageModel statusMessage)
|
||||
{
|
||||
if (statusMessage == null)
|
||||
{
|
||||
tempData.Remove("StatusMessageModel");
|
||||
return;
|
||||
}
|
||||
|
||||
tempData["StatusMessageModel"] = JsonSerializer.Serialize(statusMessage, new JsonSerializerOptions());
|
||||
}
|
||||
}
|
||||
}
|
36
BTCPayServer.Abstractions/Models/BaseIbtcPayServerPlugin.cs
Normal file
36
BTCPayServer.Abstractions/Models/BaseIbtcPayServerPlugin.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using BTCPayServer.Contracts;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BTCPayServer.Models
|
||||
{
|
||||
public abstract class BaseBTCPayServerPlugin : IBTCPayServerPlugin
|
||||
{
|
||||
public abstract string Identifier { get; }
|
||||
public abstract string Name { get; }
|
||||
|
||||
public virtual Version Version
|
||||
{
|
||||
get
|
||||
{
|
||||
return Assembly.GetAssembly(GetType())?.GetName().Version ?? new Version(1, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract string Description { get; }
|
||||
public bool SystemPlugin { get; set; }
|
||||
public bool SystemExtension { get; set; }
|
||||
public virtual string[] Dependencies { get; } = Array.Empty<string>();
|
||||
|
||||
public virtual void Execute(IApplicationBuilder applicationBuilder,
|
||||
IServiceProvider applicationBuilderApplicationServices)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Execute(IServiceCollection applicationBuilder)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
7
BTCPayServer.Abstractions/PushNuget.ps1
Normal file
7
BTCPayServer.Abstractions/PushNuget.ps1
Normal file
@ -0,0 +1,7 @@
|
||||
rm "bin\release\" -Recurse -Force
|
||||
dotnet pack --configuration Release --include-symbols -p:SymbolPackageFormat=snupkg
|
||||
$package=(ls .\bin\Release\*.nupkg).FullName
|
||||
dotnet nuget push $package --source "https://api.nuget.org/v3/index.json"
|
||||
$ver = ((ls .\bin\release\*.nupkg)[0].Name -replace '.*(\d+\.\d+\.\d+)\.nupkg','$1')
|
||||
git tag -a "BTCPayServer.Abstractions/v$ver" -m "BTCPayServer.Abstractions/$ver"
|
||||
git push origin "BTCPayServer.Abstractions/v$ver"
|
BIN
BTCPayServer.Abstractions/icon.png
Normal file
BIN
BTCPayServer.Abstractions/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
@ -27,7 +27,7 @@
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NBitcoin" Version="5.0.51" />
|
||||
<PackageReference Include="NBitcoin" Version="5.0.60" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
|
@ -24,6 +24,8 @@ namespace BTCPayServer
|
||||
var settings = new BTCPayDefaultSettings();
|
||||
_Settings.Add(chainType, settings);
|
||||
settings.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", NBXplorerDefaultSettings.GetFolderName(chainType));
|
||||
settings.DefaultPluginDirectory =
|
||||
StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", "Plugins");
|
||||
settings.DefaultConfigurationFile = Path.Combine(settings.DefaultDataDirectory, "settings.config");
|
||||
settings.DefaultPort = (chainType == NetworkType.Mainnet ? 23000 :
|
||||
chainType == NetworkType.Regtest ? 23002 :
|
||||
@ -39,6 +41,7 @@ namespace BTCPayServer
|
||||
}
|
||||
|
||||
public string DefaultDataDirectory { get; set; }
|
||||
public string DefaultPluginDirectory { get; set; }
|
||||
public string DefaultConfigurationFile { get; set; }
|
||||
public int DefaultPort { get; set; }
|
||||
}
|
||||
@ -127,9 +130,25 @@ namespace BTCPayServer
|
||||
|
||||
public abstract class BTCPayNetworkBase
|
||||
{
|
||||
private string _blockExplorerLink;
|
||||
public bool ShowSyncSummary { get; set; } = true;
|
||||
public string CryptoCode { get; internal set; }
|
||||
public string BlockExplorerLink { get; internal set; }
|
||||
|
||||
public string BlockExplorerLink
|
||||
{
|
||||
get => _blockExplorerLink;
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(BlockExplorerLinkDefault))
|
||||
{
|
||||
BlockExplorerLinkDefault = value;
|
||||
}
|
||||
|
||||
_blockExplorerLink = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string BlockExplorerLinkDefault { get; internal set; }
|
||||
public string DisplayName { get; set; }
|
||||
public int Divisibility { get; set; } = 8;
|
||||
[Obsolete("Should not be needed")]
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="3.0.18" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="3.0.19" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Altcoins)' != 'true'">
|
||||
<Compile Remove="Altcoins\**\*.cs"></Compile>
|
||||
|
@ -28,5 +28,6 @@ namespace BTCPayServer.Data
|
||||
public List<U2FDevice> U2FDevices { get; set; }
|
||||
public List<APIKeyData> APIKeys { get; set; }
|
||||
public DateTimeOffset? Created { get; set; }
|
||||
public string DisabledNotifications { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,25 @@ namespace BTCPayServer.Data
|
||||
return request.Where(p => false);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetStateString(this PayoutState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case PayoutState.AwaitingApproval:
|
||||
return "Awaiting Approval";
|
||||
case PayoutState.AwaitingPayment:
|
||||
return "Awaiting Payment";
|
||||
case PayoutState.InProgress:
|
||||
return "In Progress";
|
||||
case PayoutState.Completed:
|
||||
return "Completed";
|
||||
case PayoutState.Cancelled:
|
||||
return "Cancelled";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(state), state, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
public class PullPaymentData
|
||||
{
|
||||
|
@ -0,0 +1,26 @@
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20201015151438_AddDisabledNotificationsToUser")]
|
||||
public partial class AddDisabledNotificationsToUser : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "DisabledNotifications",
|
||||
table: "AspNetUsers",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DisabledNotifications",
|
||||
table: "AspNetUsers");
|
||||
}
|
||||
}
|
||||
}
|
@ -111,6 +111,9 @@ namespace BTCPayServer.Migrations
|
||||
b.Property<DateTimeOffset?>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DisabledNotifications")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(256);
|
||||
|
32
BTCPayServer.PluginPacker/BTCPayServer.PluginPacker.csproj
Normal file
32
BTCPayServer.PluginPacker/BTCPayServer.PluginPacker.csproj
Normal file
@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<Version>1.0.0.0</Version>
|
||||
<PackAsTool>true</PackAsTool>
|
||||
<ToolCommandName>btcpay-plugin</ToolCommandName>
|
||||
<Company>BTCPay Server</Company>
|
||||
<Copyright>Copyright © BTCPay Server 2020</Copyright>
|
||||
<Description>A dotnet tool for packaging BTCPay Server plugins</Description>
|
||||
<PackageIcon>icon.png</PackageIcon>
|
||||
<PackageTags>btcpay,btcpayserver</PackageTags>
|
||||
<PackageProjectUrl>https://github.com/btcpayserver/btcpayserver/tree/master/BTCPayServer.PluginPacker</PackageProjectUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<RepositoryUrl>https://github.com/btcpayserver/btcpayserver</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<DebugType>portable</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>1591;1573;1572;1584;1570;3021</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
|
||||
<None Include="icon.png" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
55
BTCPayServer.PluginPacker/Program.cs
Normal file
55
BTCPayServer.PluginPacker/Program.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using BTCPayServer.Contracts;
|
||||
|
||||
namespace BTCPayServer.PluginPacker
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
if (args.Length < 3)
|
||||
{
|
||||
Console.WriteLine("Usage: btcpay-plugin [directory of compiled plugin] [name of plugin] [packed plugin output directory]");
|
||||
return;
|
||||
}
|
||||
var directory = args[0];
|
||||
var name = args[1];
|
||||
var outputDir = args[2];
|
||||
var outputFile = Path.Combine(outputDir, name);
|
||||
var rootDLLPath = Path.Combine(directory, name +".dll");
|
||||
if (!File.Exists(rootDLLPath) )
|
||||
{
|
||||
throw new Exception($"{rootDLLPath} could not be found");
|
||||
}
|
||||
|
||||
var assembly = Assembly.LoadFrom(rootDLLPath);
|
||||
var extension = GetAllExtensionTypesFromAssembly(assembly).FirstOrDefault();
|
||||
if (extension is null)
|
||||
{
|
||||
throw new Exception($"{rootDLLPath} is not a valid plugin");
|
||||
}
|
||||
|
||||
var loadedPlugin = (IBTCPayServerPlugin)Activator.CreateInstance(extension);
|
||||
var json = JsonSerializer.Serialize(loadedPlugin);
|
||||
Directory.CreateDirectory(outputDir);
|
||||
if (File.Exists(outputFile + ".btcpay"))
|
||||
{
|
||||
File.Delete(outputFile + ".btcpay");
|
||||
}
|
||||
ZipFile.CreateFromDirectory(directory, outputFile + ".btcpay", CompressionLevel.Optimal, false);
|
||||
File.WriteAllText(outputFile + ".btcpay.json", json);
|
||||
}
|
||||
|
||||
private static Type[] GetAllExtensionTypesFromAssembly(Assembly assembly)
|
||||
{
|
||||
return assembly.GetTypes().Where(type =>
|
||||
typeof(IBTCPayServerPlugin).IsAssignableFrom(type) &&
|
||||
!type.IsAbstract).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
7
BTCPayServer.PluginPacker/PushNuget.ps1
Normal file
7
BTCPayServer.PluginPacker/PushNuget.ps1
Normal file
@ -0,0 +1,7 @@
|
||||
rm "bin\release\" -Recurse -Force
|
||||
dotnet pack --configuration Release --include-symbols -p:SymbolPackageFormat=snupkg
|
||||
$package=(ls .\bin\Release\*.nupkg).FullName
|
||||
dotnet nuget push $package --source "https://api.nuget.org/v3/index.json"
|
||||
$ver = ((ls .\bin\release\*.nupkg)[0].Name -replace '.*(\d+\.\d+\.\d+)\.nupkg','$1')
|
||||
git tag -a "BTCPayServer.PluginPacker/v$ver" -m "BTCPayServer.PluginPacker/$ver"
|
||||
git push origin "BTCPayServer.PluginPacker/v$ver"
|
4
BTCPayServer.PluginPacker/README.md
Normal file
4
BTCPayServer.PluginPacker/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
This tool makes it easy to create a BTCPay plugin package. To create a package you must:
|
||||
* Build your BTCPay Plugin project
|
||||
* open a terminal in this project
|
||||
* run `dotnet run PATH_TO_PLUGIN_BUILD_DIRECTORY NAME_OF_PLUGIN BUILT_PACKAGE_OUTPUT_DIRECTORY`
|
BIN
BTCPayServer.PluginPacker/icon.png
Normal file
BIN
BTCPayServer.PluginPacker/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
44
BTCPayServer.Plugins.Test/ApplicationPartsLogger.cs
Normal file
44
BTCPayServer.Plugins.Test/ApplicationPartsLogger.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test
|
||||
{
|
||||
public class ApplicationPartsLogger : IHostedService
|
||||
{
|
||||
private readonly ILogger<ApplicationPartsLogger> _logger;
|
||||
private readonly ApplicationPartManager _partManager;
|
||||
|
||||
public ApplicationPartsLogger(ILogger<ApplicationPartsLogger> logger, ApplicationPartManager partManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_partManager = partManager;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// Get the names of all the application parts. This is the short assembly name for AssemblyParts
|
||||
var applicationParts = _partManager.ApplicationParts.Select(x => x.Name);
|
||||
|
||||
// Create a controller feature, and populate it from the application parts
|
||||
var controllerFeature = new ControllerFeature();
|
||||
_partManager.PopulateFeature(controllerFeature);
|
||||
|
||||
// Get the names of all of the controllers
|
||||
var controllers = controllerFeature.Controllers.Select(x => x.Name);
|
||||
|
||||
// Log the application parts and controllers
|
||||
_logger.LogInformation("Found the following application parts: '{ApplicationParts}' with the following controllers: '{Controllers}'",
|
||||
string.Join(", ", applicationParts), string.Join(", ", controllers));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Required by the interface
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
}
|
15
BTCPayServer.Plugins.Test/BTCPayServer.Plugins.Test.csproj
Normal file
15
BTCPayServer.Plugins.Test/BTCPayServer.Plugins.Test.csproj
Normal file
@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
|
||||
<PreserveCompilationContext>false</PreserveCompilationContext>
|
||||
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
|
||||
<AssemblyVersion>1.0.0</AssemblyVersion>
|
||||
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
|
||||
<EmbeddedResource Include="Resources\**" />
|
||||
</ItemGroup>
|
||||
</Project>
|
BIN
BTCPayServer.Plugins.Test/Resources/img/screengrab.png
Normal file
BIN
BTCPayServer.Plugins.Test/Resources/img/screengrab.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
19
BTCPayServer.Plugins.Test/TestExtension.cs
Normal file
19
BTCPayServer.Plugins.Test/TestExtension.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using BTCPayServer.Contracts;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test
|
||||
{
|
||||
public class TestPlugin: BaseBTCPayServerPlugin
|
||||
{
|
||||
public override string Identifier { get; } = "BTCPayServer.Plugins.Test";
|
||||
public override string Name { get; } = "Test Plugin!";
|
||||
public override string Description { get; } = "This is a description of the loaded test extension!";
|
||||
|
||||
public override void Execute(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<IUIExtension>(new UIExtension("TestExtensionNavExtension", "header-nav"));
|
||||
services.AddHostedService<ApplicationPartsLogger>();
|
||||
}
|
||||
}
|
||||
}
|
16
BTCPayServer.Plugins.Test/TestExtensionController.cs
Normal file
16
BTCPayServer.Plugins.Test/TestExtensionController.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test
|
||||
{
|
||||
[Route("extensions/test")]
|
||||
public class TestExtensionController : Controller
|
||||
{
|
||||
// GET
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
|
||||
<li class="nav-item"><a asp-controller="TestExtension" asp-action="Index" class="nav-link js-scroll-trigger" >Dear Nicolas Dorier</a></li>
|
@ -0,0 +1,9 @@
|
||||
<section>
|
||||
<div class="container">
|
||||
<h1>Challenge Completed!!</h1>
|
||||
Here is also an image loaded from the plugin<br/>
|
||||
<a href="https://twitter.com/NicolasDorier/status/1307221679014256640">
|
||||
<img src="/Resources/img/screengrab.png"/>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
1
BTCPayServer.Plugins.Test/Views/_ViewImports.cshtml
Normal file
1
BTCPayServer.Plugins.Test/Views/_ViewImports.cshtml
Normal file
@ -0,0 +1 @@
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
@ -6,7 +6,7 @@
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.6.0" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
|
||||
<PackageReference Include="NBitcoin" Version="5.0.51" />
|
||||
<PackageReference Include="NBitcoin" Version="5.0.60" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.6.3" />
|
||||
</ItemGroup>
|
||||
|
@ -206,9 +206,9 @@ namespace BTCPayServer.Tests
|
||||
bitflyerMock.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_JPY"), new BidAsk(700000m)));
|
||||
rateProvider.Providers.Add("bitflyer", bitflyerMock);
|
||||
|
||||
var quadrigacx = new MockRateProvider();
|
||||
quadrigacx.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_CAD"), new BidAsk(6000m)));
|
||||
rateProvider.Providers.Add("quadrigacx", quadrigacx);
|
||||
var ndax = new MockRateProvider();
|
||||
ndax.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_CAD"), new BidAsk(6000m)));
|
||||
rateProvider.Providers.Add("ndax", ndax);
|
||||
|
||||
var bittrex = new MockRateProvider();
|
||||
bittrex.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("DOGE_BTC"), new BidAsk(0.004m)));
|
||||
|
@ -42,18 +42,19 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await Server.StartAsync();
|
||||
ChromeOptions options = new ChromeOptions();
|
||||
var isDebug = !Server.PayTester.InContainer;
|
||||
if (Server.PayTester.InContainer)
|
||||
{
|
||||
// this must be first option https://stackoverflow.com/questions/53073411/selenium-webdriverexceptionchrome-failed-to-start-crashed-as-google-chrome-is#comment102570662_53073789
|
||||
options.AddArgument("no-sandbox");
|
||||
}
|
||||
|
||||
var isDebug = !Server.PayTester.InContainer;
|
||||
if (!isDebug)
|
||||
{
|
||||
options.AddArguments("headless"); // Comment to view browser
|
||||
options.AddArguments("window-size=1200x1000"); // Comment to view browser
|
||||
}
|
||||
options.AddArgument("shm-size=2g");
|
||||
if (Server.PayTester.InContainer)
|
||||
{
|
||||
options.AddArgument("no-sandbox");
|
||||
}
|
||||
Driver = new ChromeDriver(Server.PayTester.InContainer ? "/usr/bin" : Directory.GetCurrentDirectory(), options);
|
||||
if (isDebug)
|
||||
{
|
||||
@ -228,7 +229,11 @@ namespace BTCPayServer.Tests
|
||||
Driver.FindElement(By.Id("Email")).SendKeys(user);
|
||||
Driver.FindElement(By.Id("Password")).SendKeys(password);
|
||||
Driver.FindElement(By.Id("LoginButton")).Click();
|
||||
}
|
||||
|
||||
public void GoToStores()
|
||||
{
|
||||
Driver.FindElement(By.Id("Stores")).Click();
|
||||
}
|
||||
|
||||
public void GoToStore(string storeId, StoreNavPages storeNavPage = StoreNavPages.Index)
|
||||
@ -303,7 +308,7 @@ namespace BTCPayServer.Tests
|
||||
public string CreateInvoice(string storeName, decimal amount = 100, string currency = "USD", string refundEmail = "")
|
||||
{
|
||||
GoToInvoices();
|
||||
Driver.FindElement(By.Id("CreateNewInvoice")).Click();
|
||||
Driver.FindElement(By.Id("CreateNewInvoice")).Click(); // ocassionally gets stuck for some reason, tried force click and wait for element
|
||||
Driver.FindElement(By.Id("Amount")).SendKeys(amount.ToString(CultureInfo.InvariantCulture));
|
||||
var currencyEl = Driver.FindElement(By.Id("Currency"));
|
||||
currencyEl.Clear();
|
||||
|
@ -300,14 +300,30 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await s.StartAsync();
|
||||
var alice = s.RegisterNewUser();
|
||||
var store = s.CreateNewStore().storeName;
|
||||
s.AddDerivationScheme();
|
||||
var storeData = s.CreateNewStore();
|
||||
// verify that hints are displayed on the store page
|
||||
Assert.True(s.Driver.PageSource.Contains("Wallet not setup for the store, please provide Derviation Scheme"),
|
||||
"Wallet hint not present");
|
||||
Assert.True(s.Driver.PageSource.Contains("Review settings if you want to receive Lightning payments"),
|
||||
"Lightning hint not present");
|
||||
|
||||
s.GoToStores();
|
||||
Assert.True(s.Driver.PageSource.Contains("warninghint_" + storeData.storeId),
|
||||
"Warning hint on list not present");
|
||||
|
||||
s.GoToStore(storeData.storeId);
|
||||
s.AddDerivationScheme(); // wallet hint should be dismissed
|
||||
s.Driver.AssertNoError();
|
||||
Assert.Contains(store, s.Driver.PageSource);
|
||||
Assert.False(s.Driver.PageSource.Contains("Wallet not setup for the store, please provide Derviation Scheme"),
|
||||
"Wallet hint not dismissed on derivation scheme add");
|
||||
|
||||
s.Driver.FindElement(By.Id("dismissLightningHint")).Click(); // dismiss lightning hint
|
||||
|
||||
Assert.Contains(storeData.storeName, s.Driver.PageSource);
|
||||
var storeUrl = s.Driver.Url;
|
||||
s.ClickOnAllSideMenus();
|
||||
s.GoToInvoices();
|
||||
var invoiceId = s.CreateInvoice(store);
|
||||
var invoiceId = s.CreateInvoice(storeData.storeName);
|
||||
s.AssertHappyMessage();
|
||||
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
|
||||
var invoiceUrl = s.Driver.Url;
|
||||
@ -362,6 +378,11 @@ namespace BTCPayServer.Tests
|
||||
s.Logout();
|
||||
LogIn(s, alice);
|
||||
s.Driver.FindElement(By.Id("Stores")).Click();
|
||||
|
||||
// there shouldn't be any hints now
|
||||
Assert.False(s.Driver.PageSource.Contains("Review settings if you want to receive Lightning payments"),
|
||||
"Lightning hint should be dismissed at this point");
|
||||
|
||||
s.Driver.FindElement(By.LinkText("Remove")).Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.Driver.FindElement(By.Id("Stores")).Click();
|
||||
@ -806,7 +827,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter);
|
||||
s.AssertHappyMessage();
|
||||
Assert.Contains("AwaitingApproval", s.Driver.PageSource);
|
||||
Assert.Contains("Awaiting Approval", s.Driver.PageSource);
|
||||
|
||||
var viewPullPaymentUrl = s.Driver.Url;
|
||||
// This one should have nothing
|
||||
@ -848,8 +869,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.Navigate().GoToUrl(viewPullPaymentUrl);
|
||||
txs = s.Driver.FindElements(By.ClassName("transaction-link"));
|
||||
Assert.Equal(2, txs.Count);
|
||||
Assert.Contains("InProgress", s.Driver.PageSource);
|
||||
|
||||
Assert.Contains("In Progress", s.Driver.PageSource);
|
||||
|
||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||
|
||||
|
@ -1893,7 +1893,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
rateVm.ScriptTest = "BTC_USD,BTC_CAD,DOGE_USD,DOGE_CAD";
|
||||
rateVm.Script = "DOGE_X = bittrex(DOGE_BTC) * BTC_X;\n" +
|
||||
"X_CAD = quadrigacx(X_CAD);\n" +
|
||||
"X_CAD = ndax(X_CAD);\n" +
|
||||
"X_X = coingecko(X_X);";
|
||||
rateVm.Spread = 50;
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(await store.Rates(rateVm, "Test"))
|
||||
|
@ -2,4 +2,5 @@
|
||||
|
||||
bitcoind_container_id="$(docker ps -q --filter label=com.docker.compose.project=btcpayservertests --filter label=com.docker.compose.service=bitcoind)"
|
||||
address=$(docker exec -ti $bitcoind_container_id bitcoin-cli -datadir="/data" getnewaddress)
|
||||
docker exec -ti $bitcoind_container_id bitcoin-cli -datadir="/data" generatetoaddress "$@" "$address"
|
||||
clean_address="${address//[$'\t\r\n']}"
|
||||
docker exec $bitcoind_container_id bitcoin-cli -datadir="/data" generatetoaddress "$@" "$clean_address"
|
||||
|
@ -82,7 +82,7 @@ services:
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.1.42
|
||||
image: nicolasdorier/nbxplorer:2.1.45
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
@ -213,52 +213,6 @@ services:
|
||||
- "merchant_lightningd_datadir:/root/.lightning"
|
||||
links:
|
||||
- bitcoind
|
||||
|
||||
litecoind:
|
||||
restart: unless-stopped
|
||||
image: nicolasdorier/docker-litecoin:0.16.3
|
||||
environment:
|
||||
BITCOIN_EXTRA_ARGS: |-
|
||||
rpcuser=ceiwHEbqWI83
|
||||
rpcpassword=DwubwWsoo3
|
||||
regtest=1
|
||||
rpcport=43782
|
||||
port=39388
|
||||
whitelist=0.0.0.0/0
|
||||
ports:
|
||||
- "43783:43782"
|
||||
expose:
|
||||
- "43782" # RPC
|
||||
- "39388" # P2P
|
||||
|
||||
elementsd-liquid:
|
||||
restart: always
|
||||
container_name: btcpayserver_elementsd_liquid
|
||||
image: btcpayserver/elements:0.18.1.7
|
||||
environment:
|
||||
ELEMENTS_CHAIN: elementsregtest
|
||||
ELEMENTS_EXTRA_ARGS: |
|
||||
mainchainrpcport=43782
|
||||
mainchainrpchost=bitcoind
|
||||
mainchainrpcuser=liquid
|
||||
mainchainrpcpassword=liquid
|
||||
rpcport=19332
|
||||
rpcbind=0.0.0.0:19332
|
||||
rpcauth=liquid:c8bf1a8961d97f224cb21224aaa8235d$$402f4a8907683d057b8c58a42940b6e54d1638322a42986ae28ebb844e603ae6
|
||||
port=19444
|
||||
whitelist=0.0.0.0/0
|
||||
validatepegin=0
|
||||
initialfreecoins=210000000000000
|
||||
con_dyna_deploy_start=99999999999
|
||||
expose:
|
||||
- "19332"
|
||||
- "19444"
|
||||
ports:
|
||||
- "19332:19332"
|
||||
- "19444:19444"
|
||||
volumes:
|
||||
- "elementsd_liquid_datadir:/data"
|
||||
|
||||
postgres:
|
||||
image: postgres:9.6.5
|
||||
ports:
|
||||
@ -341,7 +295,7 @@ services:
|
||||
- "torrcdir:/usr/local/etc/tor"
|
||||
- "tor_servicesdir:/var/lib/tor/hidden_services"
|
||||
monerod:
|
||||
image: btcpayserver/monero:0.15.0.1-amd64
|
||||
image: btcpayserver/monero:0.17.0.0-amd64
|
||||
restart: unless-stopped
|
||||
container_name: xmr_monerod
|
||||
entrypoint: sleep 999999
|
||||
@ -351,17 +305,16 @@ services:
|
||||
ports:
|
||||
- "18081:18081"
|
||||
monero_wallet:
|
||||
image: btcpayserver/monero:0.15.0.1-amd64
|
||||
restart: unless-stopped
|
||||
container_name: xmr_wallet_rpc
|
||||
entrypoint: monero-wallet-rpc --testnet --rpc-bind-ip=0.0.0.0 --disable-rpc-login --confirm-external-bind --rpc-bind-port=18082 --non-interactive --trusted-daemon --daemon-address=monerod:18081 --wallet-file=/wallet/wallet.keys --password-file=/wallet/password --tx-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/tx?cryptoCode=xmr&hash=%s"
|
||||
ports:
|
||||
- "18082:18082"
|
||||
volumes:
|
||||
- "./monero_wallet:/wallet"
|
||||
depends_on:
|
||||
image: btcpayserver/monero:0.17.0.0-amd64
|
||||
restart: unless-stopped
|
||||
container_name: xmr_wallet_rpc
|
||||
entrypoint: monero-wallet-rpc --testnet --rpc-bind-ip=0.0.0.0 --disable-rpc-login --confirm-external-bind --rpc-bind-port=18082 --non-interactive --trusted-daemon --daemon-address=monerod:18081 --wallet-file=/wallet/wallet.keys --password-file=/wallet/password --tx-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/tx?cryptoCode=xmr&hash=%s"
|
||||
ports:
|
||||
- "18082:18082"
|
||||
volumes:
|
||||
- "./monero_wallet:/wallet"
|
||||
depends_on:
|
||||
- monerod
|
||||
|
||||
litecoind:
|
||||
restart: unless-stopped
|
||||
image: nicolasdorier/docker-litecoin:0.16.3
|
||||
|
@ -79,7 +79,7 @@ services:
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.1.42
|
||||
image: nicolasdorier/nbxplorer:2.1.45
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
|
@ -1,11 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
|
||||
<Compile Remove="Build\**" />
|
||||
<Compile Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<Compile Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
@ -52,6 +55,7 @@
|
||||
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
|
||||
<PackageReference Include="CsvHelper" Version="15.0.5" />
|
||||
<PackageReference Include="HtmlSanitizer" Version="4.0.217" />
|
||||
<PackageReference Include="McMaster.NETCore.Plugins.Mvc" Version="1.3.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.9.8">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
@ -141,6 +145,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\BTCPayServer.Client\BTCPayServer.Client.csproj" />
|
||||
<ProjectReference Include="..\BTCPayServer.Data\BTCPayServer.Data.csproj" />
|
||||
<ProjectReference Include="..\BTCPayServer.Rating\BTCPayServer.Rating.csproj" />
|
||||
|
@ -1,4 +1,7 @@
|
||||
@inject LinkGenerator linkGenerator
|
||||
@inject UserManager<ApplicationUser> UserManager
|
||||
@inject CssThemeManager CssThemeManager
|
||||
@using BTCPayServer.HostedServices
|
||||
@model BTCPayServer.Components.NotificationsDropdown.NotificationSummaryViewModel
|
||||
|
||||
@if (Model.UnseenCount > 0)
|
||||
@ -32,36 +35,47 @@ else
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
<script type="text/javascript">
|
||||
@{
|
||||
var disabled = CssThemeManager.Policies.DisableInstantNotifications;
|
||||
if (!disabled)
|
||||
{
|
||||
var user = await UserManager.GetUserAsync(User);
|
||||
disabled = user.DisabledNotifications == "all";
|
||||
}
|
||||
}
|
||||
@if (!disabled)
|
||||
{
|
||||
|
||||
var supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
|
||||
<script type="text/javascript">
|
||||
|
||||
var supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
|
||||
|
||||
if (supportsWebSockets) {
|
||||
|
||||
var loc = window.location, ws_uri;
|
||||
if (loc.protocol === "https:") {
|
||||
ws_uri = "wss:";
|
||||
} else {
|
||||
ws_uri = "ws:";
|
||||
}
|
||||
ws_uri += "//" + loc.host;
|
||||
ws_uri += "@linkGenerator.GetPathByAction("SubscribeUpdates", "Notifications")";
|
||||
var newDataEndpoint = "@linkGenerator.GetPathByAction("GetNotificationDropdownUI", "Notifications")";
|
||||
|
||||
try {
|
||||
socket = new WebSocket(ws_uri);
|
||||
socket.onmessage = function (e) {
|
||||
$.get(newDataEndpoint, function(data){
|
||||
$("#notifications-nav-item").replaceWith($(data));
|
||||
});
|
||||
};
|
||||
socket.onerror = function (e) {
|
||||
console.error("Error while connecting to websocket for notifications (callback)", e);
|
||||
};
|
||||
}
|
||||
catch (e) {
|
||||
console.error("Error while connecting to websocket for notifications", e);
|
||||
var loc = window.location, ws_uri;
|
||||
if (loc.protocol === "https:") {
|
||||
ws_uri = "wss:";
|
||||
} else {
|
||||
ws_uri = "ws:";
|
||||
}
|
||||
ws_uri += "//" + loc.host;
|
||||
ws_uri += "@linkGenerator.GetPathByAction("SubscribeUpdates", "Notifications")";
|
||||
var newDataEndpoint = "@linkGenerator.GetPathByAction("GetNotificationDropdownUI", "Notifications")";
|
||||
|
||||
try {
|
||||
socket = new WebSocket(ws_uri);
|
||||
socket.onmessage = function (e) {
|
||||
$.get(newDataEndpoint, function(data){
|
||||
$("#notifications-nav-item").replaceWith($(data));
|
||||
});
|
||||
};
|
||||
socket.onerror = function (e) {
|
||||
console.error("Error while connecting to websocket for notifications (callback)", e);
|
||||
};
|
||||
}
|
||||
catch (e) {
|
||||
console.error("Error while connecting to websocket for notifications", e);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
}
|
||||
|
||||
</script>
|
||||
|
@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Contracts;
|
||||
using BTCPayServer.Models.NotificationViewModels;
|
||||
|
||||
namespace BTCPayServer.Components.NotificationsDropdown
|
||||
|
6
BTCPayServer/Components/UIExtensionPoint/Default.cshtml
Normal file
6
BTCPayServer/Components/UIExtensionPoint/Default.cshtml
Normal file
@ -0,0 +1,6 @@
|
||||
@model IEnumerable<string>
|
||||
|
||||
@foreach (var partial in Model)
|
||||
{
|
||||
await Html.RenderPartialAsync(partial);
|
||||
}
|
23
BTCPayServer/Components/UIExtensionPoint/UIExtensionPoint.cs
Normal file
23
BTCPayServer/Components/UIExtensionPoint/UIExtensionPoint.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Contracts;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Components.UIExtensionPoint
|
||||
{
|
||||
public class UiExtensionPoint : ViewComponent
|
||||
{
|
||||
private readonly IEnumerable<IUIExtension> _uiExtensions;
|
||||
|
||||
public UiExtensionPoint(IEnumerable<IUIExtension> uiExtensions)
|
||||
{
|
||||
_uiExtensions = uiExtensions;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string location)
|
||||
{
|
||||
return View(_uiExtensions.Where(extension => extension.Location.Equals(location, StringComparison.InvariantCultureIgnoreCase)).Select(extension => extension.Partial));
|
||||
}
|
||||
}
|
||||
}
|
@ -80,6 +80,7 @@ namespace BTCPayServer.Configuration
|
||||
{
|
||||
NetworkType = DefaultConfiguration.GetNetworkType(conf);
|
||||
DataDir = conf.GetDataDir(NetworkType);
|
||||
PluginDir = conf.GetPluginDir(NetworkType);
|
||||
Logs.Configuration.LogInformation("Network: " + NetworkType.ToString());
|
||||
|
||||
if (conf.GetOrDefault<bool>("launchsettings", false) && NetworkType != NetworkType.Regtest)
|
||||
@ -165,7 +166,10 @@ namespace BTCPayServer.Configuration
|
||||
|
||||
PostgresConnectionString = conf.GetOrDefault<string>("postgres", null);
|
||||
MySQLConnectionString = conf.GetOrDefault<string>("mysql", null);
|
||||
SQLiteFileName = conf.GetOrDefault<string>("sqlitefile", null);
|
||||
|
||||
BundleJsCss = conf.GetOrDefault<bool>("bundlejscss", true);
|
||||
DockerDeployment = conf.GetOrDefault<bool>("dockerdeployment", true);
|
||||
AllowAdminRegistration = conf.GetOrDefault<bool>("allow-admin-registration", false);
|
||||
TorrcFile = conf.GetOrDefault<string>("torrcfile", null);
|
||||
|
||||
@ -237,8 +241,14 @@ namespace BTCPayServer.Configuration
|
||||
}
|
||||
|
||||
DisableRegistration = conf.GetOrDefault<bool>("disable-registration", true);
|
||||
PluginRemote = conf.GetOrDefault("plugin-remote", "btcpayserver/btcpayserver-plugins");
|
||||
RecommendedPlugins = conf.GetOrDefault("recommended-plugins", "").ToLowerInvariant().Split('\r','\n','\t',' ').Where(s => !string.IsNullOrEmpty(s)).Distinct().ToArray();
|
||||
}
|
||||
|
||||
public string PluginDir { get; set; }
|
||||
public string PluginRemote { get; set; }
|
||||
public string[] RecommendedPlugins { get; set; }
|
||||
|
||||
private SSHSettings ParseSSHConfiguration(IConfiguration conf)
|
||||
{
|
||||
var settings = new SSHSettings();
|
||||
@ -281,6 +291,7 @@ namespace BTCPayServer.Configuration
|
||||
public ExternalServices ExternalServices { get; set; } = new ExternalServices();
|
||||
|
||||
public BTCPayNetworkProvider NetworkProvider { get; set; }
|
||||
public bool DockerDeployment { get; set; }
|
||||
public string PostgresConnectionString
|
||||
{
|
||||
get;
|
||||
@ -291,6 +302,11 @@ namespace BTCPayServer.Configuration
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string SQLiteFileName
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public bool BundleJsCss
|
||||
{
|
||||
get;
|
||||
|
@ -67,5 +67,11 @@ namespace BTCPayServer.Configuration
|
||||
var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(networkType);
|
||||
return configuration.GetOrDefault("datadir", defaultSettings.DefaultDataDirectory);
|
||||
}
|
||||
|
||||
public static string GetPluginDir(this IConfiguration configuration, NetworkType networkType)
|
||||
{
|
||||
var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(networkType);
|
||||
return configuration.GetOrDefault("plugindir", defaultSettings.DefaultPluginDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,8 +25,9 @@ namespace BTCPayServer.Configuration
|
||||
app.Option("--regtest | -regtest", $"Use regtest (deprecated, use --network instead)", CommandOptionType.BoolValue);
|
||||
app.Option("--allow-admin-registration", $"For debug only, will show a checkbox when a new user register to add himself as admin. (default: false)", CommandOptionType.BoolValue);
|
||||
app.Option("--chains | -c", $"Chains to support as a comma separated (default: btc; available: {chains})", CommandOptionType.SingleValue);
|
||||
app.Option("--postgres", $"Connection string to a PostgreSQL database (default: SQLite)", CommandOptionType.SingleValue);
|
||||
app.Option("--mysql", $"Connection string to a MySQL database (default: SQLite)", CommandOptionType.SingleValue);
|
||||
app.Option("--postgres", $"Connection string to a PostgreSQL database", CommandOptionType.SingleValue);
|
||||
app.Option("--mysql", $"Connection string to a MySQL database", CommandOptionType.SingleValue);
|
||||
app.Option("--sqlitefile", $"File name to an SQLite database file inside the data directory", CommandOptionType.SingleValue);
|
||||
app.Option("--externalservices", $"Links added to external services inside Server Settings / Services under the format service1:path2;service2:path2.(default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--bundlejscss", $"Bundle JavaScript and CSS files for better performance (default: true)", CommandOptionType.SingleValue);
|
||||
app.Option("--rootpath", "The root path in the URL to access BTCPay (default: /)", CommandOptionType.SingleValue);
|
||||
@ -42,6 +43,8 @@ namespace BTCPayServer.Configuration
|
||||
app.Option("--debuglog", "A rolling log file for debug messages.", CommandOptionType.SingleValue);
|
||||
app.Option("--debugloglevel", "The severity you log (default:information)", CommandOptionType.SingleValue);
|
||||
app.Option("--disable-registration", "Disables new user registrations (default:true)", CommandOptionType.SingleValue);
|
||||
app.Option("--plugin-remote", "Which github repository to fetch the available plugins list (default:btcpayserver/btcpayserver-plugins)", CommandOptionType.SingleValue);
|
||||
app.Option("--recommended-plugins", "Plugins which would be marked as recommended to be installed. Separated by newline or space", CommandOptionType.MultipleValue);
|
||||
app.Option("--xforwardedproto", "If specified, set X-Forwarded-Proto to the specified value, this may be useful if your reverse proxy handle https but is not configured to add X-Forwarded-Proto (example: --xforwardedproto https)", CommandOptionType.SingleValue);
|
||||
foreach (var network in provider.GetAll().OfType<BTCPayNetwork>())
|
||||
{
|
||||
@ -49,11 +52,14 @@ namespace BTCPayServer.Configuration
|
||||
app.Option($"--{crypto}explorerurl", $"URL of the NBXplorer for {network.CryptoCode} (default: {network.NBXplorerNetwork.DefaultSettings.DefaultUrl})", CommandOptionType.SingleValue);
|
||||
app.Option($"--{crypto}explorercookiefile", $"Path to the cookie file (default: {network.NBXplorerNetwork.DefaultSettings.DefaultCookieFile})", CommandOptionType.SingleValue);
|
||||
app.Option($"--{crypto}lightning", $"Easy configuration of lightning for the server administrator: Must be a UNIX socket of c-lightning (lightning-rpc) or URL to a charge server (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option($"--{crypto}externallndgrpc", $"The LND gRPC configuration BTCPay will expose to easily connect to the internal lnd wallet from an external wallet (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option($"--{crypto}externallndrest", $"The LND REST configuration BTCPay will expose to easily connect to the internal lnd wallet from an external wallet (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option($"--{crypto}externalrtl", $"The Ride the Lightning configuration so BTCPay will expose to easily open it in server settings (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option($"--{crypto}externalspark", $"Show spark information in Server settings / Server. The connection string to spark server (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option($"--{crypto}externalcharge", $"Show lightning charge information in Server settings/Server. The connection string to charge server (default: empty)", CommandOptionType.SingleValue);
|
||||
if (network.SupportLightning)
|
||||
{
|
||||
app.Option($"--{crypto}externallndgrpc", $"The LND gRPC configuration BTCPay will expose to easily connect to the internal lnd wallet from an external wallet (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option($"--{crypto}externallndrest", $"The LND REST configuration BTCPay will expose to easily connect to the internal lnd wallet from an external wallet (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option($"--{crypto}externalrtl", $"The Ride the Lightning configuration so BTCPay will expose to easily open it in server settings (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option($"--{crypto}externalspark", $"Show spark information in Server settings / Server. The connection string to spark server (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option($"--{crypto}externalcharge", $"Show lightning charge information in Server settings/Server. The connection string to charge server (default: empty)", CommandOptionType.SingleValue);
|
||||
}
|
||||
}
|
||||
return app;
|
||||
}
|
||||
@ -118,14 +124,18 @@ namespace BTCPayServer.Configuration
|
||||
builder.AppendLine("### Database ###");
|
||||
builder.AppendLine("#postgres=User ID=root;Password=myPassword;Host=localhost;Port=5432;Database=myDataBase;");
|
||||
builder.AppendLine("#mysql=User ID=root;Password=myPassword;Host=localhost;Port=3306;Database=myDataBase;");
|
||||
builder.AppendLine("#sqlitefile=sqlite.db");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine("### NBXplorer settings ###");
|
||||
foreach (var n in new BTCPayNetworkProvider(networkType).GetAll().OfType<BTCPayNetwork>())
|
||||
{
|
||||
builder.AppendLine($"#{n.CryptoCode}.explorer.url={n.NBXplorerNetwork.DefaultSettings.DefaultUrl}");
|
||||
builder.AppendLine($"#{n.CryptoCode}.explorer.cookiefile={ n.NBXplorerNetwork.DefaultSettings.DefaultCookieFile}");
|
||||
builder.AppendLine($"#{n.CryptoCode}.lightning=/root/.lightning/lightning-rpc");
|
||||
builder.AppendLine($"#{n.CryptoCode}.lightning=https://apitoken:API_TOKEN_SECRET@charge.example.com/");
|
||||
if (n.SupportLightning)
|
||||
{
|
||||
builder.AppendLine($"#{n.CryptoCode}.lightning=/root/.lightning/lightning-rpc");
|
||||
builder.AppendLine($"#{n.CryptoCode}.lightning=https://apitoken:API_TOKEN_SECRET@charge.example.com/");
|
||||
}
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
namespace BTCPayServer.Contracts
|
||||
{
|
||||
public interface IStoreNavExtension
|
||||
{
|
||||
string Partial { get; }
|
||||
}
|
||||
}
|
@ -537,7 +537,7 @@ namespace BTCPayServer.Controllers
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
var user = await _userManager.FindByEmailAsync(model.Email);
|
||||
if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
|
||||
if (user == null || (user.RequiresEmailConfirmation && !(await _userManager.IsEmailConfirmedAsync(user))))
|
||||
{
|
||||
// Don't reveal that the user does not exist or is not confirmed
|
||||
return RedirectToAction(nameof(ForgotPasswordConfirmation));
|
||||
@ -606,7 +606,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
AddErrors(result);
|
||||
return View();
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
|
@ -82,6 +82,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
public string Description { get; set; }
|
||||
public string NotificationUrl { get; set; }
|
||||
public string RedirectUrl { get; set; }
|
||||
public bool? RedirectAutomatically { get; set; }
|
||||
}
|
||||
|
||||
@ -115,6 +116,7 @@ namespace BTCPayServer.Controllers
|
||||
EmbeddedCSS = settings.EmbeddedCSS,
|
||||
Description = settings.Description,
|
||||
NotificationUrl = settings.NotificationUrl,
|
||||
RedirectUrl = settings.RedirectUrl,
|
||||
SearchTerm = $"storeid:{app.StoreDataId}",
|
||||
RedirectAutomatically = settings.RedirectAutomatically.HasValue ? settings.RedirectAutomatically.Value ? "true" : "false" : ""
|
||||
};
|
||||
@ -191,6 +193,7 @@ namespace BTCPayServer.Controllers
|
||||
CustomTipPercentages = ListSplit(vm.CustomTipPercentages),
|
||||
CustomCSSLink = vm.CustomCSSLink,
|
||||
NotificationUrl = vm.NotificationUrl,
|
||||
RedirectUrl = vm.RedirectUrl,
|
||||
Description = vm.Description,
|
||||
EmbeddedCSS = vm.EmbeddedCSS,
|
||||
RedirectAutomatically = string.IsNullOrEmpty(vm.RedirectAutomatically) ? (bool?)null : bool.Parse(vm.RedirectAutomatically)
|
||||
|
@ -41,12 +41,13 @@ namespace BTCPayServer.Controllers
|
||||
[HttpGet("/apps/{appId}")]
|
||||
public async Task<IActionResult> RedirectToApp(string appId)
|
||||
{
|
||||
switch (await _AppService.GetAppInfo(appId))
|
||||
|
||||
switch ((await _AppService.GetApp(appId, null)).AppType)
|
||||
{
|
||||
case ViewCrowdfundViewModel _:
|
||||
case nameof(AppType.Crowdfund):
|
||||
return RedirectToAction("ViewCrowdfund", new {appId});
|
||||
|
||||
case ViewPointOfSaleViewModel _:
|
||||
case nameof(AppType.PointOfSale):
|
||||
return RedirectToAction("ViewPointOfSale", new {appId});
|
||||
}
|
||||
|
||||
@ -203,7 +204,9 @@ namespace BTCPayServer.Controllers
|
||||
OrderId = orderId,
|
||||
NotificationURL =
|
||||
string.IsNullOrEmpty(notificationUrl) ? settings.NotificationUrl : notificationUrl,
|
||||
RedirectURL = redirectUrl ?? Request.GetDisplayUrl(),
|
||||
RedirectURL = !string.IsNullOrEmpty(redirectUrl) ? redirectUrl
|
||||
: !string.IsNullOrEmpty(settings.RedirectUrl) ? settings.RedirectUrl
|
||||
: Request.GetDisplayUrl(),
|
||||
FullNotifications = true,
|
||||
ExtendedNotifications = true,
|
||||
PosData = string.IsNullOrEmpty(posData) ? null : posData,
|
||||
|
@ -300,32 +300,35 @@ namespace BTCPayServer.Controllers
|
||||
return RedirectToAction(nameof(PullPaymentController.ViewPullPayment),
|
||||
"PullPayment",
|
||||
new { pullPaymentId = ppId });
|
||||
|
||||
|
||||
}
|
||||
|
||||
private InvoiceDetailsModel InvoicePopulatePayments(InvoiceEntity invoice)
|
||||
{
|
||||
var model = new InvoiceDetailsModel();
|
||||
model.Archived = invoice.Archived;
|
||||
model.Payments = invoice.GetPayments();
|
||||
foreach (var data in invoice.GetPaymentMethods())
|
||||
return new InvoiceDetailsModel
|
||||
{
|
||||
var accounting = data.Calculate();
|
||||
var paymentMethodId = data.GetId();
|
||||
var cryptoPayment = new InvoiceDetailsModel.CryptoPayment();
|
||||
|
||||
cryptoPayment.PaymentMethodId = paymentMethodId;
|
||||
cryptoPayment.PaymentMethod = paymentMethodId.ToPrettyString();
|
||||
cryptoPayment.Due = _CurrencyNameTable.DisplayFormatCurrency(accounting.Due.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
|
||||
cryptoPayment.Paid = _CurrencyNameTable.DisplayFormatCurrency(accounting.CryptoPaid.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
|
||||
cryptoPayment.Overpaid = _CurrencyNameTable.DisplayFormatCurrency(accounting.OverpaidHelper.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
|
||||
var paymentMethodDetails = data.GetPaymentMethodDetails();
|
||||
cryptoPayment.Address = paymentMethodDetails.GetPaymentDestination();
|
||||
cryptoPayment.Rate = ExchangeRate(data);
|
||||
model.CryptoPayments.Add(cryptoPayment);
|
||||
}
|
||||
return model;
|
||||
Archived = invoice.Archived,
|
||||
Payments = invoice.GetPayments(),
|
||||
CryptoPayments = invoice.GetPaymentMethods().Select(
|
||||
data =>
|
||||
{
|
||||
var accounting = data.Calculate();
|
||||
var paymentMethodId = data.GetId();
|
||||
return new InvoiceDetailsModel.CryptoPayment
|
||||
{
|
||||
PaymentMethodId = paymentMethodId,
|
||||
PaymentMethod = paymentMethodId.ToPrettyString(),
|
||||
Due = _CurrencyNameTable.DisplayFormatCurrency(accounting.Due.ToDecimal(MoneyUnit.BTC),
|
||||
paymentMethodId.CryptoCode),
|
||||
Paid = _CurrencyNameTable.DisplayFormatCurrency(
|
||||
accounting.CryptoPaid.ToDecimal(MoneyUnit.BTC),
|
||||
paymentMethodId.CryptoCode),
|
||||
Overpaid = _CurrencyNameTable.DisplayFormatCurrency(
|
||||
accounting.OverpaidHelper.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode),
|
||||
Address = data.GetPaymentMethodDetails().GetPaymentDestination(),
|
||||
Rate = ExchangeRate(data)
|
||||
};
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost("invoices/{invoiceId}/archive")]
|
||||
|
68
BTCPayServer/Controllers/ManageController.Notifications.cs
Normal file
68
BTCPayServer/Controllers/ManageController.Notifications.cs
Normal file
@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Contracts;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class ManageController
|
||||
{
|
||||
[HttpGet("notifications")]
|
||||
public async Task<IActionResult> NotificationSettings([FromServices] IEnumerable<INotificationHandler> notificationHandlers)
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user.DisabledNotifications == "all")
|
||||
{
|
||||
return View(new NotificationSettingsViewModel() {All = true});
|
||||
}
|
||||
var disabledNotifications =
|
||||
user.DisabledNotifications?.Split(';', StringSplitOptions.RemoveEmptyEntries)?.ToList() ??
|
||||
new List<string>();
|
||||
var notifications = notificationHandlers.SelectMany(handler => handler.Meta.Select(tuple =>
|
||||
new SelectListItem(tuple.name, tuple.identifier,
|
||||
disabledNotifications.Contains(tuple.identifier, StringComparer.InvariantCultureIgnoreCase))))
|
||||
.ToList();
|
||||
|
||||
return View(new NotificationSettingsViewModel() {DisabledNotifications = notifications});
|
||||
}
|
||||
|
||||
[HttpPost("notifications")]
|
||||
public async Task<IActionResult> NotificationSettings(NotificationSettingsViewModel vm, string command)
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (command == "disable-all")
|
||||
{
|
||||
user.DisabledNotifications = "all";
|
||||
}
|
||||
else if (command == "enable-all")
|
||||
{
|
||||
user.DisabledNotifications = "";
|
||||
}
|
||||
else if (command == "update")
|
||||
{
|
||||
var disabled = vm.DisabledNotifications.Where(item => item.Selected).Select(item => item.Value)
|
||||
.ToArray();
|
||||
user.DisabledNotifications = disabled.Any() is true
|
||||
? string.Join(';', disabled) + ";"
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
await _userManager.UpdateAsync(user);
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Updated successfully.", Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
return RedirectToAction("NotificationSettings");
|
||||
}
|
||||
|
||||
public class NotificationSettingsViewModel
|
||||
{
|
||||
public bool All { get; set; }
|
||||
public List<SelectListItem> DisabledNotifications { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -89,11 +89,11 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
#if DEBUG
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GenerateJunk(int x = 100)
|
||||
public async Task<IActionResult> GenerateJunk(int x = 100, bool admin=true)
|
||||
{
|
||||
for (int i = 0; i < x; i++)
|
||||
{
|
||||
await _notificationSender.SendNotification(new AdminScope(), new JunkNotification());
|
||||
await _notificationSender.SendNotification(admin? (NotificationScope) new AdminScope(): new UserScope(_userManager.GetUserId(User)), new JunkNotification());
|
||||
}
|
||||
|
||||
return RedirectToAction("Index");
|
||||
|
@ -233,19 +233,22 @@ namespace BTCPayServer.Controllers
|
||||
return BadRequest("Payment Request has expired");
|
||||
}
|
||||
|
||||
var statusesAllowedToDisplay = new List<InvoiceStatus>() { InvoiceStatus.New };
|
||||
var validInvoice = result.Invoices.FirstOrDefault(invoice =>
|
||||
Enum.TryParse<InvoiceStatus>(invoice.Status, true, out var status) &&
|
||||
statusesAllowedToDisplay.Contains(status));
|
||||
|
||||
if (validInvoice != null)
|
||||
var stateAllowedToDisplay = new HashSet<InvoiceState>()
|
||||
{
|
||||
new InvoiceState(InvoiceStatus.New, InvoiceExceptionStatus.None),
|
||||
new InvoiceState(InvoiceStatus.New, InvoiceExceptionStatus.PaidPartial),
|
||||
};
|
||||
var currentInvoice = result
|
||||
.Invoices
|
||||
.FirstOrDefault(invoice => stateAllowedToDisplay.Contains(invoice.State));
|
||||
if (currentInvoice != null)
|
||||
{
|
||||
if (redirectToInvoice)
|
||||
{
|
||||
return RedirectToAction("Checkout", "Invoice", new { Id = validInvoice.Id });
|
||||
return RedirectToAction("Checkout", "Invoice", new { Id = currentInvoice.Id });
|
||||
}
|
||||
|
||||
return Ok(validInvoice.Id);
|
||||
return Ok(currentInvoice.Id);
|
||||
}
|
||||
|
||||
if (result.AllowCustomPaymentAmounts && amount != null)
|
||||
@ -297,8 +300,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
var invoices = result.Invoices.Where(requestInvoice =>
|
||||
requestInvoice.Status.Equals(InvoiceState.ToString(InvoiceStatus.New),
|
||||
StringComparison.InvariantCulture) && !requestInvoice.Payments.Any());
|
||||
requestInvoice.State.Status == InvoiceStatus.New && !requestInvoice.Payments.Any());
|
||||
|
||||
if (!invoices.Any())
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer;
|
||||
using BTCPayServer.Data;
|
||||
@ -69,13 +70,13 @@ namespace BTCPayServer.Controllers
|
||||
CurrencyData = cd,
|
||||
LastUpdated = DateTime.Now,
|
||||
Payouts = payouts
|
||||
.Select(entity => new ViewPullPaymentModel.PayoutLine()
|
||||
.Select(entity => new ViewPullPaymentModel.PayoutLine
|
||||
{
|
||||
Id = entity.Entity.Id,
|
||||
Amount = entity.Blob.Amount,
|
||||
AmountFormatted = _currencyNameTable.FormatCurrency(entity.Blob.Amount, blob.Currency),
|
||||
Currency = blob.Currency,
|
||||
Status = entity.Entity.State.ToString(),
|
||||
Status = entity.Entity.State.GetStateString(),
|
||||
Destination = entity.Blob.Destination.Address.ToString(),
|
||||
Link = GetTransactionLink(_networkProvider.GetNetwork<BTCPayNetwork>(entity.Entity.GetPaymentMethodId().CryptoCode), entity.TransactionId),
|
||||
TransactionId = entity.TransactionId
|
||||
|
121
BTCPayServer/Controllers/ServerController.Plugins.cs
Normal file
121
BTCPayServer/Controllers/ServerController.Plugins.cs
Normal file
@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Contracts;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Plugins;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class ServerController
|
||||
{
|
||||
[HttpGet("server/plugins")]
|
||||
public async Task<IActionResult> ListPlugins(
|
||||
[FromServices] PluginService pluginService,
|
||||
[FromServices] BTCPayServerOptions btcPayServerOptions)
|
||||
{
|
||||
IEnumerable<PluginService.AvailablePlugin> availablePlugins;
|
||||
try
|
||||
{
|
||||
availablePlugins = await pluginService.GetRemotePlugins();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = "Remote plugins lookup failed. Try again later."
|
||||
});
|
||||
availablePlugins = Array.Empty<PluginService.AvailablePlugin>();
|
||||
}
|
||||
var res = new ListPluginsViewModel()
|
||||
{
|
||||
Installed = pluginService.LoadedPlugins,
|
||||
Available = availablePlugins,
|
||||
Commands = pluginService.GetPendingCommands(),
|
||||
CanShowRestart = btcPayServerOptions.DockerDeployment
|
||||
};
|
||||
return View(res);
|
||||
}
|
||||
|
||||
public class ListPluginsViewModel
|
||||
{
|
||||
public IEnumerable<IBTCPayServerPlugin> Installed { get; set; }
|
||||
public IEnumerable<PluginService.AvailablePlugin> Available { get; set; }
|
||||
public (string command, string plugin)[] Commands { get; set; }
|
||||
public bool CanShowRestart { get; set; }
|
||||
}
|
||||
|
||||
[HttpPost("server/plugins/uninstall")]
|
||||
public IActionResult UnInstallPlugin(
|
||||
[FromServices] PluginService pluginService, string plugin)
|
||||
{
|
||||
pluginService.UninstallPlugin(plugin);
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Plugin scheduled to be uninstalled.",
|
||||
Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
|
||||
return RedirectToAction("ListPlugins");
|
||||
}
|
||||
[HttpPost("server/plugins/cancel")]
|
||||
public IActionResult CancelPluginCommands(
|
||||
[FromServices] PluginService pluginService, string plugin)
|
||||
{
|
||||
pluginService.CancelCommands(plugin);
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Plugin action cancelled.",
|
||||
Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
|
||||
return RedirectToAction("ListPlugins");
|
||||
}
|
||||
|
||||
[HttpPost("server/plugins/install")]
|
||||
public async Task<IActionResult> InstallPlugin(
|
||||
[FromServices] PluginService pluginService, string plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
await pluginService.DownloadRemotePlugin(plugin);
|
||||
pluginService.InstallPlugin(plugin);
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Plugin scheduled to be installed.",
|
||||
Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "The plugin could not be downloaded. Try again later.", Severity = StatusMessageModel.StatusSeverity.Error
|
||||
});
|
||||
}
|
||||
|
||||
return RedirectToAction("ListPlugins");
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("server/plugins/upload")]
|
||||
public async Task<IActionResult> UploadPlugin([FromServices] PluginService pluginService,
|
||||
List<IFormFile> files)
|
||||
{
|
||||
foreach (var formFile in files.Where(file => file.Length > 0))
|
||||
{
|
||||
await pluginService.UploadPlugin(formFile);
|
||||
pluginService.InstallPlugin(formFile.FileName.TrimEnd(PluginManager.BTCPayPluginSuffix,
|
||||
StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
return RedirectToAction("ListPlugins",
|
||||
new {StatusMessage = "Files uploaded, restart server to load plugins"});
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.AccountViewModels;
|
||||
@ -105,6 +106,12 @@ namespace BTCPayServer.Controllers
|
||||
public async Task<IActionResult> Maintenance(MaintenanceViewModel vm, string command)
|
||||
{
|
||||
vm.CanUseSSH = _sshState.CanUseSSH;
|
||||
|
||||
if (!vm.CanUseSSH)
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Maintenance feature requires access to SSH properly configured in BTCPayServer configuration.";
|
||||
return View(vm);
|
||||
}
|
||||
if (!ModelState.IsValid)
|
||||
return View(vm);
|
||||
if (command == "changedomain")
|
||||
@ -182,6 +189,13 @@ namespace BTCPayServer.Controllers
|
||||
return error;
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"The old docker images will be cleaned soon...";
|
||||
}
|
||||
else if (command == "restart")
|
||||
{
|
||||
var error = await RunSSH(vm, $"btcpay-restart.sh");
|
||||
if (error != null)
|
||||
return error;
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"BTCPay will restart momentarily.";
|
||||
}
|
||||
else
|
||||
{
|
||||
return NotFound();
|
||||
@ -260,8 +274,10 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[Route("server/policies")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Policies(PoliciesSettings settings, string command = "")
|
||||
public async Task<IActionResult> Policies([FromServices] BTCPayNetworkProvider btcPayNetworkProvider,PoliciesSettings settings, string command = "")
|
||||
{
|
||||
|
||||
ViewBag.UpdateUrlPresent = _Options.UpdateUrl != null;
|
||||
ViewBag.AppsList = await GetAppSelectList();
|
||||
if (command == "add-domain")
|
||||
{
|
||||
@ -277,6 +293,8 @@ namespace BTCPayServer.Controllers
|
||||
return View(settings);
|
||||
}
|
||||
|
||||
settings.BlockExplorerLinks = settings.BlockExplorerLinks.Where(tuple => btcPayNetworkProvider.GetNetwork(tuple.CryptoCode).BlockExplorerLinkDefault != tuple.Link).ToList();
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(settings);
|
||||
@ -308,6 +326,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
await _SettingsRepository.UpdateSetting(settings);
|
||||
BlockExplorerLinkStartupTask.SetLinkOnNetworks(settings.BlockExplorerLinks, btcPayNetworkProvider);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Policies updated successfully";
|
||||
return RedirectToAction(nameof(Policies));
|
||||
}
|
||||
|
@ -102,7 +102,8 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
if (vm.WalletFile != null)
|
||||
{
|
||||
if (!DerivationSchemeSettings.TryParseFromWalletFile(await ReadAllText(vm.WalletFile), network, out strategy))
|
||||
if (!DerivationSchemeSettings.TryParseFromWalletFile(await ReadAllText(vm.WalletFile), network,
|
||||
out strategy))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
@ -113,6 +114,19 @@ namespace BTCPayServer.Controllers
|
||||
return View(nameof(AddDerivationScheme), vm);
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(vm.WalletFileContent))
|
||||
{
|
||||
if (!DerivationSchemeSettings.TryParseFromWalletFile(vm.WalletFileContent, network, out strategy))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = "QR import was not in the correct format"
|
||||
});
|
||||
vm.Confirmation = false;
|
||||
return View(nameof(AddDerivationScheme), vm);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
@ -122,16 +136,24 @@ namespace BTCPayServer.Controllers
|
||||
var newStrategy = ParseDerivationStrategy(vm.DerivationScheme, null, network);
|
||||
if (newStrategy.AccountDerivation != strategy?.AccountDerivation)
|
||||
{
|
||||
var accountKey = string.IsNullOrEmpty(vm.AccountKey) ? null : new BitcoinExtPubKey(vm.AccountKey, network.NBitcoinNetwork);
|
||||
var accountKey = string.IsNullOrEmpty(vm.AccountKey)
|
||||
? null
|
||||
: new BitcoinExtPubKey(vm.AccountKey, network.NBitcoinNetwork);
|
||||
if (accountKey != null)
|
||||
{
|
||||
var accountSettings = newStrategy.AccountKeySettings.FirstOrDefault(a => a.AccountKey == accountKey);
|
||||
var accountSettings =
|
||||
newStrategy.AccountKeySettings.FirstOrDefault(a => a.AccountKey == accountKey);
|
||||
if (accountSettings != null)
|
||||
{
|
||||
accountSettings.AccountKeyPath = vm.KeyPath == null ? null : KeyPath.Parse(vm.KeyPath);
|
||||
accountSettings.RootFingerprint = string.IsNullOrEmpty(vm.RootFingerprint) ? (HDFingerprint?)null : new HDFingerprint(NBitcoin.DataEncoders.Encoders.Hex.DecodeData(vm.RootFingerprint));
|
||||
accountSettings.AccountKeyPath =
|
||||
vm.KeyPath == null ? null : KeyPath.Parse(vm.KeyPath);
|
||||
accountSettings.RootFingerprint = string.IsNullOrEmpty(vm.RootFingerprint)
|
||||
? (HDFingerprint?)null
|
||||
: new HDFingerprint(
|
||||
NBitcoin.DataEncoders.Encoders.Hex.DecodeData(vm.RootFingerprint));
|
||||
}
|
||||
}
|
||||
|
||||
strategy = newStrategy;
|
||||
strategy.Source = vm.Source;
|
||||
vm.DerivationScheme = strategy.AccountDerivation.ToString();
|
||||
@ -163,11 +185,11 @@ namespace BTCPayServer.Controllers
|
||||
var willBeExcluded = !vm.Enabled;
|
||||
|
||||
var showAddress = // Show addresses if:
|
||||
// - If the user is testing the hint address in confirmation screen
|
||||
// - If the user is testing the hint address in confirmation screen
|
||||
(vm.Confirmation && !string.IsNullOrWhiteSpace(vm.HintAddress)) ||
|
||||
// - The user is clicking on continue after changing the config
|
||||
(!vm.Confirmation && oldConfig != vm.Config) ||
|
||||
// - The user is clickingon continue without changing config nor enabling/disabling
|
||||
// - The user is clicking on continue without changing config nor enabling/disabling
|
||||
(!vm.Confirmation && oldConfig == vm.Config && willBeExcluded == wasExcluded);
|
||||
|
||||
showAddress = showAddress && strategy != null;
|
||||
@ -179,6 +201,7 @@ namespace BTCPayServer.Controllers
|
||||
await wallet.TrackAsync(strategy.AccountDerivation);
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, strategy);
|
||||
storeBlob.SetExcluded(paymentMethodId, willBeExcluded);
|
||||
storeBlob.Hints.Wallet = false;
|
||||
store.SetStoreBlob(storeBlob);
|
||||
}
|
||||
catch
|
||||
@ -188,21 +211,22 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
await _Repo.UpdateStore(store);
|
||||
_EventAggregator.Publish(new WalletChangedEvent()
|
||||
{
|
||||
WalletId = new WalletId(storeId, cryptoCode)
|
||||
});
|
||||
_EventAggregator.Publish(new WalletChangedEvent() {WalletId = new WalletId(storeId, cryptoCode)});
|
||||
|
||||
if (willBeExcluded != wasExcluded)
|
||||
{
|
||||
var label = willBeExcluded ? "disabled" : "enabled";
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"On-Chain payments for {network.CryptoCode} has been {label}.";
|
||||
TempData[WellKnownTempData.SuccessMessage] =
|
||||
$"On-Chain payments for {network.CryptoCode} has been {label}.";
|
||||
}
|
||||
else
|
||||
{
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Derivation settings for {network.CryptoCode} has been modified.";
|
||||
TempData[WellKnownTempData.SuccessMessage] =
|
||||
$"Derivation settings for {network.CryptoCode} has been modified.";
|
||||
}
|
||||
return RedirectToAction(nameof(UpdateStore), new { storeId = storeId });
|
||||
|
||||
// This is success case when derivation scheme is added to the store
|
||||
return RedirectToAction(nameof(UpdateStore), new {storeId = storeId});
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(vm.HintAddress))
|
||||
{
|
||||
@ -267,7 +291,7 @@ namespace BTCPayServer.Controllers
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Html = $"There was an error generating your wallet: {e.Message}"
|
||||
});
|
||||
return RedirectToAction(nameof(AddDerivationScheme), new { storeId, cryptoCode });
|
||||
return RedirectToAction(nameof(AddDerivationScheme), new {storeId, cryptoCode});
|
||||
}
|
||||
|
||||
if (response == null)
|
||||
@ -277,7 +301,7 @@ namespace BTCPayServer.Controllers
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Html = "There was an error generating your wallet. Is your node available?"
|
||||
});
|
||||
return RedirectToAction(nameof(AddDerivationScheme), new { storeId, cryptoCode });
|
||||
return RedirectToAction(nameof(AddDerivationScheme), new {storeId, cryptoCode});
|
||||
}
|
||||
|
||||
var store = HttpContext.GetStoreData();
|
||||
@ -313,7 +337,7 @@ namespace BTCPayServer.Controllers
|
||||
Mnemonic = response.Mnemonic,
|
||||
Passphrase = response.Passphrase,
|
||||
IsStored = request.SavePrivateKeys,
|
||||
ReturnUrl = Url.Action(nameof(UpdateStore), new { storeId })
|
||||
ReturnUrl = Url.Action(nameof(UpdateStore), new {storeId})
|
||||
};
|
||||
return this.RedirectToRecoverySeedBackup(vm);
|
||||
}
|
||||
@ -330,7 +354,8 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet()
|
||||
{
|
||||
var isAdmin = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings)).Succeeded;
|
||||
var isAdmin = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings))
|
||||
.Succeeded;
|
||||
if (isAdmin)
|
||||
return (true, true);
|
||||
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
|
||||
|
@ -137,6 +137,7 @@ namespace BTCPayServer.Controllers
|
||||
case "save":
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
storeBlob.SetExcluded(paymentMethodId, !vm.Enabled);
|
||||
storeBlob.Hints.Lightning = false;
|
||||
store.SetStoreBlob(storeBlob);
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);
|
||||
await _Repo.UpdateStore(store);
|
||||
|
@ -372,8 +372,8 @@ namespace BTCPayServer.Controllers
|
||||
new PaymentMethodCriteriaViewModel()
|
||||
{
|
||||
PaymentMethod = criteria.PaymentMethod.ToString(),
|
||||
Type = criteria.Above? PaymentMethodCriteriaViewModel.CriteriaType.GreaterThan : PaymentMethodCriteriaViewModel.CriteriaType.LessThan,
|
||||
Value = criteria.Value?.ToString()?? ""
|
||||
Type = criteria.Above ? PaymentMethodCriteriaViewModel.CriteriaType.GreaterThan : PaymentMethodCriteriaViewModel.CriteriaType.LessThan,
|
||||
Value = criteria.Value?.ToString() ?? ""
|
||||
}).ToList();
|
||||
vm.CustomCSS = storeBlob.CustomCSS;
|
||||
vm.CustomLogo = storeBlob.CustomLogo;
|
||||
@ -394,7 +394,9 @@ namespace BTCPayServer.Controllers
|
||||
.Select(o =>
|
||||
new CheckoutExperienceViewModel.Format()
|
||||
{
|
||||
Name = o.ToPrettyString(), Value = o.ToString(), PaymentId = o
|
||||
Name = o.ToPrettyString(),
|
||||
Value = o.ToString(),
|
||||
PaymentId = o
|
||||
}).ToArray();
|
||||
|
||||
var defaultPaymentId = storeData.GetDefaultPaymentId(_NetworkProvider);
|
||||
@ -418,6 +420,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
SetCryptoCurrencies(model, CurrentStore);
|
||||
model.SetLanguages(_LangService, model.DefaultLang);
|
||||
model.PaymentMethodCriteria??= new List<PaymentMethodCriteriaViewModel>();
|
||||
for (var index = 0; index < model.PaymentMethodCriteria.Count; index++)
|
||||
{
|
||||
var methodCriterion = model.PaymentMethodCriteria[index];
|
||||
@ -425,10 +428,12 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (!CurrencyValue.TryParse(methodCriterion.Value, out var value))
|
||||
{
|
||||
model.AddModelError(viewModel => viewModel.PaymentMethodCriteria[index].Value, $"{methodCriterion.PaymentMethod}: invalid format (1.0 USD)", this);
|
||||
model.AddModelError(viewModel => viewModel.PaymentMethodCriteria[index].Value,
|
||||
$"{methodCriterion.PaymentMethod}: invalid format (1.0 USD)", this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
@ -439,7 +444,7 @@ namespace BTCPayServer.Controllers
|
||||
.Where(viewModel => !string.IsNullOrEmpty(viewModel.Value)).Select(viewModel =>
|
||||
{
|
||||
CurrencyValue.TryParse(viewModel.Value, out var cv);
|
||||
return new PaymentMethodCriteria() {Above = viewModel.Type == PaymentMethodCriteriaViewModel.CriteriaType.GreaterThan, Value = cv, PaymentMethod = PaymentMethodId.Parse(viewModel.PaymentMethod)};
|
||||
return new PaymentMethodCriteria() { Above = viewModel.Type == PaymentMethodCriteriaViewModel.CriteriaType.GreaterThan, Value = cv, PaymentMethod = PaymentMethodId.Parse(viewModel.PaymentMethod) };
|
||||
}).ToList();
|
||||
#pragma warning disable 612
|
||||
blob.LightningMaxValue = null;
|
||||
@ -471,32 +476,6 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}")]
|
||||
public IActionResult UpdateStore()
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var vm = new StoreViewModel();
|
||||
vm.Id = store.Id;
|
||||
vm.StoreName = store.StoreName;
|
||||
vm.StoreWebsite = store.StoreWebsite;
|
||||
vm.NetworkFeeMode = storeBlob.NetworkFeeMode;
|
||||
vm.AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice;
|
||||
vm.SpeedPolicy = store.SpeedPolicy;
|
||||
vm.CanDelete = _Repo.CanDeleteStores();
|
||||
AddPaymentMethods(store, storeBlob, vm);
|
||||
vm.MonitoringExpiration = (int)storeBlob.MonitoringExpiration.TotalMinutes;
|
||||
vm.InvoiceExpiration = (int)storeBlob.InvoiceExpiration.TotalMinutes;
|
||||
vm.LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate;
|
||||
vm.PaymentTolerance = storeBlob.PaymentTolerance;
|
||||
vm.PayJoinEnabled = storeBlob.PayJoinEnabled;
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
|
||||
private void AddPaymentMethods(StoreData store, StoreBlob storeBlob, StoreViewModel vm)
|
||||
{
|
||||
@ -556,6 +535,36 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}")]
|
||||
public IActionResult UpdateStore()
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var vm = new StoreViewModel();
|
||||
vm.Id = store.Id;
|
||||
vm.StoreName = store.StoreName;
|
||||
vm.StoreWebsite = store.StoreWebsite;
|
||||
vm.NetworkFeeMode = storeBlob.NetworkFeeMode;
|
||||
vm.AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice;
|
||||
vm.SpeedPolicy = store.SpeedPolicy;
|
||||
vm.CanDelete = _Repo.CanDeleteStores();
|
||||
AddPaymentMethods(store, storeBlob, vm);
|
||||
vm.MonitoringExpiration = (int)storeBlob.MonitoringExpiration.TotalMinutes;
|
||||
vm.InvoiceExpiration = (int)storeBlob.InvoiceExpiration.TotalMinutes;
|
||||
vm.LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate;
|
||||
vm.PaymentTolerance = storeBlob.PaymentTolerance;
|
||||
vm.PayJoinEnabled = storeBlob.PayJoinEnabled;
|
||||
vm.HintWallet = storeBlob.Hints.Wallet;
|
||||
vm.HintLightning = storeBlob.Hints.Lightning;
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}")]
|
||||
public async Task<IActionResult> UpdateStore(StoreViewModel model, string command = null)
|
||||
@ -625,7 +634,6 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
storeId = CurrentStore.Id
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@ -974,10 +982,30 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
storeId = CurrentStore.Id
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/dismissHint")]
|
||||
[IgnoreAntiforgeryToken]
|
||||
public async Task<IActionResult> DismissHint(string id)
|
||||
{
|
||||
var blob = CurrentStore.GetStoreBlob();
|
||||
if (id == "Wallet" || id == "Lightning")
|
||||
{
|
||||
try
|
||||
{
|
||||
var prop = blob.Hints.GetType().GetProperty(id);
|
||||
prop.SetValue(blob.Hints, false);
|
||||
}
|
||||
// disregard parse errors
|
||||
catch { }
|
||||
|
||||
|
||||
if (CurrentStore.SetStoreBlob(blob))
|
||||
{
|
||||
await _Repo.UpdateStore(CurrentStore);
|
||||
}
|
||||
}
|
||||
return Content("ack");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using ExchangeSharp;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@ -37,6 +38,23 @@ namespace BTCPayServer.Controllers
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("create")]
|
||||
public async Task<IActionResult> CreateStore(CreateStoreViewModel vm)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(vm);
|
||||
}
|
||||
var store = await _Repo.CreateStore(GetUserId(), vm.Name);
|
||||
CreatedStoreId = store.Id;
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Store successfully created";
|
||||
return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new
|
||||
{
|
||||
storeId = store.Id
|
||||
});
|
||||
}
|
||||
|
||||
public string CreatedStoreId
|
||||
{
|
||||
get; set;
|
||||
@ -108,34 +126,20 @@ namespace BTCPayServer.Controllers
|
||||
for (int i = 0; i < stores.Length; i++)
|
||||
{
|
||||
var store = stores[i];
|
||||
var blob = store.GetStoreBlob();
|
||||
result.Stores.Add(new StoresViewModel.StoreViewModel()
|
||||
{
|
||||
Id = store.Id,
|
||||
|
||||
Name = store.StoreName,
|
||||
WebSite = store.StoreWebsite,
|
||||
IsOwner = store.Role == StoreRoles.Owner
|
||||
IsOwner = store.Role == StoreRoles.Owner,
|
||||
HintWalletWarning = blob.Hints.Wallet
|
||||
});
|
||||
}
|
||||
return View(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("create")]
|
||||
public async Task<IActionResult> CreateStore(CreateStoreViewModel vm)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(vm);
|
||||
}
|
||||
var store = await _Repo.CreateStore(GetUserId(), vm.Name);
|
||||
CreatedStoreId = store.Id;
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Store successfully created";
|
||||
return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new
|
||||
{
|
||||
storeId = store.Id
|
||||
});
|
||||
}
|
||||
|
||||
private string GetUserId()
|
||||
{
|
||||
return _UserManager.GetUserId(User);
|
||||
|
@ -80,6 +80,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
vm.Decoded = psbt.ToString();
|
||||
vm.PSBT = psbt.ToBase64();
|
||||
vm.PSBTHex = psbt.ToHex();
|
||||
}
|
||||
|
||||
return View(nameof(WalletPSBT), vm ?? new WalletPSBTViewModel() { CryptoCode = walletId.CryptoCode });
|
||||
@ -104,6 +105,8 @@ namespace BTCPayServer.Controllers
|
||||
ModelState.AddModelError(nameof(vm.PSBT), "Invalid PSBT");
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
vm.PSBTHex = psbt.ToHex();
|
||||
var res = await TryHandleSigningCommands(walletId, psbt, command, new SigningContextModel(psbt));
|
||||
if (res != null)
|
||||
{
|
||||
@ -117,6 +120,7 @@ namespace BTCPayServer.Controllers
|
||||
ModelState.Remove(nameof(vm.FileName));
|
||||
ModelState.Remove(nameof(vm.UploadedPSBTFile));
|
||||
vm.PSBT = psbt.ToBase64();
|
||||
vm.PSBTHex = psbt.ToHex();
|
||||
vm.FileName = vm.UploadedPSBTFile?.FileName;
|
||||
return View(vm);
|
||||
|
||||
|
@ -188,6 +188,13 @@ namespace BTCPayServer.Data
|
||||
public bool RedirectAutomatically { get; set; }
|
||||
public bool PayJoinEnabled { get; set; }
|
||||
|
||||
public StoreHints Hints { get; set; }
|
||||
public class StoreHints
|
||||
{
|
||||
public bool Wallet { get; set; }
|
||||
public bool Lightning { get; set; }
|
||||
}
|
||||
|
||||
public IPaymentFilter GetExcludedPaymentMethods()
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
@ -46,6 +46,9 @@ namespace BTCPayServer.Data
|
||||
var result = storeData.StoreBlob == null ? new StoreBlob() : new Serializer(null).ToObject<StoreBlob>(Encoding.UTF8.GetString(storeData.StoreBlob));
|
||||
if (result.PreferredExchange == null)
|
||||
result.PreferredExchange = CoinGeckoRateProvider.CoinGeckoName;
|
||||
|
||||
if (result.Hints == null)
|
||||
result.Hints = new StoreBlob.StoreHints();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -224,15 +224,7 @@ namespace BTCPayServer
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void SetStatusMessageModel(this ITempDataDictionary tempData, StatusMessageModel statusMessage)
|
||||
{
|
||||
if (statusMessage == null)
|
||||
{
|
||||
tempData.Remove("StatusMessageModel");
|
||||
return;
|
||||
}
|
||||
tempData["StatusMessageModel"] = JObject.FromObject(statusMessage).ToString(Formatting.None);
|
||||
}
|
||||
|
||||
|
||||
public static StatusMessageModel GetStatusMessageModel(this ITempDataDictionary tempData)
|
||||
{
|
||||
|
18
BTCPayServer/Extensions/StringExtensions.cs
Normal file
18
BTCPayServer/Extensions/StringExtensions.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public static class StringExtensions
|
||||
{
|
||||
public static string TrimEnd(this string input, string suffixToRemove,
|
||||
StringComparison comparisonType)
|
||||
{
|
||||
if (input != null && suffixToRemove != null
|
||||
&& input.EndsWith(suffixToRemove, comparisonType))
|
||||
{
|
||||
return input.Substring(0, input.Length - suffixToRemove.Length);
|
||||
}
|
||||
else return input;
|
||||
}
|
||||
}
|
||||
}
|
@ -29,12 +29,20 @@ namespace BTCPayServer.HostedServices
|
||||
public List<object> Events { get; set; } = new List<object>();
|
||||
|
||||
bool _Dirty = false;
|
||||
private bool _Unaffect;
|
||||
|
||||
public void MarkDirty()
|
||||
{
|
||||
_Dirty = true;
|
||||
}
|
||||
|
||||
public void UnaffectAddresses()
|
||||
{
|
||||
_Unaffect = true;
|
||||
}
|
||||
|
||||
public bool Dirty => _Dirty;
|
||||
public bool Unaffect => _Unaffect;
|
||||
}
|
||||
|
||||
readonly InvoiceRepository _InvoiceRepository;
|
||||
@ -57,21 +65,18 @@ namespace BTCPayServer.HostedServices
|
||||
readonly CompositeDisposable leases = new CompositeDisposable();
|
||||
|
||||
|
||||
private async Task UpdateInvoice(UpdateInvoiceContext context)
|
||||
private void UpdateInvoice(UpdateInvoiceContext context)
|
||||
{
|
||||
var invoice = context.Invoice;
|
||||
if (invoice.Status == InvoiceStatus.New && invoice.ExpirationTime <= DateTimeOffset.UtcNow)
|
||||
{
|
||||
context.MarkDirty();
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
|
||||
context.UnaffectAddresses();
|
||||
invoice.Status = InvoiceStatus.Expired;
|
||||
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.Expired));
|
||||
if (invoice.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
|
||||
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.ExpiredPaidPartial));
|
||||
}
|
||||
|
||||
var payments = invoice.GetPayments().Where(p => p.Accounted).ToArray();
|
||||
var allPaymentMethods = invoice.GetPaymentMethods();
|
||||
var paymentMethod = GetNearestClearedPayment(allPaymentMethods, out var accounting);
|
||||
if (paymentMethod == null)
|
||||
@ -85,7 +90,7 @@ namespace BTCPayServer.HostedServices
|
||||
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.PaidInFull));
|
||||
invoice.Status = InvoiceStatus.Paid;
|
||||
invoice.ExceptionStatus = accounting.Paid > accounting.TotalDue ? InvoiceExceptionStatus.PaidOver : InvoiceExceptionStatus.None;
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
context.UnaffectAddresses();
|
||||
context.MarkDirty();
|
||||
}
|
||||
else if (invoice.Status == InvoiceStatus.Expired && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidLate)
|
||||
@ -136,14 +141,14 @@ namespace BTCPayServer.HostedServices
|
||||
// And not enough amount confirmed
|
||||
(confirmedAccounting.Paid < accounting.MinimumTotalDue))
|
||||
{
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
context.UnaffectAddresses();
|
||||
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.FailedToConfirm));
|
||||
invoice.Status = InvoiceStatus.Invalid;
|
||||
context.MarkDirty();
|
||||
}
|
||||
else if (confirmedAccounting.Paid >= accounting.MinimumTotalDue)
|
||||
{
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
context.UnaffectAddresses();
|
||||
invoice.Status = InvoiceStatus.Confirmed;
|
||||
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.Confirmed));
|
||||
context.MarkDirty();
|
||||
@ -278,7 +283,11 @@ namespace BTCPayServer.HostedServices
|
||||
if (invoice == null)
|
||||
break;
|
||||
var updateContext = new UpdateInvoiceContext(invoice);
|
||||
await UpdateInvoice(updateContext);
|
||||
UpdateInvoice(updateContext);
|
||||
if (updateContext.Unaffect)
|
||||
{
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
}
|
||||
if (updateContext.Dirty)
|
||||
{
|
||||
await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.GetInvoiceState());
|
||||
|
@ -12,6 +12,7 @@ using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Payments.PayJoin;
|
||||
using BTCPayServer.Plugins;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
using BTCPayServer.Security.GreenField;
|
||||
@ -83,6 +84,7 @@ namespace BTCPayServer.Hosting
|
||||
services.AddEthereumLike();
|
||||
#endif
|
||||
services.TryAddSingleton<SettingsRepository>();
|
||||
services.TryAddSingleton<ISettingsRepository>(provider => provider.GetService<SettingsRepository>());
|
||||
services.TryAddSingleton<LabelFactory>();
|
||||
services.TryAddSingleton<TorServices>();
|
||||
services.TryAddSingleton<SocketFactory>();
|
||||
@ -91,6 +93,7 @@ namespace BTCPayServer.Hosting
|
||||
services.TryAddSingleton<BTCPayServerOptions>(o =>
|
||||
o.GetRequiredService<IOptions<BTCPayServerOptions>>().Value);
|
||||
services.AddStartupTask<MigrationStartupTask>();
|
||||
services.AddStartupTask<BlockExplorerLinkStartupTask>();
|
||||
services.TryAddSingleton<InvoiceRepository>(o =>
|
||||
{
|
||||
var opts = o.GetRequiredService<BTCPayServerOptions>();
|
||||
@ -110,24 +113,30 @@ namespace BTCPayServer.Hosting
|
||||
{
|
||||
var opts = o.GetRequiredService<BTCPayServerOptions>();
|
||||
ApplicationDbContextFactory dbContext = null;
|
||||
if (!String.IsNullOrEmpty(opts.PostgresConnectionString))
|
||||
if (!string.IsNullOrEmpty(opts.PostgresConnectionString))
|
||||
{
|
||||
Logs.Configuration.LogInformation($"Postgres DB used ({opts.PostgresConnectionString})");
|
||||
Logs.Configuration.LogInformation($"Postgres DB used");
|
||||
dbContext = new ApplicationDbContextFactory(DatabaseType.Postgres, opts.PostgresConnectionString);
|
||||
}
|
||||
else if (!String.IsNullOrEmpty(opts.MySQLConnectionString))
|
||||
else if (!string.IsNullOrEmpty(opts.MySQLConnectionString))
|
||||
{
|
||||
Logs.Configuration.LogInformation($"MySQL DB used ({opts.MySQLConnectionString})");
|
||||
Logs.Configuration.LogInformation($"MySQL DB used");
|
||||
Logs.Configuration.LogWarning("MySQL is not widely tested and should be considered experimental, we advise you to use postgres instead.");
|
||||
dbContext = new ApplicationDbContextFactory(DatabaseType.MySQL, opts.MySQLConnectionString);
|
||||
}
|
||||
else
|
||||
else if (!string.IsNullOrEmpty(opts.SQLiteFileName))
|
||||
{
|
||||
var connStr = "Data Source=" + Path.Combine(opts.DataDir, "sqllite.db");
|
||||
Logs.Configuration.LogInformation($"SQLite DB used ({connStr})");
|
||||
var connStr = "Data Source=" +(Path.IsPathRooted(opts.SQLiteFileName)
|
||||
? opts.SQLiteFileName
|
||||
: Path.Combine(opts.DataDir, opts.SQLiteFileName));
|
||||
Logs.Configuration.LogInformation($"SQLite DB used");
|
||||
Logs.Configuration.LogWarning("SQLite is not widely tested and should be considered experimental, we advise you to use postgres instead.");
|
||||
dbContext = new ApplicationDbContextFactory(DatabaseType.Sqlite, connStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ConfigException("No database option was configured.");
|
||||
}
|
||||
|
||||
return dbContext;
|
||||
});
|
||||
@ -139,6 +148,7 @@ namespace BTCPayServer.Hosting
|
||||
});
|
||||
|
||||
services.TryAddSingleton<AppService>();
|
||||
services.AddSingleton<PluginService>();
|
||||
services.TryAddTransient<Safe>();
|
||||
services.TryAddSingleton<Ganss.XSS.HtmlSanitizer>(o =>
|
||||
{
|
||||
|
44
BTCPayServer/Hosting/BlockExplorerLinkStartupTask.cs
Normal file
44
BTCPayServer/Hosting/BlockExplorerLinkStartupTask.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
{
|
||||
public class BlockExplorerLinkStartupTask : IStartupTask
|
||||
{
|
||||
private readonly SettingsRepository _settingsRepository;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
|
||||
public BlockExplorerLinkStartupTask(SettingsRepository settingsRepository,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider)
|
||||
{
|
||||
_settingsRepository = settingsRepository;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var settings = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
|
||||
if (settings?.BlockExplorerLinks?.Any() is true)
|
||||
{
|
||||
SetLinkOnNetworks(settings.BlockExplorerLinks, _btcPayNetworkProvider);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetLinkOnNetworks(List<PoliciesSettings.BlockExplorerOverrideItem> links,
|
||||
BTCPayNetworkProvider networkProvider)
|
||||
{
|
||||
var networks = networkProvider.GetAll();
|
||||
foreach (var network in networks)
|
||||
{
|
||||
var overrideLink = links.SingleOrDefault(item =>
|
||||
item.CryptoCode.Equals(network.CryptoCode, StringComparison.InvariantCultureIgnoreCase));
|
||||
network.BlockExplorerLink = overrideLink?.Link ?? network.BlockExplorerLinkDefault;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ using BTCPayServer.Data;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.PaymentRequest;
|
||||
using BTCPayServer.Plugins;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Storage;
|
||||
@ -56,7 +57,7 @@ namespace BTCPayServer.Hosting
|
||||
services.AddProviderStorage();
|
||||
services.AddSession();
|
||||
services.AddSignalR();
|
||||
services.AddMvc(o =>
|
||||
var mvcBuilder= services.AddMvc(o =>
|
||||
{
|
||||
o.Filters.Add(new XFrameOptionsAttribute("DENY"));
|
||||
o.Filters.Add(new XContentTypeOptionsAttribute("nosniff"));
|
||||
@ -91,7 +92,11 @@ namespace BTCPayServer.Hosting
|
||||
#if RAZOR_RUNTIME_COMPILE
|
||||
.AddRazorRuntimeCompilation()
|
||||
#endif
|
||||
.AddPlugins(services, Configuration, LoggerFactory)
|
||||
.AddControllersAsServices();
|
||||
|
||||
|
||||
|
||||
services.TryAddScoped<ContentSecurityPolicies>();
|
||||
services.Configure<IdentityOptions>(options =>
|
||||
{
|
||||
@ -174,6 +179,7 @@ namespace BTCPayServer.Hosting
|
||||
private static void ConfigureCore(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider prov, ILoggerFactory loggerFactory, BTCPayServerOptions options)
|
||||
{
|
||||
Logs.Configure(loggerFactory);
|
||||
app.UsePlugins();
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
|
@ -33,6 +33,9 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
[Display(Name = "Callback Notification Url")]
|
||||
[Uri]
|
||||
public string NotificationUrl { get; set; }
|
||||
[Display(Name = "Redirect Url")]
|
||||
[Uri]
|
||||
public string RedirectUrl { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(30)]
|
||||
|
@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Contracts;
|
||||
|
||||
namespace BTCPayServer.Models.NotificationViewModels
|
||||
{
|
||||
@ -11,12 +11,5 @@ namespace BTCPayServer.Models.NotificationViewModels
|
||||
public List<NotificationViewModel> Items { get; set; }
|
||||
}
|
||||
|
||||
public class NotificationViewModel
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
public string Body { get; set; }
|
||||
public string ActionLink { get; set; }
|
||||
public bool Seen { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using PaymentRequestData = BTCPayServer.Data.PaymentRequestData;
|
||||
@ -139,7 +141,9 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||
public DateTime ExpiryDate { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public string AmountFormatted { get; set; }
|
||||
public string Status { get; set; }
|
||||
public InvoiceState State { get; set; }
|
||||
public InvoiceStatus Status { get; set; }
|
||||
public string StateFormatted { get; set; }
|
||||
|
||||
public List<PaymentRequestInvoicePayment> Payments { get; set; }
|
||||
public string Currency { get; set; }
|
||||
@ -149,6 +153,10 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||
{
|
||||
public string PaymentMethod { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public string RateFormatted { get; set; }
|
||||
public decimal Paid { get; set; }
|
||||
public string PaidFormatted { get; set; }
|
||||
public DateTime ReceivedDate { get; set; }
|
||||
public string Link { get; set; }
|
||||
public string Id { get; set; }
|
||||
}
|
||||
|
@ -35,6 +35,8 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
|
||||
[Display(Name = "Wallet File")]
|
||||
public IFormFile WalletFile { get; set; }
|
||||
[Display(Name = "Wallet File Content")]
|
||||
public string WalletFileContent { get; set; }
|
||||
public string Config { get; set; }
|
||||
public string Source { get; set; }
|
||||
public string DerivationSchemeFormat { get; set; }
|
||||
|
@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Validation;
|
||||
using static BTCPayServer.Data.StoreBlob;
|
||||
|
||||
namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
@ -90,6 +91,9 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
[Display(Name = "Enable Payjoin/P2EP")]
|
||||
public bool PayJoinEnabled { get; set; }
|
||||
|
||||
public bool HintWallet { get; set; }
|
||||
public bool HintLightning { get; set; }
|
||||
|
||||
public class LightningNode
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
|
@ -4,32 +4,15 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
public class StoresViewModel
|
||||
{
|
||||
public List<StoreViewModel> Stores
|
||||
{
|
||||
get; set;
|
||||
} = new List<StoreViewModel>();
|
||||
public List<StoreViewModel> Stores { get; set; } = new List<StoreViewModel>();
|
||||
|
||||
public class StoreViewModel
|
||||
{
|
||||
public string Name
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string WebSite
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Id
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public bool IsOwner
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string WebSite { get; set; }
|
||||
public bool IsOwner { get; set; }
|
||||
public bool HintWalletWarning { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||
public string CryptoCode { get; set; }
|
||||
public string Decoded { get; set; }
|
||||
string _FileName;
|
||||
public string PSBTHex { get; set; }
|
||||
public bool NBXSeedAvailable { get; set; }
|
||||
|
||||
public string FileName
|
||||
|
@ -53,7 +53,7 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||
public string RateError { get; set; }
|
||||
public bool SupportRBF { get; set; }
|
||||
[Display(Name = "Always include non-witness UTXO if available")]
|
||||
public bool AlwaysIncludeNonWitnessUTXO { get; set; } = true;
|
||||
public bool AlwaysIncludeNonWitnessUTXO { get; set; }
|
||||
[Display(Name = "Allow fee increase (RBF)")]
|
||||
public ThreeStateBool AllowFeeBump { get; set; }
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Models.WalletViewModels
|
||||
{
|
||||
@ -22,6 +23,7 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||
|
||||
public class WalletSettingsAccountKeyViewModel
|
||||
{
|
||||
[JsonProperty("ExtPubKey")]
|
||||
[DisplayName("Account key")]
|
||||
public string AccountKey { get; set; }
|
||||
[DisplayName("Master fingerprint")]
|
||||
|
@ -98,37 +98,51 @@ namespace BTCPayServer.PaymentRequest
|
||||
AnyPendingInvoice = pendingInvoice != null,
|
||||
PendingInvoiceHasPayments = pendingInvoice != null &&
|
||||
pendingInvoice.ExceptionStatus != InvoiceExceptionStatus.None,
|
||||
Invoices = invoices.Select(entity => new ViewPaymentRequestViewModel.PaymentRequestInvoice()
|
||||
Invoices = invoices.Select(entity =>
|
||||
{
|
||||
Id = entity.Id,
|
||||
Amount = entity.Price,
|
||||
AmountFormatted = _currencies.FormatCurrency(entity.Price, blob.Currency),
|
||||
Currency = entity.Currency,
|
||||
ExpiryDate = entity.ExpirationTime.DateTime,
|
||||
Status = entity.GetInvoiceState().ToString(),
|
||||
Payments = entity
|
||||
.GetPayments()
|
||||
.Select(paymentEntity =>
|
||||
var state = entity.GetInvoiceState();
|
||||
return new ViewPaymentRequestViewModel.PaymentRequestInvoice
|
||||
{
|
||||
var paymentData = paymentEntity.GetCryptoPaymentData();
|
||||
var paymentMethodId = paymentEntity.GetPaymentMethodId();
|
||||
if (paymentData is null || paymentMethodId is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Id = entity.Id,
|
||||
Amount = entity.Price,
|
||||
AmountFormatted = _currencies.FormatCurrency(entity.Price, blob.Currency),
|
||||
Currency = entity.Currency,
|
||||
ExpiryDate = entity.ExpirationTime.DateTime,
|
||||
State = state,
|
||||
StateFormatted = state.ToString(),
|
||||
Payments = entity
|
||||
.GetPayments()
|
||||
.Select(paymentEntity =>
|
||||
{
|
||||
var paymentData = paymentEntity.GetCryptoPaymentData();
|
||||
var paymentMethodId = paymentEntity.GetPaymentMethodId();
|
||||
if (paymentData is null || paymentMethodId is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string txId = paymentData.GetPaymentId();
|
||||
string link = GetTransactionLink(paymentMethodId, txId);
|
||||
return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment()
|
||||
{
|
||||
Amount = paymentData.GetValue(),
|
||||
PaymentMethod = paymentMethodId.ToString(),
|
||||
Link = link,
|
||||
Id = txId
|
||||
};
|
||||
})
|
||||
.Where(payment => payment != null)
|
||||
.ToList()
|
||||
string txId = paymentData.GetPaymentId();
|
||||
string link = GetTransactionLink(paymentMethodId, txId);
|
||||
var paymentMethod = entity.GetPaymentMethod(paymentMethodId);
|
||||
var amount = paymentData.GetValue();
|
||||
var rate = paymentMethod.Rate;
|
||||
var paid = (amount - paymentEntity.NetworkFee) * rate;
|
||||
|
||||
return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment
|
||||
{
|
||||
Amount = amount,
|
||||
Paid = paid,
|
||||
ReceivedDate = paymentEntity.ReceivedTime.DateTime,
|
||||
PaidFormatted = _currencies.FormatCurrency(paid, blob.Currency),
|
||||
RateFormatted = _currencies.FormatCurrency(rate, blob.Currency),
|
||||
PaymentMethod = paymentMethodId.ToPrettyString(),
|
||||
Link = link,
|
||||
Id = txId
|
||||
};
|
||||
})
|
||||
.Where(payment => payment != null)
|
||||
.ToList()
|
||||
};
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
|
@ -138,9 +138,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
switch (newEvent)
|
||||
{
|
||||
case NBXplorer.Models.NewBlockEvent evt:
|
||||
await Task.WhenAll((await _InvoiceRepository.GetPendingInvoices())
|
||||
.Select(invoiceId => UpdatePaymentStates(wallet, invoiceId))
|
||||
.ToArray());
|
||||
await UpdatePaymentStates(wallet, await _InvoiceRepository.GetPendingInvoices());
|
||||
_Aggregator.Publish(new Events.NewBlockEvent() { CryptoCode = evt.CryptoCode });
|
||||
break;
|
||||
case NBXplorer.Models.NewTransactionEvent evt:
|
||||
@ -206,11 +204,21 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
}
|
||||
}
|
||||
|
||||
async Task UpdatePaymentStates(BTCPayWallet wallet, string[] invoiceIds)
|
||||
{
|
||||
var invoices = await _InvoiceRepository.GetInvoices(invoiceIds);
|
||||
await Task.WhenAll(invoices.Select(i => UpdatePaymentStates(wallet, i)).ToArray());
|
||||
}
|
||||
async Task<InvoiceEntity> UpdatePaymentStates(BTCPayWallet wallet, string invoiceId)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId, false);
|
||||
if (invoice == null)
|
||||
return null;
|
||||
return await UpdatePaymentStates(wallet, invoice);
|
||||
}
|
||||
async Task<InvoiceEntity> UpdatePaymentStates(BTCPayWallet wallet, InvoiceEntity invoice)
|
||||
{
|
||||
|
||||
List<PaymentEntity> updatedPaymentEntities = new List<PaymentEntity>();
|
||||
var transactions = await wallet.GetTransactions(invoice.GetAllBitcoinPaymentData()
|
||||
.Select(p => p.Outpoint.Hash)
|
||||
@ -363,7 +371,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
var address = network.NBXplorerNetwork.CreateAddress(strategy, coin.KeyPath, coin.ScriptPubKey);
|
||||
|
||||
var paymentData = new BitcoinLikePaymentData(address, coin.Value, coin.OutPoint,
|
||||
transaction.Transaction.RBF, coin.KeyPath);
|
||||
transaction?.Transaction is null ? true : transaction.Transaction.RBF, coin.KeyPath);
|
||||
|
||||
var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin.Timestamp, paymentData, network).ConfigureAwait(false);
|
||||
alreadyAccounted.Add(coin.OutPoint);
|
||||
|
@ -25,10 +25,5 @@ namespace BTCPayServer.Payments.Lightning
|
||||
{
|
||||
return 0.0m;
|
||||
}
|
||||
|
||||
public void SetPaymentDetails(IPaymentMethodDetails newPaymentMethodDetails)
|
||||
{
|
||||
BOLT11 = newPaymentMethodDetails.GetPaymentDestination();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -137,76 +137,32 @@ namespace BTCPayServer.Payments.Lightning
|
||||
readonly CompositeDisposable leases = new CompositeDisposable();
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
leases.Add(_Aggregator.Subscribe<Events.InvoiceEvent>(inv =>
|
||||
leases.Add(_Aggregator.Subscribe<Events.InvoiceEvent>(async inv =>
|
||||
{
|
||||
if (inv.Name == InvoiceEvent.Created)
|
||||
{
|
||||
_CheckInvoices.Writer.TryWrite(inv.Invoice.Id);
|
||||
}
|
||||
|
||||
if (inv.Name == InvoiceEvent.ReceivedPayment && inv.Invoice.Status == InvoiceStatus.New && inv.Invoice.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
|
||||
{
|
||||
var pm = inv.Invoice.GetPaymentMethods().First();
|
||||
if (pm.Calculate().Due.GetValue(pm.Network as BTCPayNetwork) > 0m)
|
||||
{
|
||||
await CreateNewLNInvoiceForBTCPayInvoice(inv.Invoice);
|
||||
}
|
||||
}
|
||||
}));
|
||||
leases.Add(_Aggregator.Subscribe<Events.InvoiceDataChangedEvent>(async inv =>
|
||||
{
|
||||
if (inv.State.Status == InvoiceStatus.New &&
|
||||
inv.State.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
|
||||
{
|
||||
|
||||
var invoice = await _InvoiceRepository.GetInvoice(inv.InvoiceId);
|
||||
var paymentMethods = invoice.GetPaymentMethods()
|
||||
.Where(method => method.GetId().PaymentType == PaymentTypes.LightningLike).ToArray();
|
||||
var store = await _storeRepository.FindStore(invoice.StoreId);
|
||||
if (paymentMethods.Any())
|
||||
{
|
||||
var logs = new InvoiceLogs();
|
||||
logs.Write("Partial payment detected, attempting to update all lightning payment methods with new bolt11 with correct due amount.", InvoiceEventData.EventSeverity.Info);
|
||||
foreach (var paymentMethod in paymentMethods)
|
||||
{
|
||||
try
|
||||
{
|
||||
var supportedMethod =
|
||||
invoice.GetSupportedPaymentMethod<LightningSupportedPaymentMethod>(
|
||||
paymentMethod.GetId()).First();
|
||||
var prepObj =
|
||||
_lightningLikePaymentHandler.PreparePayment(supportedMethod, store,
|
||||
paymentMethod.Network);
|
||||
var newPaymentMethodDetails =
|
||||
await _lightningLikePaymentHandler.CreatePaymentMethodDetails(
|
||||
logs, supportedMethod,
|
||||
paymentMethod, store, paymentMethod.Network, prepObj);
|
||||
|
||||
|
||||
var instanceListenerKey = (paymentMethod.Network.CryptoCode,
|
||||
supportedMethod.GetLightningUrl().ToString());
|
||||
if (_InstanceListeners.TryGetValue(instanceListenerKey, out var instanceListener))
|
||||
{
|
||||
|
||||
await _InvoiceRepository.NewPaymentDetails(invoice.Id, newPaymentMethodDetails,
|
||||
paymentMethod.Network);
|
||||
|
||||
instanceListener.AddListenedInvoice(new ListenedInvoice()
|
||||
{
|
||||
Expiration = invoice.ExpirationTime,
|
||||
Uri = supportedMethod.GetLightningUrl().BaseUri.AbsoluteUri,
|
||||
PaymentMethodDetails = (LightningLikePaymentMethodDetails) newPaymentMethodDetails,
|
||||
SupportedPaymentMethod = supportedMethod,
|
||||
PaymentMethod = paymentMethod,
|
||||
Network = (BTCPayNetwork) paymentMethod.Network,
|
||||
InvoiceId = invoice.Id
|
||||
});
|
||||
|
||||
_Aggregator.Publish(new Events.InvoiceNewPaymentDetailsEvent(invoice.Id,
|
||||
newPaymentMethodDetails, paymentMethod.GetId()));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logs.Write($"Could not update {paymentMethod.GetId().ToPrettyString()}: {e.Message}",
|
||||
InvoiceEventData.EventSeverity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
await _InvoiceRepository.AddInvoiceLogs(invoice.Id, logs);
|
||||
_CheckInvoices.Writer.TryWrite(inv.InvoiceId);
|
||||
}
|
||||
await CreateNewLNInvoiceForBTCPayInvoice(invoice);
|
||||
}
|
||||
|
||||
}));
|
||||
_CheckingInvoice = CheckingInvoice(_Cts.Token);
|
||||
_ListenPoller = new Timer(async s =>
|
||||
@ -224,6 +180,66 @@ namespace BTCPayServer.Payments.Lightning
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task CreateNewLNInvoiceForBTCPayInvoice(InvoiceEntity invoice)
|
||||
{
|
||||
var paymentMethods = invoice.GetPaymentMethods()
|
||||
.Where(method => method.GetId().PaymentType == PaymentTypes.LightningLike)
|
||||
.ToArray();
|
||||
var store = await _storeRepository.FindStore(invoice.StoreId);
|
||||
if (paymentMethods.Any())
|
||||
{
|
||||
var logs = new InvoiceLogs();
|
||||
logs.Write(
|
||||
"Partial payment detected, attempting to update all lightning payment methods with new bolt11 with correct due amount.",
|
||||
InvoiceEventData.EventSeverity.Info);
|
||||
foreach (var paymentMethod in paymentMethods)
|
||||
{
|
||||
try
|
||||
{
|
||||
var supportedMethod = invoice
|
||||
.GetSupportedPaymentMethod<LightningSupportedPaymentMethod>(paymentMethod.GetId()).First();
|
||||
var prepObj =
|
||||
_lightningLikePaymentHandler.PreparePayment(supportedMethod, store, paymentMethod.Network);
|
||||
var newPaymentMethodDetails =
|
||||
(LightningLikePaymentMethodDetails)(await _lightningLikePaymentHandler
|
||||
.CreatePaymentMethodDetails(logs, supportedMethod, paymentMethod, store,
|
||||
paymentMethod.Network, prepObj));
|
||||
|
||||
var instanceListenerKey = (paymentMethod.Network.CryptoCode,
|
||||
supportedMethod.GetLightningUrl().ToString());
|
||||
if (_InstanceListeners.TryGetValue(instanceListenerKey, out var instanceListener))
|
||||
{
|
||||
await _InvoiceRepository.NewPaymentDetails(invoice.Id, newPaymentMethodDetails,
|
||||
paymentMethod.Network);
|
||||
|
||||
instanceListener.AddListenedInvoice(new ListenedInvoice()
|
||||
{
|
||||
Expiration = invoice.ExpirationTime,
|
||||
Uri = supportedMethod.GetLightningUrl().BaseUri.AbsoluteUri,
|
||||
PaymentMethodDetails = newPaymentMethodDetails,
|
||||
SupportedPaymentMethod = supportedMethod,
|
||||
PaymentMethod = paymentMethod,
|
||||
Network = (BTCPayNetwork)paymentMethod.Network,
|
||||
InvoiceId = invoice.Id
|
||||
});
|
||||
|
||||
_Aggregator.Publish(new Events.InvoiceNewPaymentDetailsEvent(invoice.Id,
|
||||
newPaymentMethodDetails, paymentMethod.GetId()));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logs.Write($"Could not update {paymentMethod.GetId().ToPrettyString()}: {e.Message}",
|
||||
InvoiceEventData.EventSeverity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
await _InvoiceRepository.AddInvoiceLogs(invoice.Id, logs);
|
||||
_CheckInvoices.Writer.TryWrite(invoice.Id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TimeSpan _PollInterval = TimeSpan.FromMinutes(1.0);
|
||||
public TimeSpan PollInterval
|
||||
{
|
||||
|
184
BTCPayServer/Plugins/PluginManager.cs
Normal file
184
BTCPayServer/Plugins/PluginManager.cs
Normal file
@ -0,0 +1,184 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Contracts;
|
||||
using McMaster.NETCore.Plugins;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BTCPayServer.Plugins
|
||||
{
|
||||
public static class PluginManager
|
||||
{
|
||||
public const string BTCPayPluginSuffix = ".btcpay";
|
||||
private static readonly List<Assembly> _pluginAssemblies = new List<Assembly>();
|
||||
private static ILogger _logger;
|
||||
|
||||
public static IMvcBuilder AddPlugins(this IMvcBuilder mvcBuilder, IServiceCollection serviceCollection,
|
||||
IConfiguration config, ILoggerFactory loggerFactory)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger(typeof(PluginManager));
|
||||
var pluginsFolder = config.GetPluginDir(DefaultConfiguration.GetNetworkType(config));
|
||||
var plugins = new List<IBTCPayServerPlugin>();
|
||||
|
||||
_logger.LogInformation($"Loading plugins from {pluginsFolder}");
|
||||
Directory.CreateDirectory(pluginsFolder);
|
||||
ExecuteCommands(pluginsFolder);
|
||||
List<(PluginLoader, Assembly, IFileProvider)> loadedPlugins = new List<(PluginLoader, Assembly, IFileProvider)>();
|
||||
var systemExtensions = GetDefaultLoadedPluginAssemblies();
|
||||
plugins.AddRange(systemExtensions.SelectMany(assembly =>
|
||||
GetAllPluginTypesFromAssembly(assembly).Select(GetPluginInstanceFromType)));
|
||||
foreach (IBTCPayServerPlugin btcPayServerExtension in plugins)
|
||||
{
|
||||
btcPayServerExtension.SystemPlugin = true;
|
||||
}
|
||||
foreach (var dir in Directory.GetDirectories(pluginsFolder))
|
||||
{
|
||||
var pluginName = Path.GetFileName(dir);
|
||||
|
||||
var plugin = PluginLoader.CreateFromAssemblyFile(
|
||||
Path.Combine(dir, pluginName + ".dll"), // create a plugin from for the .dll file
|
||||
config =>
|
||||
// this ensures that the version of MVC is shared between this app and the plugin
|
||||
config.PreferSharedTypes = true);
|
||||
|
||||
mvcBuilder.AddPluginLoader(plugin);
|
||||
var pluginAssembly = plugin.LoadDefaultAssembly();
|
||||
_pluginAssemblies.Add(pluginAssembly);
|
||||
var fileProvider = CreateEmbeddedFileProviderForAssembly(pluginAssembly);
|
||||
loadedPlugins.Add((plugin, pluginAssembly, fileProvider));
|
||||
plugins.AddRange(GetAllPluginTypesFromAssembly(pluginAssembly)
|
||||
.Select(GetPluginInstanceFromType));
|
||||
}
|
||||
|
||||
foreach (var plugin in plugins)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation(
|
||||
$"Adding and executing plugin {plugin.Identifier} - {plugin.Version}");
|
||||
plugin.Execute(serviceCollection);
|
||||
serviceCollection.AddSingleton(plugin);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError($"Error when loading plugin {plugin.Identifier} - {plugin.Version}{Environment.NewLine}{e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return mvcBuilder;
|
||||
}
|
||||
|
||||
public static void UsePlugins(this IApplicationBuilder applicationBuilder)
|
||||
{
|
||||
foreach (var extension in applicationBuilder.ApplicationServices
|
||||
.GetServices<IBTCPayServerPlugin>())
|
||||
{
|
||||
extension.Execute(applicationBuilder,
|
||||
applicationBuilder.ApplicationServices);
|
||||
}
|
||||
|
||||
var webHostEnvironment = applicationBuilder.ApplicationServices.GetService<IWebHostEnvironment>();
|
||||
List<IFileProvider> providers = new List<IFileProvider>() {webHostEnvironment.WebRootFileProvider};
|
||||
providers.AddRange(
|
||||
_pluginAssemblies
|
||||
.Select(CreateEmbeddedFileProviderForAssembly));
|
||||
webHostEnvironment.WebRootFileProvider = new CompositeFileProvider(providers);
|
||||
}
|
||||
private static Assembly[] GetDefaultLoadedPluginAssemblies()
|
||||
{
|
||||
return AppDomain.CurrentDomain.GetAssemblies().Where(assembly =>
|
||||
assembly?.FullName?.StartsWith("BTCPayServer.Plugins",
|
||||
StringComparison.InvariantCultureIgnoreCase) is true)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static Type[] GetAllPluginTypesFromAssembly(Assembly assembly)
|
||||
{
|
||||
return assembly.GetTypes().Where(type =>
|
||||
typeof(IBTCPayServerPlugin).IsAssignableFrom(type) &&
|
||||
!type.IsAbstract).ToArray();
|
||||
}
|
||||
|
||||
private static IBTCPayServerPlugin GetPluginInstanceFromType(Type type)
|
||||
{
|
||||
return (IBTCPayServerPlugin)Activator.CreateInstance(type, Array.Empty<object>());
|
||||
}
|
||||
|
||||
private static IFileProvider CreateEmbeddedFileProviderForAssembly(Assembly assembly)
|
||||
{
|
||||
return new EmbeddedFileProvider(assembly);
|
||||
}
|
||||
|
||||
private static void ExecuteCommands(string pluginsFolder)
|
||||
{
|
||||
var pendingCommands = GetPendingCommands(pluginsFolder);
|
||||
foreach (var command in pendingCommands)
|
||||
{
|
||||
ExecuteCommand(command, pluginsFolder);
|
||||
}
|
||||
|
||||
File.Delete(Path.Combine(pluginsFolder, "commands"));
|
||||
}
|
||||
|
||||
private static void ExecuteCommand((string command, string extension) command, string pluginsFolder)
|
||||
{
|
||||
var dirName = Path.Combine(pluginsFolder, command.extension);
|
||||
switch (command.command)
|
||||
{
|
||||
case "delete":
|
||||
if (Directory.Exists(dirName))
|
||||
{
|
||||
Directory.Delete(dirName, true);
|
||||
}
|
||||
|
||||
break;
|
||||
case "install":
|
||||
var fileName = dirName + BTCPayPluginSuffix;
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
ZipFile.ExtractToDirectory(fileName, dirName, true);
|
||||
File.Delete(fileName);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static (string command, string plugin)[] GetPendingCommands(string pluginsFolder)
|
||||
{
|
||||
if (!File.Exists(Path.Combine(pluginsFolder, "commands")))
|
||||
return Array.Empty<(string command, string plugin)>();
|
||||
var commands = File.ReadAllLines(Path.Combine(pluginsFolder, "commands"));
|
||||
return commands.Select(s =>
|
||||
{
|
||||
var split = s.Split(':');
|
||||
return (split[0].ToLower(CultureInfo.InvariantCulture), split[1]);
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
public static void QueueCommands(string pluginsFolder, params ( string action, string plugin)[] commands)
|
||||
{
|
||||
File.AppendAllLines(Path.Combine(pluginsFolder, "commands"),
|
||||
commands.Select((tuple) => $"{tuple.action}:{tuple.plugin}"));
|
||||
}
|
||||
|
||||
public static void CancelCommands(string pluginDir, string plugin)
|
||||
{
|
||||
var cmds = GetPendingCommands(pluginDir).Where(tuple =>
|
||||
!tuple.plugin.Equals(plugin, StringComparison.InvariantCultureIgnoreCase)).ToArray();
|
||||
|
||||
File.Delete(Path.Combine(pluginDir, "commands"));
|
||||
QueueCommands(pluginDir, cmds);
|
||||
}
|
||||
}
|
||||
}
|
127
BTCPayServer/Plugins/PluginService.cs
Normal file
127
BTCPayServer/Plugins/PluginService.cs
Normal file
@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Contracts;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Plugins
|
||||
{
|
||||
public class PluginService
|
||||
{
|
||||
private readonly BTCPayServerOptions _btcPayServerOptions;
|
||||
private readonly HttpClient _githubClient;
|
||||
|
||||
public PluginService(IEnumerable<IBTCPayServerPlugin> btcPayServerPlugins,
|
||||
IHttpClientFactory httpClientFactory, BTCPayServerOptions btcPayServerOptions)
|
||||
{
|
||||
LoadedPlugins = btcPayServerPlugins;
|
||||
_githubClient = httpClientFactory.CreateClient();
|
||||
_githubClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("btcpayserver", "1"));
|
||||
_btcPayServerOptions = btcPayServerOptions;
|
||||
}
|
||||
|
||||
public IEnumerable<IBTCPayServerPlugin> LoadedPlugins { get; }
|
||||
|
||||
public async Task<IEnumerable<AvailablePlugin>> GetRemotePlugins()
|
||||
{
|
||||
var resp = await _githubClient
|
||||
.GetStringAsync(new Uri($"https://api.github.com/repos/{_btcPayServerOptions.PluginRemote}/contents"));
|
||||
var files = JsonConvert.DeserializeObject<GithubFile[]>(resp);
|
||||
return await Task.WhenAll(files.Where(file => file.Name.EndsWith($"{PluginManager.BTCPayPluginSuffix}.json", StringComparison.InvariantCulture)).Select(async file =>
|
||||
{
|
||||
return await _githubClient.GetStringAsync(file.DownloadUrl).ContinueWith(
|
||||
task => JsonConvert.DeserializeObject<AvailablePlugin>(task.Result), TaskScheduler.Current);
|
||||
}));
|
||||
}
|
||||
|
||||
public async Task DownloadRemotePlugin(string plugin)
|
||||
{
|
||||
var dest = _btcPayServerOptions.PluginDir;
|
||||
var resp = await _githubClient
|
||||
.GetStringAsync(new Uri($"https://api.github.com/repos/{_btcPayServerOptions.PluginRemote}/contents"));
|
||||
var files = JsonConvert.DeserializeObject<GithubFile[]>(resp);
|
||||
var ext = files.SingleOrDefault(file => file.Name == $"{plugin}{PluginManager.BTCPayPluginSuffix}");
|
||||
if (ext is null)
|
||||
{
|
||||
throw new Exception("Plugin not found on remote");
|
||||
}
|
||||
|
||||
var filedest = Path.Combine(dest, ext.Name);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(filedest));
|
||||
new WebClient().DownloadFile(new Uri(ext.DownloadUrl), filedest);
|
||||
}
|
||||
|
||||
public void InstallPlugin(string plugin)
|
||||
{
|
||||
var dest = _btcPayServerOptions.PluginDir;
|
||||
UninstallPlugin(plugin);
|
||||
PluginManager.QueueCommands(dest, ("install", plugin));
|
||||
}
|
||||
|
||||
public async Task UploadPlugin(IFormFile plugin)
|
||||
{
|
||||
var dest = _btcPayServerOptions.PluginDir;
|
||||
var filedest = Path.Combine(dest, plugin.FileName);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(filedest));
|
||||
if (Path.GetExtension(filedest) == PluginManager.BTCPayPluginSuffix)
|
||||
{
|
||||
await using var stream = new FileStream(filedest, FileMode.Create);
|
||||
await plugin.CopyToAsync(stream);
|
||||
}
|
||||
}
|
||||
|
||||
public void UninstallPlugin(string plugin)
|
||||
{
|
||||
var dest = _btcPayServerOptions.PluginDir;
|
||||
PluginManager.QueueCommands(dest, ("delete", plugin));
|
||||
}
|
||||
|
||||
public class AvailablePlugin : IBTCPayServerPlugin
|
||||
{
|
||||
public string Identifier { get; set; }
|
||||
public string Name { get; set; }
|
||||
public Version Version { get; set; }
|
||||
public string Description { get; set; }
|
||||
public bool SystemPlugin { get; set; } = false;
|
||||
|
||||
public string[] Dependencies { get; } = Array.Empty<string>();
|
||||
|
||||
public void Execute(IApplicationBuilder applicationBuilder,
|
||||
IServiceProvider applicationBuilderApplicationServices)
|
||||
{
|
||||
}
|
||||
|
||||
public void Execute(IServiceCollection applicationBuilder)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class GithubFile
|
||||
{
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
|
||||
[JsonProperty("sha")] public string Sha { get; set; }
|
||||
|
||||
[JsonProperty("download_url")] public string DownloadUrl { get; set; }
|
||||
}
|
||||
|
||||
public (string command, string plugin)[] GetPendingCommands()
|
||||
{
|
||||
return PluginManager.GetPendingCommands(_btcPayServerOptions.PluginDir);
|
||||
}
|
||||
|
||||
public void CancelCommands(string plugin)
|
||||
{
|
||||
PluginManager.CancelCommands(_btcPayServerOptions.PluginDir, plugin);
|
||||
}
|
||||
}
|
||||
}
|
@ -20,7 +20,8 @@
|
||||
"BTCPAY_DEBUGLOG": "debug.log",
|
||||
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
|
||||
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
|
||||
"BTCPAY_UPDATEURL": ""
|
||||
"BTCPAY_UPDATEURL": "",
|
||||
"BTCPAY_DOCKERDEPLOYMENT": "true"
|
||||
},
|
||||
"applicationUrl": "http://127.0.0.1:14142/"
|
||||
},
|
||||
@ -51,7 +52,8 @@
|
||||
"BTCPAY_SSHPASSWORD": "opD3i2282D",
|
||||
"BTCPAY_DEBUGLOG": "debug.log",
|
||||
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
|
||||
"BTCPAY_SOCKSENDPOINT": "localhost:9050"
|
||||
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
|
||||
"BTCPAY_DOCKERDEPLOYMENT": "true"
|
||||
},
|
||||
"applicationUrl": "https://localhost:14142/"
|
||||
},
|
||||
@ -84,7 +86,8 @@
|
||||
"BTCPAY_SSHPASSWORD": "opD3i2282D",
|
||||
"BTCPAY_DEBUGLOG": "debug.log",
|
||||
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
|
||||
"BTCPAY_SOCKSENDPOINT": "localhost:9050"
|
||||
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
|
||||
"BTCPAY_DOCKERDEPLOYMENT": "true"
|
||||
},
|
||||
"applicationUrl": "https://localhost:14142/"
|
||||
}
|
||||
|
@ -21,7 +21,8 @@ namespace BTCPayServer.Services.Altcoins.Ethereum
|
||||
serviceCollection.AddSingleton<IHostedService, EthereumService>(provider => provider.GetService<EthereumService>());
|
||||
serviceCollection.AddSingleton<EthereumLikePaymentMethodHandler>();
|
||||
serviceCollection.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<EthereumLikePaymentMethodHandler>());
|
||||
serviceCollection.AddSingleton<IStoreNavExtension, EthereumStoreNavExtension>();
|
||||
|
||||
serviceCollection.AddSingleton<IUIExtension>(new UIExtension("Ethereum/StoreNavEthereumExtension", "store-nav"));
|
||||
serviceCollection.AddTransient<NoRedirectHttpClientHandler>();
|
||||
serviceCollection.AddSingleton<ISyncSummaryProvider, EthereumSyncSummaryProvider>();
|
||||
serviceCollection.AddHttpClient(EthereumInvoiceCreateHttpClient)
|
||||
|
@ -1,11 +0,0 @@
|
||||
#if ALTCOINS
|
||||
using BTCPayServer.Contracts;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Ethereum
|
||||
{
|
||||
public class EthereumStoreNavExtension: IStoreNavExtension
|
||||
{
|
||||
public string Partial { get; } = "Ethereum/StoreNavEthereumExtension";
|
||||
}
|
||||
}
|
||||
#endif
|
@ -23,7 +23,7 @@ namespace BTCPayServer.Services.Altcoins.Monero
|
||||
serviceCollection.AddHostedService<MoneroListener>();
|
||||
serviceCollection.AddSingleton<MoneroLikePaymentMethodHandler>();
|
||||
serviceCollection.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<MoneroLikePaymentMethodHandler>());
|
||||
serviceCollection.AddSingleton<IStoreNavExtension, MoneroStoreNavExtension>();
|
||||
serviceCollection.AddSingleton<IUIExtension>(new UIExtension("Monero/StoreNavMoneroExtension", "store-nav"));
|
||||
serviceCollection.AddSingleton<ISyncSummaryProvider, MoneroSyncSummaryProvider>();
|
||||
|
||||
return serviceCollection;
|
||||
|
@ -1,11 +0,0 @@
|
||||
#if ALTCOINS
|
||||
using BTCPayServer.Contracts;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero
|
||||
{
|
||||
public class MoneroStoreNavExtension : IStoreNavExtension
|
||||
{
|
||||
public string Partial { get; } = "Monero/StoreNavMoneroExtension";
|
||||
}
|
||||
}
|
||||
#endif
|
@ -248,13 +248,13 @@ namespace BTCPayServer.Services.Apps
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AppData> GetApp(string appId, AppType appType, bool includeStore = false)
|
||||
public async Task<AppData> GetApp(string appId, AppType? appType, bool includeStore = false)
|
||||
{
|
||||
using (var ctx = _ContextFactory.CreateContext())
|
||||
{
|
||||
var query = ctx.Apps
|
||||
.Where(us => us.Id == appId &&
|
||||
us.AppType == appType.ToString());
|
||||
(appType == null || us.AppType == appType.ToString()));
|
||||
|
||||
if (includeStore)
|
||||
{
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user