Compare commits
200 Commits
renamebitp
...
v1.0.6.2
Author | SHA1 | Date | |
---|---|---|---|
c7c0ba745d | |||
e7e0b29382 | |||
83ec280a29 | |||
bffa0e7fe5 | |||
6d2d917bf1 | |||
06d6d15441 | |||
87676ece18 | |||
acfd3f1002 | |||
d5cbe66b0e | |||
88aa34747b | |||
cc8dcade49 | |||
a64dd9af16 | |||
8405ce6369 | |||
18e68d04f9 | |||
f3010f622c | |||
ff87319a74 | |||
fa517417ed | |||
f9b86a6b2e | |||
8a21dfa93f | |||
c88ae2d7fe | |||
25e979687f | |||
af866939f4 | |||
e72becdfcb | |||
4bd97eecc9 | |||
8ffe6dcfa0 | |||
23002ac70d | |||
1262ad3cd4 | |||
d8f145c4dc | |||
9c5fd1b478 | |||
24f3285003 | |||
179520a211 | |||
ec31a4fe17 | |||
ab780485b2 | |||
07c5c2972d | |||
af593117e4 | |||
931505d135 | |||
6c7433b2f1 | |||
38ca0accde | |||
df79c2cf48 | |||
94bcbeb604 | |||
35e45333b1 | |||
f17a6f13a4 | |||
1bff7bdc47 | |||
13cb2c695f | |||
c581b80132 | |||
4dd0ae8bdc | |||
4db67e5bc5 | |||
6aa9122160 | |||
f84f2dca64 | |||
3c6992e910 | |||
7f79d16f02 | |||
790c386ba8 | |||
ee72badf21 | |||
c9c4453660 | |||
f3611ac693 | |||
cc6fe24e82 | |||
5a7730951a | |||
eef729b5f9 | |||
0bb0a38649 | |||
39029adcd8 | |||
c967f91abb | |||
1ba0685596 | |||
f2daa6a150 | |||
e021d42551 | |||
13509e31ca | |||
b9af805ac1 | |||
378d2bc8ba | |||
b73aa55a75 | |||
a729dd1380 | |||
09335e2cf1 | |||
31738c465d | |||
38fb64130d | |||
493b10393b | |||
b406f52670 | |||
ef3f314754 | |||
d2910686cd | |||
793b1b56d9 | |||
d8f9075e2a | |||
1199d4ead9 | |||
4520e1bb3e | |||
afece8193e | |||
3b14585d1a | |||
9cd32a908f | |||
b9ca02088d | |||
440ce0221a | |||
caff7eda9f | |||
aef1cefc18 | |||
1385c7cc9b | |||
8d0260b644 | |||
5d9827fb60 | |||
256d711fde | |||
1d82c3779b | |||
abc9d07977 | |||
40d95acfb9 | |||
9a92bc05db | |||
8962bf00f6 | |||
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 | |||
ad221eaaa2 | |||
74a1a5262d | |||
32ae82d4da | |||
9c6c8d4f8f | |||
45fbe6972b | |||
c262132a2f | |||
911a7ef6b5 | |||
8cb3757f5c | |||
10bf914d78 | |||
684177ee44 | |||
b2052ca308 | |||
4174fa648d | |||
182d67881d | |||
9284ac7461 | |||
55eec06e77 | |||
069acf0297 | |||
d9d2c7d213 | |||
83b28e0b00 | |||
c2d52fd48f | |||
32e6643303 | |||
5faf6be02d | |||
9ff93ac2d5 | |||
5131d8d328 | |||
2147f8ec8b | |||
b2899529c3 | |||
3d2b4cbfa8 | |||
d8da8023c2 | |||
60cadb8b6d | |||
dcf8783c2e | |||
eb9dc95c58 | |||
f94fb4f65e | |||
e0aff2cf9d | |||
d2369f45ce | |||
7b718ada63 | |||
652604a36f | |||
8a4834dd2b | |||
6d49093620 | |||
489ce0bebc | |||
58922c23a6 | |||
be3183fc45 | |||
38b4812eb3 | |||
69fdc2d821 | |||
9104b0f974 | |||
6ec7373a1a | |||
39be605459 | |||
4d4459fa4e |
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>
|
39
BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj
Normal file
39
BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj
Normal file
@ -0,0 +1,39 @@
|
||||
<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>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.4" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.4" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,4 +1,4 @@
|
||||
namespace BTCPayServer.Security
|
||||
namespace BTCPayServer.Abstractions.Constants
|
||||
{
|
||||
public class AuthenticationSchemes
|
||||
{
|
108
BTCPayServer.Abstractions/Contracts/BaseDbContextFactory.cs
Normal file
108
BTCPayServer.Abstractions/Contracts/BaseDbContextFactory.cs
Normal file
@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations.Operations;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Contracts
|
||||
{
|
||||
public abstract class BaseDbContextFactory<T> where T: DbContext
|
||||
{
|
||||
private readonly DatabaseOptions _options;
|
||||
private readonly string _schemaPrefix;
|
||||
|
||||
public BaseDbContextFactory(DatabaseOptions options, string schemaPrefix)
|
||||
{
|
||||
_options = options;
|
||||
_schemaPrefix = schemaPrefix;
|
||||
}
|
||||
|
||||
public abstract T CreateContext();
|
||||
|
||||
class CustomNpgsqlMigrationsSqlGenerator : NpgsqlMigrationsSqlGenerator
|
||||
{
|
||||
public CustomNpgsqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies, IMigrationsAnnotationProvider annotations, Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal.INpgsqlOptions opts) : base(dependencies, annotations, opts)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Generate(NpgsqlCreateDatabaseOperation operation, IModel model, MigrationCommandListBuilder builder)
|
||||
{
|
||||
builder
|
||||
.Append("CREATE DATABASE ")
|
||||
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name));
|
||||
|
||||
// POSTGRES gotcha: Indexed Text column (even if PK) are not used if we are not using C locale
|
||||
builder
|
||||
.Append(" TEMPLATE ")
|
||||
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("template0"));
|
||||
|
||||
builder
|
||||
.Append(" LC_CTYPE ")
|
||||
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("C"));
|
||||
|
||||
builder
|
||||
.Append(" LC_COLLATE ")
|
||||
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("C"));
|
||||
|
||||
builder
|
||||
.Append(" ENCODING ")
|
||||
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("UTF8"));
|
||||
|
||||
if (operation.Tablespace != null)
|
||||
{
|
||||
builder
|
||||
.Append(" TABLESPACE ")
|
||||
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Tablespace));
|
||||
}
|
||||
|
||||
builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
|
||||
|
||||
EndStatement(builder, suppressTransaction: true);
|
||||
}
|
||||
}
|
||||
|
||||
public void ConfigureBuilder(DbContextOptionsBuilder builder)
|
||||
{
|
||||
switch (_options.DatabaseType)
|
||||
{
|
||||
case DatabaseType.Sqlite:
|
||||
builder.UseSqlite(_options.ConnectionString, o =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_schemaPrefix))
|
||||
{
|
||||
o.MigrationsHistoryTable(_schemaPrefix);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case DatabaseType.Postgres:
|
||||
builder
|
||||
.UseNpgsql(_options.ConnectionString, o =>
|
||||
{
|
||||
o.EnableRetryOnFailure(10);
|
||||
if (!string.IsNullOrEmpty(_schemaPrefix))
|
||||
{
|
||||
o.MigrationsHistoryTable(_schemaPrefix);
|
||||
}
|
||||
})
|
||||
.ReplaceService<IMigrationsSqlGenerator, CustomNpgsqlMigrationsSqlGenerator>();
|
||||
break;
|
||||
case DatabaseType.MySQL:
|
||||
builder.UseMySql(_options.ConnectionString, o =>
|
||||
{
|
||||
o.EnableRetryOnFailure(10);
|
||||
|
||||
if (!string.IsNullOrEmpty(_schemaPrefix))
|
||||
{
|
||||
o.MigrationsHistoryTable(_schemaPrefix);
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
32
BTCPayServer.Abstractions/Contracts/IBTCPayServerPlugin.cs
Normal file
32
BTCPayServer.Abstractions/Contracts/IBTCPayServerPlugin.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using BTCPayServer.Abstractions.Converters;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Contracts
|
||||
{
|
||||
public interface IBTCPayServerPlugin
|
||||
{
|
||||
public string Identifier { get; }
|
||||
string Name { get; }
|
||||
[JsonConverter(typeof(VersionConverter))]
|
||||
Version Version { get; }
|
||||
string Description { get; }
|
||||
bool SystemPlugin { get; set; }
|
||||
PluginDependency[] Dependencies { get; }
|
||||
void Execute(IApplicationBuilder applicationBuilder, IServiceProvider applicationBuilderApplicationServices);
|
||||
void Execute(IServiceCollection applicationBuilder);
|
||||
|
||||
public class PluginDependency
|
||||
{
|
||||
public string Identifier { get; set; }
|
||||
public string Condition { get; set; }
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Identifier}: {Condition}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
27
BTCPayServer.Abstractions/Contracts/INotificationHandler.cs
Normal file
27
BTCPayServer.Abstractions/Contracts/INotificationHandler.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer.Abstractions.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; }
|
||||
}
|
||||
}
|
10
BTCPayServer.Abstractions/Contracts/IPluginHookAction.cs
Normal file
10
BTCPayServer.Abstractions/Contracts/IPluginHookAction.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Contracts
|
||||
{
|
||||
public interface IPluginHookAction
|
||||
{
|
||||
public string Hook { get; }
|
||||
Task Execute(object args);
|
||||
}
|
||||
}
|
11
BTCPayServer.Abstractions/Contracts/IPluginHookFilter.cs
Normal file
11
BTCPayServer.Abstractions/Contracts/IPluginHookFilter.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Contracts
|
||||
{
|
||||
public interface IPluginHookFilter
|
||||
{
|
||||
public string Hook { get; }
|
||||
|
||||
Task<object> Execute(object args);
|
||||
}
|
||||
}
|
10
BTCPayServer.Abstractions/Contracts/IPluginHookService.cs
Normal file
10
BTCPayServer.Abstractions/Contracts/IPluginHookService.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Contracts
|
||||
{
|
||||
public interface IPluginHookService
|
||||
{
|
||||
Task ApplyAction(string hook, object args);
|
||||
Task<object> ApplyFilter(string hook, object args);
|
||||
}
|
||||
}
|
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.Abstractions.Contracts
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
namespace BTCPayServer.Abstractions.Contracts
|
||||
{
|
||||
public interface IStartupTask
|
||||
{
|
@ -1,4 +1,4 @@
|
||||
namespace BTCPayServer.Contracts
|
||||
namespace BTCPayServer.Abstractions.Contracts
|
||||
{
|
||||
public interface ISyncSummaryProvider
|
||||
{
|
||||
@ -6,5 +6,4 @@ namespace BTCPayServer.Contracts
|
||||
|
||||
string Partial { get; }
|
||||
}
|
||||
|
||||
}
|
9
BTCPayServer.Abstractions/Contracts/IUIExtension.cs
Normal file
9
BTCPayServer.Abstractions/Contracts/IUIExtension.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace BTCPayServer.Abstractions.Contracts
|
||||
{
|
||||
public interface IUIExtension
|
||||
{
|
||||
string Partial { get; }
|
||||
|
||||
string Location { 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.Abstractions.Models;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Extensions
|
||||
{
|
||||
public static class SetStatusMessageModelExtensions
|
||||
{
|
||||
public static void SetStatusMessageModel(this ITempDataDictionary tempData, StatusMessageModel statusMessage)
|
||||
{
|
||||
if (statusMessage == null)
|
||||
{
|
||||
tempData.Remove("StatusMessageModel");
|
||||
return;
|
||||
}
|
||||
|
||||
tempData["StatusMessageModel"] = JsonSerializer.Serialize(statusMessage, new JsonSerializerOptions());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
namespace BTCPayServer.Abstractions.Extensions
|
||||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
36
BTCPayServer.Abstractions/Models/BaseBTCPayServerPlugin.cs
Normal file
36
BTCPayServer.Abstractions/Models/BaseBTCPayServerPlugin.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BTCPayServer.Abstractions.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 IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = Array.Empty<IBTCPayServerPlugin.PluginDependency>();
|
||||
|
||||
public virtual void Execute(IApplicationBuilder applicationBuilder,
|
||||
IServiceProvider applicationBuilderApplicationServices)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Execute(IServiceCollection applicationBuilder)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
14
BTCPayServer.Abstractions/Models/DatabaseOptions.cs
Normal file
14
BTCPayServer.Abstractions/Models/DatabaseOptions.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace BTCPayServer.Abstractions.Models
|
||||
{
|
||||
public class DatabaseOptions
|
||||
{
|
||||
public DatabaseOptions(DatabaseType type, string connString)
|
||||
{
|
||||
DatabaseType = type;
|
||||
ConnectionString = connString;
|
||||
}
|
||||
|
||||
public DatabaseType DatabaseType { get; set; }
|
||||
public string ConnectionString { get; set; }
|
||||
}
|
||||
}
|
9
BTCPayServer.Abstractions/Models/DatabaseType.cs
Normal file
9
BTCPayServer.Abstractions/Models/DatabaseType.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace BTCPayServer.Abstractions.Models
|
||||
{
|
||||
public enum DatabaseType
|
||||
{
|
||||
Sqlite,
|
||||
Postgres,
|
||||
MySQL,
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer.Models
|
||||
namespace BTCPayServer.Abstractions.Models
|
||||
{
|
||||
public class StatusMessageModel
|
||||
{
|
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"
|
16
BTCPayServer.Abstractions/Services/PluginAction.cs
Normal file
16
BTCPayServer.Abstractions/Services/PluginAction.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Services
|
||||
{
|
||||
public abstract class PluginAction<T>:IPluginHookAction
|
||||
{
|
||||
public string Hook { get; }
|
||||
public Task Execute(object args)
|
||||
{
|
||||
return Execute(args is T args1 ? args1 : default);
|
||||
}
|
||||
|
||||
public abstract Task Execute(T arg);
|
||||
}
|
||||
}
|
16
BTCPayServer.Abstractions/Services/PluginHookFilter.cs
Normal file
16
BTCPayServer.Abstractions/Services/PluginHookFilter.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Services
|
||||
{
|
||||
public abstract class PluginHookFilter<T>:IPluginHookFilter
|
||||
{
|
||||
public string Hook { get; }
|
||||
public Task<object> Execute(object args)
|
||||
{
|
||||
return Execute(args is T args1 ? args1 : default).ContinueWith(task => task.Result as object);
|
||||
}
|
||||
|
||||
public abstract Task<T> Execute(T arg);
|
||||
}
|
||||
}
|
16
BTCPayServer.Abstractions/Services/UIExtension.cs
Normal file
16
BTCPayServer.Abstractions/Services/UIExtension.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Services
|
||||
{
|
||||
public class UIExtension: IUIExtension
|
||||
{
|
||||
public UIExtension(string partial, string location)
|
||||
{
|
||||
Partial = partial;
|
||||
Location = location;
|
||||
}
|
||||
|
||||
public string Partial { get; }
|
||||
public string Location { get; }
|
||||
}
|
||||
}
|
BIN
BTCPayServer.Abstractions/icon.png
Normal file
BIN
BTCPayServer.Abstractions/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
@ -13,7 +13,7 @@
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Version Condition=" '$(Version)' == '' ">1.1.0</Version>
|
||||
<Version Condition=" '$(Version)' == '' ">1.2.0</Version>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
@ -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>
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
|
@ -26,6 +26,13 @@ namespace BTCPayServer.Client
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/invoices/{invoiceId}"), token);
|
||||
return await HandleResponse<InvoiceData>(response);
|
||||
}
|
||||
public virtual async Task<InvoicePaymentMethodDataModel[]> GetInvoicePaymentMethods(string storeId, string invoiceId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods"), token);
|
||||
return await HandleResponse<InvoicePaymentMethodDataModel[]>(response);
|
||||
}
|
||||
|
||||
public virtual async Task ArchiveInvoice(string storeId, string invoiceId,
|
||||
CancellationToken token = default)
|
||||
@ -52,7 +59,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
if (request.Status!= InvoiceStatus.Complete && request.Status!= InvoiceStatus.Invalid)
|
||||
if (request.Status!= InvoiceStatus.Settled && request.Status!= InvoiceStatus.Invalid)
|
||||
throw new ArgumentOutOfRangeException(nameof(request.Status), "Status can only be Invalid or Complete");
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/invoices/{invoiceId}/status", bodyPayload: request,
|
||||
|
65
BTCPayServer.Client/BTCPayServerClient.Webhooks.cs
Normal file
65
BTCPayServer.Client/BTCPayServerClient.Webhooks.cs
Normal file
@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public async Task<StoreWebhookData> CreateWebhook(string storeId, Client.Models.CreateStoreWebhookRequest create, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks", bodyPayload: create, method: HttpMethod.Post), token);
|
||||
return await HandleResponse<StoreWebhookData>(response);
|
||||
}
|
||||
public async Task<StoreWebhookData> GetWebhook(string storeId, string webhookId, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}"), token);
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
return null;
|
||||
return await HandleResponse<StoreWebhookData>(response);
|
||||
}
|
||||
public async Task<StoreWebhookData> UpdateWebhook(string storeId, string webhookId, Models.UpdateStoreWebhookRequest update, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}", bodyPayload: update, method: HttpMethod.Put), token);
|
||||
return await HandleResponse<StoreWebhookData>(response);
|
||||
}
|
||||
public async Task<bool> DeleteWebhook(string storeId, string webhookId, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}", method: HttpMethod.Delete), token);
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
public async Task<StoreWebhookData[]> GetWebhooks(string storeId, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks"), token);
|
||||
return await HandleResponse<StoreWebhookData[]>(response);
|
||||
}
|
||||
public async Task<WebhookDeliveryData[]> GetWebhookDeliveries(string storeId, string webhookId, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries"), token);
|
||||
return await HandleResponse<WebhookDeliveryData[]>(response);
|
||||
}
|
||||
public async Task<WebhookDeliveryData> GetWebhookDelivery(string storeId, string webhookId, string deliveryId, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}"), token);
|
||||
return await HandleResponse<WebhookDeliveryData>(response);
|
||||
}
|
||||
public async Task<string> RedeliverWebhook(string storeId, string webhookId, string deliveryId, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver", null, HttpMethod.Post), token);
|
||||
return await HandleResponse<string>(response);
|
||||
}
|
||||
|
||||
public async Task<WebhookEvent> GetWebhookDeliveryRequest(string storeId, string webhookId, string deliveryId, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/request"), token);
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
return null;
|
||||
return await HandleResponse<WebhookEvent>(response);
|
||||
}
|
||||
}
|
||||
}
|
@ -65,7 +65,8 @@ namespace BTCPayServer.Client
|
||||
protected async Task<T> HandleResponse<T>(HttpResponseMessage message)
|
||||
{
|
||||
await HandleResponse(message);
|
||||
return JsonConvert.DeserializeObject<T>(await message.Content.ReadAsStringAsync());
|
||||
var str = await message.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<T>(str);
|
||||
}
|
||||
|
||||
protected virtual HttpRequestMessage CreateHttpRequest(string path,
|
||||
|
@ -1,3 +1,6 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class ApplicationUserData
|
||||
@ -26,5 +29,11 @@ namespace BTCPayServer.Client.Models
|
||||
/// the roles of the user
|
||||
/// </summary>
|
||||
public string[] Roles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the date the user was created. Null if created before v1.0.5.6.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset? Created { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
@ -20,4 +18,12 @@ namespace BTCPayServer.Client.Models
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset CreatedTime { get; set; }
|
||||
}
|
||||
public enum InvoiceStatus
|
||||
{
|
||||
New,
|
||||
Processing,
|
||||
Expired,
|
||||
Invalid,
|
||||
Settled
|
||||
}
|
||||
}
|
||||
|
61
BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs
Normal file
61
BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class InvoicePaymentMethodDataModel
|
||||
{
|
||||
public string Destination { get; set; }
|
||||
public string PaymentLink { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Rate { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal PaymentMethodPaid { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal TotalPaid { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Due { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Amount { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal NetworkFee { get; set; }
|
||||
|
||||
public List<Payment> Payments { get; set; }
|
||||
public string PaymentMethod { get; set; }
|
||||
|
||||
public class Payment
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTime ReceivedDate { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Value { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Fee { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public PaymentStatus Status { get; set; }
|
||||
|
||||
public string Destination { get; set; }
|
||||
|
||||
public enum PaymentStatus
|
||||
{
|
||||
Invalid,
|
||||
Processing,
|
||||
Settled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public enum InvoiceStatus
|
||||
{
|
||||
New,
|
||||
Paid,
|
||||
Expired,
|
||||
Invalid,
|
||||
Complete,
|
||||
Confirmed
|
||||
}
|
||||
}
|
@ -30,14 +30,21 @@ namespace BTCPayServer.Client.Models
|
||||
public double PaymentTolerance { get; set; } = 0;
|
||||
public bool AnyoneCanCreateInvoice { get; set; }
|
||||
|
||||
|
||||
public bool RequiresRefundEmail { get; set; }
|
||||
public bool LightningAmountInSatoshi { get; set; }
|
||||
public bool LightningPrivateRouteHints { get; set; }
|
||||
public bool OnChainWithLnInvoiceFallback { get; set; }
|
||||
public bool RedirectAutomatically { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public bool ShowRecommendedFee { get; set; } = true;
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public int RecommendedFeeBlockTarget { get; set; } = 1;
|
||||
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string DefaultLang { get; set; } = "en";
|
||||
public bool LightningAmountInSatoshi { get; set; }
|
||||
|
||||
public string CustomLogo { get; set; }
|
||||
|
||||
@ -45,16 +52,13 @@ namespace BTCPayServer.Client.Models
|
||||
|
||||
public string HtmlTitle { get; set; }
|
||||
|
||||
public bool RedirectAutomatically { get; set; }
|
||||
|
||||
public bool RequiresRefundEmail { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public NetworkFeeMode NetworkFeeMode { get; set; } = NetworkFeeMode.Never;
|
||||
|
||||
public bool PayJoinEnabled { get; set; }
|
||||
public bool LightningPrivateRouteHints { get; set; }
|
||||
|
||||
|
||||
[JsonExtensionData]
|
||||
|
35
BTCPayServer.Client/Models/StoreWebhookData.cs
Normal file
35
BTCPayServer.Client/Models/StoreWebhookData.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class StoreWebhookBaseData
|
||||
{
|
||||
public class AuthorizedEventsData
|
||||
{
|
||||
public bool Everything { get; set; } = true;
|
||||
|
||||
[JsonProperty(ItemConverterType = typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||
public WebhookEventType[] SpecificEvents { get; set; } = Array.Empty<WebhookEventType>();
|
||||
}
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public string Secret { get; set; }
|
||||
public bool AutomaticRedelivery { get; set; } = true;
|
||||
public string Url { get; set; }
|
||||
public AuthorizedEventsData AuthorizedEvents { get; set; } = new AuthorizedEventsData();
|
||||
}
|
||||
public class UpdateStoreWebhookRequest : StoreWebhookBaseData
|
||||
{
|
||||
}
|
||||
public class CreateStoreWebhookRequest : StoreWebhookBaseData
|
||||
{
|
||||
}
|
||||
public class StoreWebhookData : StoreWebhookBaseData
|
||||
{
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
18
BTCPayServer.Client/Models/WebhookDeliveryData.cs
Normal file
18
BTCPayServer.Client/Models/WebhookDeliveryData.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class WebhookDeliveryData
|
||||
{
|
||||
public string Id { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
public int? HttpCode { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||
public WebhookDeliveryStatus Status { get; set; }
|
||||
}
|
||||
}
|
13
BTCPayServer.Client/Models/WebhookDeliveryStatus.cs
Normal file
13
BTCPayServer.Client/Models/WebhookDeliveryStatus.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public enum WebhookDeliveryStatus
|
||||
{
|
||||
Failed,
|
||||
HttpError,
|
||||
HttpSuccess
|
||||
}
|
||||
}
|
36
BTCPayServer.Client/Models/WebhookEvent.cs
Normal file
36
BTCPayServer.Client/Models/WebhookEvent.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class WebhookEvent
|
||||
{
|
||||
public readonly static JsonSerializerSettings DefaultSerializerSettings;
|
||||
static WebhookEvent()
|
||||
{
|
||||
DefaultSerializerSettings = new JsonSerializerSettings();
|
||||
DefaultSerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
|
||||
NBitcoin.JsonConverters.Serializer.RegisterFrontConverters(DefaultSerializerSettings);
|
||||
DefaultSerializerSettings.Formatting = Formatting.None;
|
||||
}
|
||||
public string DeliveryId { get; set; }
|
||||
public string WebhookId { get; set; }
|
||||
public string OrignalDeliveryId { get; set; }
|
||||
public bool IsRedelivery { get; set; }
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public WebhookEventType Type { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
[JsonExtensionData]
|
||||
public IDictionary<string, JToken> AdditionalData { get; set; }
|
||||
public T ReadAs<T>()
|
||||
{
|
||||
var str = JsonConvert.SerializeObject(this, DefaultSerializerSettings);
|
||||
return JsonConvert.DeserializeObject<T>(str, DefaultSerializerSettings);
|
||||
}
|
||||
}
|
||||
}
|
16
BTCPayServer.Client/Models/WebhookEventType.cs
Normal file
16
BTCPayServer.Client/Models/WebhookEventType.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public enum WebhookEventType
|
||||
{
|
||||
InvoiceCreated,
|
||||
InvoiceReceivedPayment,
|
||||
InvoiceProcessing,
|
||||
InvoiceExpired,
|
||||
InvoiceSettled,
|
||||
InvoiceInvalid
|
||||
}
|
||||
}
|
85
BTCPayServer.Client/Models/WebhookInvoiceEvent.cs
Normal file
85
BTCPayServer.Client/Models/WebhookInvoiceEvent.cs
Normal file
@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class WebhookInvoiceEvent : WebhookEvent
|
||||
{
|
||||
public WebhookInvoiceEvent()
|
||||
{
|
||||
|
||||
}
|
||||
public WebhookInvoiceEvent(WebhookEventType evtType)
|
||||
{
|
||||
this.Type = evtType;
|
||||
}
|
||||
[JsonProperty(Order = 1)]
|
||||
public string StoreId { get; set; }
|
||||
[JsonProperty(Order = 2)]
|
||||
public string InvoiceId { get; set; }
|
||||
}
|
||||
|
||||
public class WebhookInvoiceSettledEvent : WebhookInvoiceEvent
|
||||
{
|
||||
public WebhookInvoiceSettledEvent()
|
||||
{
|
||||
|
||||
}
|
||||
public WebhookInvoiceSettledEvent(WebhookEventType evtType) : base(evtType)
|
||||
{
|
||||
}
|
||||
|
||||
public bool ManuallyMarked { get; set; }
|
||||
}
|
||||
public class WebhookInvoiceInvalidEvent : WebhookInvoiceEvent
|
||||
{
|
||||
public WebhookInvoiceInvalidEvent()
|
||||
{
|
||||
|
||||
}
|
||||
public WebhookInvoiceInvalidEvent(WebhookEventType evtType) : base(evtType)
|
||||
{
|
||||
}
|
||||
|
||||
public bool ManuallyMarked { get; set; }
|
||||
}
|
||||
public class WebhookInvoiceProcessingEvent : WebhookInvoiceEvent
|
||||
{
|
||||
public WebhookInvoiceProcessingEvent()
|
||||
{
|
||||
|
||||
}
|
||||
public WebhookInvoiceProcessingEvent(WebhookEventType evtType) : base(evtType)
|
||||
{
|
||||
}
|
||||
|
||||
public bool OverPaid { get; set; }
|
||||
}
|
||||
public class WebhookInvoiceReceivedPaymentEvent : WebhookInvoiceEvent
|
||||
{
|
||||
public WebhookInvoiceReceivedPaymentEvent()
|
||||
{
|
||||
|
||||
}
|
||||
public WebhookInvoiceReceivedPaymentEvent(WebhookEventType evtType) : base(evtType)
|
||||
{
|
||||
}
|
||||
|
||||
public bool AfterExpiration { get; set; }
|
||||
}
|
||||
public class WebhookInvoiceExpiredEvent : WebhookInvoiceEvent
|
||||
{
|
||||
public WebhookInvoiceExpiredEvent()
|
||||
{
|
||||
|
||||
}
|
||||
public WebhookInvoiceExpiredEvent(WebhookEventType evtType) : base(evtType)
|
||||
{
|
||||
}
|
||||
|
||||
public bool PartiallyPaid { get; set; }
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ namespace BTCPayServer.Client
|
||||
public const string CanUseLightningNodeInStore = "btcpay.store.canuselightningnode";
|
||||
public const string CanModifyServerSettings = "btcpay.server.canmodifyserversettings";
|
||||
public const string CanModifyStoreSettings = "btcpay.store.canmodifystoresettings";
|
||||
public const string CanModifyStoreWebhooks = "btcpay.store.webhooks.canmodifywebhooks";
|
||||
public const string CanModifyStoreSettingsUnscoped = "btcpay.store.canmodifystoresettings:";
|
||||
public const string CanViewStoreSettings = "btcpay.store.canviewstoresettings";
|
||||
public const string CanViewInvoices = "btcpay.store.canviewinvoices";
|
||||
@ -29,6 +30,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
yield return CanViewInvoices;
|
||||
yield return CanCreateInvoice;
|
||||
yield return CanModifyStoreWebhooks;
|
||||
yield return CanModifyServerSettings;
|
||||
yield return CanModifyStoreSettings;
|
||||
yield return CanViewStoreSettings;
|
||||
@ -156,6 +158,7 @@ namespace BTCPayServer.Client
|
||||
switch (subpolicy)
|
||||
{
|
||||
case Policies.CanViewInvoices when this.Policy == Policies.CanModifyStoreSettings:
|
||||
case Policies.CanModifyStoreWebhooks when this.Policy == Policies.CanModifyStoreSettings:
|
||||
case Policies.CanViewInvoices when this.Policy == Policies.CanViewStoreSettings:
|
||||
case Policies.CanViewStoreSettings when this.Policy == Policies.CanModifyStoreSettings:
|
||||
case Policies.CanCreateInvoice when this.Policy == Policies.CanModifyStoreSettings:
|
||||
|
@ -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>
|
||||
|
@ -7,12 +7,10 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.4" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.4" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\BTCPayServer.Client\BTCPayServer.Client.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -63,6 +63,11 @@ namespace BTCPayServer.Data
|
||||
public DbSet<U2FDevice> U2FDevices { get; set; }
|
||||
public DbSet<NotificationData> Notifications { get; set; }
|
||||
|
||||
public DbSet<StoreWebhookData> StoreWebhooks { get; set; }
|
||||
public DbSet<WebhookData> Webhooks { get; set; }
|
||||
public DbSet<WebhookDeliveryData> WebhookDeliveries { get; set; }
|
||||
public DbSet<InvoiceWebhookDeliveryData> InvoiceWebhookDeliveries { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
var isConfigured = optionsBuilder.Options.Extensions.OfType<RelationalOptionsExtension>().Any();
|
||||
@ -73,6 +78,7 @@ namespace BTCPayServer.Data
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
Data.UserStore.OnModelCreating(builder);
|
||||
NotificationData.OnModelCreating(builder);
|
||||
InvoiceData.OnModelCreating(builder);
|
||||
PaymentData.OnModelCreating(builder);
|
||||
@ -90,6 +96,11 @@ namespace BTCPayServer.Data
|
||||
PullPaymentData.OnModelCreating(builder);
|
||||
PayoutData.OnModelCreating(builder);
|
||||
RefundData.OnModelCreating(builder);
|
||||
U2FDevice.OnModelCreating(builder);
|
||||
|
||||
Data.WebhookDeliveryData.OnModelCreating(builder);
|
||||
Data.StoreWebhookData.OnModelCreating(builder);
|
||||
Data.InvoiceWebhookDeliveryData.OnModelCreating(builder);
|
||||
|
||||
if (Database.IsSqlite() && !_designTime)
|
||||
{
|
||||
|
@ -1,96 +1,20 @@
|
||||
using System;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations.Operations;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public enum DatabaseType
|
||||
public class ApplicationDbContextFactory : BaseDbContextFactory<ApplicationDbContext>
|
||||
{
|
||||
Sqlite,
|
||||
Postgres,
|
||||
MySQL,
|
||||
}
|
||||
public class ApplicationDbContextFactory
|
||||
{
|
||||
readonly string _ConnectionString;
|
||||
readonly DatabaseType _Type;
|
||||
public ApplicationDbContextFactory(DatabaseType type, string connectionString)
|
||||
public ApplicationDbContextFactory(DatabaseOptions options) : base(options, "")
|
||||
{
|
||||
_ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
||||
_Type = type;
|
||||
}
|
||||
|
||||
|
||||
public DatabaseType Type
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Type;
|
||||
}
|
||||
}
|
||||
|
||||
public ApplicationDbContext CreateContext()
|
||||
public override ApplicationDbContext CreateContext()
|
||||
{
|
||||
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
|
||||
ConfigureBuilder(builder);
|
||||
return new ApplicationDbContext(builder.Options);
|
||||
}
|
||||
|
||||
class CustomNpgsqlMigrationsSqlGenerator : NpgsqlMigrationsSqlGenerator
|
||||
{
|
||||
public CustomNpgsqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies, IMigrationsAnnotationProvider annotations, Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal.INpgsqlOptions opts) : base(dependencies, annotations, opts)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Generate(NpgsqlCreateDatabaseOperation operation, IModel model, MigrationCommandListBuilder builder)
|
||||
{
|
||||
builder
|
||||
.Append("CREATE DATABASE ")
|
||||
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name));
|
||||
|
||||
// POSTGRES gotcha: Indexed Text column (even if PK) are not used if we are not using C locale
|
||||
builder
|
||||
.Append(" TEMPLATE ")
|
||||
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("template0"));
|
||||
|
||||
builder
|
||||
.Append(" LC_CTYPE ")
|
||||
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("C"));
|
||||
|
||||
builder
|
||||
.Append(" LC_COLLATE ")
|
||||
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("C"));
|
||||
|
||||
builder
|
||||
.Append(" ENCODING ")
|
||||
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("UTF8"));
|
||||
|
||||
if (operation.Tablespace != null)
|
||||
{
|
||||
builder
|
||||
.Append(" TABLESPACE ")
|
||||
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Tablespace));
|
||||
}
|
||||
|
||||
builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
|
||||
|
||||
EndStatement(builder, suppressTransaction: true);
|
||||
}
|
||||
}
|
||||
|
||||
public void ConfigureBuilder(DbContextOptionsBuilder builder)
|
||||
{
|
||||
if (_Type == DatabaseType.Sqlite)
|
||||
builder.UseSqlite(_ConnectionString, o => o.MigrationsAssembly("BTCPayServer.Data"));
|
||||
else if (_Type == DatabaseType.Postgres)
|
||||
builder
|
||||
.UseNpgsql(_ConnectionString, o => o.MigrationsAssembly("BTCPayServer.Data").EnableRetryOnFailure(10))
|
||||
.ReplaceService<IMigrationsSqlGenerator, CustomNpgsqlMigrationsSqlGenerator>();
|
||||
else if (_Type == DatabaseType.MySQL)
|
||||
builder.UseMySql(_ConnectionString, o => o.MigrationsAssembly("BTCPayServer.Data").EnableRetryOnFailure(10));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
@ -26,5 +27,7 @@ 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; }
|
||||
}
|
||||
}
|
||||
|
26
BTCPayServer.Data/Data/InvoiceWebhookDeliveryData.cs
Normal file
26
BTCPayServer.Data/Data/InvoiceWebhookDeliveryData.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class InvoiceWebhookDeliveryData
|
||||
{
|
||||
public string InvoiceId { get; set; }
|
||||
public InvoiceData Invoice { get; set; }
|
||||
public string DeliveryId { get; set; }
|
||||
public WebhookDeliveryData Delivery { get; set; }
|
||||
internal static void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
builder.Entity<InvoiceWebhookDeliveryData>()
|
||||
.HasKey(p => new { p.InvoiceId, p.DeliveryId });
|
||||
builder.Entity<InvoiceWebhookDeliveryData>()
|
||||
.HasOne(o => o.Invoice)
|
||||
.WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||
builder.Entity<InvoiceWebhookDeliveryData>()
|
||||
.HasOne(o => o.Delivery)
|
||||
.WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
{
|
||||
|
30
BTCPayServer.Data/Data/StoreWebhookData.cs
Normal file
30
BTCPayServer.Data/Data/StoreWebhookData.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class StoreWebhookData
|
||||
{
|
||||
public string StoreId { get; set; }
|
||||
public string WebhookId { get; set; }
|
||||
public WebhookData Webhook { get; set; }
|
||||
public StoreData Store { get; set; }
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
builder.Entity<StoreWebhookData>()
|
||||
.HasKey(p => new { p.StoreId, p.WebhookId });
|
||||
|
||||
builder.Entity<StoreWebhookData>()
|
||||
.HasOne(o => o.Webhook)
|
||||
.WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<StoreWebhookData>()
|
||||
.HasOne(o => o.Store)
|
||||
.WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
@ -18,5 +19,15 @@ namespace BTCPayServer.Data
|
||||
|
||||
public string ApplicationUserId { get; set; }
|
||||
public ApplicationUser ApplicationUser { get; set; }
|
||||
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
builder.Entity<U2FDevice>()
|
||||
.HasOne(o => o.ApplicationUser)
|
||||
.WithMany(i => i.U2FDevices)
|
||||
.HasForeignKey(i => i.ApplicationUserId).OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
22
BTCPayServer.Data/Data/WebhookData.cs
Normal file
22
BTCPayServer.Data/Data/WebhookData.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class WebhookData
|
||||
{
|
||||
[Key]
|
||||
[MaxLength(25)]
|
||||
public string Id
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
[Required]
|
||||
public byte[] Blob { get; set; }
|
||||
public List<WebhookDeliveryData> Deliveries { get; set; }
|
||||
}
|
||||
}
|
36
BTCPayServer.Data/Data/WebhookDeliveryData.cs
Normal file
36
BTCPayServer.Data/Data/WebhookDeliveryData.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class WebhookDeliveryData
|
||||
{
|
||||
[Key]
|
||||
[MaxLength(25)]
|
||||
public string Id { get; set; }
|
||||
[MaxLength(25)]
|
||||
[Required]
|
||||
public string WebhookId { get; set; }
|
||||
public WebhookData Webhook { get; set; }
|
||||
|
||||
[Required]
|
||||
public DateTimeOffset Timestamp
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Required]
|
||||
public byte[] Blob { get; set; }
|
||||
internal static void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
builder.Entity<WebhookDeliveryData>()
|
||||
.HasOne(o => o.Webhook)
|
||||
.WithMany(a => a.Deliveries).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.Entity<WebhookDeliveryData>().HasIndex(o => o.WebhookId);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20201002145033_AddCreateDateToUser")]
|
||||
public partial class AddCreateDateToUser : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||
name: "Created",
|
||||
table: "AspNetUsers",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (this.SupportDropColumn(migrationBuilder.ActiveProvider))
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Created",
|
||||
table: "AspNetUsers");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20201007090617_u2fDeviceCascade")]
|
||||
public partial class u2fDeviceCascade : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (this.SupportDropForeignKey(migrationBuilder.ActiveProvider))
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_U2FDevices_AspNetUsers_ApplicationUserId",
|
||||
table: "U2FDevices");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_U2FDevices_AspNetUsers_ApplicationUserId",
|
||||
table: "U2FDevices",
|
||||
column: "ApplicationUserId",
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (this.SupportDropForeignKey(migrationBuilder.ActiveProvider))
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_U2FDevices_AspNetUsers_ApplicationUserId",
|
||||
table: "U2FDevices");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_U2FDevices_AspNetUsers_ApplicationUserId",
|
||||
table: "U2FDevices",
|
||||
column: "ApplicationUserId",
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
115
BTCPayServer.Data/Migrations/20201108054749_webhooks.cs
Normal file
115
BTCPayServer.Data/Migrations/20201108054749_webhooks.cs
Normal file
@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20201108054749_webhooks")]
|
||||
public partial class webhooks : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Webhooks",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(maxLength: 25, nullable: false),
|
||||
Blob = table.Column<byte[]>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Webhooks", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "StoreWebhooks",
|
||||
columns: table => new
|
||||
{
|
||||
StoreId = table.Column<string>(nullable: false),
|
||||
WebhookId = table.Column<string>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_StoreWebhooks", x => new { x.StoreId, x.WebhookId });
|
||||
table.ForeignKey(
|
||||
name: "FK_StoreWebhooks_Stores_StoreId",
|
||||
column: x => x.StoreId,
|
||||
principalTable: "Stores",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_StoreWebhooks_Webhooks_WebhookId",
|
||||
column: x => x.WebhookId,
|
||||
principalTable: "Webhooks",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "WebhookDeliveries",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(maxLength: 25, nullable: false),
|
||||
WebhookId = table.Column<string>(maxLength: 25, nullable: false),
|
||||
Timestamp = table.Column<DateTimeOffset>(nullable: false),
|
||||
Blob = table.Column<byte[]>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_WebhookDeliveries", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_WebhookDeliveries_Webhooks_WebhookId",
|
||||
column: x => x.WebhookId,
|
||||
principalTable: "Webhooks",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "InvoiceWebhookDeliveries",
|
||||
columns: table => new
|
||||
{
|
||||
InvoiceId = table.Column<string>(nullable: false),
|
||||
DeliveryId = table.Column<string>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_InvoiceWebhookDeliveries", x => new { x.InvoiceId, x.DeliveryId });
|
||||
table.ForeignKey(
|
||||
name: "FK_InvoiceWebhookDeliveries_WebhookDeliveries_DeliveryId",
|
||||
column: x => x.DeliveryId,
|
||||
principalTable: "WebhookDeliveries",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_InvoiceWebhookDeliveries_Invoices_InvoiceId",
|
||||
column: x => x.InvoiceId,
|
||||
principalTable: "Invoices",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WebhookDeliveries_WebhookId",
|
||||
table: "WebhookDeliveries",
|
||||
column: "WebhookId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "InvoiceWebhookDeliveries");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "StoreWebhooks");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "WebhookDeliveries");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Webhooks");
|
||||
}
|
||||
}
|
||||
}
|
@ -108,6 +108,12 @@ namespace BTCPayServer.Migrations
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DisabledNotifications")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(256);
|
||||
@ -251,6 +257,25 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("InvoiceEvents");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceWebhookDeliveryData", b =>
|
||||
{
|
||||
b.Property<string>("InvoiceId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeliveryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InvoiceId", "DeliveryId");
|
||||
|
||||
b.HasIndex("DeliveryId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("InvoiceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("InvoiceWebhookDeliveries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.NotificationData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -525,7 +550,8 @@ namespace BTCPayServer.Migrations
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PullPaymentDataId")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(30);
|
||||
|
||||
b.HasKey("InvoiceDataId", "PullPaymentDataId");
|
||||
|
||||
@ -581,6 +607,25 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("Stores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreWebhookData", b =>
|
||||
{
|
||||
b.Property<string>("StoreId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("WebhookId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("StoreId", "WebhookId");
|
||||
|
||||
b.HasIndex("StoreId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("WebhookId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("StoreWebhooks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoredFile", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -689,6 +734,46 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("WalletTransactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WebhookData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.IsRequired()
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Webhooks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WebhookDeliveryData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.IsRequired()
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<DateTimeOffset>("Timestamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("WebhookId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("WebhookId");
|
||||
|
||||
b.ToTable("WebhookDeliveries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -876,6 +961,21 @@ namespace BTCPayServer.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceWebhookDeliveryData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.WebhookDeliveryData", "Delivery")
|
||||
.WithOne()
|
||||
.HasForeignKey("BTCPayServer.Data.InvoiceWebhookDeliveryData", "DeliveryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "Invoice")
|
||||
.WithOne()
|
||||
.HasForeignKey("BTCPayServer.Data.InvoiceWebhookDeliveryData", "InvoiceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.NotificationData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.ApplicationUser", "ApplicationUser")
|
||||
@ -949,6 +1049,21 @@ namespace BTCPayServer.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreWebhookData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "Store")
|
||||
.WithOne()
|
||||
.HasForeignKey("BTCPayServer.Data.StoreWebhookData", "StoreId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BTCPayServer.Data.WebhookData", "Webhook")
|
||||
.WithOne()
|
||||
.HasForeignKey("BTCPayServer.Data.StoreWebhookData", "WebhookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoredFile", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.ApplicationUser", "ApplicationUser")
|
||||
@ -960,7 +1075,8 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("U2FDevices")
|
||||
.HasForeignKey("ApplicationUserId");
|
||||
.HasForeignKey("ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
|
||||
@ -987,6 +1103,15 @@ namespace BTCPayServer.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WebhookDeliveryData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.WebhookData", "Webhook")
|
||||
.WithMany("Deliveries")
|
||||
.HasForeignKey("WebhookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
|
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.Abstractions.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 |
19
BTCPayServer.Plugins.Test/BTCPayServer.Plugins.Test.csproj
Normal file
19
BTCPayServer.Plugins.Test/BTCPayServer.Plugins.Test.csproj
Normal file
@ -0,0 +1,19 @@
|
||||
<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>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.4">
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -0,0 +1,35 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Plugins.Test.Data;
|
||||
using BTCPayServer.Plugins.Test.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test
|
||||
{
|
||||
[Route("extensions/test")]
|
||||
public class TestExtensionController : Controller
|
||||
{
|
||||
private readonly TestPluginService _testPluginService;
|
||||
|
||||
public TestExtensionController(TestPluginService testPluginService)
|
||||
{
|
||||
_testPluginService = testPluginService;
|
||||
}
|
||||
|
||||
// GET
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
return View(new TestPluginPageViewModel()
|
||||
{
|
||||
Data = await _testPluginService.Get()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class TestPluginPageViewModel
|
||||
{
|
||||
public List<TestPluginData> Data { get; set; }
|
||||
}
|
||||
}
|
14
BTCPayServer.Plugins.Test/Data/TestPluginData.cs
Normal file
14
BTCPayServer.Plugins.Test/Data/TestPluginData.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test.Data
|
||||
{
|
||||
public class TestPluginData
|
||||
{
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public string Id { get; set; }
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
46
BTCPayServer.Plugins.Test/Data/TestPluginDbContext.cs
Normal file
46
BTCPayServer.Plugins.Test/Data/TestPluginDbContext.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Plugins.Test.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test
|
||||
{
|
||||
public class TestPluginDbContext : DbContext
|
||||
{
|
||||
private readonly bool _designTime;
|
||||
|
||||
public DbSet<TestPluginData> TestPluginRecords { get; set; }
|
||||
|
||||
public TestPluginDbContext(DbContextOptions<TestPluginDbContext> options, bool designTime = false)
|
||||
: base(options)
|
||||
{
|
||||
_designTime = designTime;
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
modelBuilder.HasDefaultSchema("BTCPayServer.Plugins.Test");
|
||||
if (Database.IsSqlite() && !_designTime)
|
||||
{
|
||||
// SQLite does not have proper support for DateTimeOffset via Entity Framework Core, see the limitations
|
||||
// here: https://docs.microsoft.com/en-us/ef/core/providers/sqlite/limitations#query-limitations
|
||||
// To work around this, when the Sqlite database provider is used, all model properties of type DateTimeOffset
|
||||
// use the DateTimeOffsetToBinaryConverter
|
||||
// Based on: https://github.com/aspnet/EntityFrameworkCore/issues/10784#issuecomment-415769754
|
||||
// This only supports millisecond precision, but should be sufficient for most use cases.
|
||||
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
|
||||
{
|
||||
var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == typeof(DateTimeOffset));
|
||||
foreach (var property in properties)
|
||||
{
|
||||
modelBuilder
|
||||
.Entity(entityType.Name)
|
||||
.Property(property.Name)
|
||||
.HasConversion(new Microsoft.EntityFrameworkCore.Storage.ValueConversion.DateTimeOffsetToBinaryConverter());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
BTCPayServer.Plugins.Test/Migrations/20201117154419_Init.cs
Normal file
37
BTCPayServer.Plugins.Test/Migrations/20201117154419_Init.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test.Migrations
|
||||
{
|
||||
[DbContext(typeof(TestPluginDbContext))]
|
||||
[Migration("20201117154419_Init")]
|
||||
public partial class Init : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.EnsureSchema(
|
||||
name: "BTCPayServer.Plugins.Test");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TestPluginRecords",
|
||||
schema: "BTCPayServer.Plugins.Test",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(nullable: false),
|
||||
Timestamp = table.Column<DateTimeOffset>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TestPluginRecords", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "TestPluginRecords",
|
||||
schema: "BTCPayServer.Plugins.Test");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using BTCPayServer.Plugins.Test;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test.Migrations
|
||||
{
|
||||
[DbContext(typeof(TestPluginDbContext))]
|
||||
partial class TestPluginDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("BTCPayServer.Plugins.Test")
|
||||
.HasAnnotation("ProductVersion", "3.1.10");
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Plugins.Test.Data.TestPluginData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("Timestamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("TestPluginRecords");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
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 |
44
BTCPayServer.Plugins.Test/Services/ApplicationPartsLogger.cs
Normal file
44
BTCPayServer.Plugins.Test/Services/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;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
using System.Reflection;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Plugins.Test.Migrations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test
|
||||
{
|
||||
|
||||
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<TestPluginDbContext>
|
||||
{
|
||||
public TestPluginDbContext CreateDbContext(string[] args)
|
||||
{
|
||||
|
||||
var builder = new DbContextOptionsBuilder<TestPluginDbContext>();
|
||||
|
||||
builder.UseSqlite("Data Source=temp.db");
|
||||
|
||||
return new TestPluginDbContext(builder.Options, true);
|
||||
}
|
||||
}
|
||||
|
||||
public class TestPluginDbContextFactory : BaseDbContextFactory<TestPluginDbContext>
|
||||
{
|
||||
public TestPluginDbContextFactory(DatabaseOptions options) : base(options, "BTCPayServer.Plugins.Test")
|
||||
{
|
||||
}
|
||||
|
||||
public override TestPluginDbContext CreateContext()
|
||||
{
|
||||
var builder = new DbContextOptionsBuilder<TestPluginDbContext>();
|
||||
ConfigureBuilder(builder);
|
||||
return new TestPluginDbContext(builder.Options);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
33
BTCPayServer.Plugins.Test/Services/TestPluginService.cs
Normal file
33
BTCPayServer.Plugins.Test/Services/TestPluginService.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Plugins.Test.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test.Services
|
||||
{
|
||||
public class TestPluginService
|
||||
{
|
||||
private readonly TestPluginDbContextFactory _testPluginDbContextFactory;
|
||||
|
||||
public TestPluginService(TestPluginDbContextFactory testPluginDbContextFactory)
|
||||
{
|
||||
_testPluginDbContextFactory = testPluginDbContextFactory;
|
||||
}
|
||||
|
||||
public async Task AddTestDataRecord()
|
||||
{
|
||||
await using var context = _testPluginDbContextFactory.CreateContext();
|
||||
|
||||
await context.TestPluginRecords.AddAsync(new TestPluginData() {Timestamp = DateTimeOffset.UtcNow});
|
||||
}
|
||||
|
||||
|
||||
public async Task<List<TestPluginData>> Get()
|
||||
{
|
||||
await using var context = _testPluginDbContextFactory.CreateContext();
|
||||
|
||||
return await context.TestPluginRecords.ToListAsync();
|
||||
}
|
||||
}
|
||||
}
|
39
BTCPayServer.Plugins.Test/TestExtension.cs
Normal file
39
BTCPayServer.Plugins.Test/TestExtension.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Abstractions.Services;
|
||||
using BTCPayServer.Plugins.Test.Services;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
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>();
|
||||
services.AddSingleton<TestPluginService>();
|
||||
services.AddSingleton<TestPluginDbContextFactory>();
|
||||
services.AddDbContext<TestPluginDbContext>((provider, o) =>
|
||||
{
|
||||
var factory = provider.GetRequiredService<TestPluginDbContextFactory>();
|
||||
factory.ConfigureBuilder(o);
|
||||
});
|
||||
}
|
||||
|
||||
public override void Execute(IApplicationBuilder applicationBuilder, IServiceProvider applicationBuilderApplicationServices)
|
||||
{
|
||||
base.Execute(applicationBuilder, applicationBuilderApplicationServices);
|
||||
applicationBuilderApplicationServices.GetService<TestPluginDbContextFactory>().CreateContext().Database.Migrate();
|
||||
applicationBuilderApplicationServices.GetService<TestPluginService>().AddTestDataRecord().GetAwaiter().GetResult();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
21
BTCPayServer.Plugins.Test/Views/TestExtension/Index.cshtml
Normal file
21
BTCPayServer.Plugins.Test/Views/TestExtension/Index.cshtml
Normal file
@ -0,0 +1,21 @@
|
||||
@model BTCPayServer.Plugins.Test.TestPluginPageViewModel
|
||||
<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 class="row">
|
||||
<h2>Persisted Data</h2>
|
||||
<p>The following is data persisted to the configured database but in an isolated DbContext. Every time you start BTCPayw with this plugin enabled, a timestamp is logged.</p>
|
||||
<ul class="list-group">>
|
||||
@foreach (var item in Model.Data)
|
||||
{
|
||||
<li class="list-group-item">@item.Id at @item.Timestamp.ToString("F")</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</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>
|
||||
|
@ -875,7 +875,7 @@ normal:
|
||||
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
|
||||
{
|
||||
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null, null),
|
||||
});
|
||||
var networkBTC = networkProvider.GetNetwork("BTC");
|
||||
var networkLTC = networkProvider.GetNetwork("LTC");
|
||||
|
@ -89,7 +89,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("button[value='btcpay.store.canmodifystoresettings:change-store-mode']")).Click();
|
||||
//there should be a store already by default in the dropdown
|
||||
var dropdown = s.Driver.FindElement(By.Name("PermissionValues[3].SpecificStores[0]"));
|
||||
var dropdown = s.Driver.FindElement(By.Name("PermissionValues[4].SpecificStores[0]"));
|
||||
var option = dropdown.FindElement(By.TagName("option"));
|
||||
var storeId = option.GetAttribute("value");
|
||||
option.Click();
|
||||
|
@ -21,6 +21,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.13" />
|
||||
<PackageReference Include="Selenium.Support" Version="3.141.0" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="85.0.4183.8700" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
|
@ -26,7 +26,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using AuthenticationSchemes = BTCPayServer.Security.AuthenticationSchemes;
|
||||
using AuthenticationSchemes = BTCPayServer.Abstractions.Constants.AuthenticationSchemes;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -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)));
|
||||
|
@ -3,6 +3,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using ExchangeSharp;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
@ -62,9 +63,9 @@ namespace BTCPayServer.Tests
|
||||
semaphore.Dispose();
|
||||
}
|
||||
|
||||
public async Task<HttpContext> GetNextRequest()
|
||||
public async Task<HttpContext> GetNextRequest(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _channel.Reader.ReadAsync();
|
||||
return await _channel.Reader.ReadAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ using BTCPayServer.Tests.Logging;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using NBitcoin.OpenAsset;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@ -149,12 +150,12 @@ namespace BTCPayServer.Tests
|
||||
var user1 = await unauthClient.CreateUser(
|
||||
new CreateApplicationUserRequest() { Email = "test@gmail.com", Password = "abceudhqw" });
|
||||
Assert.Empty(user1.Roles);
|
||||
|
||||
|
||||
// We have no admin, so it should work
|
||||
var user2 = await unauthClient.CreateUser(
|
||||
new CreateApplicationUserRequest() { Email = "test2@gmail.com", Password = "abceudhqw" });
|
||||
Assert.Empty(user2.Roles);
|
||||
|
||||
|
||||
// Duplicate email
|
||||
await AssertValidationError(new[] { "Email" },
|
||||
async () => await unauthClient.CreateUser(
|
||||
@ -168,7 +169,9 @@ namespace BTCPayServer.Tests
|
||||
IsAdministrator = true
|
||||
});
|
||||
Assert.Contains("ServerAdmin", admin.Roles);
|
||||
|
||||
Assert.NotNull(admin.Created);
|
||||
Assert.True((DateTimeOffset.Now - admin.Created).Value.Seconds < 10);
|
||||
|
||||
// Creating a new user without proper creds is now impossible (unauthorized)
|
||||
// Because if registration are locked and that an admin exists, we don't accept unauthenticated connection
|
||||
await AssertHttpError(401,
|
||||
@ -609,6 +612,101 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanUseWebhooks()
|
||||
{
|
||||
void AssertHook(FakeServer fakeServer, Client.Models.StoreWebhookData hook)
|
||||
{
|
||||
Assert.True(hook.Enabled);
|
||||
Assert.True(hook.AuthorizedEvents.Everything);
|
||||
Assert.False(hook.AutomaticRedelivery);
|
||||
Assert.Equal(fakeServer.ServerUri.AbsoluteUri, hook.Url);
|
||||
}
|
||||
using var tester = ServerTester.Create();
|
||||
using var fakeServer = new FakeServer();
|
||||
await fakeServer.Start();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
var clientProfile = await user.CreateClient(Policies.CanModifyStoreWebhooks, Policies.CanCreateInvoice);
|
||||
var hook = await clientProfile.CreateWebhook(user.StoreId, new CreateStoreWebhookRequest()
|
||||
{
|
||||
Url = fakeServer.ServerUri.AbsoluteUri,
|
||||
AutomaticRedelivery = false
|
||||
});
|
||||
Assert.NotNull(hook.Secret);
|
||||
AssertHook(fakeServer, hook);
|
||||
hook = await clientProfile.GetWebhook(user.StoreId, hook.Id);
|
||||
AssertHook(fakeServer, hook);
|
||||
var hooks = await clientProfile.GetWebhooks(user.StoreId);
|
||||
hook = Assert.Single(hooks);
|
||||
AssertHook(fakeServer, hook);
|
||||
await clientProfile.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest() { Currency = "USD", Amount = 100 });
|
||||
var req = await fakeServer.GetNextRequest();
|
||||
req.Response.StatusCode = 200;
|
||||
fakeServer.Done();
|
||||
hook = await clientProfile.UpdateWebhook(user.StoreId, hook.Id, new UpdateStoreWebhookRequest()
|
||||
{
|
||||
Url = hook.Url,
|
||||
Secret = "lol",
|
||||
AutomaticRedelivery = false
|
||||
});
|
||||
Assert.Null(hook.Secret);
|
||||
AssertHook(fakeServer, hook);
|
||||
var deliveries = await clientProfile.GetWebhookDeliveries(user.StoreId, hook.Id);
|
||||
var delivery = Assert.Single(deliveries);
|
||||
delivery = await clientProfile.GetWebhookDelivery(user.StoreId, hook.Id, delivery.Id);
|
||||
Assert.NotNull(delivery);
|
||||
Assert.Equal(WebhookDeliveryStatus.HttpSuccess, delivery.Status);
|
||||
|
||||
var newDeliveryId = await clientProfile.RedeliverWebhook(user.StoreId, hook.Id, delivery.Id);
|
||||
req = await fakeServer.GetNextRequest();
|
||||
req.Response.StatusCode = 404;
|
||||
fakeServer.Done();
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var newDelivery = await clientProfile.GetWebhookDelivery(user.StoreId, hook.Id, newDeliveryId);
|
||||
Assert.NotNull(newDelivery);
|
||||
Assert.Equal(404, newDelivery.HttpCode);
|
||||
var req = await clientProfile.GetWebhookDeliveryRequest(user.StoreId, hook.Id, newDeliveryId);
|
||||
Assert.Equal(delivery.Id, req.OrignalDeliveryId);
|
||||
Assert.True(req.IsRedelivery);
|
||||
Assert.Equal(WebhookDeliveryStatus.HttpError, newDelivery.Status);
|
||||
});
|
||||
deliveries = await clientProfile.GetWebhookDeliveries(user.StoreId, hook.Id);
|
||||
Assert.Equal(2, deliveries.Length);
|
||||
Assert.Equal(newDeliveryId, deliveries[0].Id);
|
||||
var jObj = await clientProfile.GetWebhookDeliveryRequest(user.StoreId, hook.Id, newDeliveryId);
|
||||
Assert.NotNull(jObj);
|
||||
|
||||
Logs.Tester.LogInformation("Should not be able to access webhook without proper auth");
|
||||
var unauthorized = await user.CreateClient(Policies.CanCreateInvoice);
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await unauthorized.GetWebhookDeliveryRequest(user.StoreId, hook.Id, newDeliveryId);
|
||||
});
|
||||
|
||||
Logs.Tester.LogInformation("Can use btcpay.store.canmodifystoresettings to query webhooks");
|
||||
clientProfile = await user.CreateClient(Policies.CanModifyStoreSettings, Policies.CanCreateInvoice);
|
||||
await clientProfile.GetWebhookDeliveryRequest(user.StoreId, hook.Id, newDeliveryId);
|
||||
|
||||
Logs.Tester.LogInformation("Testing corner cases");
|
||||
Assert.Null(await clientProfile.GetWebhookDeliveryRequest(user.StoreId, "lol", newDeliveryId));
|
||||
Assert.Null(await clientProfile.GetWebhookDeliveryRequest(user.StoreId, hook.Id, "lol"));
|
||||
Assert.Null(await clientProfile.GetWebhookDeliveryRequest(user.StoreId, "lol", "lol"));
|
||||
Assert.Null(await clientProfile.GetWebhook(user.StoreId, "lol"));
|
||||
await AssertHttpError(404, async () =>
|
||||
{
|
||||
await clientProfile.UpdateWebhook(user.StoreId, "lol", new UpdateStoreWebhookRequest() { Url = hook.Url });
|
||||
});
|
||||
|
||||
Assert.True(await clientProfile.DeleteWebhook(user.StoreId, hook.Id));
|
||||
Assert.False(await clientProfile.DeleteWebhook(user.StoreId, hook.Id));
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task HealthControllerTests()
|
||||
@ -819,6 +917,7 @@ namespace BTCPayServer.Tests
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync();
|
||||
await user.MakeAdmin();
|
||||
await user.SetupWebhook();
|
||||
var client = await user.CreateClient(Policies.Unrestricted);
|
||||
var viewOnly = await user.CreateClient(Policies.CanViewInvoices);
|
||||
|
||||
@ -846,9 +945,15 @@ namespace BTCPayServer.Tests
|
||||
Assert.Single(invoices);
|
||||
Assert.Equal(newInvoice.Id, invoices.First().Id);
|
||||
|
||||
//get payment request
|
||||
//get
|
||||
var invoice = await viewOnly.GetInvoice(user.StoreId, newInvoice.Id);
|
||||
Assert.Equal(newInvoice.Metadata, invoice.Metadata);
|
||||
var paymentMethods = await viewOnly.GetInvoicePaymentMethods(user.StoreId, newInvoice.Id);
|
||||
Assert.Single(paymentMethods);
|
||||
var paymentMethod = paymentMethods.First();
|
||||
Assert.Equal("BTC", paymentMethod.PaymentMethod);
|
||||
Assert.Empty(paymentMethod.Payments);
|
||||
|
||||
|
||||
//update
|
||||
invoice = await viewOnly.GetInvoice(user.StoreId, newInvoice.Id);
|
||||
@ -857,7 +962,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await client.MarkInvoiceStatus(user.StoreId, invoice.Id, new MarkInvoiceStatusRequest()
|
||||
{
|
||||
Status = InvoiceStatus.Complete
|
||||
Status = InvoiceStatus.Settled
|
||||
});
|
||||
});
|
||||
|
||||
@ -876,10 +981,43 @@ namespace BTCPayServer.Tests
|
||||
await client.UnarchiveInvoice(user.StoreId, invoice.Id);
|
||||
Assert.NotNull(await client.GetInvoice(user.StoreId, invoice.Id));
|
||||
|
||||
|
||||
foreach (var marked in new[] { InvoiceStatus.Settled, InvoiceStatus.Invalid })
|
||||
{
|
||||
var inv = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest() { Currency = "USD", Amount = 100 });
|
||||
await user.PayInvoice(inv.Id);
|
||||
await client.MarkInvoiceStatus(user.StoreId, inv.Id, new MarkInvoiceStatusRequest()
|
||||
{
|
||||
Status = marked
|
||||
});
|
||||
var result = await client.GetInvoice(user.StoreId, inv.Id);
|
||||
if (marked == InvoiceStatus.Settled)
|
||||
{
|
||||
Assert.Equal(InvoiceStatus.Settled, result.Status);
|
||||
user.AssertHasWebhookEvent<WebhookInvoiceSettledEvent>(WebhookEventType.InvoiceSettled,
|
||||
o =>
|
||||
{
|
||||
Assert.Equal(inv.Id, o.InvoiceId);
|
||||
Assert.True(o.ManuallyMarked);
|
||||
});
|
||||
}
|
||||
if (marked == InvoiceStatus.Invalid)
|
||||
{
|
||||
Assert.Equal(InvoiceStatus.Invalid, result.Status);
|
||||
var evt = user.AssertHasWebhookEvent<WebhookInvoiceInvalidEvent>(WebhookEventType.InvoiceInvalid,
|
||||
o =>
|
||||
{
|
||||
Assert.Equal(inv.Id, o.InvoiceId);
|
||||
Assert.True(o.ManuallyMarked);
|
||||
});
|
||||
Assert.NotNull(await client.GetWebhookDelivery(evt.StoreId, evt.WebhookId, evt.DeliveryId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanUseLightningAPI()
|
||||
@ -905,7 +1043,7 @@ namespace BTCPayServer.Tests
|
||||
var info = await client.GetLightningNodeInfo("BTC");
|
||||
Assert.Single(info.NodeURIs);
|
||||
Assert.NotEqual(0, info.BlockHeight);
|
||||
|
||||
|
||||
var err = await Assert.ThrowsAsync<HttpRequestException>(async () => await client.GetLightningNodeChannels("BTC"));
|
||||
Assert.Contains("503", err.Message);
|
||||
// Not permission for the store!
|
||||
|
@ -3,6 +3,7 @@ using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
@ -269,7 +270,7 @@ namespace BTCPayServer.Tests
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var invoice = await s.Server.PayTester.GetService<InvoiceRepository>().GetInvoice(invoiceId);
|
||||
Assert.Equal(InvoiceStatus.Paid, invoice.Status);
|
||||
Assert.Equal(InvoiceStatusLegacy.Paid, invoice.Status);
|
||||
});
|
||||
|
||||
s.GoToInvoices();
|
||||
@ -329,7 +330,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
var invoice = await s.Server.PayTester.GetService<InvoiceRepository>().GetInvoice(invoiceId);
|
||||
var dto = invoice.EntityToDTO();
|
||||
Assert.Equal(InvoiceStatus.Paid, invoice.Status);
|
||||
Assert.Equal(InvoiceStatusLegacy.Paid, invoice.Status);
|
||||
});
|
||||
s.GoToInvoices();
|
||||
paymentValueRowColumn = s.Driver.FindElement(By.Id($"invoice_{invoiceId}"))
|
||||
@ -719,7 +720,7 @@ retry:
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var invoice = await tester.PayTester.GetService<InvoiceRepository>().GetInvoice(lastInvoiceId);
|
||||
Assert.Equal(InvoiceStatus.Paid, invoice.Status);
|
||||
Assert.Equal(InvoiceStatusLegacy.Paid, invoice.Status);
|
||||
Assert.Equal(InvoiceExceptionStatus.None, invoice.ExceptionStatus);
|
||||
var coins = await btcPayWallet.GetUnspentCoins(receiverUser.DerivationScheme);
|
||||
foreach (var coin in coins)
|
||||
@ -1046,7 +1047,7 @@ retry:
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var invoiceEntity = await tester.PayTester.GetService<InvoiceRepository>().GetInvoice(invoice7.Id);
|
||||
Assert.Equal(InvoiceStatus.Paid, invoiceEntity.Status);
|
||||
Assert.Equal(InvoiceStatusLegacy.Paid, invoiceEntity.Status);
|
||||
Assert.Contains(invoiceEntity.GetPayments(), p => p.Accounted &&
|
||||
((BitcoinLikePaymentData)p.GetCryptoPaymentData()).PayjoinInformation is null);
|
||||
});
|
||||
@ -1075,7 +1076,7 @@ retry:
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var invoiceEntity = await tester.PayTester.GetService<InvoiceRepository>().GetInvoice(invoice7.Id);
|
||||
Assert.Equal(InvoiceStatus.New, invoiceEntity.Status);
|
||||
Assert.Equal(InvoiceStatusLegacy.New, invoiceEntity.Status);
|
||||
Assert.True(invoiceEntity.GetPayments().All(p => !p.Accounted));
|
||||
ourOutpoint = invoiceEntity.GetAllBitcoinPaymentData().First().PayjoinInformation.ContributedOutPoints[0];
|
||||
});
|
||||
|
@ -207,12 +207,12 @@ namespace BTCPayServer.Tests
|
||||
pair => pair.Key == "Id" && pair.Value.ToString() == invoiceId);
|
||||
|
||||
var invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
|
||||
Assert.Equal(InvoiceState.ToString(InvoiceStatus.New), invoice.Status);
|
||||
Assert.Equal(InvoiceState.ToString(InvoiceStatusLegacy.New), invoice.Status);
|
||||
Assert.IsType<OkObjectResult>(await
|
||||
paymentRequestController.CancelUnpaidPendingInvoice(paymentRequestId, false));
|
||||
|
||||
invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
|
||||
Assert.Equal(InvoiceState.ToString(InvoiceStatus.Invalid), invoice.Status);
|
||||
Assert.Equal(InvoiceState.ToString(InvoiceStatusLegacy.Invalid), invoice.Status);
|
||||
|
||||
Assert.IsType<BadRequestObjectResult>(await
|
||||
paymentRequestController.CancelUnpaidPendingInvoice(paymentRequestId, false));
|
||||
|
@ -6,6 +6,7 @@ using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Models;
|
||||
@ -42,18 +43,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 +230,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 +309,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();
|
||||
@ -312,10 +318,9 @@ namespace BTCPayServer.Tests
|
||||
Driver.FindElement(By.Name("StoreId")).SendKeys(storeName);
|
||||
Driver.FindElement(By.Id("Create")).Click();
|
||||
|
||||
Assert.True(Driver.PageSource.Contains("just created!"), "Unable to create Invoice");
|
||||
AssertHappyMessage();
|
||||
var statusElement = Driver.FindElement(By.ClassName("alert-success"));
|
||||
var id = statusElement.Text.Split(" ")[1];
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
@ -384,6 +389,11 @@ namespace BTCPayServer.Tests
|
||||
Driver.FindElement(By.Id($"Wallet{navPages}")).Click();
|
||||
}
|
||||
}
|
||||
|
||||
public void GoToUrl(string relativeUrl)
|
||||
{
|
||||
Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, relativeUrl));
|
||||
}
|
||||
|
||||
public void GoToServer(ServerNavPages navPages = ServerNavPages.Index)
|
||||
{
|
||||
|
@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
@ -12,11 +15,18 @@ using BTCPayServer.Views.Server;
|
||||
using BTCPayServer.Views.Wallets;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitcoin.Payment;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Support.Extensions;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
using Org.BouncyCastle.Ocsp;
|
||||
using Renci.SshNet.Security.Cryptography;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -133,19 +143,20 @@ namespace BTCPayServer.Tests
|
||||
//let's test invite link
|
||||
s.Logout();
|
||||
s.GoToRegister();
|
||||
var newAdminUser = s.RegisterNewUser(true);
|
||||
var newAdminUser = s.RegisterNewUser(true);
|
||||
s.GoToServer(ServerNavPages.Users);
|
||||
s.Driver.FindElement(By.Id("CreateUser")).Click();
|
||||
|
||||
|
||||
var usr = RandomUtils.GetUInt256().ToString().Substring(64 - 20) + "@a.com";
|
||||
s.Driver.FindElement(By.Id("Email")).SendKeys(usr);
|
||||
s.Driver.FindElement(By.Id("Save")).Click();
|
||||
var url = s.AssertHappyMessage().FindElement(By.TagName("a")).Text;;
|
||||
var url = s.AssertHappyMessage().FindElement(By.TagName("a")).Text;
|
||||
;
|
||||
s.Logout();
|
||||
s.Driver.Navigate().GoToUrl(url);
|
||||
Assert.Equal("hidden",s.Driver.FindElement(By.Id("Email")).GetAttribute("type"));
|
||||
Assert.Equal(usr,s.Driver.FindElement(By.Id("Email")).GetAttribute("value"));
|
||||
|
||||
Assert.Equal("hidden", s.Driver.FindElement(By.Id("Email")).GetAttribute("type"));
|
||||
Assert.Equal(usr, s.Driver.FindElement(By.Id("Email")).GetAttribute("value"));
|
||||
|
||||
s.Driver.FindElement(By.Id("Password")).SendKeys("123456");
|
||||
s.Driver.FindElement(By.Id("ConfirmPassword")).SendKeys("123456");
|
||||
s.Driver.FindElement(By.Id("SetPassword")).Click();
|
||||
@ -203,6 +214,46 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanSetupEmailServer()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
var alice = s.RegisterNewUser(isAdmin: true);
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/emails"));
|
||||
if (s.Driver.PageSource.Contains("Configured"))
|
||||
{
|
||||
s.Driver.FindElement(By.CssSelector("button[value=\"ResetPassword\"]")).Submit();
|
||||
s.AssertHappyMessage();
|
||||
}
|
||||
CanSetupEmailCore(s);
|
||||
s.CreateNewStore();
|
||||
s.GoToUrl($"stores/{s.StoreId}/emails");
|
||||
CanSetupEmailCore(s);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CanSetupEmailCore(SeleniumTester s)
|
||||
{
|
||||
s.Driver.FindElement(By.ClassName("dropdown-toggle")).Click();
|
||||
s.Driver.FindElement(By.ClassName("dropdown-item")).Click();
|
||||
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test@gmail.com");
|
||||
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
|
||||
s.AssertHappyMessage();
|
||||
s.Driver.FindElement(By.Id("Settings_Password")).SendKeys("mypassword");
|
||||
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
|
||||
Assert.Contains("Configured", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test_fix@gmail.com");
|
||||
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
|
||||
Assert.Contains("Configured", s.Driver.PageSource);
|
||||
Assert.Contains("test_fix", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.CssSelector("button[value=\"ResetPassword\"]")).Submit();
|
||||
s.AssertHappyMessage();
|
||||
Assert.DoesNotContain("Configured", s.Driver.PageSource);
|
||||
Assert.Contains("test_fix", s.Driver.PageSource);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanUseDynamicDns()
|
||||
{
|
||||
@ -260,14 +311,31 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await s.StartAsync();
|
||||
var alice = s.RegisterNewUser();
|
||||
var store = s.CreateNewStore().storeName;
|
||||
s.AddDerivationScheme();
|
||||
var storeData = s.CreateNewStore();
|
||||
var onchainHint = "A store requires a wallet to receive payments. Set up your wallet.";
|
||||
var offchainHint = "A connection to a Lightning node is required to receive Lightning payments.";
|
||||
|
||||
// verify that hints are displayed on the store page
|
||||
Assert.True(s.Driver.PageSource.Contains(onchainHint), "Wallet hint not present");
|
||||
Assert.True(s.Driver.PageSource.Contains(offchainHint), "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(onchainHint),
|
||||
"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;
|
||||
@ -322,6 +390,10 @@ 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(offchainHint), "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();
|
||||
@ -534,6 +606,132 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanUseWebhooks()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser(true);
|
||||
var store = s.CreateNewStore();
|
||||
s.GoToStore(store.storeId, Views.Stores.StoreNavPages.Webhooks);
|
||||
|
||||
Logs.Tester.LogInformation("Let's create two webhooks");
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
s.Driver.FindElement(By.Id("CreateWebhook")).Click();
|
||||
s.Driver.FindElement(By.Name("PayloadUrl")).SendKeys($"http://127.0.0.1/callback{i}");
|
||||
new SelectElement(s.Driver.FindElement(By.Name("Everything")))
|
||||
.SelectByValue("false");
|
||||
s.Driver.FindElement(By.Id("InvoiceCreated")).Click();
|
||||
s.Driver.FindElement(By.Id("InvoiceProcessing")).Click();
|
||||
s.Driver.FindElement(By.Name("add")).Click();
|
||||
}
|
||||
|
||||
Logs.Tester.LogInformation("Let's delete one of them");
|
||||
var deletes = s.Driver.FindElements(By.LinkText("Delete"));
|
||||
Assert.Equal(2, deletes.Count);
|
||||
deletes[0].Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
deletes = s.Driver.FindElements(By.LinkText("Delete"));
|
||||
Assert.Single(deletes);
|
||||
s.AssertHappyMessage();
|
||||
|
||||
Logs.Tester.LogInformation("Let's try to update one of them");
|
||||
s.Driver.FindElement(By.LinkText("Modify")).Click();
|
||||
|
||||
using FakeServer server = new FakeServer();
|
||||
await server.Start();
|
||||
s.Driver.FindElement(By.Name("PayloadUrl")).Clear();
|
||||
s.Driver.FindElement(By.Name("PayloadUrl")).SendKeys(server.ServerUri.AbsoluteUri);
|
||||
s.Driver.FindElement(By.Name("Secret")).Clear();
|
||||
s.Driver.FindElement(By.Name("Secret")).SendKeys("HelloWorld");
|
||||
s.Driver.FindElement(By.Name("update")).Click();
|
||||
s.AssertHappyMessage();
|
||||
s.Driver.FindElement(By.LinkText("Modify")).Click();
|
||||
foreach (var value in Enum.GetValues(typeof(WebhookEventType)))
|
||||
{
|
||||
// Here we make sure we did not forget an event type in the list
|
||||
// However, maybe some event should not appear here because not at the store level.
|
||||
// Fix as needed.
|
||||
Assert.Contains($"value=\"{value}\"", s.Driver.PageSource);
|
||||
}
|
||||
// This one should be checked
|
||||
Assert.Contains($"value=\"InvoiceProcessing\" checked", s.Driver.PageSource);
|
||||
Assert.Contains($"value=\"InvoiceCreated\" checked", s.Driver.PageSource);
|
||||
// This one never been checked
|
||||
Assert.DoesNotContain($"value=\"InvoiceReceivedPayment\" checked", s.Driver.PageSource);
|
||||
|
||||
s.Driver.FindElement(By.Name("update")).Click();
|
||||
s.AssertHappyMessage();
|
||||
Assert.Contains(server.ServerUri.AbsoluteUri, s.Driver.PageSource);
|
||||
|
||||
Logs.Tester.LogInformation("Let's see if we can generate an event");
|
||||
s.GoToStore(store.storeId);
|
||||
s.AddDerivationScheme();
|
||||
s.CreateInvoice(store.storeName);
|
||||
var request = await server.GetNextRequest();
|
||||
var headers = request.Request.Headers;
|
||||
var actualSig = headers["BTCPay-Sig"].First();
|
||||
var bytes = await request.Request.Body.ReadBytesAsync((int)headers.ContentLength.Value);
|
||||
var expectedSig = $"sha256={Encoders.Hex.EncodeData(new HMACSHA256(Encoding.UTF8.GetBytes("HelloWorld")).ComputeHash(bytes))}";
|
||||
Assert.Equal(expectedSig, actualSig);
|
||||
request.Response.StatusCode = 200;
|
||||
server.Done();
|
||||
|
||||
Logs.Tester.LogInformation("Let's make a failed event");
|
||||
s.CreateInvoice(store.storeName);
|
||||
request = await server.GetNextRequest();
|
||||
request.Response.StatusCode = 404;
|
||||
server.Done();
|
||||
|
||||
// The delivery is done asynchronously, so small wait here
|
||||
await Task.Delay(500);
|
||||
s.GoToStore(store.storeId, Views.Stores.StoreNavPages.Webhooks);
|
||||
s.Driver.FindElement(By.LinkText("Modify")).Click();
|
||||
var elements = s.Driver.FindElements(By.ClassName("redeliver"));
|
||||
// One worked, one failed
|
||||
s.Driver.FindElement(By.ClassName("fa-times"));
|
||||
s.Driver.FindElement(By.ClassName("fa-check"));
|
||||
elements[0].Click();
|
||||
s.AssertHappyMessage();
|
||||
request = await server.GetNextRequest();
|
||||
request.Response.StatusCode = 404;
|
||||
server.Done();
|
||||
|
||||
Logs.Tester.LogInformation("Can we browse the json content?");
|
||||
CanBrowseContent(s);
|
||||
|
||||
s.GoToInvoices();
|
||||
s.Driver.FindElement(By.LinkText("Details")).Click();
|
||||
CanBrowseContent(s);
|
||||
var element = s.Driver.FindElement(By.ClassName("redeliver"));
|
||||
element.Click();
|
||||
s.AssertHappyMessage();
|
||||
request = await server.GetNextRequest();
|
||||
request.Response.StatusCode = 404;
|
||||
server.Done();
|
||||
|
||||
Logs.Tester.LogInformation("Let's see if we can delete store with some webhooks inside");
|
||||
s.GoToStore(store.storeId);
|
||||
s.Driver.ExecuteJavaScript("window.scrollBy(0,1000);");
|
||||
s.Driver.FindElement(By.Id("danger-zone-expander")).Click();
|
||||
s.Driver.FindElement(By.Id("delete-store")).Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.AssertHappyMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private static void CanBrowseContent(SeleniumTester s)
|
||||
{
|
||||
s.Driver.FindElement(By.ClassName("delivery-content")).Click();
|
||||
var windows = s.Driver.WindowHandles;
|
||||
Assert.Equal(2, windows.Count);
|
||||
s.Driver.SwitchTo().Window(windows[1]);
|
||||
JObject.Parse(s.Driver.FindElement(By.TagName("body")).Text);
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(windows[0]);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanManageWallet()
|
||||
@ -766,7 +964,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
|
||||
@ -808,8 +1006,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);
|
||||
|
||||
|
@ -23,6 +23,7 @@ namespace BTCPayServer.Tests
|
||||
return new ServerTester(scope, newDb);
|
||||
}
|
||||
|
||||
public List<IDisposable> Resources = new List<IDisposable>();
|
||||
readonly string _Directory;
|
||||
public ServerTester(string scope, bool newDb)
|
||||
{
|
||||
@ -145,7 +146,7 @@ namespace BTCPayServer.Tests
|
||||
var tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var sub = PayTester.GetService<EventAggregator>().Subscribe<T>(evt =>
|
||||
{
|
||||
if(correctEvent is null)
|
||||
if (correctEvent is null)
|
||||
tcs.TrySetResult(evt);
|
||||
else if (correctEvent(evt))
|
||||
{
|
||||
@ -207,6 +208,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var r in this.Resources)
|
||||
r.Dispose();
|
||||
Logs.Tester.LogInformation("Disposing the BTCPayTester...");
|
||||
foreach (var store in Stores)
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.ServerViewModels;
|
||||
@ -222,7 +223,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
//delete file
|
||||
Assert.IsType<RedirectToActionResult>(await controller.DeleteFile(fileId));
|
||||
controller.TempData.GetStatusMessageModel();
|
||||
statusMessageModel = controller.TempData.GetStatusMessageModel();
|
||||
Assert.NotNull(statusMessageModel);
|
||||
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Success, statusMessageModel.Severity);
|
||||
|
@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
@ -23,8 +25,10 @@ using NBitcoin.Payment;
|
||||
using NBitpayClient;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -427,5 +431,86 @@ namespace BTCPayServer.Tests
|
||||
return null;
|
||||
return parsedBip21;
|
||||
}
|
||||
|
||||
class WebhookListener : IDisposable
|
||||
{
|
||||
private Client.Models.StoreWebhookData _wh;
|
||||
private FakeServer _server;
|
||||
private readonly List<WebhookInvoiceEvent> _webhookEvents;
|
||||
private CancellationTokenSource _cts;
|
||||
public WebhookListener(Client.Models.StoreWebhookData wh, FakeServer server, List<WebhookInvoiceEvent> webhookEvents)
|
||||
{
|
||||
_wh = wh;
|
||||
_server = server;
|
||||
_webhookEvents = webhookEvents;
|
||||
_cts = new CancellationTokenSource();
|
||||
_ = Listen(_cts.Token);
|
||||
}
|
||||
|
||||
async Task Listen(CancellationToken cancellation)
|
||||
{
|
||||
while (!cancellation.IsCancellationRequested)
|
||||
{
|
||||
var req = await _server.GetNextRequest(cancellation);
|
||||
var bytes = await req.Request.Body.ReadBytesAsync((int)req.Request.Headers.ContentLength);
|
||||
var callback = Encoding.UTF8.GetString(bytes);
|
||||
_webhookEvents.Add(JsonConvert.DeserializeObject<WebhookInvoiceEvent>(callback));
|
||||
req.Response.StatusCode = 200;
|
||||
_server.Done();
|
||||
}
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
_cts.Cancel();
|
||||
_server.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public List<WebhookInvoiceEvent> WebhookEvents { get; set; } = new List<WebhookInvoiceEvent>();
|
||||
public TEvent AssertHasWebhookEvent<TEvent>(WebhookEventType eventType, Action<TEvent> assert) where TEvent : class
|
||||
{
|
||||
foreach (var evt in WebhookEvents)
|
||||
{
|
||||
if (evt.Type == eventType)
|
||||
{
|
||||
var typedEvt = evt.ReadAs<TEvent>();
|
||||
try
|
||||
{
|
||||
assert(typedEvt);
|
||||
return typedEvt;
|
||||
}
|
||||
catch (XunitException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Assert.True(false, "No webhook event match the assertion");
|
||||
return null;
|
||||
}
|
||||
public async Task SetupWebhook()
|
||||
{
|
||||
FakeServer server = new FakeServer();
|
||||
await server.Start();
|
||||
var client = await CreateClient(Policies.CanModifyStoreWebhooks);
|
||||
var wh = await client.CreateWebhook(StoreId, new CreateStoreWebhookRequest()
|
||||
{
|
||||
AutomaticRedelivery = false,
|
||||
Url = server.ServerUri.AbsoluteUri
|
||||
});
|
||||
|
||||
parent.Resources.Add(new WebhookListener(wh, server, WebhookEvents));
|
||||
}
|
||||
|
||||
public async Task PayInvoice(string invoiceId)
|
||||
{
|
||||
var inv = await BitPay.GetInvoiceAsync(invoiceId);
|
||||
var net = parent.ExplorerNode.Network;
|
||||
this.parent.ExplorerNode.SendToAddress(BitcoinAddress.Create(inv.BitcoinAddress, net), inv.BtcDue);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var localInvoice = await BitPay.GetInvoiceAsync(invoiceId, Facade.Merchant);
|
||||
Assert.Equal("paid", localInvoice.Status);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
@ -38,6 +39,7 @@ using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.U2F.Models;
|
||||
using BTCPayServer.Validation;
|
||||
using DBriize.Utils;
|
||||
using ExchangeSharp;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@ -232,6 +234,55 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(valid);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task EnsureSwaggerPermissionsDocumented()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var acc = tester.NewAccount();
|
||||
|
||||
var description =
|
||||
"BTCPay Server supports authenticating and authorizing users through an API Key that is generated by them. Send the API Key as a header value to Authorization with the format: `token {token}`. For a smoother experience, you can generate a url that redirects users to an API key creation screen.\n\n The following permissions are available to the context of the user creating the API Key:\n\n#OTHERPERMISSIONS#\n\nThe following permissions are available if the user is an administrator:\n\n#SERVERPERMISSIONS#\n\nThe following permissions applies to all stores of the user, you can limit to a specific store with the following format: `btcpay.store.cancreateinvoice:6HSHAEU4iYWtjxtyRs9KyPjM9GAQp8kw2T9VWbGG1FnZ`:\n\n#STOREPERMISSIONS#\n\nNote that API Keys only limits permission of a user and can never expand it. If an API Key has the permission `btcpay.server.canmodifyserversettings` but that the user account creating this API Key is not administrator, the API Key will not be able to modify the server settings.\n";
|
||||
|
||||
var storePolicies =
|
||||
ManageController.AddApiKeyViewModel.PermissionValueItem.PermissionDescriptions.Where(pair =>
|
||||
Policies.IsStorePolicy(pair.Key) && !pair.Key.EndsWith(":", StringComparison.InvariantCulture));
|
||||
var serverPolicies =
|
||||
ManageController.AddApiKeyViewModel.PermissionValueItem.PermissionDescriptions.Where(pair =>
|
||||
Policies.IsServerPolicy(pair.Key));
|
||||
var otherPolicies =
|
||||
ManageController.AddApiKeyViewModel.PermissionValueItem.PermissionDescriptions.Where(pair =>
|
||||
!Policies.IsStorePolicy(pair.Key) && !Policies.IsServerPolicy(pair.Key));
|
||||
|
||||
description = description.ReplaceMultiple(new Dictionary<string, string>()
|
||||
{
|
||||
{
|
||||
"#OTHERPERMISSIONS#",
|
||||
string.Join("\n", otherPolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}"))
|
||||
},
|
||||
{
|
||||
"#SERVERPERMISSIONS#",
|
||||
string.Join("\n", serverPolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}"))
|
||||
},
|
||||
{
|
||||
"#STOREPERMISSIONS#",
|
||||
string.Join("\n", storePolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}"))
|
||||
}
|
||||
});
|
||||
Logs.Tester.LogInformation(description);
|
||||
|
||||
var sresp = Assert
|
||||
.IsType<JsonResult>(await tester.PayTester.GetController<HomeController>(acc.UserId, acc.StoreId)
|
||||
.Swagger()).Value.ToJson();
|
||||
|
||||
JObject json = JObject.Parse(sresp);
|
||||
|
||||
Assert.Equal(description, json["components"]["securitySchemes"]["API Key"]["description"].Value<string>());
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task CheckLinks(Regex regex, HttpClient httpClient, string file)
|
||||
{
|
||||
@ -364,7 +415,7 @@ namespace BTCPayServer.Tests
|
||||
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
|
||||
{
|
||||
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null, null),
|
||||
});
|
||||
var entity = new InvoiceEntity();
|
||||
entity.Networks = networkProvider;
|
||||
@ -563,7 +614,7 @@ namespace BTCPayServer.Tests
|
||||
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
|
||||
{
|
||||
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null, null),
|
||||
});
|
||||
var entity = new InvoiceEntity();
|
||||
entity.Networks = networkProvider;
|
||||
@ -770,30 +821,31 @@ namespace BTCPayServer.Tests
|
||||
BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest), Money.Coins(0.00005m));
|
||||
});
|
||||
await tester.ExplorerNode.GenerateAsync(1);
|
||||
await Task.Delay(100); // wait a bit for payment to process before fetching new invoice
|
||||
var newInvoice = await user.BitPay.GetInvoiceAsync(invoice.Id);
|
||||
var newBolt11 = newInvoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11;
|
||||
var oldBolt11= invoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11;
|
||||
Assert.NotEqual(newBolt11,oldBolt11);
|
||||
var newBolt11 = newInvoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11;
|
||||
var oldBolt11 = invoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11;
|
||||
Assert.NotEqual(newBolt11, oldBolt11);
|
||||
Assert.Equal(newInvoice.BtcDue.GetValue(), BOLT11PaymentRequest.Parse(newBolt11, Network.RegTest).MinimumAmount.ToDecimal(LightMoneyUnit.BTC));
|
||||
|
||||
Logs.Tester.LogInformation($"Paying invoice {newInvoice.Id} remaining due amount {newInvoice.BtcDue.GetValue()} via lightning" );
|
||||
|
||||
Logs.Tester.LogInformation($"Paying invoice {newInvoice.Id} remaining due amount {newInvoice.BtcDue.GetValue()} via lightning");
|
||||
var evt = await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
|
||||
{
|
||||
await tester.SendLightningPaymentAsync(newInvoice);
|
||||
}, evt => evt.InvoiceId == invoice.Id);
|
||||
|
||||
var fetchedInvoice = await tester.PayTester.InvoiceRepository.GetInvoice(evt.InvoiceId);
|
||||
Assert.Contains(fetchedInvoice.Status, new []{InvoiceStatus.Complete, InvoiceStatus.Confirmed});
|
||||
Assert.Contains(fetchedInvoice.Status, new[] { InvoiceStatusLegacy.Complete, InvoiceStatusLegacy.Confirmed });
|
||||
Assert.Equal(InvoiceExceptionStatus.None, fetchedInvoice.ExceptionStatus);
|
||||
|
||||
Logs.Tester.LogInformation($"Paying invoice {invoice.Id} original full amount bolt11 invoice " );
|
||||
|
||||
Logs.Tester.LogInformation($"Paying invoice {invoice.Id} original full amount bolt11 invoice ");
|
||||
evt = await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
|
||||
{
|
||||
await tester.SendLightningPaymentAsync(invoice);
|
||||
}, evt => evt.InvoiceId == invoice.Id);
|
||||
Assert.Equal(evt.InvoiceId, invoice.Id);
|
||||
fetchedInvoice = await tester.PayTester.InvoiceRepository.GetInvoice(evt.InvoiceId);
|
||||
Assert.Equal( 3,fetchedInvoice.Payments.Count);
|
||||
Assert.Equal(3, fetchedInvoice.Payments.Count);
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
@ -999,7 +1051,6 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var invoice2 = acc.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.NotNull(invoice2);
|
||||
}
|
||||
@ -1089,7 +1140,7 @@ namespace BTCPayServer.Tests
|
||||
response.EnsureSuccessStatusCode();
|
||||
AssertConnectionDropped();
|
||||
|
||||
Logs.Tester.LogInformation("Querying an onion address which can't be found should send http 500");
|
||||
Logs.Tester.LogInformation("Querying an onin address which can't be found should send http 500");
|
||||
response = await client.GetAsync("http://dwoduwoi.onion/");
|
||||
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
|
||||
AssertConnectionDropped();
|
||||
@ -1459,7 +1510,7 @@ namespace BTCPayServer.Tests
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var i = await tester.PayTester.InvoiceRepository.GetInvoice(invoice2.Id);
|
||||
Assert.Equal(InvoiceStatus.New, i.Status);
|
||||
Assert.Equal(InvoiceStatusLegacy.New, i.Status);
|
||||
Assert.Single(i.GetPayments());
|
||||
Assert.False(i.GetPayments().First().Accounted);
|
||||
});
|
||||
@ -1508,7 +1559,7 @@ namespace BTCPayServer.Tests
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// [Fact(Timeout = TestTimeout)]
|
||||
[Fact()]
|
||||
[Trait("Integration", "Integration")]
|
||||
@ -1527,9 +1578,9 @@ namespace BTCPayServer.Tests
|
||||
BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest),
|
||||
Money.Coins(0.01m));
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var payments = Assert.IsType<InvoiceDetailsModel>(
|
||||
Assert.IsType<ViewResult>(await user.GetController<InvoiceController>().Invoice(invoice.Id)).Model)
|
||||
.Payments;
|
||||
@ -1893,7 +1944,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"))
|
||||
@ -1990,7 +2041,64 @@ namespace BTCPayServer.Tests
|
||||
};
|
||||
var criteriaCompat = store.GetPaymentMethodCriteria(tester.NetworkProvider, blob);
|
||||
Assert.Single(criteriaCompat);
|
||||
Assert.NotNull(criteriaCompat.FirstOrDefault(methodCriteria => methodCriteria.Value.ToString() == "2 USD" && methodCriteria.Above && methodCriteria.PaymentMethod == new PaymentMethodId("BTC", BitcoinPaymentType.Instance) ));
|
||||
Assert.NotNull(criteriaCompat.FirstOrDefault(methodCriteria => methodCriteria.Value.ToString() == "2 USD" && methodCriteria.Above && methodCriteria.PaymentMethod == new PaymentMethodId("BTC", BitcoinPaymentType.Instance)));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanSetUnifiedQrCode()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.ActivateLightning();
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit);
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.CLightning);
|
||||
|
||||
var invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
{
|
||||
Price = 5.5m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
// validate that invoice data model doesn't have lightning string initially
|
||||
var res = await user.GetController<InvoiceController>().Checkout(invoice.Id);
|
||||
var paymentMethodFirst = Assert.IsType<PaymentModel>(
|
||||
Assert.IsType<ViewResult>(res).Model
|
||||
);
|
||||
Assert.DoesNotContain("&lightning=", paymentMethodFirst.InvoiceBitcoinUrlQR);
|
||||
|
||||
// enable unified QR code in settings
|
||||
var vm = Assert.IsType<CheckoutExperienceViewModel>(Assert
|
||||
.IsType<ViewResult>(user.GetController<StoresController>().CheckoutExperience()).Model
|
||||
);
|
||||
vm.OnChainWithLnInvoiceFallback = true;
|
||||
Assert.IsType<RedirectToActionResult>(
|
||||
user.GetController<StoresController>().CheckoutExperience(vm).Result
|
||||
);
|
||||
|
||||
// validate that QR code now has both onchain and offchain payment urls
|
||||
res = await user.GetController<InvoiceController>().Checkout(invoice.Id);
|
||||
var paymentMethodSecond = Assert.IsType<PaymentModel>(
|
||||
Assert.IsType<ViewResult>(res).Model
|
||||
);
|
||||
Assert.Contains("&lightning=", paymentMethodSecond.InvoiceBitcoinUrlQR);
|
||||
Assert.StartsWith("bitcoin:", paymentMethodSecond.InvoiceBitcoinUrlQR);
|
||||
var split = paymentMethodSecond.InvoiceBitcoinUrlQR.Split('?')[0];
|
||||
|
||||
// Standard for uppercase Bech32 addresses in QR codes is still not implemented in all wallets
|
||||
// When it is widely propagated consider uncommenting these lines
|
||||
//Assert.True($"BITCOIN:{paymentMethodSecond.BtcAddress.ToUpperInvariant()}" == split);
|
||||
Assert.True($"bitcoin:{paymentMethodSecond.BtcAddress}" == split);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2030,7 +2138,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
Assert.Single(invoice.CryptoInfo);
|
||||
Assert.Equal(PaymentTypes.LightningLike.ToString(), invoice.CryptoInfo[0].PaymentType);
|
||||
|
||||
|
||||
//test backward compat
|
||||
var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
|
||||
var blob = store.GetStoreBlob();
|
||||
@ -2044,8 +2152,8 @@ namespace BTCPayServer.Tests
|
||||
};
|
||||
var criteriaCompat = store.GetPaymentMethodCriteria(tester.NetworkProvider, blob);
|
||||
Assert.Single(criteriaCompat);
|
||||
Assert.NotNull(criteriaCompat.FirstOrDefault(methodCriteria => methodCriteria.Value.ToString() == "2 USD" && !methodCriteria.Above && methodCriteria.PaymentMethod == new PaymentMethodId("BTC", LightningPaymentType.Instance) ));
|
||||
|
||||
Assert.NotNull(criteriaCompat.FirstOrDefault(methodCriteria => methodCriteria.Value.ToString() == "2 USD" && !methodCriteria.Above && methodCriteria.PaymentMethod == new PaymentMethodId("BTC", LightningPaymentType.Instance)));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -2527,6 +2635,7 @@ namespace BTCPayServer.Tests
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
await user.SetupWebhook();
|
||||
var invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
{
|
||||
@ -2583,7 +2692,6 @@ namespace BTCPayServer.Tests
|
||||
var cashCow = tester.ExplorerNode;
|
||||
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||
var iii = ctx.AddressInvoices.ToArray();
|
||||
Assert.True(IsMapped(invoice, ctx));
|
||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||
|
||||
@ -2687,6 +2795,23 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
||||
Assert.Equal("paidOver", (string)((JValue)localInvoice.ExceptionStatus).Value);
|
||||
});
|
||||
|
||||
// Test on the webhooks
|
||||
user.AssertHasWebhookEvent<WebhookInvoiceSettledEvent>(WebhookEventType.InvoiceSettled,
|
||||
c =>
|
||||
{
|
||||
Assert.False(c.ManuallyMarked);
|
||||
});
|
||||
user.AssertHasWebhookEvent<WebhookInvoiceProcessingEvent>(WebhookEventType.InvoiceProcessing,
|
||||
c =>
|
||||
{
|
||||
Assert.True(c.OverPaid);
|
||||
});
|
||||
user.AssertHasWebhookEvent<WebhookInvoiceReceivedPaymentEvent>(WebhookEventType.InvoiceReceivedPayment,
|
||||
c =>
|
||||
{
|
||||
Assert.False(c.AfterExpiration);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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\**" />
|
||||
@ -18,10 +21,6 @@
|
||||
<None Remove="Build\**" />
|
||||
<None Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<None Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
<Content Update="Views\Shared\NBXSyncSummary.cshtml">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Currencies.txt" />
|
||||
@ -52,6 +51,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 +141,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" />
|
||||
@ -246,5 +247,5 @@
|
||||
<_ContentIncludedByDefault Remove="Views\Components\NotificationsDropdown\Default.cshtml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ProjectExtensions><VisualStudio><UserProperties wwwroot_4swagger_4v1_4swagger_1template_1invoices_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1pull-payments_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1serverinfo_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1stores_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" /></VisualStudio></ProjectExtensions>
|
||||
<ProjectExtensions><VisualStudio><UserProperties wwwroot_4swagger_4v1_4swagger_1template_1invoices_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1pull-payments_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1serverinfo_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1stores_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1users_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1webhooks_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" /></VisualStudio></ProjectExtensions>
|
||||
</Project>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user