Compare commits
77 Commits
extensions
...
v1.0.6.2
Author | SHA1 | Date | |
---|---|---|---|
c7c0ba745d | |||
e7e0b29382 | |||
83ec280a29 | |||
bffa0e7fe5 | |||
6d2d917bf1 | |||
06d6d15441 | |||
87676ece18 | |||
acfd3f1002 | |||
d5cbe66b0e | |||
88aa34747b | |||
cc8dcade49 | |||
a64dd9af16 | |||
8405ce6369 | |||
18e68d04f9 | |||
f3010f622c | |||
ff87319a74 | |||
fa517417ed | |||
f9b86a6b2e | |||
8a21dfa93f | |||
c88ae2d7fe | |||
25e979687f | |||
af866939f4 | |||
e72becdfcb | |||
4bd97eecc9 | |||
8ffe6dcfa0 | |||
23002ac70d | |||
1262ad3cd4 | |||
d8f145c4dc | |||
9c5fd1b478 | |||
24f3285003 | |||
179520a211 | |||
ec31a4fe17 | |||
ab780485b2 | |||
07c5c2972d | |||
af593117e4 | |||
931505d135 | |||
6c7433b2f1 | |||
38ca0accde | |||
df79c2cf48 | |||
94bcbeb604 | |||
35e45333b1 | |||
f17a6f13a4 | |||
1bff7bdc47 | |||
13cb2c695f | |||
c581b80132 | |||
4dd0ae8bdc | |||
4db67e5bc5 | |||
6aa9122160 | |||
f84f2dca64 | |||
3c6992e910 | |||
7f79d16f02 | |||
790c386ba8 | |||
ee72badf21 | |||
c9c4453660 | |||
f3611ac693 | |||
cc6fe24e82 | |||
5a7730951a | |||
eef729b5f9 | |||
0bb0a38649 | |||
39029adcd8 | |||
c967f91abb | |||
1ba0685596 | |||
f2daa6a150 | |||
e021d42551 | |||
13509e31ca | |||
b9af805ac1 | |||
378d2bc8ba | |||
b73aa55a75 | |||
a729dd1380 | |||
09335e2cf1 | |||
31738c465d | |||
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
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
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)
|
||||
@ -52,7 +59,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
if (request.Status!= InvoiceStatus.Complete && request.Status!= InvoiceStatus.Invalid)
|
||||
if (request.Status!= InvoiceStatus.Settled && request.Status!= InvoiceStatus.Invalid)
|
||||
throw new ArgumentOutOfRangeException(nameof(request.Status), "Status can only be Invalid or Complete");
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/invoices/{invoiceId}/status", bodyPayload: request,
|
||||
|
65
BTCPayServer.Client/BTCPayServerClient.Webhooks.cs
Normal file
65
BTCPayServer.Client/BTCPayServerClient.Webhooks.cs
Normal file
@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public async Task<StoreWebhookData> CreateWebhook(string storeId, Client.Models.CreateStoreWebhookRequest create, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks", bodyPayload: create, method: HttpMethod.Post), token);
|
||||
return await HandleResponse<StoreWebhookData>(response);
|
||||
}
|
||||
public async Task<StoreWebhookData> GetWebhook(string storeId, string webhookId, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}"), token);
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
return null;
|
||||
return await HandleResponse<StoreWebhookData>(response);
|
||||
}
|
||||
public async Task<StoreWebhookData> UpdateWebhook(string storeId, string webhookId, Models.UpdateStoreWebhookRequest update, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}", bodyPayload: update, method: HttpMethod.Put), token);
|
||||
return await HandleResponse<StoreWebhookData>(response);
|
||||
}
|
||||
public async Task<bool> DeleteWebhook(string storeId, string webhookId, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}", method: HttpMethod.Delete), token);
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
public async Task<StoreWebhookData[]> GetWebhooks(string storeId, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks"), token);
|
||||
return await HandleResponse<StoreWebhookData[]>(response);
|
||||
}
|
||||
public async Task<WebhookDeliveryData[]> GetWebhookDeliveries(string storeId, string webhookId, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries"), token);
|
||||
return await HandleResponse<WebhookDeliveryData[]>(response);
|
||||
}
|
||||
public async Task<WebhookDeliveryData> GetWebhookDelivery(string storeId, string webhookId, string deliveryId, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}"), token);
|
||||
return await HandleResponse<WebhookDeliveryData>(response);
|
||||
}
|
||||
public async Task<string> RedeliverWebhook(string storeId, string webhookId, string deliveryId, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver", null, HttpMethod.Post), token);
|
||||
return await HandleResponse<string>(response);
|
||||
}
|
||||
|
||||
public async Task<WebhookEvent> GetWebhookDeliveryRequest(string storeId, string webhookId, string deliveryId, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/request"), token);
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
return null;
|
||||
return await HandleResponse<WebhookEvent>(response);
|
||||
}
|
||||
}
|
||||
}
|
@ -65,7 +65,8 @@ namespace BTCPayServer.Client
|
||||
protected async Task<T> HandleResponse<T>(HttpResponseMessage message)
|
||||
{
|
||||
await HandleResponse(message);
|
||||
return JsonConvert.DeserializeObject<T>(await message.Content.ReadAsStringAsync());
|
||||
var str = await message.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<T>(str);
|
||||
}
|
||||
|
||||
protected virtual HttpRequestMessage CreateHttpRequest(string path,
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
@ -20,4 +18,12 @@ namespace BTCPayServer.Client.Models
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset CreatedTime { get; set; }
|
||||
}
|
||||
public enum InvoiceStatus
|
||||
{
|
||||
New,
|
||||
Processing,
|
||||
Expired,
|
||||
Invalid,
|
||||
Settled
|
||||
}
|
||||
}
|
||||
|
61
BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs
Normal file
61
BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class InvoicePaymentMethodDataModel
|
||||
{
|
||||
public string Destination { get; set; }
|
||||
public string PaymentLink { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Rate { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal PaymentMethodPaid { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal TotalPaid { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Due { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Amount { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal NetworkFee { get; set; }
|
||||
|
||||
public List<Payment> Payments { get; set; }
|
||||
public string PaymentMethod { get; set; }
|
||||
|
||||
public class Payment
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTime ReceivedDate { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Value { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Fee { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public PaymentStatus Status { get; set; }
|
||||
|
||||
public string Destination { get; set; }
|
||||
|
||||
public enum PaymentStatus
|
||||
{
|
||||
Invalid,
|
||||
Processing,
|
||||
Settled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public enum InvoiceStatus
|
||||
{
|
||||
New,
|
||||
Paid,
|
||||
Expired,
|
||||
Invalid,
|
||||
Complete,
|
||||
Confirmed
|
||||
}
|
||||
}
|
@ -30,14 +30,21 @@ namespace BTCPayServer.Client.Models
|
||||
public double PaymentTolerance { get; set; } = 0;
|
||||
public bool AnyoneCanCreateInvoice { get; set; }
|
||||
|
||||
|
||||
public bool RequiresRefundEmail { get; set; }
|
||||
public bool LightningAmountInSatoshi { get; set; }
|
||||
public bool LightningPrivateRouteHints { get; set; }
|
||||
public bool OnChainWithLnInvoiceFallback { get; set; }
|
||||
public bool RedirectAutomatically { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public bool ShowRecommendedFee { get; set; } = true;
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public int RecommendedFeeBlockTarget { get; set; } = 1;
|
||||
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string DefaultLang { get; set; } = "en";
|
||||
public bool LightningAmountInSatoshi { get; set; }
|
||||
|
||||
public string CustomLogo { get; set; }
|
||||
|
||||
@ -45,16 +52,13 @@ namespace BTCPayServer.Client.Models
|
||||
|
||||
public string HtmlTitle { get; set; }
|
||||
|
||||
public bool RedirectAutomatically { get; set; }
|
||||
|
||||
public bool RequiresRefundEmail { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public NetworkFeeMode NetworkFeeMode { get; set; } = NetworkFeeMode.Never;
|
||||
|
||||
public bool PayJoinEnabled { get; set; }
|
||||
public bool LightningPrivateRouteHints { get; set; }
|
||||
|
||||
|
||||
[JsonExtensionData]
|
||||
|
35
BTCPayServer.Client/Models/StoreWebhookData.cs
Normal file
35
BTCPayServer.Client/Models/StoreWebhookData.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class StoreWebhookBaseData
|
||||
{
|
||||
public class AuthorizedEventsData
|
||||
{
|
||||
public bool Everything { get; set; } = true;
|
||||
|
||||
[JsonProperty(ItemConverterType = typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||
public WebhookEventType[] SpecificEvents { get; set; } = Array.Empty<WebhookEventType>();
|
||||
}
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public string Secret { get; set; }
|
||||
public bool AutomaticRedelivery { get; set; } = true;
|
||||
public string Url { get; set; }
|
||||
public AuthorizedEventsData AuthorizedEvents { get; set; } = new AuthorizedEventsData();
|
||||
}
|
||||
public class UpdateStoreWebhookRequest : StoreWebhookBaseData
|
||||
{
|
||||
}
|
||||
public class CreateStoreWebhookRequest : StoreWebhookBaseData
|
||||
{
|
||||
}
|
||||
public class StoreWebhookData : StoreWebhookBaseData
|
||||
{
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
18
BTCPayServer.Client/Models/WebhookDeliveryData.cs
Normal file
18
BTCPayServer.Client/Models/WebhookDeliveryData.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class WebhookDeliveryData
|
||||
{
|
||||
public string Id { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
public int? HttpCode { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||
public WebhookDeliveryStatus Status { get; set; }
|
||||
}
|
||||
}
|
13
BTCPayServer.Client/Models/WebhookDeliveryStatus.cs
Normal file
13
BTCPayServer.Client/Models/WebhookDeliveryStatus.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public enum WebhookDeliveryStatus
|
||||
{
|
||||
Failed,
|
||||
HttpError,
|
||||
HttpSuccess
|
||||
}
|
||||
}
|
36
BTCPayServer.Client/Models/WebhookEvent.cs
Normal file
36
BTCPayServer.Client/Models/WebhookEvent.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class WebhookEvent
|
||||
{
|
||||
public readonly static JsonSerializerSettings DefaultSerializerSettings;
|
||||
static WebhookEvent()
|
||||
{
|
||||
DefaultSerializerSettings = new JsonSerializerSettings();
|
||||
DefaultSerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
|
||||
NBitcoin.JsonConverters.Serializer.RegisterFrontConverters(DefaultSerializerSettings);
|
||||
DefaultSerializerSettings.Formatting = Formatting.None;
|
||||
}
|
||||
public string DeliveryId { get; set; }
|
||||
public string WebhookId { get; set; }
|
||||
public string OrignalDeliveryId { get; set; }
|
||||
public bool IsRedelivery { get; set; }
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public WebhookEventType Type { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
[JsonExtensionData]
|
||||
public IDictionary<string, JToken> AdditionalData { get; set; }
|
||||
public T ReadAs<T>()
|
||||
{
|
||||
var str = JsonConvert.SerializeObject(this, DefaultSerializerSettings);
|
||||
return JsonConvert.DeserializeObject<T>(str, DefaultSerializerSettings);
|
||||
}
|
||||
}
|
||||
}
|
16
BTCPayServer.Client/Models/WebhookEventType.cs
Normal file
16
BTCPayServer.Client/Models/WebhookEventType.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public enum WebhookEventType
|
||||
{
|
||||
InvoiceCreated,
|
||||
InvoiceReceivedPayment,
|
||||
InvoiceProcessing,
|
||||
InvoiceExpired,
|
||||
InvoiceSettled,
|
||||
InvoiceInvalid
|
||||
}
|
||||
}
|
85
BTCPayServer.Client/Models/WebhookInvoiceEvent.cs
Normal file
85
BTCPayServer.Client/Models/WebhookInvoiceEvent.cs
Normal file
@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class WebhookInvoiceEvent : WebhookEvent
|
||||
{
|
||||
public WebhookInvoiceEvent()
|
||||
{
|
||||
|
||||
}
|
||||
public WebhookInvoiceEvent(WebhookEventType evtType)
|
||||
{
|
||||
this.Type = evtType;
|
||||
}
|
||||
[JsonProperty(Order = 1)]
|
||||
public string StoreId { get; set; }
|
||||
[JsonProperty(Order = 2)]
|
||||
public string InvoiceId { get; set; }
|
||||
}
|
||||
|
||||
public class WebhookInvoiceSettledEvent : WebhookInvoiceEvent
|
||||
{
|
||||
public WebhookInvoiceSettledEvent()
|
||||
{
|
||||
|
||||
}
|
||||
public WebhookInvoiceSettledEvent(WebhookEventType evtType) : base(evtType)
|
||||
{
|
||||
}
|
||||
|
||||
public bool ManuallyMarked { get; set; }
|
||||
}
|
||||
public class WebhookInvoiceInvalidEvent : WebhookInvoiceEvent
|
||||
{
|
||||
public WebhookInvoiceInvalidEvent()
|
||||
{
|
||||
|
||||
}
|
||||
public WebhookInvoiceInvalidEvent(WebhookEventType evtType) : base(evtType)
|
||||
{
|
||||
}
|
||||
|
||||
public bool ManuallyMarked { get; set; }
|
||||
}
|
||||
public class WebhookInvoiceProcessingEvent : WebhookInvoiceEvent
|
||||
{
|
||||
public WebhookInvoiceProcessingEvent()
|
||||
{
|
||||
|
||||
}
|
||||
public WebhookInvoiceProcessingEvent(WebhookEventType evtType) : base(evtType)
|
||||
{
|
||||
}
|
||||
|
||||
public bool OverPaid { get; set; }
|
||||
}
|
||||
public class WebhookInvoiceReceivedPaymentEvent : WebhookInvoiceEvent
|
||||
{
|
||||
public WebhookInvoiceReceivedPaymentEvent()
|
||||
{
|
||||
|
||||
}
|
||||
public WebhookInvoiceReceivedPaymentEvent(WebhookEventType evtType) : base(evtType)
|
||||
{
|
||||
}
|
||||
|
||||
public bool AfterExpiration { get; set; }
|
||||
}
|
||||
public class WebhookInvoiceExpiredEvent : WebhookInvoiceEvent
|
||||
{
|
||||
public WebhookInvoiceExpiredEvent()
|
||||
{
|
||||
|
||||
}
|
||||
public WebhookInvoiceExpiredEvent(WebhookEventType evtType) : base(evtType)
|
||||
{
|
||||
}
|
||||
|
||||
public bool PartiallyPaid { get; set; }
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ namespace BTCPayServer.Client
|
||||
public const string CanUseLightningNodeInStore = "btcpay.store.canuselightningnode";
|
||||
public const string CanModifyServerSettings = "btcpay.server.canmodifyserversettings";
|
||||
public const string CanModifyStoreSettings = "btcpay.store.canmodifystoresettings";
|
||||
public const string CanModifyStoreWebhooks = "btcpay.store.webhooks.canmodifywebhooks";
|
||||
public const string CanModifyStoreSettingsUnscoped = "btcpay.store.canmodifystoresettings:";
|
||||
public const string CanViewStoreSettings = "btcpay.store.canviewstoresettings";
|
||||
public const string CanViewInvoices = "btcpay.store.canviewinvoices";
|
||||
@ -29,6 +30,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
yield return CanViewInvoices;
|
||||
yield return CanCreateInvoice;
|
||||
yield return CanModifyStoreWebhooks;
|
||||
yield return CanModifyServerSettings;
|
||||
yield return CanModifyStoreSettings;
|
||||
yield return CanViewStoreSettings;
|
||||
@ -156,6 +158,7 @@ namespace BTCPayServer.Client
|
||||
switch (subpolicy)
|
||||
{
|
||||
case Policies.CanViewInvoices when this.Policy == Policies.CanModifyStoreSettings:
|
||||
case Policies.CanModifyStoreWebhooks when this.Policy == Policies.CanModifyStoreSettings:
|
||||
case Policies.CanViewInvoices when this.Policy == Policies.CanViewStoreSettings:
|
||||
case Policies.CanViewStoreSettings when this.Policy == Policies.CanModifyStoreSettings:
|
||||
case Policies.CanCreateInvoice when this.Policy == Policies.CanModifyStoreSettings:
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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);
|
||||
}
|
||||
}
|
||||
}
|
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");
|
||||
}
|
||||
}
|
||||
}
|
@ -257,6 +257,25 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("InvoiceEvents");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceWebhookDeliveryData", b =>
|
||||
{
|
||||
b.Property<string>("InvoiceId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeliveryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InvoiceId", "DeliveryId");
|
||||
|
||||
b.HasIndex("DeliveryId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("InvoiceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("InvoiceWebhookDeliveries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.NotificationData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -588,6 +607,25 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("Stores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreWebhookData", b =>
|
||||
{
|
||||
b.Property<string>("StoreId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("WebhookId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("StoreId", "WebhookId");
|
||||
|
||||
b.HasIndex("StoreId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("WebhookId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("StoreWebhooks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoredFile", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -696,6 +734,46 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("WalletTransactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WebhookData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.IsRequired()
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Webhooks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WebhookDeliveryData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.IsRequired()
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<DateTimeOffset>("Timestamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("WebhookId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("WebhookId");
|
||||
|
||||
b.ToTable("WebhookDeliveries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -883,6 +961,21 @@ namespace BTCPayServer.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceWebhookDeliveryData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.WebhookDeliveryData", "Delivery")
|
||||
.WithOne()
|
||||
.HasForeignKey("BTCPayServer.Data.InvoiceWebhookDeliveryData", "DeliveryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "Invoice")
|
||||
.WithOne()
|
||||
.HasForeignKey("BTCPayServer.Data.InvoiceWebhookDeliveryData", "InvoiceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.NotificationData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.ApplicationUser", "ApplicationUser")
|
||||
@ -956,6 +1049,21 @@ namespace BTCPayServer.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreWebhookData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "Store")
|
||||
.WithOne()
|
||||
.HasForeignKey("BTCPayServer.Data.StoreWebhookData", "StoreId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BTCPayServer.Data.WebhookData", "Webhook")
|
||||
.WithOne()
|
||||
.HasForeignKey("BTCPayServer.Data.StoreWebhookData", "WebhookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoredFile", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.ApplicationUser", "ApplicationUser")
|
||||
@ -995,6 +1103,15 @@ namespace BTCPayServer.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WebhookDeliveryData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.WebhookData", "Webhook")
|
||||
.WithMany("Deliveries")
|
||||
.HasForeignKey("WebhookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
|
@ -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>
|
||||
|
@ -875,7 +875,7 @@ normal:
|
||||
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
|
||||
{
|
||||
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null, null),
|
||||
});
|
||||
var networkBTC = networkProvider.GetNetwork("BTC");
|
||||
var networkLTC = networkProvider.GetNetwork("LTC");
|
||||
|
@ -89,7 +89,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("button[value='btcpay.store.canmodifystoresettings:change-store-mode']")).Click();
|
||||
//there should be a store already by default in the dropdown
|
||||
var dropdown = s.Driver.FindElement(By.Name("PermissionValues[3].SpecificStores[0]"));
|
||||
var dropdown = s.Driver.FindElement(By.Name("PermissionValues[4].SpecificStores[0]"));
|
||||
var option = dropdown.FindElement(By.TagName("option"));
|
||||
var storeId = option.GetAttribute("value");
|
||||
option.Click();
|
||||
|
@ -21,6 +21,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.13" />
|
||||
<PackageReference Include="Selenium.Support" Version="3.141.0" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="85.0.4183.8700" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
|
@ -26,7 +26,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using AuthenticationSchemes = BTCPayServer.Security.AuthenticationSchemes;
|
||||
using AuthenticationSchemes = BTCPayServer.Abstractions.Constants.AuthenticationSchemes;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
|
@ -3,6 +3,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using ExchangeSharp;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
@ -62,9 +63,9 @@ namespace BTCPayServer.Tests
|
||||
semaphore.Dispose();
|
||||
}
|
||||
|
||||
public async Task<HttpContext> GetNextRequest()
|
||||
public async Task<HttpContext> GetNextRequest(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _channel.Reader.ReadAsync();
|
||||
return await _channel.Reader.ReadAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ using BTCPayServer.Tests.Logging;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using NBitcoin.OpenAsset;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@ -149,12 +150,12 @@ namespace BTCPayServer.Tests
|
||||
var user1 = await unauthClient.CreateUser(
|
||||
new CreateApplicationUserRequest() { Email = "test@gmail.com", Password = "abceudhqw" });
|
||||
Assert.Empty(user1.Roles);
|
||||
|
||||
|
||||
// We have no admin, so it should work
|
||||
var user2 = await unauthClient.CreateUser(
|
||||
new CreateApplicationUserRequest() { Email = "test2@gmail.com", Password = "abceudhqw" });
|
||||
Assert.Empty(user2.Roles);
|
||||
|
||||
|
||||
// Duplicate email
|
||||
await AssertValidationError(new[] { "Email" },
|
||||
async () => await unauthClient.CreateUser(
|
||||
@ -170,7 +171,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,
|
||||
@ -611,6 +612,101 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanUseWebhooks()
|
||||
{
|
||||
void AssertHook(FakeServer fakeServer, Client.Models.StoreWebhookData hook)
|
||||
{
|
||||
Assert.True(hook.Enabled);
|
||||
Assert.True(hook.AuthorizedEvents.Everything);
|
||||
Assert.False(hook.AutomaticRedelivery);
|
||||
Assert.Equal(fakeServer.ServerUri.AbsoluteUri, hook.Url);
|
||||
}
|
||||
using var tester = ServerTester.Create();
|
||||
using var fakeServer = new FakeServer();
|
||||
await fakeServer.Start();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
var clientProfile = await user.CreateClient(Policies.CanModifyStoreWebhooks, Policies.CanCreateInvoice);
|
||||
var hook = await clientProfile.CreateWebhook(user.StoreId, new CreateStoreWebhookRequest()
|
||||
{
|
||||
Url = fakeServer.ServerUri.AbsoluteUri,
|
||||
AutomaticRedelivery = false
|
||||
});
|
||||
Assert.NotNull(hook.Secret);
|
||||
AssertHook(fakeServer, hook);
|
||||
hook = await clientProfile.GetWebhook(user.StoreId, hook.Id);
|
||||
AssertHook(fakeServer, hook);
|
||||
var hooks = await clientProfile.GetWebhooks(user.StoreId);
|
||||
hook = Assert.Single(hooks);
|
||||
AssertHook(fakeServer, hook);
|
||||
await clientProfile.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest() { Currency = "USD", Amount = 100 });
|
||||
var req = await fakeServer.GetNextRequest();
|
||||
req.Response.StatusCode = 200;
|
||||
fakeServer.Done();
|
||||
hook = await clientProfile.UpdateWebhook(user.StoreId, hook.Id, new UpdateStoreWebhookRequest()
|
||||
{
|
||||
Url = hook.Url,
|
||||
Secret = "lol",
|
||||
AutomaticRedelivery = false
|
||||
});
|
||||
Assert.Null(hook.Secret);
|
||||
AssertHook(fakeServer, hook);
|
||||
var deliveries = await clientProfile.GetWebhookDeliveries(user.StoreId, hook.Id);
|
||||
var delivery = Assert.Single(deliveries);
|
||||
delivery = await clientProfile.GetWebhookDelivery(user.StoreId, hook.Id, delivery.Id);
|
||||
Assert.NotNull(delivery);
|
||||
Assert.Equal(WebhookDeliveryStatus.HttpSuccess, delivery.Status);
|
||||
|
||||
var newDeliveryId = await clientProfile.RedeliverWebhook(user.StoreId, hook.Id, delivery.Id);
|
||||
req = await fakeServer.GetNextRequest();
|
||||
req.Response.StatusCode = 404;
|
||||
fakeServer.Done();
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var newDelivery = await clientProfile.GetWebhookDelivery(user.StoreId, hook.Id, newDeliveryId);
|
||||
Assert.NotNull(newDelivery);
|
||||
Assert.Equal(404, newDelivery.HttpCode);
|
||||
var req = await clientProfile.GetWebhookDeliveryRequest(user.StoreId, hook.Id, newDeliveryId);
|
||||
Assert.Equal(delivery.Id, req.OrignalDeliveryId);
|
||||
Assert.True(req.IsRedelivery);
|
||||
Assert.Equal(WebhookDeliveryStatus.HttpError, newDelivery.Status);
|
||||
});
|
||||
deliveries = await clientProfile.GetWebhookDeliveries(user.StoreId, hook.Id);
|
||||
Assert.Equal(2, deliveries.Length);
|
||||
Assert.Equal(newDeliveryId, deliveries[0].Id);
|
||||
var jObj = await clientProfile.GetWebhookDeliveryRequest(user.StoreId, hook.Id, newDeliveryId);
|
||||
Assert.NotNull(jObj);
|
||||
|
||||
Logs.Tester.LogInformation("Should not be able to access webhook without proper auth");
|
||||
var unauthorized = await user.CreateClient(Policies.CanCreateInvoice);
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await unauthorized.GetWebhookDeliveryRequest(user.StoreId, hook.Id, newDeliveryId);
|
||||
});
|
||||
|
||||
Logs.Tester.LogInformation("Can use btcpay.store.canmodifystoresettings to query webhooks");
|
||||
clientProfile = await user.CreateClient(Policies.CanModifyStoreSettings, Policies.CanCreateInvoice);
|
||||
await clientProfile.GetWebhookDeliveryRequest(user.StoreId, hook.Id, newDeliveryId);
|
||||
|
||||
Logs.Tester.LogInformation("Testing corner cases");
|
||||
Assert.Null(await clientProfile.GetWebhookDeliveryRequest(user.StoreId, "lol", newDeliveryId));
|
||||
Assert.Null(await clientProfile.GetWebhookDeliveryRequest(user.StoreId, hook.Id, "lol"));
|
||||
Assert.Null(await clientProfile.GetWebhookDeliveryRequest(user.StoreId, "lol", "lol"));
|
||||
Assert.Null(await clientProfile.GetWebhook(user.StoreId, "lol"));
|
||||
await AssertHttpError(404, async () =>
|
||||
{
|
||||
await clientProfile.UpdateWebhook(user.StoreId, "lol", new UpdateStoreWebhookRequest() { Url = hook.Url });
|
||||
});
|
||||
|
||||
Assert.True(await clientProfile.DeleteWebhook(user.StoreId, hook.Id));
|
||||
Assert.False(await clientProfile.DeleteWebhook(user.StoreId, hook.Id));
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task HealthControllerTests()
|
||||
@ -821,6 +917,7 @@ namespace BTCPayServer.Tests
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync();
|
||||
await user.MakeAdmin();
|
||||
await user.SetupWebhook();
|
||||
var client = await user.CreateClient(Policies.Unrestricted);
|
||||
var viewOnly = await user.CreateClient(Policies.CanViewInvoices);
|
||||
|
||||
@ -848,9 +945,15 @@ namespace BTCPayServer.Tests
|
||||
Assert.Single(invoices);
|
||||
Assert.Equal(newInvoice.Id, invoices.First().Id);
|
||||
|
||||
//get payment request
|
||||
//get
|
||||
var invoice = await viewOnly.GetInvoice(user.StoreId, newInvoice.Id);
|
||||
Assert.Equal(newInvoice.Metadata, invoice.Metadata);
|
||||
var paymentMethods = await viewOnly.GetInvoicePaymentMethods(user.StoreId, newInvoice.Id);
|
||||
Assert.Single(paymentMethods);
|
||||
var paymentMethod = paymentMethods.First();
|
||||
Assert.Equal("BTC", paymentMethod.PaymentMethod);
|
||||
Assert.Empty(paymentMethod.Payments);
|
||||
|
||||
|
||||
//update
|
||||
invoice = await viewOnly.GetInvoice(user.StoreId, newInvoice.Id);
|
||||
@ -859,7 +962,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await client.MarkInvoiceStatus(user.StoreId, invoice.Id, new MarkInvoiceStatusRequest()
|
||||
{
|
||||
Status = InvoiceStatus.Complete
|
||||
Status = InvoiceStatus.Settled
|
||||
});
|
||||
});
|
||||
|
||||
@ -878,10 +981,43 @@ namespace BTCPayServer.Tests
|
||||
await client.UnarchiveInvoice(user.StoreId, invoice.Id);
|
||||
Assert.NotNull(await client.GetInvoice(user.StoreId, invoice.Id));
|
||||
|
||||
|
||||
foreach (var marked in new[] { InvoiceStatus.Settled, InvoiceStatus.Invalid })
|
||||
{
|
||||
var inv = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest() { Currency = "USD", Amount = 100 });
|
||||
await user.PayInvoice(inv.Id);
|
||||
await client.MarkInvoiceStatus(user.StoreId, inv.Id, new MarkInvoiceStatusRequest()
|
||||
{
|
||||
Status = marked
|
||||
});
|
||||
var result = await client.GetInvoice(user.StoreId, inv.Id);
|
||||
if (marked == InvoiceStatus.Settled)
|
||||
{
|
||||
Assert.Equal(InvoiceStatus.Settled, result.Status);
|
||||
user.AssertHasWebhookEvent<WebhookInvoiceSettledEvent>(WebhookEventType.InvoiceSettled,
|
||||
o =>
|
||||
{
|
||||
Assert.Equal(inv.Id, o.InvoiceId);
|
||||
Assert.True(o.ManuallyMarked);
|
||||
});
|
||||
}
|
||||
if (marked == InvoiceStatus.Invalid)
|
||||
{
|
||||
Assert.Equal(InvoiceStatus.Invalid, result.Status);
|
||||
var evt = user.AssertHasWebhookEvent<WebhookInvoiceInvalidEvent>(WebhookEventType.InvoiceInvalid,
|
||||
o =>
|
||||
{
|
||||
Assert.Equal(inv.Id, o.InvoiceId);
|
||||
Assert.True(o.ManuallyMarked);
|
||||
});
|
||||
Assert.NotNull(await client.GetWebhookDelivery(evt.StoreId, evt.WebhookId, evt.DeliveryId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanUseLightningAPI()
|
||||
@ -907,7 +1043,7 @@ namespace BTCPayServer.Tests
|
||||
var info = await client.GetLightningNodeInfo("BTC");
|
||||
Assert.Single(info.NodeURIs);
|
||||
Assert.NotEqual(0, info.BlockHeight);
|
||||
|
||||
|
||||
var err = await Assert.ThrowsAsync<HttpRequestException>(async () => await client.GetLightningNodeChannels("BTC"));
|
||||
Assert.Contains("503", err.Message);
|
||||
// Not permission for the store!
|
||||
|
@ -3,6 +3,7 @@ using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
@ -269,7 +270,7 @@ namespace BTCPayServer.Tests
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var invoice = await s.Server.PayTester.GetService<InvoiceRepository>().GetInvoice(invoiceId);
|
||||
Assert.Equal(InvoiceStatus.Paid, invoice.Status);
|
||||
Assert.Equal(InvoiceStatusLegacy.Paid, invoice.Status);
|
||||
});
|
||||
|
||||
s.GoToInvoices();
|
||||
@ -329,7 +330,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
var invoice = await s.Server.PayTester.GetService<InvoiceRepository>().GetInvoice(invoiceId);
|
||||
var dto = invoice.EntityToDTO();
|
||||
Assert.Equal(InvoiceStatus.Paid, invoice.Status);
|
||||
Assert.Equal(InvoiceStatusLegacy.Paid, invoice.Status);
|
||||
});
|
||||
s.GoToInvoices();
|
||||
paymentValueRowColumn = s.Driver.FindElement(By.Id($"invoice_{invoiceId}"))
|
||||
@ -719,7 +720,7 @@ retry:
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var invoice = await tester.PayTester.GetService<InvoiceRepository>().GetInvoice(lastInvoiceId);
|
||||
Assert.Equal(InvoiceStatus.Paid, invoice.Status);
|
||||
Assert.Equal(InvoiceStatusLegacy.Paid, invoice.Status);
|
||||
Assert.Equal(InvoiceExceptionStatus.None, invoice.ExceptionStatus);
|
||||
var coins = await btcPayWallet.GetUnspentCoins(receiverUser.DerivationScheme);
|
||||
foreach (var coin in coins)
|
||||
@ -1046,7 +1047,7 @@ retry:
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var invoiceEntity = await tester.PayTester.GetService<InvoiceRepository>().GetInvoice(invoice7.Id);
|
||||
Assert.Equal(InvoiceStatus.Paid, invoiceEntity.Status);
|
||||
Assert.Equal(InvoiceStatusLegacy.Paid, invoiceEntity.Status);
|
||||
Assert.Contains(invoiceEntity.GetPayments(), p => p.Accounted &&
|
||||
((BitcoinLikePaymentData)p.GetCryptoPaymentData()).PayjoinInformation is null);
|
||||
});
|
||||
@ -1075,7 +1076,7 @@ retry:
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var invoiceEntity = await tester.PayTester.GetService<InvoiceRepository>().GetInvoice(invoice7.Id);
|
||||
Assert.Equal(InvoiceStatus.New, invoiceEntity.Status);
|
||||
Assert.Equal(InvoiceStatusLegacy.New, invoiceEntity.Status);
|
||||
Assert.True(invoiceEntity.GetPayments().All(p => !p.Accounted));
|
||||
ourOutpoint = invoiceEntity.GetAllBitcoinPaymentData().First().PayjoinInformation.ContributedOutPoints[0];
|
||||
});
|
||||
|
@ -207,12 +207,12 @@ namespace BTCPayServer.Tests
|
||||
pair => pair.Key == "Id" && pair.Value.ToString() == invoiceId);
|
||||
|
||||
var invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
|
||||
Assert.Equal(InvoiceState.ToString(InvoiceStatus.New), invoice.Status);
|
||||
Assert.Equal(InvoiceState.ToString(InvoiceStatusLegacy.New), invoice.Status);
|
||||
Assert.IsType<OkObjectResult>(await
|
||||
paymentRequestController.CancelUnpaidPendingInvoice(paymentRequestId, false));
|
||||
|
||||
invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
|
||||
Assert.Equal(InvoiceState.ToString(InvoiceStatus.Invalid), invoice.Status);
|
||||
Assert.Equal(InvoiceState.ToString(InvoiceStatusLegacy.Invalid), invoice.Status);
|
||||
|
||||
Assert.IsType<BadRequestObjectResult>(await
|
||||
paymentRequestController.CancelUnpaidPendingInvoice(paymentRequestId, false));
|
||||
|
@ -6,6 +6,7 @@ using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Models;
|
||||
@ -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,11 +312,12 @@ namespace BTCPayServer.Tests
|
||||
await s.StartAsync();
|
||||
var alice = s.RegisterNewUser();
|
||||
var storeData = s.CreateNewStore();
|
||||
var onchainHint = "A store requires a wallet to receive payments. Set up your wallet.";
|
||||
var offchainHint = "A connection to a Lightning node is required to receive Lightning payments.";
|
||||
|
||||
// verify that hints are displayed on the store page
|
||||
Assert.True(s.Driver.PageSource.Contains("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),
|
||||
@ -314,7 +326,7 @@ namespace BTCPayServer.Tests
|
||||
s.GoToStore(storeData.storeId);
|
||||
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"),
|
||||
Assert.False(s.Driver.PageSource.Contains(onchainHint),
|
||||
"Wallet hint not dismissed on derivation scheme add");
|
||||
|
||||
s.Driver.FindElement(By.Id("dismissLightningHint")).Click(); // dismiss lightning hint
|
||||
@ -380,8 +392,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 +606,132 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanUseWebhooks()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser(true);
|
||||
var store = s.CreateNewStore();
|
||||
s.GoToStore(store.storeId, Views.Stores.StoreNavPages.Webhooks);
|
||||
|
||||
Logs.Tester.LogInformation("Let's create two webhooks");
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
s.Driver.FindElement(By.Id("CreateWebhook")).Click();
|
||||
s.Driver.FindElement(By.Name("PayloadUrl")).SendKeys($"http://127.0.0.1/callback{i}");
|
||||
new SelectElement(s.Driver.FindElement(By.Name("Everything")))
|
||||
.SelectByValue("false");
|
||||
s.Driver.FindElement(By.Id("InvoiceCreated")).Click();
|
||||
s.Driver.FindElement(By.Id("InvoiceProcessing")).Click();
|
||||
s.Driver.FindElement(By.Name("add")).Click();
|
||||
}
|
||||
|
||||
Logs.Tester.LogInformation("Let's delete one of them");
|
||||
var deletes = s.Driver.FindElements(By.LinkText("Delete"));
|
||||
Assert.Equal(2, deletes.Count);
|
||||
deletes[0].Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
deletes = s.Driver.FindElements(By.LinkText("Delete"));
|
||||
Assert.Single(deletes);
|
||||
s.AssertHappyMessage();
|
||||
|
||||
Logs.Tester.LogInformation("Let's try to update one of them");
|
||||
s.Driver.FindElement(By.LinkText("Modify")).Click();
|
||||
|
||||
using FakeServer server = new FakeServer();
|
||||
await server.Start();
|
||||
s.Driver.FindElement(By.Name("PayloadUrl")).Clear();
|
||||
s.Driver.FindElement(By.Name("PayloadUrl")).SendKeys(server.ServerUri.AbsoluteUri);
|
||||
s.Driver.FindElement(By.Name("Secret")).Clear();
|
||||
s.Driver.FindElement(By.Name("Secret")).SendKeys("HelloWorld");
|
||||
s.Driver.FindElement(By.Name("update")).Click();
|
||||
s.AssertHappyMessage();
|
||||
s.Driver.FindElement(By.LinkText("Modify")).Click();
|
||||
foreach (var value in Enum.GetValues(typeof(WebhookEventType)))
|
||||
{
|
||||
// Here we make sure we did not forget an event type in the list
|
||||
// However, maybe some event should not appear here because not at the store level.
|
||||
// Fix as needed.
|
||||
Assert.Contains($"value=\"{value}\"", s.Driver.PageSource);
|
||||
}
|
||||
// This one should be checked
|
||||
Assert.Contains($"value=\"InvoiceProcessing\" checked", s.Driver.PageSource);
|
||||
Assert.Contains($"value=\"InvoiceCreated\" checked", s.Driver.PageSource);
|
||||
// This one never been checked
|
||||
Assert.DoesNotContain($"value=\"InvoiceReceivedPayment\" checked", s.Driver.PageSource);
|
||||
|
||||
s.Driver.FindElement(By.Name("update")).Click();
|
||||
s.AssertHappyMessage();
|
||||
Assert.Contains(server.ServerUri.AbsoluteUri, s.Driver.PageSource);
|
||||
|
||||
Logs.Tester.LogInformation("Let's see if we can generate an event");
|
||||
s.GoToStore(store.storeId);
|
||||
s.AddDerivationScheme();
|
||||
s.CreateInvoice(store.storeName);
|
||||
var request = await server.GetNextRequest();
|
||||
var headers = request.Request.Headers;
|
||||
var actualSig = headers["BTCPay-Sig"].First();
|
||||
var bytes = await request.Request.Body.ReadBytesAsync((int)headers.ContentLength.Value);
|
||||
var expectedSig = $"sha256={Encoders.Hex.EncodeData(new HMACSHA256(Encoding.UTF8.GetBytes("HelloWorld")).ComputeHash(bytes))}";
|
||||
Assert.Equal(expectedSig, actualSig);
|
||||
request.Response.StatusCode = 200;
|
||||
server.Done();
|
||||
|
||||
Logs.Tester.LogInformation("Let's make a failed event");
|
||||
s.CreateInvoice(store.storeName);
|
||||
request = await server.GetNextRequest();
|
||||
request.Response.StatusCode = 404;
|
||||
server.Done();
|
||||
|
||||
// The delivery is done asynchronously, so small wait here
|
||||
await Task.Delay(500);
|
||||
s.GoToStore(store.storeId, Views.Stores.StoreNavPages.Webhooks);
|
||||
s.Driver.FindElement(By.LinkText("Modify")).Click();
|
||||
var elements = s.Driver.FindElements(By.ClassName("redeliver"));
|
||||
// One worked, one failed
|
||||
s.Driver.FindElement(By.ClassName("fa-times"));
|
||||
s.Driver.FindElement(By.ClassName("fa-check"));
|
||||
elements[0].Click();
|
||||
s.AssertHappyMessage();
|
||||
request = await server.GetNextRequest();
|
||||
request.Response.StatusCode = 404;
|
||||
server.Done();
|
||||
|
||||
Logs.Tester.LogInformation("Can we browse the json content?");
|
||||
CanBrowseContent(s);
|
||||
|
||||
s.GoToInvoices();
|
||||
s.Driver.FindElement(By.LinkText("Details")).Click();
|
||||
CanBrowseContent(s);
|
||||
var element = s.Driver.FindElement(By.ClassName("redeliver"));
|
||||
element.Click();
|
||||
s.AssertHappyMessage();
|
||||
request = await server.GetNextRequest();
|
||||
request.Response.StatusCode = 404;
|
||||
server.Done();
|
||||
|
||||
Logs.Tester.LogInformation("Let's see if we can delete store with some webhooks inside");
|
||||
s.GoToStore(store.storeId);
|
||||
s.Driver.ExecuteJavaScript("window.scrollBy(0,1000);");
|
||||
s.Driver.FindElement(By.Id("danger-zone-expander")).Click();
|
||||
s.Driver.FindElement(By.Id("delete-store")).Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.AssertHappyMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private static void CanBrowseContent(SeleniumTester s)
|
||||
{
|
||||
s.Driver.FindElement(By.ClassName("delivery-content")).Click();
|
||||
var windows = s.Driver.WindowHandles;
|
||||
Assert.Equal(2, windows.Count);
|
||||
s.Driver.SwitchTo().Window(windows[1]);
|
||||
JObject.Parse(s.Driver.FindElement(By.TagName("body")).Text);
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(windows[0]);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanManageWallet()
|
||||
|
@ -23,6 +23,7 @@ namespace BTCPayServer.Tests
|
||||
return new ServerTester(scope, newDb);
|
||||
}
|
||||
|
||||
public List<IDisposable> Resources = new List<IDisposable>();
|
||||
readonly string _Directory;
|
||||
public ServerTester(string scope, bool newDb)
|
||||
{
|
||||
@ -145,7 +146,7 @@ namespace BTCPayServer.Tests
|
||||
var tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var sub = PayTester.GetService<EventAggregator>().Subscribe<T>(evt =>
|
||||
{
|
||||
if(correctEvent is null)
|
||||
if (correctEvent is null)
|
||||
tcs.TrySetResult(evt);
|
||||
else if (correctEvent(evt))
|
||||
{
|
||||
@ -207,6 +208,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var r in this.Resources)
|
||||
r.Dispose();
|
||||
Logs.Tester.LogInformation("Disposing the BTCPayTester...");
|
||||
foreach (var store in Stores)
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.ServerViewModels;
|
||||
@ -222,7 +223,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
//delete file
|
||||
Assert.IsType<RedirectToActionResult>(await controller.DeleteFile(fileId));
|
||||
controller.TempData.GetStatusMessageModel();
|
||||
statusMessageModel = controller.TempData.GetStatusMessageModel();
|
||||
Assert.NotNull(statusMessageModel);
|
||||
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Success, statusMessageModel.Severity);
|
||||
|
@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
@ -23,8 +25,10 @@ using NBitcoin.Payment;
|
||||
using NBitpayClient;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -427,5 +431,86 @@ namespace BTCPayServer.Tests
|
||||
return null;
|
||||
return parsedBip21;
|
||||
}
|
||||
|
||||
class WebhookListener : IDisposable
|
||||
{
|
||||
private Client.Models.StoreWebhookData _wh;
|
||||
private FakeServer _server;
|
||||
private readonly List<WebhookInvoiceEvent> _webhookEvents;
|
||||
private CancellationTokenSource _cts;
|
||||
public WebhookListener(Client.Models.StoreWebhookData wh, FakeServer server, List<WebhookInvoiceEvent> webhookEvents)
|
||||
{
|
||||
_wh = wh;
|
||||
_server = server;
|
||||
_webhookEvents = webhookEvents;
|
||||
_cts = new CancellationTokenSource();
|
||||
_ = Listen(_cts.Token);
|
||||
}
|
||||
|
||||
async Task Listen(CancellationToken cancellation)
|
||||
{
|
||||
while (!cancellation.IsCancellationRequested)
|
||||
{
|
||||
var req = await _server.GetNextRequest(cancellation);
|
||||
var bytes = await req.Request.Body.ReadBytesAsync((int)req.Request.Headers.ContentLength);
|
||||
var callback = Encoding.UTF8.GetString(bytes);
|
||||
_webhookEvents.Add(JsonConvert.DeserializeObject<WebhookInvoiceEvent>(callback));
|
||||
req.Response.StatusCode = 200;
|
||||
_server.Done();
|
||||
}
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
_cts.Cancel();
|
||||
_server.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public List<WebhookInvoiceEvent> WebhookEvents { get; set; } = new List<WebhookInvoiceEvent>();
|
||||
public TEvent AssertHasWebhookEvent<TEvent>(WebhookEventType eventType, Action<TEvent> assert) where TEvent : class
|
||||
{
|
||||
foreach (var evt in WebhookEvents)
|
||||
{
|
||||
if (evt.Type == eventType)
|
||||
{
|
||||
var typedEvt = evt.ReadAs<TEvent>();
|
||||
try
|
||||
{
|
||||
assert(typedEvt);
|
||||
return typedEvt;
|
||||
}
|
||||
catch (XunitException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Assert.True(false, "No webhook event match the assertion");
|
||||
return null;
|
||||
}
|
||||
public async Task SetupWebhook()
|
||||
{
|
||||
FakeServer server = new FakeServer();
|
||||
await server.Start();
|
||||
var client = await CreateClient(Policies.CanModifyStoreWebhooks);
|
||||
var wh = await client.CreateWebhook(StoreId, new CreateStoreWebhookRequest()
|
||||
{
|
||||
AutomaticRedelivery = false,
|
||||
Url = server.ServerUri.AbsoluteUri
|
||||
});
|
||||
|
||||
parent.Resources.Add(new WebhookListener(wh, server, WebhookEvents));
|
||||
}
|
||||
|
||||
public async Task PayInvoice(string invoiceId)
|
||||
{
|
||||
var inv = await BitPay.GetInvoiceAsync(invoiceId);
|
||||
var net = parent.ExplorerNode.Network;
|
||||
this.parent.ExplorerNode.SendToAddress(BitcoinAddress.Create(inv.BitcoinAddress, net), inv.BtcDue);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var localInvoice = await BitPay.GetInvoiceAsync(invoiceId, Facade.Merchant);
|
||||
Assert.Equal("paid", localInvoice.Status);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
@ -38,6 +39,7 @@ using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.U2F.Models;
|
||||
using BTCPayServer.Validation;
|
||||
using DBriize.Utils;
|
||||
using ExchangeSharp;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@ -232,6 +234,55 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(valid);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task EnsureSwaggerPermissionsDocumented()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var acc = tester.NewAccount();
|
||||
|
||||
var description =
|
||||
"BTCPay Server supports authenticating and authorizing users through an API Key that is generated by them. Send the API Key as a header value to Authorization with the format: `token {token}`. For a smoother experience, you can generate a url that redirects users to an API key creation screen.\n\n The following permissions are available to the context of the user creating the API Key:\n\n#OTHERPERMISSIONS#\n\nThe following permissions are available if the user is an administrator:\n\n#SERVERPERMISSIONS#\n\nThe following permissions applies to all stores of the user, you can limit to a specific store with the following format: `btcpay.store.cancreateinvoice:6HSHAEU4iYWtjxtyRs9KyPjM9GAQp8kw2T9VWbGG1FnZ`:\n\n#STOREPERMISSIONS#\n\nNote that API Keys only limits permission of a user and can never expand it. If an API Key has the permission `btcpay.server.canmodifyserversettings` but that the user account creating this API Key is not administrator, the API Key will not be able to modify the server settings.\n";
|
||||
|
||||
var storePolicies =
|
||||
ManageController.AddApiKeyViewModel.PermissionValueItem.PermissionDescriptions.Where(pair =>
|
||||
Policies.IsStorePolicy(pair.Key) && !pair.Key.EndsWith(":", StringComparison.InvariantCulture));
|
||||
var serverPolicies =
|
||||
ManageController.AddApiKeyViewModel.PermissionValueItem.PermissionDescriptions.Where(pair =>
|
||||
Policies.IsServerPolicy(pair.Key));
|
||||
var otherPolicies =
|
||||
ManageController.AddApiKeyViewModel.PermissionValueItem.PermissionDescriptions.Where(pair =>
|
||||
!Policies.IsStorePolicy(pair.Key) && !Policies.IsServerPolicy(pair.Key));
|
||||
|
||||
description = description.ReplaceMultiple(new Dictionary<string, string>()
|
||||
{
|
||||
{
|
||||
"#OTHERPERMISSIONS#",
|
||||
string.Join("\n", otherPolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}"))
|
||||
},
|
||||
{
|
||||
"#SERVERPERMISSIONS#",
|
||||
string.Join("\n", serverPolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}"))
|
||||
},
|
||||
{
|
||||
"#STOREPERMISSIONS#",
|
||||
string.Join("\n", storePolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}"))
|
||||
}
|
||||
});
|
||||
Logs.Tester.LogInformation(description);
|
||||
|
||||
var sresp = Assert
|
||||
.IsType<JsonResult>(await tester.PayTester.GetController<HomeController>(acc.UserId, acc.StoreId)
|
||||
.Swagger()).Value.ToJson();
|
||||
|
||||
JObject json = JObject.Parse(sresp);
|
||||
|
||||
Assert.Equal(description, json["components"]["securitySchemes"]["API Key"]["description"].Value<string>());
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task CheckLinks(Regex regex, HttpClient httpClient, string file)
|
||||
{
|
||||
@ -364,7 +415,7 @@ namespace BTCPayServer.Tests
|
||||
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
|
||||
{
|
||||
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null, null),
|
||||
});
|
||||
var entity = new InvoiceEntity();
|
||||
entity.Networks = networkProvider;
|
||||
@ -563,7 +614,7 @@ namespace BTCPayServer.Tests
|
||||
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
|
||||
{
|
||||
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null, null),
|
||||
});
|
||||
var entity = new InvoiceEntity();
|
||||
entity.Networks = networkProvider;
|
||||
@ -770,30 +821,31 @@ namespace BTCPayServer.Tests
|
||||
BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest), Money.Coins(0.00005m));
|
||||
});
|
||||
await tester.ExplorerNode.GenerateAsync(1);
|
||||
await Task.Delay(100); // wait a bit for payment to process before fetching new invoice
|
||||
var newInvoice = await user.BitPay.GetInvoiceAsync(invoice.Id);
|
||||
var newBolt11 = newInvoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11;
|
||||
var oldBolt11= invoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11;
|
||||
Assert.NotEqual(newBolt11,oldBolt11);
|
||||
var newBolt11 = newInvoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11;
|
||||
var oldBolt11 = invoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11;
|
||||
Assert.NotEqual(newBolt11, oldBolt11);
|
||||
Assert.Equal(newInvoice.BtcDue.GetValue(), BOLT11PaymentRequest.Parse(newBolt11, Network.RegTest).MinimumAmount.ToDecimal(LightMoneyUnit.BTC));
|
||||
|
||||
Logs.Tester.LogInformation($"Paying invoice {newInvoice.Id} remaining due amount {newInvoice.BtcDue.GetValue()} via lightning" );
|
||||
|
||||
Logs.Tester.LogInformation($"Paying invoice {newInvoice.Id} remaining due amount {newInvoice.BtcDue.GetValue()} via lightning");
|
||||
var evt = await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
|
||||
{
|
||||
await tester.SendLightningPaymentAsync(newInvoice);
|
||||
}, evt => evt.InvoiceId == invoice.Id);
|
||||
|
||||
var fetchedInvoice = await tester.PayTester.InvoiceRepository.GetInvoice(evt.InvoiceId);
|
||||
Assert.Contains(fetchedInvoice.Status, new []{InvoiceStatus.Complete, InvoiceStatus.Confirmed});
|
||||
Assert.Contains(fetchedInvoice.Status, new[] { InvoiceStatusLegacy.Complete, InvoiceStatusLegacy.Confirmed });
|
||||
Assert.Equal(InvoiceExceptionStatus.None, fetchedInvoice.ExceptionStatus);
|
||||
|
||||
Logs.Tester.LogInformation($"Paying invoice {invoice.Id} original full amount bolt11 invoice " );
|
||||
|
||||
Logs.Tester.LogInformation($"Paying invoice {invoice.Id} original full amount bolt11 invoice ");
|
||||
evt = await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
|
||||
{
|
||||
await tester.SendLightningPaymentAsync(invoice);
|
||||
}, evt => evt.InvoiceId == invoice.Id);
|
||||
Assert.Equal(evt.InvoiceId, invoice.Id);
|
||||
fetchedInvoice = await tester.PayTester.InvoiceRepository.GetInvoice(evt.InvoiceId);
|
||||
Assert.Equal( 3,fetchedInvoice.Payments.Count);
|
||||
Assert.Equal(3, fetchedInvoice.Payments.Count);
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
@ -999,7 +1051,6 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var invoice2 = acc.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.NotNull(invoice2);
|
||||
}
|
||||
@ -1089,7 +1140,7 @@ namespace BTCPayServer.Tests
|
||||
response.EnsureSuccessStatusCode();
|
||||
AssertConnectionDropped();
|
||||
|
||||
Logs.Tester.LogInformation("Querying an onion address which can't be found should send http 500");
|
||||
Logs.Tester.LogInformation("Querying an onin address which can't be found should send http 500");
|
||||
response = await client.GetAsync("http://dwoduwoi.onion/");
|
||||
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
|
||||
AssertConnectionDropped();
|
||||
@ -1459,7 +1510,7 @@ namespace BTCPayServer.Tests
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var i = await tester.PayTester.InvoiceRepository.GetInvoice(invoice2.Id);
|
||||
Assert.Equal(InvoiceStatus.New, i.Status);
|
||||
Assert.Equal(InvoiceStatusLegacy.New, i.Status);
|
||||
Assert.Single(i.GetPayments());
|
||||
Assert.False(i.GetPayments().First().Accounted);
|
||||
});
|
||||
@ -1508,7 +1559,7 @@ namespace BTCPayServer.Tests
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// [Fact(Timeout = TestTimeout)]
|
||||
[Fact()]
|
||||
[Trait("Integration", "Integration")]
|
||||
@ -1527,9 +1578,9 @@ namespace BTCPayServer.Tests
|
||||
BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest),
|
||||
Money.Coins(0.01m));
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var payments = Assert.IsType<InvoiceDetailsModel>(
|
||||
Assert.IsType<ViewResult>(await user.GetController<InvoiceController>().Invoice(invoice.Id)).Model)
|
||||
.Payments;
|
||||
@ -1990,7 +2041,64 @@ namespace BTCPayServer.Tests
|
||||
};
|
||||
var criteriaCompat = store.GetPaymentMethodCriteria(tester.NetworkProvider, blob);
|
||||
Assert.Single(criteriaCompat);
|
||||
Assert.NotNull(criteriaCompat.FirstOrDefault(methodCriteria => methodCriteria.Value.ToString() == "2 USD" && methodCriteria.Above && methodCriteria.PaymentMethod == new PaymentMethodId("BTC", BitcoinPaymentType.Instance) ));
|
||||
Assert.NotNull(criteriaCompat.FirstOrDefault(methodCriteria => methodCriteria.Value.ToString() == "2 USD" && methodCriteria.Above && methodCriteria.PaymentMethod == new PaymentMethodId("BTC", BitcoinPaymentType.Instance)));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanSetUnifiedQrCode()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.ActivateLightning();
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit);
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.CLightning);
|
||||
|
||||
var invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
{
|
||||
Price = 5.5m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
// validate that invoice data model doesn't have lightning string initially
|
||||
var res = await user.GetController<InvoiceController>().Checkout(invoice.Id);
|
||||
var paymentMethodFirst = Assert.IsType<PaymentModel>(
|
||||
Assert.IsType<ViewResult>(res).Model
|
||||
);
|
||||
Assert.DoesNotContain("&lightning=", paymentMethodFirst.InvoiceBitcoinUrlQR);
|
||||
|
||||
// enable unified QR code in settings
|
||||
var vm = Assert.IsType<CheckoutExperienceViewModel>(Assert
|
||||
.IsType<ViewResult>(user.GetController<StoresController>().CheckoutExperience()).Model
|
||||
);
|
||||
vm.OnChainWithLnInvoiceFallback = true;
|
||||
Assert.IsType<RedirectToActionResult>(
|
||||
user.GetController<StoresController>().CheckoutExperience(vm).Result
|
||||
);
|
||||
|
||||
// validate that QR code now has both onchain and offchain payment urls
|
||||
res = await user.GetController<InvoiceController>().Checkout(invoice.Id);
|
||||
var paymentMethodSecond = Assert.IsType<PaymentModel>(
|
||||
Assert.IsType<ViewResult>(res).Model
|
||||
);
|
||||
Assert.Contains("&lightning=", paymentMethodSecond.InvoiceBitcoinUrlQR);
|
||||
Assert.StartsWith("bitcoin:", paymentMethodSecond.InvoiceBitcoinUrlQR);
|
||||
var split = paymentMethodSecond.InvoiceBitcoinUrlQR.Split('?')[0];
|
||||
|
||||
// Standard for uppercase Bech32 addresses in QR codes is still not implemented in all wallets
|
||||
// When it is widely propagated consider uncommenting these lines
|
||||
//Assert.True($"BITCOIN:{paymentMethodSecond.BtcAddress.ToUpperInvariant()}" == split);
|
||||
Assert.True($"bitcoin:{paymentMethodSecond.BtcAddress}" == split);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2030,7 +2138,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
Assert.Single(invoice.CryptoInfo);
|
||||
Assert.Equal(PaymentTypes.LightningLike.ToString(), invoice.CryptoInfo[0].PaymentType);
|
||||
|
||||
|
||||
//test backward compat
|
||||
var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
|
||||
var blob = store.GetStoreBlob();
|
||||
@ -2044,8 +2152,8 @@ namespace BTCPayServer.Tests
|
||||
};
|
||||
var criteriaCompat = store.GetPaymentMethodCriteria(tester.NetworkProvider, blob);
|
||||
Assert.Single(criteriaCompat);
|
||||
Assert.NotNull(criteriaCompat.FirstOrDefault(methodCriteria => methodCriteria.Value.ToString() == "2 USD" && !methodCriteria.Above && methodCriteria.PaymentMethod == new PaymentMethodId("BTC", LightningPaymentType.Instance) ));
|
||||
|
||||
Assert.NotNull(criteriaCompat.FirstOrDefault(methodCriteria => methodCriteria.Value.ToString() == "2 USD" && !methodCriteria.Above && methodCriteria.PaymentMethod == new PaymentMethodId("BTC", LightningPaymentType.Instance)));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -2527,6 +2635,7 @@ namespace BTCPayServer.Tests
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
await user.SetupWebhook();
|
||||
var invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
{
|
||||
@ -2583,7 +2692,6 @@ namespace BTCPayServer.Tests
|
||||
var cashCow = tester.ExplorerNode;
|
||||
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||
var iii = ctx.AddressInvoices.ToArray();
|
||||
Assert.True(IsMapped(invoice, ctx));
|
||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||
|
||||
@ -2687,6 +2795,23 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
||||
Assert.Equal("paidOver", (string)((JValue)localInvoice.ExceptionStatus).Value);
|
||||
});
|
||||
|
||||
// Test on the webhooks
|
||||
user.AssertHasWebhookEvent<WebhookInvoiceSettledEvent>(WebhookEventType.InvoiceSettled,
|
||||
c =>
|
||||
{
|
||||
Assert.False(c.ManuallyMarked);
|
||||
});
|
||||
user.AssertHasWebhookEvent<WebhookInvoiceProcessingEvent>(WebhookEventType.InvoiceProcessing,
|
||||
c =>
|
||||
{
|
||||
Assert.True(c.OverPaid);
|
||||
});
|
||||
user.AssertHasWebhookEvent<WebhookInvoiceReceivedPaymentEvent>(WebhookEventType.InvoiceReceivedPayment,
|
||||
c =>
|
||||
{
|
||||
Assert.False(c.AfterExpiration);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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" />
|
||||
@ -251,5 +247,5 @@
|
||||
<_ContentIncludedByDefault Remove="Views\Components\NotificationsDropdown\Default.cshtml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ProjectExtensions><VisualStudio><UserProperties wwwroot_4swagger_4v1_4swagger_1template_1invoices_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1pull-payments_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1serverinfo_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1stores_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" /></VisualStudio></ProjectExtensions>
|
||||
<ProjectExtensions><VisualStudio><UserProperties wwwroot_4swagger_4v1_4swagger_1template_1invoices_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1pull-payments_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1serverinfo_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1stores_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1users_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1webhooks_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" /></VisualStudio></ProjectExtensions>
|
||||
</Project>
|
||||
|
@ -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,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
|
||||
|
@ -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,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
@ -212,8 +213,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 +287,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
CreatedTime = entity.InvoiceTime,
|
||||
Amount = entity.Price,
|
||||
Id = entity.Id,
|
||||
Status = entity.Status,
|
||||
Status = entity.Status.ToModernStatus(),
|
||||
AdditionalStatus = entity.ExceptionStatus,
|
||||
Currency = entity.Currency,
|
||||
Metadata = entity.Metadata.ToJObject(),
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
|
@ -139,7 +139,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
ModelState.AddModelError(nameof(request.FeeRate), "FeeRate must be more than 0");
|
||||
}
|
||||
|
||||
if (ModelState.IsValid)
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
@ -263,13 +263,21 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
var invoice = await lightningClient.CreateInvoice(
|
||||
new CreateInvoiceParams(request.Amount, request.Description, request.Expiry)
|
||||
{
|
||||
PrivateRouteHints = request.PrivateRouteHints
|
||||
},
|
||||
CancellationToken.None);
|
||||
return Ok(ToModel(invoice));
|
||||
|
||||
try
|
||||
{
|
||||
var invoice = await lightningClient.CreateInvoice(
|
||||
new CreateInvoiceParams(request.Amount, request.Description, request.Expiry)
|
||||
{
|
||||
PrivateRouteHints = request.PrivateRouteHints
|
||||
},
|
||||
CancellationToken.None);
|
||||
return Ok(ToModel(invoice));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return this.CreateAPIError("generic-error", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private LightningInvoiceData ToModel(LightningInvoice invoice)
|
||||
|
@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
|
@ -3,6 +3,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
|
198
BTCPayServer/Controllers/GreenField/StoreWebhooksController.cs
Normal file
198
BTCPayServer/Controllers/GreenField/StoreWebhooksController.cs
Normal file
@ -0,0 +1,198 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Amazon.Runtime;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Google.Apis.Auth.OAuth2;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield,
|
||||
Policy = Policies.CanModifyStoreWebhooks)]
|
||||
[EnableCors(CorsPolicies.All)]
|
||||
public class StoreWebhooksController : ControllerBase
|
||||
{
|
||||
public StoreWebhooksController(StoreRepository storeRepository, WebhookNotificationManager webhookNotificationManager)
|
||||
{
|
||||
StoreRepository = storeRepository;
|
||||
WebhookNotificationManager = webhookNotificationManager;
|
||||
}
|
||||
|
||||
public StoreRepository StoreRepository { get; }
|
||||
public WebhookNotificationManager WebhookNotificationManager { get; }
|
||||
|
||||
[HttpGet("~/api/v1/stores/{storeId}/webhooks/{webhookId?}")]
|
||||
public async Task<IActionResult> ListWebhooks(string storeId, string webhookId)
|
||||
{
|
||||
if (webhookId is null)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
return Ok((await StoreRepository.GetWebhooks(storeId))
|
||||
.Select(o => FromModel(o, false))
|
||||
.ToList());
|
||||
}
|
||||
else
|
||||
{
|
||||
var w = await StoreRepository.GetWebhook(storeId, webhookId);
|
||||
if (w is null)
|
||||
return NotFound();
|
||||
return Ok(FromModel(w, false));
|
||||
}
|
||||
}
|
||||
[HttpPost("~/api/v1/stores/{storeId}/webhooks")]
|
||||
public async Task<IActionResult> CreateWebhook(string storeId, Client.Models.CreateStoreWebhookRequest create)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
ValidateWebhookRequest(create);
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
var webhookId = await StoreRepository.CreateWebhook(storeId, ToModel(create));
|
||||
var w = await StoreRepository.GetWebhook(storeId, webhookId);
|
||||
if (w is null)
|
||||
return NotFound();
|
||||
return Ok(FromModel(w, true));
|
||||
}
|
||||
|
||||
private void ValidateWebhookRequest(StoreWebhookBaseData create)
|
||||
{
|
||||
if (!Uri.TryCreate(create?.Url, UriKind.Absolute, out var uri))
|
||||
ModelState.AddModelError(nameof(Url), "Invalid Url");
|
||||
}
|
||||
|
||||
[HttpPut("~/api/v1/stores/{storeId}/webhooks/{webhookId}")]
|
||||
public async Task<IActionResult> UpdateWebhook(string storeId, string webhookId, Client.Models.UpdateStoreWebhookRequest update)
|
||||
{
|
||||
ValidateWebhookRequest(update);
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
var w = await StoreRepository.GetWebhook(storeId, webhookId);
|
||||
if (w is null)
|
||||
return NotFound();
|
||||
await StoreRepository.UpdateWebhook(storeId, webhookId, ToModel(update));
|
||||
return await ListWebhooks(storeId, webhookId);
|
||||
}
|
||||
[HttpDelete("~/api/v1/stores/{storeId}/webhooks/{webhookId}")]
|
||||
public async Task<IActionResult> DeleteWebhook(string storeId, string webhookId)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
var w = await StoreRepository.GetWebhook(storeId, webhookId);
|
||||
if (w is null)
|
||||
return NotFound();
|
||||
await StoreRepository.DeleteWebhook(storeId, webhookId);
|
||||
return Ok();
|
||||
}
|
||||
private WebhookBlob ToModel(StoreWebhookBaseData create)
|
||||
{
|
||||
return new WebhookBlob()
|
||||
{
|
||||
Active = create.Enabled,
|
||||
Url = create.Url,
|
||||
Secret = create.Secret,
|
||||
AuthorizedEvents = create.AuthorizedEvents is Client.Models.StoreWebhookBaseData.AuthorizedEventsData aed ?
|
||||
new AuthorizedWebhookEvents()
|
||||
{
|
||||
Everything = aed.Everything,
|
||||
SpecificEvents = aed.SpecificEvents
|
||||
}:
|
||||
new AuthorizedWebhookEvents() { Everything = true },
|
||||
AutomaticRedelivery = create.AutomaticRedelivery,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("~/api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId?}")]
|
||||
public async Task<IActionResult> ListDeliveries(string storeId, string webhookId, string deliveryId, int? count = null)
|
||||
{
|
||||
if (deliveryId is null)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
return Ok((await StoreRepository.GetWebhookDeliveries(storeId, webhookId, count))
|
||||
.Select(o => FromModel(o))
|
||||
.ToList());
|
||||
}
|
||||
else
|
||||
{
|
||||
var delivery = await StoreRepository.GetWebhookDelivery(storeId, webhookId, deliveryId);
|
||||
if (delivery is null)
|
||||
return NotFound();
|
||||
return Ok(FromModel(delivery));
|
||||
}
|
||||
}
|
||||
[HttpPost("~/api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver")]
|
||||
public async Task<IActionResult> RedeliverWebhook(string storeId, string webhookId, string deliveryId)
|
||||
{
|
||||
var delivery = await StoreRepository.GetWebhookDelivery(HttpContext.GetStoreData().Id, webhookId, deliveryId);
|
||||
if (delivery is null)
|
||||
return NotFound();
|
||||
return this.Ok(new JValue(await WebhookNotificationManager.Redeliver(deliveryId)));
|
||||
}
|
||||
|
||||
[HttpGet("~/api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/request")]
|
||||
public async Task<IActionResult> GetDeliveryRequest(string storeId, string webhookId, string deliveryId)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
var delivery = await StoreRepository.GetWebhookDelivery(storeId, webhookId, deliveryId);
|
||||
if (delivery is null)
|
||||
return NotFound();
|
||||
return File(delivery.GetBlob().Request, "application/json");
|
||||
}
|
||||
|
||||
private Client.Models.WebhookDeliveryData FromModel(Data.WebhookDeliveryData data)
|
||||
{
|
||||
var b = data.GetBlob();
|
||||
return new Client.Models.WebhookDeliveryData()
|
||||
{
|
||||
Id = data.Id,
|
||||
Timestamp = data.Timestamp,
|
||||
Status = b.Status,
|
||||
ErrorMessage = b.ErrorMessage,
|
||||
HttpCode = b.HttpCode
|
||||
};
|
||||
}
|
||||
|
||||
Client.Models.StoreWebhookData FromModel(Data.WebhookData data, bool includeSecret)
|
||||
{
|
||||
var b = data.GetBlob();
|
||||
return new Client.Models.StoreWebhookData()
|
||||
{
|
||||
Id = data.Id,
|
||||
Url = b.Url,
|
||||
Enabled = b.Active,
|
||||
Secret = includeSecret ? b.Secret : null,
|
||||
AutomaticRedelivery = b.AutomaticRedelivery,
|
||||
AuthorizedEvents = new Client.Models.StoreWebhookData.AuthorizedEventsData()
|
||||
{
|
||||
Everything = b.AuthorizedEvents.Everything,
|
||||
SpecificEvents = b.AuthorizedEvents.SpecificEvents
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
@ -120,21 +121,22 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
//we do not include OnChainMinValue and LightningMaxValue because moving the CurrencyValueJsonConverter to the Client csproj is hard and requires a refactor (#1571 & #1572)
|
||||
NetworkFeeMode = storeBlob.NetworkFeeMode,
|
||||
RequiresRefundEmail = storeBlob.RequiresRefundEmail,
|
||||
LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi,
|
||||
LightningPrivateRouteHints = storeBlob.LightningPrivateRouteHints,
|
||||
OnChainWithLnInvoiceFallback = storeBlob.OnChainWithLnInvoiceFallback,
|
||||
RedirectAutomatically = storeBlob.RedirectAutomatically,
|
||||
ShowRecommendedFee = storeBlob.ShowRecommendedFee,
|
||||
RecommendedFeeBlockTarget = storeBlob.RecommendedFeeBlockTarget,
|
||||
DefaultLang = storeBlob.DefaultLang,
|
||||
MonitoringExpiration = storeBlob.MonitoringExpiration,
|
||||
InvoiceExpiration = storeBlob.InvoiceExpiration,
|
||||
LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi,
|
||||
CustomLogo = storeBlob.CustomLogo,
|
||||
CustomCSS = storeBlob.CustomCSS,
|
||||
HtmlTitle = storeBlob.HtmlTitle,
|
||||
AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice,
|
||||
LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate,
|
||||
PaymentTolerance = storeBlob.PaymentTolerance,
|
||||
RedirectAutomatically = storeBlob.RedirectAutomatically,
|
||||
PayJoinEnabled = storeBlob.PayJoinEnabled,
|
||||
LightningPrivateRouteHints = storeBlob.LightningPrivateRouteHints
|
||||
PayJoinEnabled = storeBlob.PayJoinEnabled
|
||||
};
|
||||
}
|
||||
|
||||
@ -155,21 +157,22 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
//we do not include OnChainMinValue and LightningMaxValue because moving the CurrencyValueJsonConverter to the Client csproj is hard and requires a refactor (#1571 & #1572)
|
||||
blob.NetworkFeeMode = restModel.NetworkFeeMode;
|
||||
blob.RequiresRefundEmail = restModel.RequiresRefundEmail;
|
||||
blob.LightningAmountInSatoshi = restModel.LightningAmountInSatoshi;
|
||||
blob.LightningPrivateRouteHints = restModel.LightningPrivateRouteHints;
|
||||
blob.OnChainWithLnInvoiceFallback = restModel.OnChainWithLnInvoiceFallback;
|
||||
blob.RedirectAutomatically = restModel.RedirectAutomatically;
|
||||
blob.ShowRecommendedFee = restModel.ShowRecommendedFee;
|
||||
blob.RecommendedFeeBlockTarget = restModel.RecommendedFeeBlockTarget;
|
||||
blob.DefaultLang = restModel.DefaultLang;
|
||||
blob.MonitoringExpiration = restModel.MonitoringExpiration;
|
||||
blob.InvoiceExpiration = restModel.InvoiceExpiration;
|
||||
blob.LightningAmountInSatoshi = restModel.LightningAmountInSatoshi;
|
||||
blob.CustomLogo = restModel.CustomLogo;
|
||||
blob.CustomCSS = restModel.CustomCSS;
|
||||
blob.HtmlTitle = restModel.HtmlTitle;
|
||||
blob.AnyoneCanInvoice = restModel.AnyoneCanCreateInvoice;
|
||||
blob.LightningDescriptionTemplate = restModel.LightningDescriptionTemplate;
|
||||
blob.PaymentTolerance = restModel.PaymentTolerance;
|
||||
blob.RedirectAutomatically = restModel.RedirectAutomatically;
|
||||
blob.PayJoinEnabled = restModel.PayJoinEnabled;
|
||||
blob.LightningPrivateRouteHints = restModel.LightningPrivateRouteHints;
|
||||
model.SetStoreBlob(blob);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Security;
|
||||
|
@ -2,6 +2,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
|
@ -5,6 +5,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Models;
|
||||
|
@ -2,6 +2,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Models;
|
||||
@ -51,7 +52,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("invoices")]
|
||||
public async Task<DataWrapper<InvoiceResponse[]>> GetInvoices(
|
||||
public async Task<IActionResult> GetInvoices(
|
||||
string token,
|
||||
DateTimeOffset? dateStart = null,
|
||||
DateTimeOffset? dateEnd = null,
|
||||
@ -61,6 +62,8 @@ namespace BTCPayServer.Controllers
|
||||
int? limit = null,
|
||||
int? offset = null)
|
||||
{
|
||||
if (User.Identity.AuthenticationType == Security.Bitpay.BitpayAuthenticationTypes.Anonymous)
|
||||
return Forbid(Security.Bitpay.BitpayAuthenticationTypes.Anonymous);
|
||||
if (dateEnd != null)
|
||||
dateEnd = dateEnd.Value + TimeSpan.FromDays(1); //Should include the end day
|
||||
|
||||
@ -79,7 +82,7 @@ namespace BTCPayServer.Controllers
|
||||
var entities = (await _InvoiceRepository.GetInvoices(query))
|
||||
.Select((o) => o.EntityToDTO()).ToArray();
|
||||
|
||||
return DataWrapper.Create(entities);
|
||||
return Json(DataWrapper.Create(entities));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,9 @@ using System.Net.Mime;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
@ -39,6 +42,51 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class InvoiceController
|
||||
{
|
||||
|
||||
[HttpGet]
|
||||
[Route("invoices/{invoiceId}/deliveries/{deliveryId}/request")]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> WebhookDelivery(string invoiceId, string deliveryId)
|
||||
{
|
||||
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
InvoiceId = new[] { invoiceId },
|
||||
UserId = GetUserId()
|
||||
})).FirstOrDefault();
|
||||
if (invoice is null)
|
||||
return NotFound();
|
||||
var delivery = await _InvoiceRepository.GetWebhookDelivery(invoiceId, deliveryId);
|
||||
if (delivery is null)
|
||||
return NotFound();
|
||||
return this.File(delivery.GetBlob().Request, "application/json");
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("invoices/{invoiceId}/deliveries/{deliveryId}/redeliver")]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> RedeliverWebhook(string storeId, string invoiceId, string deliveryId)
|
||||
{
|
||||
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
InvoiceId = new[] { invoiceId },
|
||||
StoreId = new[] { storeId },
|
||||
UserId = GetUserId()
|
||||
})).FirstOrDefault();
|
||||
if (invoice is null)
|
||||
return NotFound();
|
||||
var delivery = await _InvoiceRepository.GetWebhookDelivery(invoiceId, deliveryId);
|
||||
if (delivery is null)
|
||||
return NotFound();
|
||||
var newDeliveryId = await WebhookNotificationManager.Redeliver(deliveryId);
|
||||
if (newDeliveryId is null)
|
||||
return NotFound();
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Successfully planned a redelivery";
|
||||
return RedirectToAction(nameof(Invoice),
|
||||
new
|
||||
{
|
||||
invoiceId
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("invoices/{invoiceId}")]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
@ -58,6 +106,7 @@ namespace BTCPayServer.Controllers
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
var model = new InvoiceDetailsModel()
|
||||
{
|
||||
StoreId = store.Id,
|
||||
StoreName = store.StoreName,
|
||||
StoreLink = Url.Action(nameof(StoresController.UpdateStore), "Stores", new { storeId = store.Id }),
|
||||
Id = invoice.Id,
|
||||
@ -80,6 +129,9 @@ namespace BTCPayServer.Controllers
|
||||
PosData = PosDataParser.ParsePosData(invoice.Metadata.PosData),
|
||||
Archived = invoice.Archived,
|
||||
CanRefund = CanRefund(invoice.GetInvoiceState()),
|
||||
Deliveries = (await _InvoiceRepository.GetWebhookDeliveries(invoiceId))
|
||||
.Select(c => new Models.StoreViewModels.DeliveryViewModel(c))
|
||||
.ToList()
|
||||
};
|
||||
model.Addresses = invoice.HistoricalAddresses.Select(h =>
|
||||
new InvoiceDetailsModel.AddressModel
|
||||
@ -98,13 +150,13 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
bool CanRefund(InvoiceState invoiceState)
|
||||
{
|
||||
return invoiceState.Status == InvoiceStatus.Confirmed ||
|
||||
invoiceState.Status == InvoiceStatus.Complete ||
|
||||
(invoiceState.Status == InvoiceStatus.Expired &&
|
||||
return invoiceState.Status == InvoiceStatusLegacy.Confirmed ||
|
||||
invoiceState.Status == InvoiceStatusLegacy.Complete ||
|
||||
(invoiceState.Status == InvoiceStatusLegacy.Expired &&
|
||||
(invoiceState.ExceptionStatus == InvoiceExceptionStatus.PaidLate ||
|
||||
invoiceState.ExceptionStatus == InvoiceExceptionStatus.PaidOver ||
|
||||
invoiceState.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)) ||
|
||||
invoiceState.Status == InvoiceStatus.Invalid;
|
||||
invoiceState.Status == InvoiceStatusLegacy.Invalid;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@ -464,12 +516,9 @@ namespace BTCPayServer.Controllers
|
||||
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
|
||||
var paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
|
||||
var dto = invoice.EntityToDTO();
|
||||
var cryptoInfo = dto.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId);
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var currency = invoice.Currency;
|
||||
var accounting = paymentMethod.Calculate();
|
||||
|
||||
|
||||
CoinSwitchSettings coinswitch = (storeBlob.CoinSwitchSettings != null && storeBlob.CoinSwitchSettings.Enabled &&
|
||||
storeBlob.CoinSwitchSettings.IsConfigured())
|
||||
? storeBlob.CoinSwitchSettings
|
||||
@ -492,12 +541,11 @@ namespace BTCPayServer.Controllers
|
||||
CryptoImage = Request.GetRelativePathOrAbsolute(paymentMethodHandler.GetCryptoImage(paymentMethodId)),
|
||||
BtcAddress = paymentMethodDetails.GetPaymentDestination(),
|
||||
BtcDue = accounting.Due.ShowMoney(divisibility),
|
||||
InvoiceCurrency = invoice.Currency,
|
||||
OrderAmount = (accounting.TotalDue - accounting.NetworkFee).ShowMoney(divisibility),
|
||||
OrderAmountFiat = OrderAmountFromInvoice(network.CryptoCode, invoice),
|
||||
CustomerEmail = invoice.RefundMail,
|
||||
RequiresRefundEmail = storeBlob.RequiresRefundEmail,
|
||||
ShowRecommendedFee = storeBlob.ShowRecommendedFee,
|
||||
FeeRate = paymentMethodDetails.GetFeeRate(),
|
||||
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
|
||||
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
|
||||
MaxTimeMinutes = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalMinutes,
|
||||
@ -506,7 +554,6 @@ namespace BTCPayServer.Controllers
|
||||
MerchantRefLink = invoice.RedirectURL?.AbsoluteUri ?? "/",
|
||||
RedirectAutomatically = invoice.RedirectAutomatically,
|
||||
StoreName = store.StoreName,
|
||||
PeerInfo = (paymentMethodDetails as LightningLikePaymentMethodDetails)?.NodeInfo,
|
||||
TxCount = accounting.TxRequired,
|
||||
BtcPaid = accounting.Paid.ShowMoney(divisibility),
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
@ -544,12 +591,7 @@ namespace BTCPayServer.Controllers
|
||||
.OrderByDescending(a => a.CryptoCode == "BTC").ThenBy(a => a.PaymentMethodName).ThenBy(a => a.IsLightning ? 1 : 0)
|
||||
.ToList()
|
||||
};
|
||||
|
||||
paymentMethodHandler.PreparePaymentModel(model, dto, storeBlob);
|
||||
if (model.IsLightning && storeBlob.LightningAmountInSatoshi && model.CryptoCode == "Sats")
|
||||
{
|
||||
model.Rate = _CurrencyNameTable.DisplayFormatCurrency(paymentMethod.Rate / 100_000_000, paymentMethod.ParentEntity.Currency);
|
||||
}
|
||||
paymentMethodHandler.PreparePaymentModel(model, dto, storeBlob, paymentMethod);
|
||||
model.UISettings = paymentMethodHandler.GetCheckoutUISettings();
|
||||
model.PaymentMethodId = paymentMethodId.ToString();
|
||||
var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds);
|
||||
@ -596,7 +638,7 @@ namespace BTCPayServer.Controllers
|
||||
if (!HttpContext.WebSockets.IsWebSocketRequest)
|
||||
return NotFound();
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
||||
if (invoice == null || invoice.Status == InvoiceStatus.Complete || invoice.Status == InvoiceStatus.Invalid || invoice.Status == InvoiceStatus.Expired)
|
||||
if (invoice == null || invoice.Status == InvoiceStatusLegacy.Complete || invoice.Status == InvoiceStatusLegacy.Invalid || invoice.Status == InvoiceStatusLegacy.Expired)
|
||||
return NotFound();
|
||||
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
||||
CompositeDisposable leases = new CompositeDisposable();
|
||||
@ -673,7 +715,7 @@ namespace BTCPayServer.Controllers
|
||||
model.Invoices.Add(new InvoiceModel()
|
||||
{
|
||||
Status = state,
|
||||
ShowCheckout = invoice.Status == InvoiceStatus.New,
|
||||
ShowCheckout = invoice.Status == InvoiceStatusLegacy.New,
|
||||
Date = invoice.InvoiceTime,
|
||||
InvoiceId = invoice.Id,
|
||||
OrderId = invoice.Metadata.OrderId ?? string.Empty,
|
||||
@ -831,7 +873,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
else if (newState == "complete")
|
||||
{
|
||||
await _InvoiceRepository.MarkInvoiceStatus(invoiceId, InvoiceStatus.Complete);
|
||||
await _InvoiceRepository.MarkInvoiceStatus(invoiceId, InvoiceStatus.Settled);
|
||||
model.StatusString = new InvoiceState("complete", "marked").ToString();
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,9 @@ namespace BTCPayServer.Controllers
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
private readonly PullPaymentHostedService _paymentHostedService;
|
||||
readonly IServiceProvider _ServiceProvider;
|
||||
|
||||
public WebhookNotificationManager WebhookNotificationManager { get; }
|
||||
|
||||
public InvoiceController(
|
||||
IServiceProvider serviceProvider,
|
||||
InvoiceRepository invoiceRepository,
|
||||
@ -57,7 +60,8 @@ namespace BTCPayServer.Controllers
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
|
||||
ApplicationDbContextFactory dbContextFactory,
|
||||
PullPaymentHostedService paymentHostedService)
|
||||
PullPaymentHostedService paymentHostedService,
|
||||
WebhookNotificationManager webhookNotificationManager)
|
||||
{
|
||||
_ServiceProvider = serviceProvider;
|
||||
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
||||
@ -70,6 +74,7 @@ namespace BTCPayServer.Controllers
|
||||
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_paymentHostedService = paymentHostedService;
|
||||
WebhookNotificationManager = webhookNotificationManager;
|
||||
_CSP = csp;
|
||||
}
|
||||
|
||||
@ -195,7 +200,7 @@ namespace BTCPayServer.Controllers
|
||||
throw new BitpayHttpException(400, "Invalid email");
|
||||
entity.RefundMail = entity.Metadata.BuyerEmail;
|
||||
}
|
||||
entity.Status = InvoiceStatus.New;
|
||||
entity.Status = InvoiceStatusLegacy.New;
|
||||
HashSet<CurrencyPair> currencyPairsToFetch = new HashSet<CurrencyPair>();
|
||||
var rules = storeBlob.GetRateRules(_NetworkProvider);
|
||||
var excludeFilter = storeBlob.GetExcludedPaymentMethods(); // Here we can compose filters from other origin with PaymentFilter.Any()
|
||||
@ -215,7 +220,7 @@ namespace BTCPayServer.Controllers
|
||||
if (paymentMethodCriteria.Value != null)
|
||||
{
|
||||
currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, paymentMethodCriteria.Value.Currency));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -305,7 +310,9 @@ namespace BTCPayServer.Controllers
|
||||
}).ToArray());
|
||||
}
|
||||
|
||||
private async Task<PaymentMethod> CreatePaymentMethodAsync(Dictionary<CurrencyPair, Task<RateResult>> fetchingByCurrencyPair, IPaymentMethodHandler handler, ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetworkBase network, InvoiceEntity entity, StoreData store, InvoiceLogs logs)
|
||||
private async Task<PaymentMethod> CreatePaymentMethodAsync(Dictionary<CurrencyPair, Task<RateResult>> fetchingByCurrencyPair,
|
||||
IPaymentMethodHandler handler, ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetworkBase network, InvoiceEntity entity,
|
||||
StoreData store, InvoiceLogs logs)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -317,12 +324,14 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
return null;
|
||||
}
|
||||
PaymentMethod paymentMethod = new PaymentMethod();
|
||||
paymentMethod.ParentEntity = entity;
|
||||
paymentMethod.Network = network;
|
||||
var paymentMethod = new PaymentMethod
|
||||
{
|
||||
ParentEntity = entity,
|
||||
Network = network,
|
||||
Rate = rate.BidAsk.Bid,
|
||||
PreferOnion = Uri.TryCreate(entity.ServerUrl, UriKind.Absolute, out var u) && u.DnsSafeHost.EndsWith(".onion", StringComparison.OrdinalIgnoreCase)
|
||||
};
|
||||
paymentMethod.SetId(supportedPaymentMethod.PaymentId);
|
||||
paymentMethod.Rate = rate.BidAsk.Bid;
|
||||
paymentMethod.PreferOnion = Uri.TryCreate(entity.ServerUrl, UriKind.Absolute, out var u) && u.DnsSafeHost.EndsWith(".onion", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
using (logs.Measure($"{logPrefix} Payment method details creation"))
|
||||
{
|
||||
@ -339,7 +348,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var amount = paymentMethod.Calculate().Due.GetValue(network as BTCPayNetwork);
|
||||
var limitValueCrypto = criteria.Value.Value / currentRateToCrypto.BidAsk.Bid;
|
||||
|
||||
|
||||
if (amount < limitValueCrypto && criteria.Above)
|
||||
{
|
||||
logs.Write($"{logPrefix} invoice amount below accepted value for payment method", InvoiceEventData.EventSeverity.Error);
|
||||
@ -369,7 +378,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logs.Write($"{supportedPaymentMethod.PaymentId.CryptoCode}: Unexpected exception ({ex.ToString()})", InvoiceEventData.EventSeverity.Error);
|
||||
logs.Write($"{supportedPaymentMethod.PaymentId.CryptoCode}: Unexpected exception ({ex})", InvoiceEventData.EventSeverity.Error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
@ -465,6 +467,8 @@ namespace BTCPayServer.Controllers
|
||||
{BTCPayServer.Client.Policies.CanCreateUser, ("Create new users", "The app will be able to create new users on this server.")},
|
||||
{BTCPayServer.Client.Policies.CanModifyStoreSettings, ("Modify your stores", "The app will be able to view, modify, delete and create new invoices on all your stores.")},
|
||||
{$"{BTCPayServer.Client.Policies.CanModifyStoreSettings}:", ("Manage selected stores", "The app will be able to view, modify, delete and create new invoices on the selected stores.")},
|
||||
{BTCPayServer.Client.Policies.CanModifyStoreWebhooks, ("Modify stores webhooks", "The app will be mofidy the webhooks of all your stores.")},
|
||||
{$"{BTCPayServer.Client.Policies.CanModifyStoreWebhooks}:", ("Modify selected stores' webhooks", "The app will be mofidy the webhooks of the selected stores.")},
|
||||
{BTCPayServer.Client.Policies.CanViewStoreSettings, ("View your stores", "The app will be able to view stores settings.")},
|
||||
{$"{BTCPayServer.Client.Policies.CanViewStoreSettings}:", ("View your stores", "The app will be able to view the selected stores' settings.")},
|
||||
{BTCPayServer.Client.Policies.CanModifyServerSettings, ("Manage your server", "The app will have total control on the server settings of your server")},
|
||||
|
@ -2,7 +2,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Contracts;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
@ -1,4 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.U2F.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.ManageViewModels;
|
||||
using BTCPayServer.Security;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user