Compare commits
133 Commits
extensions
...
v1.0.6.3
Author | SHA1 | Date | |
---|---|---|---|
ab64bbc3b0 | |||
5da83e9945 | |||
1386f205fd | |||
a294ad41cb | |||
ed497cab99 | |||
282d0abb62 | |||
034b732e7c | |||
b1e9c005b7 | |||
0d144d088e | |||
b2855e74ef | |||
0652e30c30 | |||
1c58fabc30 | |||
262ba6ee1e | |||
e0534577d1 | |||
c12423f28b | |||
3dd6577b32 | |||
a638b4fb28 | |||
798cf66e3f | |||
6cf29123f3 | |||
e65d42bd61 | |||
febf8ac5b3 | |||
c8a9b66694 | |||
9c185f1081 | |||
0a444508f8 | |||
1d00ee41c4 | |||
266434fe7b | |||
7c88333060 | |||
e0b561b12a | |||
16e5e2d757 | |||
8a07c62603 | |||
4df847bc10 | |||
b5df44ce8e | |||
716952cd58 | |||
7b9b418e93 | |||
dfd7c6d4a6 | |||
920caaa524 | |||
13f10657b8 | |||
dd5fd2e5bb | |||
97b00053c4 | |||
76e46d2fa7 | |||
4c62c5dc22 | |||
c7209df7e2 | |||
c17b8e4d9e | |||
0ab33b704c | |||
ba027de3f7 | |||
3ac257bfda | |||
ec495ea4d1 | |||
3b035158a2 | |||
36582a2741 | |||
450d3cb7d2 | |||
8d157ac5ca | |||
89dc379761 | |||
3e800e7e53 | |||
b12c6289d0 | |||
7e50267cb0 | |||
6714eb9f7f | |||
f1d6f0b2a6 | |||
445606e2b1 | |||
ad05f479a8 | |||
a78c1c8278 | |||
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 | |||
256d711fde | |||
1d82c3779b | |||
abc9d07977 | |||
40d95acfb9 | |||
9a92bc05db | |||
8962bf00f6 |
@ -30,4 +30,10 @@
|
||||
<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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ using BTCPayServer.Abstractions.Converters;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BTCPayServer.Contracts
|
||||
namespace BTCPayServer.Abstractions.Contracts
|
||||
{
|
||||
public interface IBTCPayServerPlugin
|
||||
{
|
||||
@ -14,8 +14,19 @@ namespace BTCPayServer.Contracts
|
||||
Version Version { get; }
|
||||
string Description { get; }
|
||||
bool SystemPlugin { get; set; }
|
||||
string[] Dependencies { get; }
|
||||
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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer.Contracts
|
||||
namespace BTCPayServer.Abstractions.Contracts
|
||||
{
|
||||
public abstract class BaseNotification
|
||||
{
|
||||
|
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);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Services
|
||||
namespace BTCPayServer.Abstractions.Contracts
|
||||
{
|
||||
public interface ISettingsRepository
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
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; }
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
using System.Text.Json;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
|
||||
namespace BTCPayServer
|
||||
namespace BTCPayServer.Abstractions.Extensions
|
||||
{
|
||||
public static class SetStatusMessageModelExtensions
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using BTCPayServer.Contracts;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BTCPayServer.Models
|
||||
namespace BTCPayServer.Abstractions.Models
|
||||
{
|
||||
public abstract class BaseBTCPayServerPlugin : IBTCPayServerPlugin
|
||||
{
|
||||
@ -22,7 +22,7 @@ namespace BTCPayServer.Models
|
||||
public abstract string Description { get; }
|
||||
public bool SystemPlugin { get; set; }
|
||||
public bool SystemExtension { get; set; }
|
||||
public virtual string[] Dependencies { get; } = Array.Empty<string>();
|
||||
public virtual IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = Array.Empty<IBTCPayServerPlugin.PluginDependency>();
|
||||
|
||||
public virtual void Execute(IApplicationBuilder applicationBuilder,
|
||||
IServiceProvider applicationBuilderApplicationServices)
|
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
|
||||
{
|
||||
|
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);
|
||||
}
|
||||
}
|
@ -1,12 +1,7 @@
|
||||
namespace BTCPayServer.Contracts
|
||||
{
|
||||
public interface IUIExtension
|
||||
{
|
||||
string Partial { get; }
|
||||
|
||||
string Location { get; }
|
||||
}
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Services
|
||||
{
|
||||
public class UIExtension: IUIExtension
|
||||
{
|
||||
public UIExtension(string partial, string location)
|
@ -13,7 +13,7 @@
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Version Condition=" '$(Version)' == '' ">1.1.1</Version>
|
||||
<Version Condition=" '$(Version)' == '' ">1.2.0</Version>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
|
@ -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)
|
||||
@ -47,12 +54,23 @@ namespace BTCPayServer.Client
|
||||
return await HandleResponse<InvoiceData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<InvoiceData> UpdateInvoice(string storeId, string invoiceId,
|
||||
UpdateInvoiceRequest request, CancellationToken token = default)
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/invoices/{invoiceId}", bodyPayload: request,
|
||||
method: HttpMethod.Put), token);
|
||||
return await HandleResponse<InvoiceData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<InvoiceData> MarkInvoiceStatus(string storeId, string invoiceId,
|
||||
MarkInvoiceStatusRequest request, CancellationToken token = default)
|
||||
{
|
||||
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,
|
||||
|
47
BTCPayServer.Client/BTCPayServerClient.Notifications.cs
Normal file
47
BTCPayServer.Client/BTCPayServerClient.Notifications.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<IEnumerable<NotificationData>> GetNotifications(bool? seen = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/users/me/notifications",
|
||||
seen != null ? new Dictionary<string, object>() {{nameof(seen), seen}} : null), token);
|
||||
return await HandleResponse<IEnumerable<NotificationData>>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<NotificationData> GetNotification(string notificationId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/users/me/notifications/{notificationId}"), token);
|
||||
return await HandleResponse<NotificationData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<NotificationData> UpdateNotification(string notificationId, bool? seen,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/users/me/notifications/{notificationId}",
|
||||
method: HttpMethod.Put, bodyPayload: new UpdateNotification() {Seen = seen}), token);
|
||||
return await HandleResponse<NotificationData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task RemoveNotification(string notificationId, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/users/me/notifications/{notificationId}",
|
||||
method: HttpMethod.Delete), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
}
|
||||
}
|
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,
|
||||
|
@ -17,6 +17,7 @@ namespace BTCPayServer.Client.Models
|
||||
|
||||
public class CheckoutOptions
|
||||
{
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public SpeedPolicy? SpeedPolicy { get; set; }
|
||||
|
||||
@ -30,6 +31,9 @@ namespace BTCPayServer.Client.Models
|
||||
public TimeSpan? Monitoring { get; set; }
|
||||
|
||||
public double? PaymentTolerance { get; set; }
|
||||
[JsonProperty("redirectURL")]
|
||||
public string RedirectURL { get; set; }
|
||||
public string DefaultLanguage { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,7 @@ namespace BTCPayServer.Client.Models
|
||||
}
|
||||
public GreenfieldAPIError(string code, string message)
|
||||
{
|
||||
if (code == null)
|
||||
throw new ArgumentNullException(nameof(code));
|
||||
code = code ?? "generic-error";
|
||||
if (message == null)
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
Code = code;
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
@ -9,6 +7,7 @@ namespace BTCPayServer.Client.Models
|
||||
public class InvoiceData : CreateInvoiceRequest
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string CheckoutLink { get; set; }
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public InvoiceStatus Status { get; set; }
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
@ -20,4 +19,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
|
||||
}
|
||||
}
|
16
BTCPayServer.Client/Models/NotificationData.cs
Normal file
16
BTCPayServer.Client/Models/NotificationData.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class NotificationData
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Body { get; set; }
|
||||
public bool Seen { get; set; }
|
||||
public Uri Link { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset CreatedTime { get; set; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
9
BTCPayServer.Client/Models/UpdateInvoiceRequest.cs
Normal file
9
BTCPayServer.Client/Models/UpdateInvoiceRequest.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class UpdateInvoiceRequest
|
||||
{
|
||||
public JObject Metadata { get; set; }
|
||||
}
|
||||
}
|
7
BTCPayServer.Client/Models/UpdateNotification.cs
Normal file
7
BTCPayServer.Client/Models/UpdateNotification.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class UpdateNotification
|
||||
{
|
||||
public bool? Seen { 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";
|
||||
@ -20,6 +21,8 @@ namespace BTCPayServer.Client
|
||||
public const string CanModifyPaymentRequests = "btcpay.store.canmodifypaymentrequests";
|
||||
public const string CanModifyProfile = "btcpay.user.canmodifyprofile";
|
||||
public const string CanViewProfile = "btcpay.user.canviewprofile";
|
||||
public const string CanManageNotificationsForUser = "btcpay.user.canmanagenotificationsforuser";
|
||||
public const string CanViewNotificationsForUser = "btcpay.user.canviewnotificationsforuser";
|
||||
public const string CanCreateUser = "btcpay.server.cancreateuser";
|
||||
public const string CanManagePullPayments = "btcpay.store.canmanagepullpayments";
|
||||
public const string Unrestricted = "unrestricted";
|
||||
@ -29,6 +32,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
yield return CanViewInvoices;
|
||||
yield return CanCreateInvoice;
|
||||
yield return CanModifyStoreWebhooks;
|
||||
yield return CanModifyServerSettings;
|
||||
yield return CanModifyStoreSettings;
|
||||
yield return CanViewStoreSettings;
|
||||
@ -37,6 +41,8 @@ namespace BTCPayServer.Client
|
||||
yield return CanModifyProfile;
|
||||
yield return CanViewProfile;
|
||||
yield return CanCreateUser;
|
||||
yield return CanManageNotificationsForUser;
|
||||
yield return CanViewNotificationsForUser;
|
||||
yield return Unrestricted;
|
||||
yield return CanUseInternalLightningNode;
|
||||
yield return CanCreateLightningInvoiceInternalNode;
|
||||
@ -156,6 +162,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:
|
||||
@ -165,6 +172,7 @@ namespace BTCPayServer.Client
|
||||
case Policies.CanViewPaymentRequests when this.Policy == Policies.CanViewStoreSettings:
|
||||
case Policies.CanCreateLightningInvoiceInternalNode when this.Policy == Policies.CanUseInternalLightningNode:
|
||||
case Policies.CanCreateLightningInvoiceInStore when this.Policy == Policies.CanUseLightningNodeInStore:
|
||||
case Policies.CanViewNotificationsForUser when this.Policy == Policies.CanManageNotificationsForUser:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
@ -4,14 +4,14 @@ namespace BTCPayServer
|
||||
{
|
||||
public partial class BTCPayNetworkProvider
|
||||
{
|
||||
public void InitBitcoinGold()
|
||||
public void InitBGold()
|
||||
{
|
||||
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("BTG");
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "BGold",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://explorer.bitcoingold.org/insight/tx/{0}/" : "https://test-explorer.bitcoingold.org/insight/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://btgexplorer.com/tx/{0}" : "https://testnet.btgexplorer.com/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "bitcoingold",
|
||||
DefaultRateRules = new[]
|
@ -5,22 +5,22 @@ namespace BTCPayServer
|
||||
{
|
||||
public partial class BTCPayNetworkProvider
|
||||
{
|
||||
public void InitBitcoinplus()
|
||||
public void InitBPlus()
|
||||
{
|
||||
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("XBC");
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Bitcoinplus",
|
||||
DisplayName = "BPlus",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://chainz.cryptoid.info/xbc/tx.dws?{0}" : "https://chainz.cryptoid.info/xbc/tx.dws?{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "bitcoinplus",
|
||||
UriScheme = "bplus-fix-it",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"XBC_X = XBC_BTC * BTC_X",
|
||||
"XBC_BTC = cryptopia(XBC_BTC)"
|
||||
"XBC_X = XBC_BTC * BTC_X",
|
||||
"XBC_BTC = cryptopia(XBC_BTC)"
|
||||
},
|
||||
CryptoImagePath = "imlegacy/bitcoinplus.png",
|
||||
CryptoImagePath = "imlegacy/xbc.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("65'") : new KeyPath("1'")
|
||||
});
|
@ -25,7 +25,7 @@ namespace BTCPayServer
|
||||
_Settings.Add(chainType, settings);
|
||||
settings.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", NBXplorerDefaultSettings.GetFolderName(chainType));
|
||||
settings.DefaultPluginDirectory =
|
||||
StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", "Extensions");
|
||||
StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", "Plugins");
|
||||
settings.DefaultConfigurationFile = Path.Combine(settings.DefaultDataDirectory, "settings.config");
|
||||
settings.DefaultPort = (chainType == NetworkType.Mainnet ? 23000 :
|
||||
chainType == NetworkType.Regtest ? 23002 :
|
||||
|
@ -48,7 +48,7 @@ namespace BTCPayServer
|
||||
InitLitecoin();
|
||||
InitBitcore();
|
||||
InitDogecoin();
|
||||
InitBitcoinGold();
|
||||
InitBGold();
|
||||
InitMonacoin();
|
||||
InitDash();
|
||||
InitFeathercoin();
|
||||
@ -79,7 +79,7 @@ namespace BTCPayServer
|
||||
}
|
||||
|
||||
// Disabled because of https://twitter.com/Cryptopia_NZ/status/1085084168852291586
|
||||
//InitBitcoinplus();
|
||||
//InitBPlus();
|
||||
//InitUfo();
|
||||
#endif
|
||||
}
|
||||
@ -133,4 +133,4 @@ namespace BTCPayServer
|
||||
return network as T;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
@ -91,7 +97,11 @@ namespace BTCPayServer.Data
|
||||
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)
|
||||
{
|
||||
// SQLite does not have proper support for DateTimeOffset via Entity Framework Core, see the limitations
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -85,6 +85,7 @@ namespace BTCPayServer.Data
|
||||
.HasOne(o => o.StoreData)
|
||||
.WithMany(a => a.Invoices).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.Entity<InvoiceData>().HasIndex(o => o.StoreDataId);
|
||||
builder.Entity<InvoiceData>().HasIndex(o => o.OrderId);
|
||||
builder.Entity<InvoiceData>()
|
||||
.HasOne(o => o.CurrentRefund);
|
||||
}
|
||||
|
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);
|
||||
}
|
||||
}
|
||||
}
|
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -26,11 +26,4 @@ namespace BTCPayServer.Data
|
||||
.WithMany(w => w.WalletTransactions).OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
|
||||
public class WalletTransactionInfo
|
||||
{
|
||||
public string Comment { get; set; } = string.Empty;
|
||||
[JsonIgnore]
|
||||
public HashSet<string> Labels { get; set; } = new HashSet<string>();
|
||||
}
|
||||
}
|
||||
|
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);
|
||||
}
|
||||
}
|
||||
}
|
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");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20201208054211_invoicesorderindex")]
|
||||
public partial class invoicesorderindex : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Invoices_OrderId",
|
||||
table: "Invoices",
|
||||
column: "OrderId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Invoices_OrderId",
|
||||
table: "Invoices");
|
||||
}
|
||||
}
|
||||
}
|
@ -228,6 +228,8 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("OrderId");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.HasIndex("Id", "CurrentRefundId");
|
||||
@ -257,6 +259,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")
|
||||
@ -588,6 +609,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")
|
||||
@ -696,6 +736,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")
|
||||
@ -883,6 +963,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")
|
||||
@ -956,6 +1051,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")
|
||||
@ -995,6 +1105,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)
|
||||
|
@ -4,7 +4,7 @@ using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using BTCPayServer.Contracts;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
|
||||
namespace BTCPayServer.PluginPacker
|
||||
{
|
||||
|
@ -12,4 +12,8 @@
|
||||
<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
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
using BTCPayServer.Contracts;
|
||||
using BTCPayServer.Models;
|
||||
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
|
||||
@ -14,6 +19,21 @@ namespace BTCPayServer.Plugins.Test
|
||||
{
|
||||
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();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test
|
||||
{
|
||||
[Route("extensions/test")]
|
||||
public class TestExtensionController : Controller
|
||||
{
|
||||
// GET
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
@model BTCPayServer.Plugins.Test.TestPluginPageViewModel
|
||||
<section>
|
||||
<div class="container">
|
||||
<h1>Challenge Completed!!</h1>
|
||||
@ -5,5 +6,16 @@
|
||||
<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>
|
||||
|
@ -788,7 +788,7 @@ noninventoryitem:
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(appId, vmpos).Result);
|
||||
|
||||
//inventoryitem has 1 item available
|
||||
await tester.WaitForEvent<InvoiceEvent>(() =>
|
||||
await tester.WaitForEvent<AppInventoryUpdaterHostedService.UpdateAppInventory>(() =>
|
||||
{
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "inventoryitem").Result);
|
||||
@ -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();
|
||||
@ -173,6 +173,24 @@ namespace BTCPayServer.Tests
|
||||
|
||||
s.Driver.Navigate().GoToUrl(authUrl);
|
||||
Assert.False(s.Driver.Url.StartsWith("https://international.com/callback"));
|
||||
|
||||
// Make sure we can check all permissions when not an admin
|
||||
await user.MakeAdmin(false);
|
||||
s.Logout();
|
||||
s.GoToLogin();
|
||||
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
||||
s.GoToProfile(ManageNavPages.APIKeys);
|
||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||
int checkedPermissionCount = 0;
|
||||
foreach (var checkbox in s.Driver.FindElements(By.ClassName("form-check-input")))
|
||||
{
|
||||
checkedPermissionCount++;
|
||||
checkbox.Click();
|
||||
}
|
||||
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||
var allAPIKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
||||
var apikeydata = await TestApiAgainstAccessToken<ApiKeyData>(allAPIKey, $"api/v1/api-keys/current", tester.PayTester.HttpClient);
|
||||
Assert.Equal(checkedPermissionCount, apikeydata.Permissions.Length);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,16 +10,18 @@ using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Notifications;
|
||||
using BTCPayServer.Services.Notifications.Blobs;
|
||||
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;
|
||||
using NUglify.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using CreateApplicationUserRequest = BTCPayServer.Client.Models.CreateApplicationUserRequest;
|
||||
@ -30,9 +32,6 @@ namespace BTCPayServer.Tests
|
||||
public class GreenfieldAPITests
|
||||
{
|
||||
public const int TestTimeout = TestUtils.TestTimeout;
|
||||
|
||||
public const string TestApiPath = "api/test/apikey";
|
||||
|
||||
public GreenfieldAPITests(ITestOutputHelper helper)
|
||||
{
|
||||
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
|
||||
@ -149,12 +148,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(
|
||||
@ -170,7 +169,7 @@ namespace BTCPayServer.Tests
|
||||
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,
|
||||
@ -246,6 +245,20 @@ namespace BTCPayServer.Tests
|
||||
Password = "afewfoiewiou",
|
||||
IsAdministrator = true
|
||||
}));
|
||||
|
||||
// If we set DisableNonAdminCreateUserApi = true, it should always fail to create a user unless you are an admin
|
||||
await settings.UpdateSetting(new PoliciesSettings() { LockSubscription = false, DisableNonAdminCreateUserApi = true });
|
||||
await AssertHttpError(403,
|
||||
async () =>
|
||||
await unauthClient.CreateUser(
|
||||
new CreateApplicationUserRequest() { Email = "test9@gmail.com", Password = "afewfoiewiou" }));
|
||||
await AssertHttpError(403,
|
||||
async () =>
|
||||
await user1Client.CreateUser(
|
||||
new CreateApplicationUserRequest() { Email = "test9@gmail.com", Password = "afewfoiewiou" }));
|
||||
await adminClient.CreateUser(
|
||||
new CreateApplicationUserRequest() { Email = "test9@gmail.com", Password = "afewfoiewiou" });
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -611,6 +624,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()
|
||||
@ -821,6 +929,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);
|
||||
|
||||
@ -848,9 +957,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);
|
||||
@ -859,11 +974,26 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await client.MarkInvoiceStatus(user.StoreId, invoice.Id, new MarkInvoiceStatusRequest()
|
||||
{
|
||||
Status = InvoiceStatus.Complete
|
||||
Status = InvoiceStatus.Settled
|
||||
});
|
||||
});
|
||||
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnly.UpdateInvoice(user.StoreId, newInvoice.Id,
|
||||
new UpdateInvoiceRequest()
|
||||
{
|
||||
Metadata = JObject.Parse("{\"itemCode\": \"updated\", newstuff: [1,2,3,4,5]}")
|
||||
});
|
||||
});
|
||||
invoice = await client.UpdateInvoice(user.StoreId, newInvoice.Id,
|
||||
new UpdateInvoiceRequest()
|
||||
{
|
||||
Metadata = JObject.Parse("{\"itemCode\": \"updated\", newstuff: [1,2,3,4,5]}")
|
||||
});
|
||||
|
||||
Assert.Equal("updated",invoice.Metadata["itemCode"].Value<string>());
|
||||
Assert.Equal(15,((JArray) invoice.Metadata["newstuff"]).Values<int>().Sum());
|
||||
//archive
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
@ -878,10 +1008,79 @@ 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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
newInvoice = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest()
|
||||
{
|
||||
Currency = "USD",
|
||||
Amount = 1,
|
||||
Checkout = new CreateInvoiceRequest.CheckoutOptions()
|
||||
{
|
||||
DefaultLanguage = "it-it ",
|
||||
RedirectURL = "http://toto.com/lol"
|
||||
}
|
||||
});
|
||||
Assert.EndsWith($"/i/{newInvoice.Id}", newInvoice.CheckoutLink);
|
||||
var controller = tester.PayTester.GetController<InvoiceController>(user.UserId, user.StoreId);
|
||||
var model = (PaymentModel)((ViewResult)await controller.Checkout(newInvoice.Id)).Model;
|
||||
Assert.Equal("it-IT", model.DefaultLang);
|
||||
Assert.Equal("http://toto.com/lol", model.MerchantRefLink);
|
||||
|
||||
var langs = tester.PayTester.GetService<LanguageService>();
|
||||
foreach (var match in new[] { "it", "it-IT", "it-LOL" })
|
||||
{
|
||||
Assert.Equal("it-IT", langs.FindBestMatch(match).Code);
|
||||
}
|
||||
foreach (var match in new[] { "pt-BR" })
|
||||
{
|
||||
Assert.Equal("pt-BR", langs.FindBestMatch(match).Code);
|
||||
}
|
||||
foreach (var match in new[] { "en", "en-US" })
|
||||
{
|
||||
Assert.Equal("en", langs.FindBestMatch(match).Code);
|
||||
}
|
||||
foreach (var match in new[] { "pt", "pt-pt", "pt-PT" })
|
||||
{
|
||||
Assert.Equal("pt-PT", langs.FindBestMatch(match).Code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanUseLightningAPI()
|
||||
@ -907,7 +1106,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!
|
||||
@ -970,6 +1169,46 @@ namespace BTCPayServer.Tests
|
||||
Assert.NotEqual(0, info.BlockHeight);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task NotificationAPITests()
|
||||
{
|
||||
using var tester = ServerTester.Create();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync(true);
|
||||
var client = await user.CreateClient(Policies.CanManageNotificationsForUser);
|
||||
var viewOnlyClient = await user.CreateClient(Policies.CanViewNotificationsForUser);
|
||||
await tester.PayTester.GetService<NotificationSender>()
|
||||
.SendNotification(new UserScope(user.UserId), new NewVersionNotification());
|
||||
|
||||
Assert.Single(await viewOnlyClient.GetNotifications());
|
||||
Assert.Single(await viewOnlyClient.GetNotifications(false));
|
||||
Assert.Empty(await viewOnlyClient.GetNotifications(true));
|
||||
|
||||
Assert.Single(await client.GetNotifications());
|
||||
Assert.Single(await client.GetNotifications(false));
|
||||
Assert.Empty(await client.GetNotifications(true));
|
||||
var notification = (await client.GetNotifications()).First();
|
||||
notification = await client.GetNotification(notification.Id);
|
||||
Assert.False(notification.Seen);
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.UpdateNotification(notification.Id, true);
|
||||
});
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.RemoveNotification(notification.Id);
|
||||
});
|
||||
Assert.True((await client.UpdateNotification(notification.Id, true)).Seen);
|
||||
Assert.Single(await viewOnlyClient.GetNotifications(true));
|
||||
Assert.Empty(await viewOnlyClient.GetNotifications(false));
|
||||
await client.RemoveNotification(notification.Id);
|
||||
Assert.Empty(await viewOnlyClient.GetNotifications(true));
|
||||
Assert.Empty(await viewOnlyClient.GetNotifications(false));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1013,5 +1252,6 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(1.2, jsonConverter.ReadJson(Get(stringJson), typeof(double), null, null));
|
||||
Assert.Equal(1.2, jsonConverter.ReadJson(Get(stringJson), typeof(double?), null, null));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
@ -317,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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
@ -301,23 +312,25 @@ namespace BTCPayServer.Tests
|
||||
await s.StartAsync();
|
||||
var alice = s.RegisterNewUser();
|
||||
var storeData = s.CreateNewStore();
|
||||
var onchainHint = "Set up your wallet to receive payments at your store.";
|
||||
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("Wallet not setup for the store, please provide Derviation Scheme"),
|
||||
"Wallet hint not present");
|
||||
Assert.True(s.Driver.PageSource.Contains("Review settings if you want to receive Lightning payments"),
|
||||
"Lightning hint not present");
|
||||
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);
|
||||
Assert.True(s.Driver.PageSource.Contains(onchainHint), "Wallet hint should be present at this point");
|
||||
Assert.True(s.Driver.PageSource.Contains(offchainHint), "Lightning hint should be present at this point");
|
||||
|
||||
s.AddDerivationScheme(); // wallet hint should be dismissed
|
||||
s.Driver.AssertNoError();
|
||||
Assert.False(s.Driver.PageSource.Contains("Wallet not setup for the store, please provide Derviation Scheme"),
|
||||
"Wallet hint not dismissed on derivation scheme add");
|
||||
|
||||
s.Driver.FindElement(By.Id("dismissLightningHint")).Click(); // dismiss lightning hint
|
||||
Assert.False(s.Driver.PageSource.Contains(onchainHint),
|
||||
"Wallet hint not dismissed on derivation scheme add");// dismiss lightning hint
|
||||
|
||||
Assert.Contains(storeData.storeName, s.Driver.PageSource);
|
||||
var storeUrl = s.Driver.Url;
|
||||
@ -380,8 +393,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Stores")).Click();
|
||||
|
||||
// there shouldn't be any hints now
|
||||
Assert.False(s.Driver.PageSource.Contains("Review settings if you want to receive Lightning payments"),
|
||||
"Lightning hint should be dismissed at this point");
|
||||
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();
|
||||
@ -595,6 +607,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()
|
||||
|
@ -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;
|
||||
@ -18,13 +20,16 @@ using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using NBitcoin;
|
||||
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 +432,94 @@ 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
|
||||
{
|
||||
int retry = 0;
|
||||
retry:
|
||||
foreach (var evt in WebhookEvents)
|
||||
{
|
||||
if (evt.Type == eventType)
|
||||
{
|
||||
var typedEvt = evt.ReadAs<TEvent>();
|
||||
try
|
||||
{
|
||||
assert(typedEvt);
|
||||
return typedEvt;
|
||||
}
|
||||
catch (XunitException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
if (retry < 3)
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
retry++;
|
||||
goto retry;
|
||||
}
|
||||
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;
|
||||
@ -34,10 +35,13 @@ using BTCPayServer.Security.Bitpay;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Labels;
|
||||
using BTCPayServer.Services.Mails;
|
||||
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 +236,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 +417,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;
|
||||
@ -533,6 +586,95 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void CanParseLegacyLabels()
|
||||
{
|
||||
static void AssertContainsRawLabel(WalletTransactionInfo info)
|
||||
{
|
||||
foreach (var item in new[] { "blah", "lol", "hello" })
|
||||
{
|
||||
Assert.True(info.Labels.ContainsKey(item));
|
||||
var rawLabel = Assert.IsType<RawLabel>(info.Labels[item]);
|
||||
Assert.Equal("raw", rawLabel.Type);
|
||||
Assert.Equal(item, rawLabel.Text);
|
||||
}
|
||||
}
|
||||
var data = new WalletTransactionData();
|
||||
data.Labels = "blah,lol,hello,lol";
|
||||
var info = data.GetBlobInfo();
|
||||
Assert.Equal(3, info.Labels.Count);
|
||||
AssertContainsRawLabel(info);
|
||||
data.SetBlobInfo(info);
|
||||
Assert.Contains("raw", data.Labels);
|
||||
Assert.Contains("{", data.Labels);
|
||||
Assert.Contains("[", data.Labels);
|
||||
info = data.GetBlobInfo();
|
||||
AssertContainsRawLabel(info);
|
||||
|
||||
|
||||
data = new WalletTransactionData()
|
||||
{
|
||||
Labels = "pos",
|
||||
Blob = Encoders.Hex.DecodeData("1f8b08000000000000037abf7b7fb592737e6e6e6a5e89929592522d000000ffff030036bc6ad911000000")
|
||||
};
|
||||
info = data.GetBlobInfo();
|
||||
var label = Assert.Single(info.Labels);
|
||||
Assert.Equal("raw", label.Value.Type);
|
||||
Assert.Equal("pos", label.Value.Text);
|
||||
Assert.Equal("pos", label.Key);
|
||||
|
||||
|
||||
static void AssertContainsLabel(WalletTransactionInfo info)
|
||||
{
|
||||
Assert.Equal(2, info.Labels.Count);
|
||||
var invoiceLabel = Assert.IsType<ReferenceLabel>(info.Labels["invoice"]);
|
||||
Assert.Equal("BFm1MCJPBCDeRoWXvPcwnM", invoiceLabel.Reference);
|
||||
Assert.Equal("invoice", invoiceLabel.Text);
|
||||
Assert.Equal("invoice", invoiceLabel.Type);
|
||||
|
||||
var appLabel = Assert.IsType<ReferenceLabel>(info.Labels["app"]);
|
||||
Assert.Equal("87kj5yKay8mB4UUZcJhZH5TqDKMD3CznjwLjiu1oYZXe", appLabel.Reference);
|
||||
Assert.Equal("app", appLabel.Text);
|
||||
Assert.Equal("app", appLabel.Type);
|
||||
}
|
||||
data = new WalletTransactionData()
|
||||
{
|
||||
Labels = "[\"{\\n \\\"value\\\": \\\"invoice\\\",\\n \\\"id\\\": \\\"BFm1MCJPBCDeRoWXvPcwnM\\\"\\n}\",\"{\\n \\\"value\\\": \\\"app\\\",\\n \\\"id\\\": \\\"87kj5yKay8mB4UUZcJhZH5TqDKMD3CznjwLjiu1oYZXe\\\"\\n}\"]",
|
||||
};
|
||||
info = data.GetBlobInfo();
|
||||
AssertContainsLabel(info);
|
||||
data.SetBlobInfo(info);
|
||||
info = data.GetBlobInfo();
|
||||
AssertContainsLabel(info);
|
||||
|
||||
static void AssertPayoutLabel(WalletTransactionInfo info)
|
||||
{
|
||||
Assert.Single(info.Labels);
|
||||
var l = Assert.IsType<PayoutLabel>(info.Labels["payout"]);
|
||||
Assert.Equal("pullPaymentId", l.PullPaymentId);
|
||||
Assert.Equal("walletId", l.WalletId);
|
||||
Assert.Equal("payoutId", l.PayoutId);
|
||||
}
|
||||
|
||||
var payoutId = "payoutId";
|
||||
var pullPaymentId = "pullPaymentId";
|
||||
var walletId = "walletId";
|
||||
// How it was serialized before
|
||||
|
||||
data = new WalletTransactionData()
|
||||
{
|
||||
Labels = new JArray(JObject.FromObject(new { value = "payout", id = payoutId, pullPaymentId, walletId })).ToString()
|
||||
};
|
||||
info = data.GetBlobInfo();
|
||||
AssertPayoutLabel(info);
|
||||
data.SetBlobInfo(info);
|
||||
info = data.GetBlobInfo();
|
||||
AssertPayoutLabel(info);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Fact]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void DeterministicUTXOSorter()
|
||||
@ -563,7 +705,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;
|
||||
@ -768,32 +910,33 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await tester.ExplorerNode.SendToAddressAsync(
|
||||
BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest), Money.Coins(0.00005m));
|
||||
});
|
||||
}, e => e.InvoiceId == invoice.Id && e.PaymentMethodId.PaymentType == LightningPaymentType.Instance );
|
||||
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 +1142,6 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var invoice2 = acc.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.NotNull(invoice2);
|
||||
}
|
||||
@ -1089,7 +1231,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();
|
||||
@ -1182,8 +1324,8 @@ namespace BTCPayServer.Tests
|
||||
tx = Assert.Single(transactions.Transactions);
|
||||
|
||||
Assert.Equal("hello", tx.Comment);
|
||||
Assert.Contains("test", tx.Labels.Select(l => l.Value));
|
||||
Assert.Contains("test2", tx.Labels.Select(l => l.Value));
|
||||
Assert.Contains("test", tx.Labels.Select(l => l.Text));
|
||||
Assert.Contains("test2", tx.Labels.Select(l => l.Text));
|
||||
Assert.Equal(2, tx.Labels.GroupBy(l => l.Color).Count());
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(
|
||||
@ -1194,8 +1336,8 @@ namespace BTCPayServer.Tests
|
||||
tx = Assert.Single(transactions.Transactions);
|
||||
|
||||
Assert.Equal("hello", tx.Comment);
|
||||
Assert.Contains("test", tx.Labels.Select(l => l.Value));
|
||||
Assert.DoesNotContain("test2", tx.Labels.Select(l => l.Value));
|
||||
Assert.Contains("test", tx.Labels.Select(l => l.Text));
|
||||
Assert.DoesNotContain("test2", tx.Labels.Select(l => l.Text));
|
||||
Assert.Single(tx.Labels.GroupBy(l => l.Color));
|
||||
|
||||
var walletInfo = await tester.PayTester.GetService<WalletRepository>().GetWalletInfo(walletId);
|
||||
@ -1270,7 +1412,7 @@ namespace BTCPayServer.Tests
|
||||
var resp = await ctrl.Generate(newVersion);
|
||||
|
||||
var vm = Assert.IsType<Models.NotificationViewModels.IndexViewModel>(
|
||||
Assert.IsType<ViewResult>(ctrl.Index()).Model);
|
||||
Assert.IsType<ViewResult>(await ctrl.Index()).Model);
|
||||
|
||||
Assert.True(vm.Skip == 0);
|
||||
Assert.True(vm.Count == 50);
|
||||
@ -1459,7 +1601,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 +1650,7 @@ namespace BTCPayServer.Tests
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// [Fact(Timeout = TestTimeout)]
|
||||
[Fact()]
|
||||
[Trait("Integration", "Integration")]
|
||||
@ -1527,9 +1669,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;
|
||||
@ -1990,7 +2132,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 +2229,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 +2243,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 +2726,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 +2783,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 +2886,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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -3204,7 +3420,7 @@ namespace BTCPayServer.Tests
|
||||
var newVersion = MockVersionFetcher.MOCK_NEW_VERSION;
|
||||
|
||||
var vm = Assert.IsType<Models.NotificationViewModels.IndexViewModel>(
|
||||
Assert.IsType<ViewResult>(ctrl.Index()).Model);
|
||||
Assert.IsType<ViewResult>(await ctrl.Index()).Model);
|
||||
|
||||
Assert.True(vm.Skip == 0);
|
||||
Assert.True(vm.Count == 50);
|
||||
@ -3220,5 +3436,57 @@ namespace BTCPayServer.Tests
|
||||
Assert.False(fn.Seen);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task EmailSenderTests()
|
||||
{
|
||||
using (var tester = ServerTester.Create(newDb: true))
|
||||
{
|
||||
await tester.StartAsync();
|
||||
|
||||
var acc = tester.NewAccount();
|
||||
acc.GrantAccess(true);
|
||||
|
||||
var settings = tester.PayTester.GetService<SettingsRepository>();
|
||||
var emailSenderFactory = tester.PayTester.GetService<EmailSenderFactory>();
|
||||
|
||||
Assert.Null(await Assert.IsType<ServerEmailSender>(emailSenderFactory.GetEmailSender()).GetEmailSettings());
|
||||
Assert.Null(await Assert.IsType<StoreEmailSender>(emailSenderFactory.GetEmailSender(acc.StoreId)).GetEmailSettings());
|
||||
|
||||
|
||||
await settings.UpdateSetting(new PoliciesSettings() { DisableStoresToUseServerEmailSettings = false });
|
||||
await settings.UpdateSetting(new EmailSettings()
|
||||
{
|
||||
From = "admin@admin.com",
|
||||
Login = "admin@admin.com",
|
||||
Password = "admin@admin.com",
|
||||
Port = 1234,
|
||||
Server = "admin.com",
|
||||
EnableSSL = true
|
||||
});
|
||||
Assert.Equal("admin@admin.com",(await Assert.IsType<ServerEmailSender>(emailSenderFactory.GetEmailSender()).GetEmailSettings()).Login);
|
||||
Assert.Equal("admin@admin.com",(await Assert.IsType<StoreEmailSender>(emailSenderFactory.GetEmailSender(acc.StoreId)).GetEmailSettings()).Login);
|
||||
|
||||
await settings.UpdateSetting(new PoliciesSettings() { DisableStoresToUseServerEmailSettings = true });
|
||||
Assert.Equal("admin@admin.com",(await Assert.IsType<ServerEmailSender>(emailSenderFactory.GetEmailSender()).GetEmailSettings()).Login);
|
||||
Assert.Null(await Assert.IsType<StoreEmailSender>(emailSenderFactory.GetEmailSender(acc.StoreId)).GetEmailSettings());
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(await acc.GetController<StoresController>().Emails(acc.StoreId, new EmailsViewModel(new EmailSettings()
|
||||
{
|
||||
From = "store@store.com",
|
||||
Login = "store@store.com",
|
||||
Password = "store@store.com",
|
||||
Port = 1234,
|
||||
Server = "store.com",
|
||||
EnableSSL = true
|
||||
}), ""));
|
||||
|
||||
Assert.Equal("store@store.com",(await Assert.IsType<StoreEmailSender>(emailSenderFactory.GetEmailSender(acc.StoreId)).GetEmailSettings()).Login);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -21,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" />
|
||||
@ -157,9 +153,6 @@
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Home\BitpayTranslator.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\DynamicDnsServices.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
@ -251,5 +244,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_1misc_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1pull-payments_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1serverinfo_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1stores_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1users_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1webhooks_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" /></VisualStudio></ProjectExtensions>
|
||||
</Project>
|
||||
|
@ -2,6 +2,7 @@
|
||||
@inject UserManager<ApplicationUser> UserManager
|
||||
@inject CssThemeManager CssThemeManager
|
||||
@using BTCPayServer.HostedServices
|
||||
@using Microsoft.AspNetCore.Http.Extensions
|
||||
@model BTCPayServer.Components.NotificationsDropdown.NotificationSummaryViewModel
|
||||
|
||||
@if (Model.UnseenCount > 0)
|
||||
@ -24,6 +25,9 @@
|
||||
</a>
|
||||
}
|
||||
<a class="dropdown-item text-secondary" asp-controller="Notifications" asp-action="Index">See All</a>
|
||||
<form asp-controller="Notifications" asp-action="MarkAllAsSeen" asp-route-returnUrl="@Context.Request.GetDisplayUrl()" method="post">
|
||||
<button class="dropdown-item text-secondary" type="submit"><i class="fa fa-eye"></i> Mark all as seen</button>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Contracts;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Models.NotificationViewModels;
|
||||
|
||||
namespace BTCPayServer.Components.NotificationsDropdown
|
||||
|
@ -1,57 +1,52 @@
|
||||
@model BasePagingViewModel
|
||||
|
||||
@if (Model.Total > 0)
|
||||
@{
|
||||
var pageSizeOptions = new [] { 50, 100, 250, 500 };
|
||||
}
|
||||
|
||||
@if (Model.Total > pageSizeOptions.Min())
|
||||
{
|
||||
<nav aria-label="..." class="w-100">
|
||||
<ul class="pagination float-left">
|
||||
<li class="page-item @(Model.Skip == 0 ? "disabled" : null)">
|
||||
<a class="page-link" tabindex="-1" href="@NavigatePages(-1, Model.Count)">«</a>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
@if (Model.Total <= Model.Count)
|
||||
{
|
||||
<span class="page-link">
|
||||
1–@Model.Total
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="page-link">
|
||||
@(Model.Skip + 1)–@(Model.Skip + Model.Count), Total: @Model.Total
|
||||
</span>
|
||||
}
|
||||
</li>
|
||||
<li class="page-item @(Model.Total > (Model.Skip + Model.Count) ? null : "disabled")">
|
||||
<a class="page-link" href="@NavigatePages(1, Model.Count)">»</a>
|
||||
</li>
|
||||
</ul>
|
||||
@if (Model.Total > Model.Count)
|
||||
{
|
||||
<ul class="pagination float-left">
|
||||
<li class="page-item @(Model.Skip == 0 ? "disabled" : null)">
|
||||
<a class="page-link" tabindex="-1" href="@NavigatePages(-1, Model.Count)">«</a>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
@if (Model.Total <= Model.Count)
|
||||
{
|
||||
<span class="page-link">
|
||||
1–@Model.Total
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="page-link">
|
||||
@(Model.Skip + 1)–@(Model.Skip + Model.Count), Total: @Model.Total
|
||||
</span>
|
||||
}
|
||||
</li>
|
||||
<li class="page-item @(Model.Total > (Model.Skip + Model.Count) ? null : "disabled")">
|
||||
<a class="page-link" href="@NavigatePages(1, Model.Count)">»</a>
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
|
||||
@if (Model.Total >= 50)
|
||||
@if (Model.Total >= pageSizeOptions.Min())
|
||||
{
|
||||
<ul class="pagination float-right">
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">Page Size:</span>
|
||||
</li>
|
||||
<li class="page-item @(Model.Count == 50 ? "active" : null)">
|
||||
<a class="page-link" href="@NavigatePages(0, 50)">50</a>
|
||||
</li>
|
||||
@if (Model.Total >= 100)
|
||||
@foreach (int pageSize in pageSizeOptions)
|
||||
{
|
||||
<li class="page-item @(Model.Count == 100 ? "active" : null)">
|
||||
<a class="page-link" href="@NavigatePages(0, 100)">100</a>
|
||||
</li>
|
||||
}
|
||||
@if (Model.Total >= 250)
|
||||
{
|
||||
<li class="page-item @(Model.Count == 250 ? "active" : null)">
|
||||
<a class="page-link" href="@NavigatePages(0, 250)">250</a>
|
||||
</li>
|
||||
}
|
||||
@if (Model.Total >= 500)
|
||||
{
|
||||
<li class="page-item @(Model.Count == 500 ? "active" : null)">
|
||||
<a class="page-link" href="@NavigatePages(0, 500)">500</a>
|
||||
</li>
|
||||
if (Model.Total >= pageSize)
|
||||
{
|
||||
<li class="page-item @(Model.Count == pageSize ? "active" : null)">
|
||||
<a class="page-link" href="@NavigatePages(0, pageSize)">@pageSize</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
@ -70,14 +65,22 @@
|
||||
skip = Model.Skip + count;
|
||||
}
|
||||
|
||||
var act = Url.Action(null, new
|
||||
var query = new Dictionary<string, object>
|
||||
{
|
||||
searchTerm = Model.SearchTerm,
|
||||
timezoneOffset = Model.TimezoneOffset,
|
||||
skip = skip,
|
||||
count = count,
|
||||
});
|
||||
{ "searchTerm", Model.SearchTerm },
|
||||
{ "timezoneOffset", Model.TimezoneOffset },
|
||||
{ "skip", skip },
|
||||
{ "count", count }
|
||||
};
|
||||
|
||||
return act;
|
||||
if (Model.PaginationQuery != null)
|
||||
{
|
||||
// merge both, prefering the `query` properties in case of duplicate keys
|
||||
query = query.Concat(Model.PaginationQuery)
|
||||
.GroupBy(e => e.Key)
|
||||
.ToDictionary(g => g.Key, g => g.First().Value);
|
||||
}
|
||||
|
||||
return Url.Action(null, query);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Contracts;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Components.UIExtensionPoint
|
||||
|
@ -241,9 +241,13 @@ namespace BTCPayServer.Configuration
|
||||
}
|
||||
|
||||
DisableRegistration = conf.GetOrDefault<bool>("disable-registration", true);
|
||||
PluginRemote = conf.GetOrDefault("plugin-remote", "btcpayserver/btcpayserver-plugins");
|
||||
RecommendedPlugins = conf.GetOrDefault("recommended-plugins", "").ToLowerInvariant().Split('\r','\n','\t',' ').Where(s => !string.IsNullOrEmpty(s)).Distinct().ToArray();
|
||||
}
|
||||
|
||||
public string PluginDir { get; set; }
|
||||
public string PluginRemote { get; set; }
|
||||
public string[] RecommendedPlugins { get; set; }
|
||||
|
||||
private SSHSettings ParseSSHConfiguration(IConfiguration conf)
|
||||
{
|
||||
|
@ -43,6 +43,8 @@ namespace BTCPayServer.Configuration
|
||||
app.Option("--debuglog", "A rolling log file for debug messages.", CommandOptionType.SingleValue);
|
||||
app.Option("--debugloglevel", "The severity you log (default:information)", CommandOptionType.SingleValue);
|
||||
app.Option("--disable-registration", "Disables new user registrations (default:true)", CommandOptionType.SingleValue);
|
||||
app.Option("--plugin-remote", "Which github repository to fetch the available plugins list (default:btcpayserver/btcpayserver-plugins)", CommandOptionType.SingleValue);
|
||||
app.Option("--recommended-plugins", "Plugins which would be marked as recommended to be installed. Separated by newline or space", CommandOptionType.MultipleValue);
|
||||
app.Option("--xforwardedproto", "If specified, set X-Forwarded-Proto to the specified value, this may be useful if your reverse proxy handle https but is not configured to add X-Forwarded-Proto (example: --xforwardedproto https)", CommandOptionType.SingleValue);
|
||||
foreach (var network in provider.GetAll().OfType<BTCPayNetwork>())
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
@ -9,7 +10,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
[Authorize(AuthenticationSchemes = Security.AuthenticationSchemes.Bitpay)]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Bitpay)]
|
||||
[BitpayAPIConstraint()]
|
||||
public class AccessTokenController : Controller
|
||||
{
|
||||
|
@ -1,6 +1,9 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Logging;
|
||||
|
@ -1,6 +1,9 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
|
@ -4,6 +4,8 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Filters;
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
|
@ -1,21 +1,19 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Validation;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using CreateInvoiceRequest = BTCPayServer.Client.Models.CreateInvoiceRequest;
|
||||
using InvoiceData = BTCPayServer.Client.Models.InvoiceData;
|
||||
|
||||
@ -28,11 +26,16 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
private readonly InvoiceController _invoiceController;
|
||||
private readonly InvoiceRepository _invoiceRepository;
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
|
||||
public GreenFieldInvoiceController(InvoiceController invoiceController, InvoiceRepository invoiceRepository)
|
||||
public LanguageService LanguageService { get; }
|
||||
|
||||
public GreenFieldInvoiceController(InvoiceController invoiceController, InvoiceRepository invoiceRepository, LinkGenerator linkGenerator, LanguageService languageService)
|
||||
{
|
||||
_invoiceController = invoiceController;
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_linkGenerator = linkGenerator;
|
||||
LanguageService = languageService;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewInvoices,
|
||||
@ -92,6 +95,26 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPut("~/api/v1/stores/{storeId}/invoices/{invoiceId}")]
|
||||
public async Task<IActionResult> UpdateInvoice(string storeId, string invoiceId, UpdateInvoiceRequest request)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var result = await _invoiceRepository.UpdateInvoiceMetadata(invoiceId, storeId, request.Metadata);
|
||||
if (result != null)
|
||||
{
|
||||
return Ok(ToModel(result));
|
||||
}
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanCreateInvoice,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/stores/{storeId}/invoices")]
|
||||
@ -112,7 +135,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Currency), "Currency is required");
|
||||
}
|
||||
|
||||
request.Checkout = request.Checkout ?? new CreateInvoiceRequest.CheckoutOptions();
|
||||
if (request.Checkout.PaymentMethods?.Any() is true)
|
||||
{
|
||||
for (int i = 0; i < request.Checkout.PaymentMethods.Length; i++)
|
||||
@ -138,6 +161,21 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
"PaymentTolerance can only be between 0 and 100 percent", this);
|
||||
}
|
||||
|
||||
if (request.Checkout.DefaultLanguage != null)
|
||||
{
|
||||
var lang = LanguageService.FindBestMatch(request.Checkout.DefaultLanguage);
|
||||
if (lang == null)
|
||||
{
|
||||
request.AddModelError(invoiceRequest => invoiceRequest.Checkout.DefaultLanguage,
|
||||
"The requested defaultLang does not exists, Browse the ~/misc/lang page of your BTCPay Server instance to see the list of supported languages.", this);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ensure this is good case
|
||||
request.Checkout.DefaultLanguage = lang.Code;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
|
||||
@ -212,8 +250,71 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
await _invoiceRepository.ToggleInvoiceArchival(invoiceId, false, storeId);
|
||||
return await GetInvoice(storeId, invoiceId);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewInvoices,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods")]
|
||||
public async Task<IActionResult> GetInvoicePaymentMethods(string storeId, string invoiceId)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||
if (invoice.StoreId != store.Id)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(ToPaymentMethodModels(invoice));
|
||||
}
|
||||
|
||||
private InvoicePaymentMethodDataModel[] ToPaymentMethodModels(InvoiceEntity entity)
|
||||
{
|
||||
return entity.GetPaymentMethods().Select(
|
||||
method =>
|
||||
{
|
||||
var accounting = method.Calculate();
|
||||
var details = method.GetPaymentMethodDetails();
|
||||
var payments = method.ParentEntity.GetPayments().Where(paymentEntity =>
|
||||
paymentEntity.GetPaymentMethodId() == method.GetId());
|
||||
|
||||
return new InvoicePaymentMethodDataModel()
|
||||
{
|
||||
PaymentMethod = method.GetId().ToStringNormalized(),
|
||||
Destination = details.GetPaymentDestination(),
|
||||
Rate = method.Rate,
|
||||
Due = accounting.Due.ToDecimal(MoneyUnit.BTC),
|
||||
TotalPaid = accounting.Paid.ToDecimal(MoneyUnit.BTC),
|
||||
PaymentMethodPaid = accounting.CryptoPaid.ToDecimal(MoneyUnit.BTC),
|
||||
Amount = accounting.Due.ToDecimal(MoneyUnit.BTC),
|
||||
NetworkFee = accounting.NetworkFee.ToDecimal(MoneyUnit.BTC),
|
||||
PaymentLink =
|
||||
method.GetId().PaymentType.GetPaymentLink(method.Network, details, accounting.Due,
|
||||
Request.GetAbsoluteRoot()),
|
||||
Payments = payments.Select(paymentEntity =>
|
||||
{
|
||||
var data = paymentEntity.GetCryptoPaymentData();
|
||||
return new InvoicePaymentMethodDataModel.Payment()
|
||||
{
|
||||
Destination = data.GetDestination(),
|
||||
Id = data.GetPaymentId(),
|
||||
Status = !paymentEntity.Accounted
|
||||
? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Invalid
|
||||
: data.PaymentConfirmed(paymentEntity, entity.SpeedPolicy) ||
|
||||
data.PaymentCompleted(paymentEntity)
|
||||
? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Settled
|
||||
: InvoicePaymentMethodDataModel.Payment.PaymentStatus.Processing,
|
||||
Fee = paymentEntity.NetworkFee,
|
||||
Value = data.GetValue(),
|
||||
ReceivedDate = paymentEntity.ReceivedTime.DateTime
|
||||
};
|
||||
}).ToList()
|
||||
};
|
||||
}).ToArray();
|
||||
}
|
||||
private InvoiceData ToModel(InvoiceEntity entity)
|
||||
{
|
||||
return new InvoiceData()
|
||||
@ -223,7 +324,8 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
CreatedTime = entity.InvoiceTime,
|
||||
Amount = entity.Price,
|
||||
Id = entity.Id,
|
||||
Status = entity.Status,
|
||||
CheckoutLink = _linkGenerator.CheckoutLink(entity.Id, Request.Scheme, Request.Host, Request.PathBase),
|
||||
Status = entity.Status.ToModernStatus(),
|
||||
AdditionalStatus = entity.ExceptionStatus,
|
||||
Currency = entity.Currency,
|
||||
Metadata = entity.Metadata.ToJObject(),
|
||||
@ -234,7 +336,8 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
PaymentTolerance = entity.PaymentTolerance,
|
||||
PaymentMethods =
|
||||
entity.GetPaymentMethods().Select(method => method.GetId().ToStringNormalized()).ToArray(),
|
||||
SpeedPolicy = entity.SpeedPolicy
|
||||
SpeedPolicy = entity.SpeedPolicy,
|
||||
DefaultLanguage = entity.DefaultLanguage
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user