Compare commits
1 Commits
v1.5.1
...
user-disab
Author | SHA1 | Date | |
---|---|---|---|
597116bb26 |
@ -31,10 +31,9 @@
|
||||
<None Include="icon.png" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlSanitizer" Version="5.0.372" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.1" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.3" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.1" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -90,7 +90,7 @@ namespace BTCPayServer.Abstractions.Extensions
|
||||
public static string ToTimeAgo(this DateTimeOffset date)
|
||||
{
|
||||
var diff = DateTimeOffset.UtcNow - date;
|
||||
var formatted = diff.TotalSeconds > 0
|
||||
var formatted = diff.Seconds > 0
|
||||
? $"{diff.TimeString()} ago"
|
||||
: $"in {diff.Negate().TimeString()}";
|
||||
return formatted;
|
||||
@ -113,9 +113,9 @@ namespace BTCPayServer.Abstractions.Extensions
|
||||
return $"{(int)timeSpan.TotalDays} day{Plural((int)timeSpan.TotalDays)}";
|
||||
}
|
||||
|
||||
private static string Plural(int value)
|
||||
private static string Plural(int totalDays)
|
||||
{
|
||||
return value > 1 ? "s" : string.Empty;
|
||||
return totalDays > 1 ? "s" : string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Version Condition=" '$(Version)' == '' ">1.7.0</Version>
|
||||
<Version Condition=" '$(Version)' == '' ">1.6.0</Version>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
|
@ -55,7 +55,7 @@ namespace BTCPayServer.Client
|
||||
}
|
||||
|
||||
public virtual async Task<IEnumerable<OnChainWalletTransactionData>> ShowOnChainWalletTransactions(
|
||||
string storeId, string cryptoCode, TransactionStatus[] statusFilter = null, string labelFilter = null,
|
||||
string storeId, string cryptoCode, TransactionStatus[] statusFilter = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var query = new Dictionary<string, object>();
|
||||
@ -63,9 +63,6 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
query.Add(nameof(statusFilter), statusFilter);
|
||||
}
|
||||
if (labelFilter != null) {
|
||||
query.Add(nameof(labelFilter), labelFilter);
|
||||
}
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions", query), token);
|
||||
|
@ -1,18 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<IEnumerable<PayoutProcessorData>> GetPayoutProcessors(
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/payout-processors"), token);
|
||||
return await HandleResponse<IEnumerable<PayoutProcessorData>>(response);
|
||||
}
|
||||
}
|
||||
}
|
@ -41,23 +41,12 @@ namespace BTCPayServer.Client
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/pull-payments/{HttpUtility.UrlEncode(pullPaymentId)}/payouts", queryPayload: query, method: HttpMethod.Get), cancellationToken);
|
||||
return await HandleResponse<PayoutData[]>(response);
|
||||
}
|
||||
public virtual async Task<PayoutData[]> GetStorePayouts(string storeId, bool includeCancelled = false, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Dictionary<string, object> query = new Dictionary<string, object>();
|
||||
query.Add("includeCancelled", includeCancelled);
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/payouts", queryPayload: query, method: HttpMethod.Get), cancellationToken);
|
||||
return await HandleResponse<PayoutData[]>(response);
|
||||
}
|
||||
public virtual async Task<PayoutData> CreatePayout(string pullPaymentId, CreatePayoutRequest payoutRequest, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/pull-payments/{HttpUtility.UrlEncode(pullPaymentId)}/payouts", bodyPayload: payoutRequest, method: HttpMethod.Post), cancellationToken);
|
||||
return await HandleResponse<PayoutData>(response);
|
||||
}
|
||||
public virtual async Task<PayoutData> CreatePayout(string storeId, CreatePayoutThroughStoreRequest payoutRequest, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/payouts", bodyPayload: payoutRequest, method: HttpMethod.Post), cancellationToken);
|
||||
return await HandleResponse<PayoutData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task CancelPayout(string storeId, string payoutId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{HttpUtility.UrlEncode(storeId)}/payouts/{HttpUtility.UrlEncode(payoutId)}", method: HttpMethod.Delete), cancellationToken);
|
||||
|
@ -1,48 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<IEnumerable<PayoutProcessorData>> GetPayoutProcessors(string storeId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/payout-processors"), token);
|
||||
return await HandleResponse<IEnumerable<PayoutProcessorData>>(response);
|
||||
}
|
||||
public virtual async Task RemovePayoutProcessor(string storeId, string processor, string paymentMethod, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/payout-processors/{processor}/{paymentMethod}", null, HttpMethod.Delete), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public virtual async Task<IEnumerable<LightningAutomatedPayoutSettings>> GetStoreLightningAutomatedPayoutProcessors(string storeId, string? paymentMethod = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/payout-processors/LightningAutomatedPayoutSenderFactory{(paymentMethod is null? string.Empty: $"/{paymentMethod}")}"), token);
|
||||
return await HandleResponse<IEnumerable<LightningAutomatedPayoutSettings>>(response);
|
||||
}
|
||||
public virtual async Task<LightningAutomatedPayoutSettings> UpdateStoreLightningAutomatedPayoutProcessors(string storeId, string paymentMethod,LightningAutomatedPayoutSettings request, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/payout-processors/LightningAutomatedPayoutSenderFactory/{paymentMethod}",null, request, HttpMethod.Put ), token);
|
||||
return await HandleResponse<LightningAutomatedPayoutSettings>(response);
|
||||
}
|
||||
public virtual async Task<OnChainAutomatedPayoutSettings> UpdateStoreOnChainAutomatedPayoutProcessors(string storeId, string paymentMethod,OnChainAutomatedPayoutSettings request, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/payout-processors/OnChainAutomatedPayoutSenderFactory/{paymentMethod}",null, request, HttpMethod.Put ), token);
|
||||
return await HandleResponse<OnChainAutomatedPayoutSettings>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<IEnumerable<OnChainAutomatedPayoutSettings>> GetStoreOnChainAutomatedPayoutProcessors(string storeId, string? paymentMethod = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/payout-processors/OnChainAutomatedPayoutSenderFactory{(paymentMethod is null? string.Empty: $"/{paymentMethod}")}"), token);
|
||||
return await HandleResponse<IEnumerable<OnChainAutomatedPayoutSettings>>(response);
|
||||
}
|
||||
}
|
||||
}
|
@ -33,10 +33,12 @@ namespace BTCPayServer.Client
|
||||
return await HandleResponse<ApplicationUserData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task LockUser(string idOrEmail, bool locked, CancellationToken token = default)
|
||||
public virtual async Task ToggleUser(string idOrEmail, bool enabled, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/users/{idOrEmail}/lock", null,
|
||||
new LockUserRequest() {Locked = locked}, HttpMethod.Post), token);
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/users/{idOrEmail}/toggle", null,new
|
||||
{
|
||||
enabled
|
||||
} , HttpMethod.Post), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
|
7
BTCPayServer.Client/Models/AddCustomerEmailRequest.cs
Normal file
7
BTCPayServer.Client/Models/AddCustomerEmailRequest.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class AddCustomerEmailRequest
|
||||
{
|
||||
public string Email { get; set; }
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
#nullable enable
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class CreatePayoutThroughStoreRequest : CreatePayoutRequest
|
||||
{
|
||||
public string? PullPaymentId { get; set; }
|
||||
public bool? Approved { get; set; }
|
||||
}
|
@ -22,6 +22,5 @@ namespace BTCPayServer.Client.Models
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset? StartsAt { get; set; }
|
||||
public string[] PaymentMethods { get; set; }
|
||||
public bool AutoApproveClaims { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
using System;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class LightningAutomatedPayoutSettings
|
||||
{
|
||||
public string PaymentMethod { get; set; }
|
||||
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
|
||||
public TimeSpan IntervalSeconds { get; set; }
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
namespace BTCPayServer.Client;
|
||||
|
||||
public class LockUserRequest
|
||||
{
|
||||
public bool Locked { get; set; }
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
using System;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class OnChainAutomatedPayoutSettings
|
||||
{
|
||||
public string PaymentMethod { get; set; }
|
||||
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
|
||||
public TimeSpan IntervalSeconds { get; set; }
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
#nullable enable
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using NBitcoin;
|
||||
@ -14,6 +15,6 @@ namespace BTCPayServer.Client.Models
|
||||
public float? MaxFeePercent { get; set; }
|
||||
|
||||
[JsonConverter(typeof(MoneyJsonConverter))]
|
||||
public Money MaxFeeFlat { get; set; }
|
||||
public Money? MaxFeeFlat { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class PayoutProcessorData
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FriendlyName { get; set; }
|
||||
public string[] PaymentMethods { get; set; }
|
||||
}
|
||||
}
|
@ -31,6 +31,5 @@ namespace BTCPayServer.Client.Models
|
||||
public TimeSpan BOLT11Expiration { get; set; }
|
||||
public bool Archived { get; set; }
|
||||
public string ViewLink { get; set; }
|
||||
public bool AutoApproveClaims { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Data.Data;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
@ -62,8 +61,6 @@ namespace BTCPayServer.Data
|
||||
public DbSet<WalletTransactionData> WalletTransactions { get; set; }
|
||||
public DbSet<WebhookDeliveryData> WebhookDeliveries { get; set; }
|
||||
public DbSet<WebhookData> Webhooks { get; set; }
|
||||
public DbSet<LightningAddressData> LightningAddresses{ get; set; }
|
||||
public DbSet<PayoutProcessorData> PayoutProcessors { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
@ -90,7 +87,7 @@ namespace BTCPayServer.Data
|
||||
InvoiceData.OnModelCreating(builder);
|
||||
NotificationData.OnModelCreating(builder);
|
||||
//OffchainTransactionData.OnModelCreating(builder);
|
||||
BTCPayServer.Data.PairedSINData.OnModelCreating(builder);
|
||||
Data.PairedSINData.OnModelCreating(builder);
|
||||
PairingCodeData.OnModelCreating(builder);
|
||||
//PayjoinLock.OnModelCreating(builder);
|
||||
PaymentRequestData.OnModelCreating(builder);
|
||||
@ -105,12 +102,10 @@ namespace BTCPayServer.Data
|
||||
//StoreData.OnModelCreating(builder);
|
||||
U2FDevice.OnModelCreating(builder);
|
||||
Fido2Credential.OnModelCreating(builder);
|
||||
BTCPayServer.Data.UserStore.OnModelCreating(builder);
|
||||
Data.UserStore.OnModelCreating(builder);
|
||||
//WalletData.OnModelCreating(builder);
|
||||
WalletTransactionData.OnModelCreating(builder);
|
||||
WebhookDeliveryData.OnModelCreating(builder);
|
||||
LightningAddressData.OnModelCreating(builder);
|
||||
PayoutProcessorData.OnModelCreating(builder);
|
||||
//WebhookData.OnModelCreating(builder);
|
||||
|
||||
|
||||
|
@ -1,31 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BTCPayServer.Data;
|
||||
|
||||
public class LightningAddressData
|
||||
{
|
||||
public string Username { get; set; }
|
||||
public string StoreDataId { get; set; }
|
||||
public byte[] Blob { get; set; }
|
||||
|
||||
public StoreData Store { get; set; }
|
||||
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
builder.Entity<LightningAddressData>()
|
||||
.HasOne(o => o.Store)
|
||||
.WithMany(a => a.LightningAddresses)
|
||||
.HasForeignKey(data => data.StoreDataId)
|
||||
.IsRequired()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.Entity<LightningAddressData>().HasKey(o => o.Username);
|
||||
}
|
||||
}
|
||||
|
||||
public class LightningAddressDataBlob
|
||||
{
|
||||
public string CurrencyCode { get; set; }
|
||||
public decimal? Min { get; set; }
|
||||
public decimal? Max { get; set; }
|
||||
}
|
@ -14,7 +14,6 @@ namespace BTCPayServer.Data
|
||||
public string Id { get; set; }
|
||||
public DateTimeOffset Date { get; set; }
|
||||
public string PullPaymentDataId { get; set; }
|
||||
public string StoreDataId { get; set; }
|
||||
public PullPaymentData PullPaymentData { get; set; }
|
||||
[MaxLength(20)]
|
||||
public PayoutState State { get; set; }
|
||||
@ -26,16 +25,12 @@ namespace BTCPayServer.Data
|
||||
#nullable enable
|
||||
public string? Destination { get; set; }
|
||||
#nullable restore
|
||||
public StoreData StoreData { get; set; }
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
builder.Entity<PayoutData>()
|
||||
.HasOne(o => o.PullPaymentData)
|
||||
.WithMany(o => o.Payouts).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.Entity<PayoutData>()
|
||||
.HasOne(o => o.StoreData)
|
||||
.WithMany(o => o.Payouts).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.Entity<PayoutData>()
|
||||
.Property(o => o.State)
|
||||
.HasConversion<string>();
|
||||
|
@ -1,24 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BTCPayServer.Data.Data;
|
||||
|
||||
public class PayoutProcessorData
|
||||
{
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public string Id { get; set; }
|
||||
public string StoreId { get; set; }
|
||||
public StoreData Store { get; set; }
|
||||
public string PaymentMethod { get; set; }
|
||||
public string Processor { get; set; }
|
||||
|
||||
public byte[] Blob { get; set; }
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
|
||||
builder.Entity<PayoutProcessorData>()
|
||||
.HasOne(o => o.Store)
|
||||
.WithMany(data => data.PayoutProcessors).OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
@ -2,8 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data.Data;
|
||||
using PayoutProcessorData = BTCPayServer.Data.Data.PayoutProcessorData;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
@ -43,8 +41,5 @@ namespace BTCPayServer.Data
|
||||
|
||||
public List<PairedSINData> PairedSINs { get; set; }
|
||||
public IEnumerable<APIKeyData> APIKeys { get; set; }
|
||||
public IEnumerable<LightningAddressData> LightningAddresses { get; set; }
|
||||
public IEnumerable<PayoutProcessorData> PayoutProcessors { get; set; }
|
||||
public IEnumerable<PayoutData> Payouts { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,92 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20220311135252_AddPayoutProcessors")]
|
||||
public partial class AddPayoutProcessors : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "StoreDataId",
|
||||
table: "Payouts",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PayoutProcessors",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
StoreId = table.Column<string>(type: "TEXT", nullable: true),
|
||||
PaymentMethod = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Processor = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Blob = table.Column<byte[]>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PayoutProcessors", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PayoutProcessors_Stores_StoreId",
|
||||
column: x => x.StoreId,
|
||||
principalTable: "Stores",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Payouts_StoreDataId",
|
||||
table: "Payouts",
|
||||
column: "StoreDataId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PayoutProcessors_StoreId",
|
||||
table: "PayoutProcessors",
|
||||
column: "StoreId");
|
||||
if (this.SupportAddForeignKey(ActiveProvider))
|
||||
{
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Payouts_Stores_StoreDataId",
|
||||
table: "Payouts",
|
||||
column: "StoreDataId",
|
||||
principalTable: "Stores",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (this.SupportDropForeignKey(ActiveProvider))
|
||||
{
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Payouts_Stores_StoreDataId",
|
||||
table: "Payouts");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PayoutProcessors");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Payouts_StoreDataId",
|
||||
table: "Payouts");
|
||||
}
|
||||
if(this.SupportDropColumn(ActiveProvider))
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "StoreDataId",
|
||||
table: "Payouts");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20220414132313_AddLightningAddress")]
|
||||
public partial class AddLightningAddress : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "LightningAddresses",
|
||||
columns: table => new
|
||||
{
|
||||
Username = table.Column<string>(type: "TEXT", nullable: false),
|
||||
StoreDataId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Blob = table.Column<byte[]>( nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_LightningAddresses", x => x.Username);
|
||||
table.ForeignKey(
|
||||
name: "FK_LightningAddresses_Stores_StoreDataId",
|
||||
column: x => x.StoreDataId,
|
||||
principalTable: "Stores",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_LightningAddresses_StoreDataId",
|
||||
table: "LightningAddresses",
|
||||
column: "StoreDataId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "LightningAddresses");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@ -6,8 +6,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
@ -16,7 +14,40 @@ namespace BTCPayServer.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.1");
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "3.1.19");
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<string>("Label")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StoreId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("ApiKeys");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
@ -36,38 +67,6 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("AddressInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<string>("Label")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StoreId")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("ApiKeys");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AppData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -120,8 +119,8 @@ namespace BTCPayServer.Migrations
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
@ -133,12 +132,12 @@ namespace BTCPayServer.Migrations
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("TEXT");
|
||||
@ -159,19 +158,19 @@ namespace BTCPayServer.Migrations
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
.HasName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex");
|
||||
.HasName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers", (string)null);
|
||||
b.ToTable("AspNetUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.Fido2Credential", b =>
|
||||
@ -332,35 +331,16 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("InvoiceWebhookDeliveries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.LightningAddressData", b =>
|
||||
{
|
||||
b.Property<string>("Username")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<string>("StoreDataId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Username");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("LightningAddresses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.NotificationData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(36)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(36);
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.HasColumnType("BLOB");
|
||||
@ -370,8 +350,8 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.Property<string>("NotificationType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(100);
|
||||
|
||||
b.Property<bool>("Seen")
|
||||
.HasColumnType("INTEGER");
|
||||
@ -386,8 +366,8 @@ namespace BTCPayServer.Migrations
|
||||
modelBuilder.Entity("BTCPayServer.Data.OffchainTransactionData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(64);
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.HasColumnType("BLOB");
|
||||
@ -457,8 +437,8 @@ namespace BTCPayServer.Migrations
|
||||
modelBuilder.Entity("BTCPayServer.Data.PayjoinLock", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(100);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@ -520,8 +500,8 @@ namespace BTCPayServer.Migrations
|
||||
modelBuilder.Entity("BTCPayServer.Data.PayoutData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(30);
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.HasColumnType("BLOB");
|
||||
@ -534,8 +514,8 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.Property<string>("PaymentMethodId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(20);
|
||||
|
||||
b.Property<byte[]>("Proof")
|
||||
.HasColumnType("BLOB");
|
||||
@ -545,11 +525,8 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.Property<string>("State")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StoreDataId")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(20);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@ -557,8 +534,6 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.HasIndex("State");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.HasIndex("Destination", "State");
|
||||
|
||||
b.ToTable("Payouts");
|
||||
@ -577,8 +552,8 @@ namespace BTCPayServer.Migrations
|
||||
modelBuilder.Entity("BTCPayServer.Data.PlannedTransaction", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(100);
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.HasColumnType("BLOB");
|
||||
@ -594,8 +569,8 @@ namespace BTCPayServer.Migrations
|
||||
modelBuilder.Entity("BTCPayServer.Data.PullPaymentData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(30);
|
||||
|
||||
b.Property<bool>("Archived")
|
||||
.HasColumnType("INTEGER");
|
||||
@ -613,8 +588,8 @@ namespace BTCPayServer.Migrations
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StoreId")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@ -629,8 +604,8 @@ namespace BTCPayServer.Migrations
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PullPaymentDataId")
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(30);
|
||||
|
||||
b.HasKey("InvoiceDataId", "PullPaymentDataId");
|
||||
|
||||
@ -686,6 +661,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")
|
||||
@ -711,25 +705,6 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("Files");
|
||||
});
|
||||
|
||||
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.U2FDevice", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -816,8 +791,8 @@ namespace BTCPayServer.Migrations
|
||||
modelBuilder.Entity("BTCPayServer.Data.WebhookData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(25)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.IsRequired()
|
||||
@ -831,8 +806,8 @@ namespace BTCPayServer.Migrations
|
||||
modelBuilder.Entity("BTCPayServer.Data.WebhookDeliveryData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(25)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.IsRequired()
|
||||
@ -843,8 +818,8 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.Property<string>("WebhookId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(25)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@ -853,31 +828,6 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("WebhookDeliveries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.PayoutProcessors.PayoutProcessorData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<string>("PaymentMethod")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Processor")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StoreId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreId");
|
||||
|
||||
b.ToTable("PayoutProcessors");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -888,20 +838,20 @@ namespace BTCPayServer.Migrations
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex");
|
||||
.HasName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles", (string)null);
|
||||
b.ToTable("AspNetRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
@ -924,7 +874,7 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims", (string)null);
|
||||
b.ToTable("AspNetRoleClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
@ -947,7 +897,7 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims", (string)null);
|
||||
b.ToTable("AspNetUserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
@ -969,7 +919,7 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins", (string)null);
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
@ -984,7 +934,7 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles", (string)null);
|
||||
b.ToTable("AspNetUserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
@ -1003,17 +953,7 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("AddressInvoices")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("InvoiceData");
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b =>
|
||||
@ -1027,10 +967,14 @@ namespace BTCPayServer.Migrations
|
||||
.WithMany("APIKeys")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
b.Navigation("StoreData");
|
||||
|
||||
b.Navigation("User");
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("AddressInvoices")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AppData", b =>
|
||||
@ -1039,8 +983,6 @@ namespace BTCPayServer.Migrations
|
||||
.WithMany("Apps")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("StoreData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.Fido2Credential", b =>
|
||||
@ -1049,8 +991,6 @@ namespace BTCPayServer.Migrations
|
||||
.WithMany("Fido2Credentials")
|
||||
.HasForeignKey("ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("ApplicationUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||
@ -1060,8 +1000,6 @@ namespace BTCPayServer.Migrations
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("InvoiceData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
@ -1074,10 +1012,6 @@ namespace BTCPayServer.Migrations
|
||||
b.HasOne("BTCPayServer.Data.RefundData", "CurrentRefund")
|
||||
.WithMany()
|
||||
.HasForeignKey("Id", "CurrentRefundId");
|
||||
|
||||
b.Navigation("CurrentRefund");
|
||||
|
||||
b.Navigation("StoreData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceEventData", b =>
|
||||
@ -1087,8 +1021,6 @@ namespace BTCPayServer.Migrations
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("InvoiceData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceSearchData", b =>
|
||||
@ -1097,8 +1029,6 @@ namespace BTCPayServer.Migrations
|
||||
.WithMany("InvoiceSearchData")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("InvoiceData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceWebhookDeliveryData", b =>
|
||||
@ -1114,21 +1044,6 @@ namespace BTCPayServer.Migrations
|
||||
.HasForeignKey("BTCPayServer.Data.InvoiceWebhookDeliveryData", "InvoiceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Delivery");
|
||||
|
||||
b.Navigation("Invoice");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.LightningAddressData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "Store")
|
||||
.WithMany("LightningAddresses")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Store");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.NotificationData", b =>
|
||||
@ -1138,8 +1053,6 @@ namespace BTCPayServer.Migrations
|
||||
.HasForeignKey("ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ApplicationUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b =>
|
||||
@ -1148,8 +1061,6 @@ namespace BTCPayServer.Migrations
|
||||
.WithMany("PairedSINs")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("StoreData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
|
||||
@ -1158,8 +1069,6 @@ namespace BTCPayServer.Migrations
|
||||
.WithMany("Payments")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("InvoiceData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PaymentRequestData", b =>
|
||||
@ -1168,8 +1077,6 @@ namespace BTCPayServer.Migrations
|
||||
.WithMany("PaymentRequests")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("StoreData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PayoutData", b =>
|
||||
@ -1178,15 +1085,6 @@ namespace BTCPayServer.Migrations
|
||||
.WithMany("Payouts")
|
||||
.HasForeignKey("PullPaymentDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("Payouts")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("PullPaymentData");
|
||||
|
||||
b.Navigation("StoreData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
|
||||
@ -1196,8 +1094,6 @@ namespace BTCPayServer.Migrations
|
||||
.HasForeignKey("Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("InvoiceData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PullPaymentData", b =>
|
||||
@ -1206,8 +1102,6 @@ namespace BTCPayServer.Migrations
|
||||
.WithMany("PullPayments")
|
||||
.HasForeignKey("StoreId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("StoreData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.RefundData", b =>
|
||||
@ -1223,19 +1117,6 @@ namespace BTCPayServer.Migrations
|
||||
.HasForeignKey("PullPaymentDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("InvoiceData");
|
||||
|
||||
b.Navigation("PullPaymentData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoredFile", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("StoredFiles")
|
||||
.HasForeignKey("ApplicationUserId");
|
||||
|
||||
b.Navigation("ApplicationUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreWebhookData", b =>
|
||||
@ -1251,10 +1132,13 @@ namespace BTCPayServer.Migrations
|
||||
.HasForeignKey("BTCPayServer.Data.StoreWebhookData", "WebhookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
b.Navigation("Store");
|
||||
|
||||
b.Navigation("Webhook");
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoredFile", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("StoredFiles")
|
||||
.HasForeignKey("ApplicationUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.U2FDevice", b =>
|
||||
@ -1263,8 +1147,6 @@ namespace BTCPayServer.Migrations
|
||||
.WithMany("U2FDevices")
|
||||
.HasForeignKey("ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("ApplicationUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
|
||||
@ -1280,10 +1162,6 @@ namespace BTCPayServer.Migrations
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ApplicationUser");
|
||||
|
||||
b.Navigation("StoreData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WalletTransactionData", b =>
|
||||
@ -1293,8 +1171,6 @@ namespace BTCPayServer.Migrations
|
||||
.HasForeignKey("WalletDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("WalletData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WebhookDeliveryData", b =>
|
||||
@ -1304,18 +1180,6 @@ namespace BTCPayServer.Migrations
|
||||
.HasForeignKey("WebhookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Webhook");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.PayoutProcessors.PayoutProcessorData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "Store")
|
||||
.WithMany("PayoutProcessors")
|
||||
.HasForeignKey("StoreId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("Store");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
@ -1354,7 +1218,7 @@ namespace BTCPayServer.Migrations
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BTCPayServer.Data.ApplicationUser", null)
|
||||
.WithMany("UserRoles")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
@ -1368,78 +1232,6 @@ namespace BTCPayServer.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.ApplicationUser", b =>
|
||||
{
|
||||
b.Navigation("APIKeys");
|
||||
|
||||
b.Navigation("Fido2Credentials");
|
||||
|
||||
b.Navigation("Notifications");
|
||||
|
||||
b.Navigation("StoredFiles");
|
||||
|
||||
b.Navigation("U2FDevices");
|
||||
|
||||
b.Navigation("UserRoles");
|
||||
|
||||
b.Navigation("UserStores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.Navigation("AddressInvoices");
|
||||
|
||||
b.Navigation("Events");
|
||||
|
||||
b.Navigation("HistoricalAddressInvoices");
|
||||
|
||||
b.Navigation("InvoiceSearchData");
|
||||
|
||||
b.Navigation("Payments");
|
||||
|
||||
b.Navigation("PendingInvoices");
|
||||
|
||||
b.Navigation("Refunds");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PullPaymentData", b =>
|
||||
{
|
||||
b.Navigation("Payouts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreData", b =>
|
||||
{
|
||||
b.Navigation("APIKeys");
|
||||
|
||||
b.Navigation("Apps");
|
||||
|
||||
b.Navigation("Invoices");
|
||||
|
||||
b.Navigation("LightningAddresses");
|
||||
|
||||
b.Navigation("PairedSINs");
|
||||
|
||||
b.Navigation("PaymentRequests");
|
||||
|
||||
b.Navigation("Payouts");
|
||||
|
||||
b.Navigation("PullPayments");
|
||||
|
||||
b.Navigation("PayoutProcessors");
|
||||
|
||||
b.Navigation("UserStores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WalletData", b =>
|
||||
{
|
||||
b.Navigation("WalletTransactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WebhookData", b =>
|
||||
{
|
||||
b.Navigation("Deliveries");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
|
||||
<PackageReference Include="Selenium.Support" Version="3.141.0" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="100.0.4896.6000" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="98.0.4758.10200" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
@ -449,10 +449,10 @@ namespace BTCPayServer.Tests
|
||||
await tester.StartAsync();
|
||||
var acc = tester.NewAccount();
|
||||
acc.Register();
|
||||
await acc.CreateStoreAsync();
|
||||
acc.CreateStore();
|
||||
var storeId = (await acc.RegisterDerivationSchemeAsync("BTC", importKeysToNBX: true)).StoreId;
|
||||
var client = await acc.CreateClient();
|
||||
var result = await client.CreatePullPayment(storeId, new CreatePullPaymentRequest()
|
||||
var result = await client.CreatePullPayment(storeId, new Client.Models.CreatePullPaymentRequest()
|
||||
{
|
||||
Name = "Test",
|
||||
Description = "Test description",
|
||||
@ -2163,9 +2163,6 @@ namespace BTCPayServer.Tests
|
||||
new[] { TransactionStatus.Unconfirmed }), data => data.TransactionHash == txdata.TransactionHash);
|
||||
Assert.Contains(
|
||||
await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode), data => data.TransactionHash == txdata.TransactionHash);
|
||||
Assert.Contains(
|
||||
await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode, null, "test label"), data => data.TransactionHash == txdata.TransactionHash);
|
||||
|
||||
await tester.WaitForEvent<NewBlockEvent>(async () =>
|
||||
{
|
||||
|
||||
@ -2340,165 +2337,5 @@ namespace BTCPayServer.Tests
|
||||
await adminClient.SendEmail(admin.StoreId,
|
||||
new SendEmailRequest() { Body = "lol", Subject = "subj", Email = "sdasdas" });
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task DisabledEnabledUserTests()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
var admin = tester.NewAccount();
|
||||
await admin.GrantAccessAsync(true);
|
||||
var adminClient = await admin.CreateClient(Policies.Unrestricted);
|
||||
|
||||
var newUser = tester.NewAccount();
|
||||
await newUser.GrantAccessAsync();
|
||||
var newUserClient = await newUser.CreateClient(Policies.Unrestricted);
|
||||
Assert.False((await newUserClient.GetCurrentUser()).Disabled);
|
||||
|
||||
await adminClient.LockUser(newUser.UserId, true, CancellationToken.None);
|
||||
|
||||
Assert.True((await adminClient.GetUserByIdOrEmail(newUser.UserId)).Disabled);
|
||||
await AssertAPIError("unauthenticated",async () =>
|
||||
{
|
||||
await newUserClient.GetCurrentUser();
|
||||
});
|
||||
var newUserBasicClient = new BTCPayServerClient(newUserClient.Host, newUser.RegisterDetails.Email,
|
||||
newUser.RegisterDetails.Password);
|
||||
await AssertAPIError("unauthenticated",async () =>
|
||||
{
|
||||
await newUserBasicClient.GetCurrentUser();
|
||||
});
|
||||
|
||||
await adminClient.LockUser(newUser.UserId, false, CancellationToken.None);
|
||||
Assert.False((await adminClient.GetUserByIdOrEmail(newUser.UserId)).Disabled);
|
||||
await newUserClient.GetCurrentUser();
|
||||
await newUserBasicClient.GetCurrentUser();
|
||||
// Twice for good measure
|
||||
await adminClient.LockUser(newUser.UserId, false, CancellationToken.None);
|
||||
Assert.False((await adminClient.GetUserByIdOrEmail(newUser.UserId)).Disabled);
|
||||
await newUserClient.GetCurrentUser();
|
||||
await newUserBasicClient.GetCurrentUser();
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanUsePayoutProcessorsThroughAPI()
|
||||
{
|
||||
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
|
||||
var admin = tester.NewAccount();
|
||||
await admin.GrantAccessAsync(true);
|
||||
|
||||
var adminClient = await admin.CreateClient(Policies.Unrestricted);
|
||||
|
||||
var registeredProcessors = await adminClient.GetPayoutProcessors();
|
||||
Assert.Equal(2,registeredProcessors.Count());
|
||||
await adminClient.GenerateOnChainWallet(admin.StoreId, "BTC", new GenerateOnChainWalletRequest()
|
||||
{
|
||||
SavePrivateKeys = true
|
||||
});
|
||||
|
||||
var preApprovedPayoutWithoutPullPayment = await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
Amount = 0.0001m,
|
||||
Approved = true,
|
||||
PaymentMethod = "BTC",
|
||||
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
});
|
||||
|
||||
var notApprovedPayoutWithoutPullPayment = await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
Amount = 0.00001m,
|
||||
Approved = false,
|
||||
PaymentMethod = "BTC",
|
||||
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
});
|
||||
|
||||
var pullPayment = await adminClient.CreatePullPayment(admin.StoreId, new CreatePullPaymentRequest()
|
||||
{
|
||||
Amount = 100,
|
||||
Currency = "USD",
|
||||
Name = "pull payment",
|
||||
PaymentMethods = new []{ "BTC"}
|
||||
});
|
||||
|
||||
var notapprovedPayoutWithPullPayment = await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
PullPaymentId = pullPayment.Id,
|
||||
Amount = 10,
|
||||
Approved = false,
|
||||
PaymentMethod = "BTC",
|
||||
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
});
|
||||
await adminClient.ApprovePayout(admin.StoreId, notapprovedPayoutWithPullPayment.Id,
|
||||
new ApprovePayoutRequest() { });
|
||||
|
||||
var payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
|
||||
Assert.Equal(3, payouts.Length);
|
||||
Assert.Single(payouts, data => data.State == PayoutState.AwaitingApproval);
|
||||
await adminClient.ApprovePayout(admin.StoreId, notApprovedPayoutWithoutPullPayment.Id,
|
||||
new ApprovePayoutRequest() { });
|
||||
|
||||
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
|
||||
Assert.Equal(3, payouts.Length);
|
||||
Assert.Empty(payouts.Where(data => data.State == PayoutState.AwaitingApproval));
|
||||
Assert.Empty(payouts.Where(data => data.PaymentMethodAmount is null));
|
||||
|
||||
Assert.Empty( await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC"));
|
||||
|
||||
|
||||
Assert.Empty( await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC"));
|
||||
Assert.Empty(await adminClient.GetPayoutProcessors(admin.StoreId));
|
||||
|
||||
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC",
|
||||
new OnChainAutomatedPayoutSettings() {IntervalSeconds = TimeSpan.FromSeconds(100000)});
|
||||
Assert.Equal(100000, Assert.Single( await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
|
||||
|
||||
var tpGen = Assert.Single(await adminClient.GetPayoutProcessors(admin.StoreId));
|
||||
Assert.Equal("BTC", Assert.Single(tpGen.PaymentMethods));
|
||||
//still too poor to process any payouts
|
||||
Assert.Empty( await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC"));
|
||||
|
||||
|
||||
await adminClient.RemovePayoutProcessor(admin.StoreId, tpGen.Name, tpGen.PaymentMethods.First());
|
||||
|
||||
Assert.Empty( await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC"));
|
||||
Assert.Empty(await adminClient.GetPayoutProcessors(admin.StoreId));
|
||||
|
||||
await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.000012m));
|
||||
await tester.ExplorerNode.GenerateAsync(1);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
Assert.Single(await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC"));
|
||||
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
Assert.Equal(3, payouts.Length);
|
||||
});
|
||||
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC",
|
||||
new OnChainAutomatedPayoutSettings() {IntervalSeconds = TimeSpan.FromSeconds(5)});
|
||||
Assert.Equal(5, Assert.Single( await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
Assert.Equal(2, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count());
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
Assert.Single(payouts.Where(data => data.State == PayoutState.InProgress));
|
||||
});
|
||||
|
||||
await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.01m));
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
Assert.Equal(4, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count());
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
Assert.Empty(payouts.Where(data => data.State != PayoutState.InProgress));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ You can also generate blocks:
|
||||
|
||||
- Install and run [Polar](https://lightningpolar.com/). Setup a small network of nodes.
|
||||
- Go to your store's General Settings and enable Lightning.
|
||||
- Build your connection string using the Connect information in the Polar app.
|
||||
- Build your connection string using the Connect infomation in the Polar app.
|
||||
|
||||
LND Connection string example:
|
||||
type=lnd-rest;server=https://127.0.0.1:8084/;macaroonfilepath="local path to admin.macaroon on your computer, without these quotes";allowinsecure=true
|
||||
@ -38,7 +38,7 @@ type=lnd-rest;server=https://127.0.0.1:8084/;macaroonfilepath="local path to adm
|
||||
Now you can create a Lightning invoice on BTCPay Server regtest and make a payment through Polar.
|
||||
|
||||
PLEASE NOTE: You may get an exception break in Visual Studio. You must quickly click "Continue" in VS so the invoice is generated.
|
||||
Or, uncheck the box that says, "Break when this exception type is thrown".
|
||||
Or, uncheck the box that says, "Break when this exceptiontype is thrown".
|
||||
|
||||
|
||||
### Using the test litecoin-cli
|
||||
@ -88,8 +88,8 @@ Or, uncheck the box that says, "Break when this exception type is thrown".
|
||||
|
||||
### `docker-compose up dev` failed or tests are not passing, what should I do?
|
||||
|
||||
1. Run `docker-compose down --volumes` (this will reset your test environment)
|
||||
2. Run `docker-compose pull` (this will ensure you have the latest images)
|
||||
1. Run `docker-compose down --v` (this will reset your test environment)
|
||||
2. Run `docker-compose pull` (this will ensure you have the lastest images)
|
||||
3. Run again with `docker-compose up dev`
|
||||
|
||||
### How to run the Altcoin environment?
|
||||
|
@ -673,12 +673,11 @@ namespace BTCPayServer.Tests
|
||||
s.LogIn(userId);
|
||||
// Make sure after login, we are not redirected to the PoS
|
||||
Assert.DoesNotContain("Tea shop", s.Driver.PageSource);
|
||||
var prevUrl = s.Driver.Url;
|
||||
|
||||
// We are only if explicitly going to /
|
||||
s.GoToUrl("/");
|
||||
Assert.Contains("Tea shop", s.Driver.PageSource);
|
||||
s.Driver.Navigate().GoToUrl(new Uri(prevUrl, UriKind.Absolute));
|
||||
s.Driver.Navigate().Back();
|
||||
|
||||
// Let's check with domain mapping as well.
|
||||
s.GoToServer(ServerNavPages.Policies);
|
||||
@ -1412,28 +1411,6 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.Completed}-view")).Click();
|
||||
Assert.Contains(bolt, s.Driver.PageSource);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//auto-approve pull payments
|
||||
|
||||
s.GoToStore(StoreNavPages.PullPayments);
|
||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
|
||||
s.Driver.SetCheckbox(By.Id("AutoApproveClaims"), true);
|
||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0" + Keys.Enter);
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
address = await s.Server.ExplorerNode.GetNewAddressAsync();
|
||||
s.Driver.FindElement(By.Id("Destination")).Clear();
|
||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter);
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
|
||||
Assert.Contains(PayoutState.AwaitingPayment.GetStateString(), s.Driver.PageSource);
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -1736,6 +1713,11 @@ namespace BTCPayServer.Tests
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
|
||||
s.Driver.ToggleCollapse("AddAddress");
|
||||
s.Driver.FindElement(By.Id("Add_Username")).SendKeys(lnaddress1);
|
||||
s.Driver.FindElement(By.CssSelector("button[value='add']")).Click();
|
||||
s.Driver.FindElement(By.ClassName("text-danger"));
|
||||
|
||||
s.Driver.FindElement(By.Id("Add_Username")).Clear();
|
||||
var lnaddress2 = "EUR" + Guid.NewGuid().ToString();
|
||||
s.Driver.FindElement(By.Id("Add_Username")).SendKeys(lnaddress2);
|
||||
|
||||
|
@ -32,7 +32,7 @@ services:
|
||||
TESTS_SOCKSENDPOINT: "tor:9050"
|
||||
expose:
|
||||
- "80"
|
||||
depends_on:
|
||||
links:
|
||||
- dev
|
||||
- selenium
|
||||
extra_hosts:
|
||||
@ -46,7 +46,7 @@ services:
|
||||
dev:
|
||||
image: alpine:3.7
|
||||
command: [ "/bin/sh", "-c", "trap : TERM INT; while :; do echo Ready to code and debug like a rockstar!!!; sleep 2073600; done & wait" ]
|
||||
depends_on:
|
||||
links:
|
||||
- nbxplorer
|
||||
- postgres
|
||||
- customer_lightningd
|
||||
@ -79,7 +79,7 @@ services:
|
||||
connect=bitcoind:39388
|
||||
fallbackfee=0.0002
|
||||
rpcallowip=0.0.0.0/0
|
||||
depends_on:
|
||||
links:
|
||||
- nbxplorer
|
||||
- postgres
|
||||
- customer_lnd
|
||||
@ -117,7 +117,7 @@ services:
|
||||
NBXPLORER_POSTGRES: User ID=postgres;Include Error Detail=true;Host=postgres;Port=5432;Database=nbxplorer
|
||||
NBXPLORER_NOAUTH: 1
|
||||
NBXPLORER_EXPOSERPC: 1
|
||||
depends_on:
|
||||
links:
|
||||
- bitcoind
|
||||
- litecoind
|
||||
- elementsd-liquid
|
||||
@ -176,7 +176,7 @@ services:
|
||||
volumes:
|
||||
- "bitcoin_datadir:/etc/bitcoin"
|
||||
- "customer_lightningd_datadir:/root/.lightning"
|
||||
depends_on:
|
||||
links:
|
||||
- bitcoind
|
||||
|
||||
lightning-charged:
|
||||
@ -197,7 +197,7 @@ services:
|
||||
- "9735" # Lightning
|
||||
ports:
|
||||
- "54938:9112" # Charge
|
||||
depends_on:
|
||||
links:
|
||||
- bitcoind
|
||||
- merchant_lightningd
|
||||
|
||||
@ -224,7 +224,7 @@ services:
|
||||
volumes:
|
||||
- "bitcoin_datadir:/etc/bitcoin"
|
||||
- "merchant_lightningd_datadir:/root/.lightning"
|
||||
depends_on:
|
||||
links:
|
||||
- bitcoind
|
||||
postgres:
|
||||
image: postgres:13.4
|
||||
@ -266,7 +266,7 @@ services:
|
||||
volumes:
|
||||
- "merchant_lnd_datadir:/data"
|
||||
- "bitcoin_datadir:/deps/.bitcoin"
|
||||
depends_on:
|
||||
links:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
@ -301,7 +301,7 @@ services:
|
||||
volumes:
|
||||
- "customer_lnd_datadir:/root/.lnd"
|
||||
- "bitcoin_datadir:/deps/.bitcoin"
|
||||
depends_on:
|
||||
links:
|
||||
- bitcoind
|
||||
|
||||
tor:
|
||||
|
@ -30,7 +30,7 @@ services:
|
||||
TESTS_SOCKSENDPOINT: "tor:9050"
|
||||
expose:
|
||||
- "80"
|
||||
depends_on:
|
||||
links:
|
||||
- dev
|
||||
- selenium
|
||||
extra_hosts:
|
||||
@ -44,7 +44,7 @@ services:
|
||||
dev:
|
||||
image: alpine:3.7
|
||||
command: [ "/bin/sh", "-c", "trap : TERM INT; while :; do echo Ready to code and debug like a rockstar!!!; sleep 2073600; done & wait" ]
|
||||
depends_on:
|
||||
links:
|
||||
- nbxplorer
|
||||
- postgres
|
||||
- customer_lightningd
|
||||
@ -76,7 +76,7 @@ services:
|
||||
connect=bitcoind:39388
|
||||
rpcallowip=0.0.0.0/0
|
||||
fallbackfee=0.0002
|
||||
depends_on:
|
||||
links:
|
||||
- nbxplorer
|
||||
- postgres
|
||||
- customer_lnd
|
||||
@ -106,7 +106,7 @@ services:
|
||||
NBXPLORER_POSTGRES: User ID=postgres;Include Error Detail=true;Host=postgres;Port=5432;Database=nbxplorer
|
||||
NBXPLORER_EXPOSERPC: 1
|
||||
NBXPLORER_NOAUTH: 1
|
||||
depends_on:
|
||||
links:
|
||||
- bitcoind
|
||||
|
||||
|
||||
@ -163,7 +163,7 @@ services:
|
||||
volumes:
|
||||
- "bitcoin_datadir:/etc/bitcoin"
|
||||
- "customer_lightningd_datadir:/root/.lightning"
|
||||
depends_on:
|
||||
links:
|
||||
- bitcoind
|
||||
|
||||
lightning-charged:
|
||||
@ -184,7 +184,7 @@ services:
|
||||
- "9735" # Lightning
|
||||
ports:
|
||||
- "54938:9112" # Charge
|
||||
depends_on:
|
||||
links:
|
||||
- bitcoind
|
||||
- merchant_lightningd
|
||||
|
||||
@ -211,7 +211,7 @@ services:
|
||||
volumes:
|
||||
- "bitcoin_datadir:/etc/bitcoin"
|
||||
- "merchant_lightningd_datadir:/root/.lightning"
|
||||
depends_on:
|
||||
links:
|
||||
- bitcoind
|
||||
|
||||
postgres:
|
||||
@ -256,7 +256,7 @@ services:
|
||||
volumes:
|
||||
- "merchant_lnd_datadir:/data"
|
||||
- "bitcoin_datadir:/deps/.bitcoin"
|
||||
depends_on:
|
||||
links:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
@ -292,7 +292,7 @@ services:
|
||||
volumes:
|
||||
- "customer_lnd_datadir:/root/.lnd"
|
||||
- "bitcoin_datadir:/deps/.bitcoin"
|
||||
depends_on:
|
||||
links:
|
||||
- bitcoind
|
||||
|
||||
tor:
|
||||
|
@ -48,7 +48,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BIP78.Sender" Version="0.2.2" />
|
||||
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.2" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.3.6" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.3.4" />
|
||||
<PackageReference Include="BuildBundlerMinifier" Version="3.2.449" />
|
||||
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435" />
|
||||
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
|
||||
|
@ -197,26 +197,17 @@
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(() => {
|
||||
// apply collapse settings
|
||||
const navCollapsed = window.localStorage.getItem('btcpay-nav-collapsed')
|
||||
const collapsed = navCollapsed ? JSON.parse(navCollapsed) : []
|
||||
collapsed.forEach(id => {
|
||||
const el = document.getElementById(id)
|
||||
const btn = el && el.previousElementSibling.querySelector(`[aria-controls="${id}"]`)
|
||||
if (el && btn) {
|
||||
el.classList.remove('show')
|
||||
btn.classList.add('collapsed')
|
||||
btn.setAttribute('aria-expanded', 'false')
|
||||
}
|
||||
})
|
||||
// hide empty plugins drawer
|
||||
const pluginsItem = document.getElementById('Nav-Plugins').closest('.accordion-item')
|
||||
const pluginsContent = pluginsItem.querySelector('.navbar-nav').innerHTML.trim()
|
||||
if (pluginsContent === '') {
|
||||
pluginsItem.setAttribute('hidden', true)
|
||||
const navCollapsed = window.localStorage.getItem('btcpay-nav-collapsed')
|
||||
const collapsed = navCollapsed ? JSON.parse(navCollapsed) : []
|
||||
collapsed.forEach(id => {
|
||||
const el = document.getElementById(id)
|
||||
const btn = el && el.previousElementSibling.querySelector(`[aria-controls="${id}"]`)
|
||||
if (el && btn) {
|
||||
el.classList.remove('show')
|
||||
btn.classList.add('collapsed')
|
||||
btn.setAttribute('aria-expanded', 'false')
|
||||
}
|
||||
})()
|
||||
})
|
||||
</script>
|
||||
}
|
||||
else if (Env.IsSecure)
|
||||
|
@ -8,19 +8,16 @@
|
||||
</header>
|
||||
<div class="h3">@Model.PayoutsPending</div>
|
||||
</div>
|
||||
@if (Model.Transactions is not null)
|
||||
{
|
||||
<div class="store-number">
|
||||
<header>
|
||||
<h6>TXs in the last @Model.TransactionDays days</h6>
|
||||
@if (Model.Transactions.Value > 0)
|
||||
{
|
||||
<a asp-controller="UIWallets" asp-action="WalletTransactions" asp-route-walletId="@Model.WalletId">View All</a>
|
||||
}
|
||||
</header>
|
||||
<div class="h3">@Model.Transactions.Value</div>
|
||||
</div>
|
||||
}
|
||||
<div class="store-number">
|
||||
<header>
|
||||
<h6>TXs in the last @Model.TransactionDays days</h6>
|
||||
@if (Model.Transactions > 0)
|
||||
{
|
||||
<a asp-controller="UIWallets" asp-action="WalletTransactions" asp-route-walletId="@Model.WalletId">View All</a>
|
||||
}
|
||||
</header>
|
||||
<div class="h3">@Model.Transactions</div>
|
||||
</div>
|
||||
<div class="store-number">
|
||||
<header>
|
||||
<h6>Refunds Issued</h6>
|
||||
|
@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using Dapper;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Components.StoreRecentTransactions;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
@ -19,28 +17,24 @@ namespace BTCPayServer.Components.StoreNumbers;
|
||||
|
||||
public class StoreNumbers : ViewComponent
|
||||
{
|
||||
private string CryptoCode;
|
||||
private const string CryptoCode = "BTC";
|
||||
private const int TransactionDays = 7;
|
||||
|
||||
private readonly StoreRepository _storeRepo;
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
private readonly BTCPayWalletProvider _walletProvider;
|
||||
private readonly NBXplorerConnectionFactory _nbxConnectionFactory;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
|
||||
public StoreNumbers(
|
||||
StoreRepository storeRepo,
|
||||
ApplicationDbContextFactory dbContextFactory,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
BTCPayWalletProvider walletProvider,
|
||||
NBXplorerConnectionFactory nbxConnectionFactory)
|
||||
BTCPayWalletProvider walletProvider)
|
||||
{
|
||||
_storeRepo = storeRepo;
|
||||
_walletProvider = walletProvider;
|
||||
_nbxConnectionFactory = nbxConnectionFactory;
|
||||
_networkProvider = networkProvider;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
CryptoCode = networkProvider.DefaultNetwork.CryptoCode;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(StoreData store)
|
||||
@ -56,14 +50,16 @@ public class StoreNumbers : ViewComponent
|
||||
|
||||
var walletId = new WalletId(store.Id, CryptoCode);
|
||||
var derivation = store.GetDerivationSchemeSettings(_networkProvider, walletId.CryptoCode);
|
||||
int? transactionsCount = null;
|
||||
if (derivation != null && _nbxConnectionFactory.Available)
|
||||
var transactionsCount = 0;
|
||||
if (derivation != null)
|
||||
{
|
||||
await using var conn = await _nbxConnectionFactory.OpenConnection();
|
||||
var wid = NBXplorer.Client.DBUtils.nbxv1_get_wallet_id(derivation.Network.CryptoCode, derivation.AccountDerivation.ToString());
|
||||
var network = derivation.Network;
|
||||
var wallet = _walletProvider.GetWallet(network);
|
||||
var allTransactions = await wallet.FetchTransactions(derivation.AccountDerivation);
|
||||
var afterDate = DateTimeOffset.UtcNow - TimeSpan.FromDays(TransactionDays);
|
||||
var count = await conn.ExecuteScalarAsync<long>("SELECT COUNT(*) FROM wallets_history WHERE code=@code AND wallet_id=@wid AND seen_at > @afterDate", new { code = derivation.Network.CryptoCode, wid, afterDate });
|
||||
transactionsCount = (int)count;
|
||||
transactionsCount = allTransactions.UnconfirmedTransactions.Transactions
|
||||
.Concat(allTransactions.ConfirmedTransactions.Transactions)
|
||||
.Count(t => t.Timestamp > afterDate);
|
||||
}
|
||||
|
||||
var vm = new StoreNumbersViewModel
|
||||
|
@ -8,7 +8,7 @@ public class StoreNumbersViewModel
|
||||
public StoreData Store { get; set; }
|
||||
public WalletId WalletId { get; set; }
|
||||
public int PayoutsPending { get; set; }
|
||||
public int? Transactions { get; set; }
|
||||
public int Transactions { get; set; }
|
||||
public int RefundsIssued { get; set; }
|
||||
public int TransactionDays { get; set; }
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ namespace BTCPayServer.Components.StoreRecentTransactions;
|
||||
|
||||
public class StoreRecentTransactions : ViewComponent
|
||||
{
|
||||
private string CryptoCode;
|
||||
private const string CryptoCode = "BTC";
|
||||
private readonly StoreRepository _storeRepo;
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
private readonly BTCPayWalletProvider _walletProvider;
|
||||
@ -41,7 +41,6 @@ public class StoreRecentTransactions : ViewComponent
|
||||
ConnectionFactory = connectionFactory;
|
||||
_walletProvider = walletProvider;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
CryptoCode = networkProvider.DefaultNetwork.CryptoCode;
|
||||
}
|
||||
|
||||
|
||||
|
@ -4,14 +4,11 @@
|
||||
<div id="StoreWalletBalance-@Model.Store.Id" class="widget store-wallet-balance">
|
||||
<h6 class="mb-2">Wallet Balance</h6>
|
||||
<header class="mb-3">
|
||||
@if (Model.Balance is not null)
|
||||
{
|
||||
<div class="balance">
|
||||
<h3 class="d-inline-block me-1">@Model.Balance</h3>
|
||||
<span class="text-secondary fw-semibold">@Model.CryptoCode</span>
|
||||
</div>
|
||||
}
|
||||
<div class="btn-group mt-1" role="group" aria-label="Filter">
|
||||
<div class="balance">
|
||||
<h3 class="d-inline-block me-1">@Model.Balance</h3>
|
||||
<span class="text-secondary fw-semibold">@Model.CryptoCode</span>
|
||||
</div>
|
||||
<div class="btn-group mt-1" role="group" aria-label="Filter">
|
||||
<input type="radio" class="btn-check" name="filter" id="filter-week" value="week" @(Model.Type == WalletHistogramType.Week ? "checked" : "")>
|
||||
<label class="btn btn-link" for="filter-week">1W</label>
|
||||
<input type="radio" class="btn-check" name="filter" id="filter-month" value="month" @(Model.Type == WalletHistogramType.Month ? "checked" : "")>
|
||||
@ -27,8 +24,7 @@
|
||||
const baseUrl = @Safe.Json(Url.Action("WalletHistogram", "UIWallets", new { walletId = Model.WalletId, type = WalletHistogramType.Week }));
|
||||
const render = data => {
|
||||
const { series, labels, balance } = data;
|
||||
if (balance)
|
||||
document.querySelector(`#${id} h3`).innerText = balance;
|
||||
document.querySelector(`#${id} h3`).innerText = balance;
|
||||
const min = Math.min(...series);
|
||||
const max = Math.max(...series);
|
||||
const low = Math.max(min - ((max - min) / 5), 0);
|
||||
|
@ -18,17 +18,16 @@ namespace BTCPayServer.Components.StoreWalletBalance;
|
||||
|
||||
public class StoreWalletBalance : ViewComponent
|
||||
{
|
||||
private string CryptoCode;
|
||||
private const string CryptoCode = "BTC";
|
||||
private const WalletHistogramType DefaultType = WalletHistogramType.Week;
|
||||
|
||||
private readonly StoreRepository _storeRepo;
|
||||
private readonly WalletHistogramService _walletHistogramService;
|
||||
|
||||
public StoreWalletBalance(StoreRepository storeRepo, WalletHistogramService walletHistogramService, BTCPayNetworkProvider networkProvider)
|
||||
public StoreWalletBalance(StoreRepository storeRepo, WalletHistogramService walletHistogramService)
|
||||
{
|
||||
_storeRepo = storeRepo;
|
||||
_walletHistogramService = walletHistogramService;
|
||||
CryptoCode = networkProvider.DefaultNetwork.CryptoCode;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(StoreData store)
|
||||
@ -43,7 +42,7 @@ public class StoreWalletBalance : ViewComponent
|
||||
WalletId = walletId,
|
||||
Series = data?.Series,
|
||||
Labels = data?.Labels,
|
||||
Balance = data?.Balance,
|
||||
Balance = data?.Balance ?? 0,
|
||||
Type = DefaultType
|
||||
};
|
||||
|
||||
|
@ -6,7 +6,7 @@ namespace BTCPayServer.Components.StoreWalletBalance;
|
||||
|
||||
public class StoreWalletBalanceViewModel
|
||||
{
|
||||
public decimal? Balance { get; set; }
|
||||
public decimal Balance { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public StoreData Store { get; set; }
|
||||
public WalletId WalletId { get; set; }
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
@ -41,9 +40,9 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
[Authorize(Policy = Policies.CanUseInternalLightningNode,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/server/lightning/{cryptoCode}/info")]
|
||||
public override Task<IActionResult> GetInfo(string cryptoCode, CancellationToken cancellationToken = default)
|
||||
public override Task<IActionResult> GetInfo(string cryptoCode)
|
||||
{
|
||||
return base.GetInfo(cryptoCode, cancellationToken);
|
||||
return base.GetInfo(cryptoCode);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseInternalLightningNode,
|
||||
@ -57,17 +56,17 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
[Authorize(Policy = Policies.CanUseInternalLightningNode,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/server/lightning/{cryptoCode}/channels")]
|
||||
public override Task<IActionResult> GetChannels(string cryptoCode, CancellationToken cancellationToken = default)
|
||||
public override Task<IActionResult> GetChannels(string cryptoCode)
|
||||
{
|
||||
return base.GetChannels(cryptoCode, cancellationToken);
|
||||
return base.GetChannels(cryptoCode);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseInternalLightningNode,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/server/lightning/{cryptoCode}/channels")]
|
||||
public override Task<IActionResult> OpenChannel(string cryptoCode, OpenLightningChannelRequest request, CancellationToken cancellationToken = default)
|
||||
public override Task<IActionResult> OpenChannel(string cryptoCode, OpenLightningChannelRequest request)
|
||||
{
|
||||
return base.OpenChannel(cryptoCode, request, cancellationToken);
|
||||
return base.OpenChannel(cryptoCode, request);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseInternalLightningNode,
|
||||
@ -81,33 +80,33 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
[Authorize(Policy = Policies.CanUseInternalLightningNode,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/server/lightning/{cryptoCode}/payments/{paymentHash}")]
|
||||
public override Task<IActionResult> GetPayment(string cryptoCode, string paymentHash, CancellationToken cancellationToken = default)
|
||||
public override Task<IActionResult> GetPayment(string cryptoCode, string paymentHash)
|
||||
{
|
||||
return base.GetPayment(cryptoCode, paymentHash, cancellationToken);
|
||||
return base.GetPayment(cryptoCode, paymentHash);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseInternalLightningNode,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/server/lightning/{cryptoCode}/invoices/{id}")]
|
||||
public override Task<IActionResult> GetInvoice(string cryptoCode, string id, CancellationToken cancellationToken = default)
|
||||
public override Task<IActionResult> GetInvoice(string cryptoCode, string id)
|
||||
{
|
||||
return base.GetInvoice(cryptoCode, id, cancellationToken);
|
||||
return base.GetInvoice(cryptoCode, id);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseInternalLightningNode,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/server/lightning/{cryptoCode}/invoices/pay")]
|
||||
public override Task<IActionResult> PayInvoice(string cryptoCode, PayLightningInvoiceRequest lightningInvoice, CancellationToken cancellationToken = default)
|
||||
public override Task<IActionResult> PayInvoice(string cryptoCode, PayLightningInvoiceRequest lightningInvoice)
|
||||
{
|
||||
return base.PayInvoice(cryptoCode, lightningInvoice, cancellationToken);
|
||||
return base.PayInvoice(cryptoCode, lightningInvoice);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanCreateLightningInvoiceInternalNode,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/server/lightning/{cryptoCode}/invoices")]
|
||||
public override Task<IActionResult> CreateInvoice(string cryptoCode, CreateLightningInvoiceRequest request, CancellationToken cancellationToken = default)
|
||||
public override Task<IActionResult> CreateInvoice(string cryptoCode, CreateLightningInvoiceRequest request)
|
||||
{
|
||||
return base.CreateInvoice(cryptoCode, request, cancellationToken);
|
||||
return base.CreateInvoice(cryptoCode, request);
|
||||
}
|
||||
|
||||
protected override async Task<ILightningClient> GetLightningClient(string cryptoCode, bool doingAdminThings)
|
||||
|
@ -1,16 +1,16 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
@ -43,9 +43,9 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/info")]
|
||||
public override Task<IActionResult> GetInfo(string cryptoCode, CancellationToken cancellationToken = default)
|
||||
public override Task<IActionResult> GetInfo(string cryptoCode)
|
||||
{
|
||||
return base.GetInfo(cryptoCode, cancellationToken);
|
||||
return base.GetInfo(cryptoCode);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
|
||||
@ -58,16 +58,16 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/channels")]
|
||||
public override Task<IActionResult> GetChannels(string cryptoCode, CancellationToken cancellationToken = default)
|
||||
public override Task<IActionResult> GetChannels(string cryptoCode)
|
||||
{
|
||||
return base.GetChannels(cryptoCode, cancellationToken);
|
||||
return base.GetChannels(cryptoCode);
|
||||
}
|
||||
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/channels")]
|
||||
public override Task<IActionResult> OpenChannel(string cryptoCode, OpenLightningChannelRequest request, CancellationToken cancellationToken = default)
|
||||
public override Task<IActionResult> OpenChannel(string cryptoCode, OpenLightningChannelRequest request)
|
||||
{
|
||||
return base.OpenChannel(cryptoCode, request, cancellationToken);
|
||||
return base.OpenChannel(cryptoCode, request);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
|
||||
@ -81,50 +81,46 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/payments/{paymentHash}")]
|
||||
public override Task<IActionResult> GetPayment(string cryptoCode, string paymentHash, CancellationToken cancellationToken = default)
|
||||
public override Task<IActionResult> GetPayment(string cryptoCode, string paymentHash)
|
||||
{
|
||||
return base.GetPayment(cryptoCode, paymentHash, cancellationToken);
|
||||
return base.GetPayment(cryptoCode, paymentHash);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices/pay")]
|
||||
public override Task<IActionResult> PayInvoice(string cryptoCode, PayLightningInvoiceRequest lightningInvoice, CancellationToken cancellationToken = default)
|
||||
public override Task<IActionResult> PayInvoice(string cryptoCode, PayLightningInvoiceRequest lightningInvoice)
|
||||
{
|
||||
return base.PayInvoice(cryptoCode, lightningInvoice, cancellationToken);
|
||||
return base.PayInvoice(cryptoCode, lightningInvoice);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices/{id}")]
|
||||
public override Task<IActionResult> GetInvoice(string cryptoCode, string id, CancellationToken cancellationToken = default)
|
||||
public override Task<IActionResult> GetInvoice(string cryptoCode, string id)
|
||||
{
|
||||
return base.GetInvoice(cryptoCode, id, cancellationToken);
|
||||
return base.GetInvoice(cryptoCode, id);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanCreateLightningInvoiceInStore,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices")]
|
||||
public override Task<IActionResult> CreateInvoice(string cryptoCode, CreateLightningInvoiceRequest request, CancellationToken cancellationToken = default)
|
||||
public override Task<IActionResult> CreateInvoice(string cryptoCode, CreateLightningInvoiceRequest request)
|
||||
{
|
||||
return base.CreateInvoice(cryptoCode, request, cancellationToken);
|
||||
return base.CreateInvoice(cryptoCode, request);
|
||||
}
|
||||
|
||||
protected override Task<ILightningClient> GetLightningClient(string cryptoCode,
|
||||
bool doingAdminThings)
|
||||
{
|
||||
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
if (network == null)
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null || network == null)
|
||||
{
|
||||
throw ErrorCryptoCodeNotFound();
|
||||
}
|
||||
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
{
|
||||
throw new JsonHttpException(StoreNotFound());
|
||||
}
|
||||
|
||||
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
@ -147,10 +143,5 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
throw ErrorLightningNodeNotConfiguredForStore();
|
||||
}
|
||||
|
||||
private IActionResult StoreNotFound()
|
||||
{
|
||||
return this.CreateAPIError(404, "store-not-found", "The store was not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,11 +39,11 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
_authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> GetInfo(string cryptoCode, CancellationToken cancellationToken = default)
|
||||
public virtual async Task<IActionResult> GetInfo(string cryptoCode)
|
||||
{
|
||||
var lightningClient = await GetLightningClient(cryptoCode, true);
|
||||
var info = await lightningClient.GetInfo(cancellationToken);
|
||||
return Ok(new LightningNodeInformationData
|
||||
var info = await lightningClient.GetInfo();
|
||||
return Ok(new LightningNodeInformationData()
|
||||
{
|
||||
BlockHeight = info.BlockHeight,
|
||||
NodeURIs = info.NodeInfoList.Select(nodeInfo => nodeInfo).ToArray()
|
||||
@ -75,12 +75,12 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return Ok();
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> GetChannels(string cryptoCode, CancellationToken cancellationToken = default)
|
||||
public virtual async Task<IActionResult> GetChannels(string cryptoCode)
|
||||
{
|
||||
var lightningClient = await GetLightningClient(cryptoCode, true);
|
||||
|
||||
var channels = await lightningClient.ListChannels(cancellationToken);
|
||||
return Ok(channels.Select(channel => new LightningChannelData
|
||||
var channels = await lightningClient.ListChannels();
|
||||
return Ok(channels.Select(channel => new LightningChannelData()
|
||||
{
|
||||
Capacity = channel.Capacity,
|
||||
ChannelPoint = channel.ChannelPoint.ToString(),
|
||||
@ -92,7 +92,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
|
||||
|
||||
public virtual async Task<IActionResult> OpenChannel(string cryptoCode, OpenLightningChannelRequest request, CancellationToken cancellationToken = default)
|
||||
public virtual async Task<IActionResult> OpenChannel(string cryptoCode, OpenLightningChannelRequest request)
|
||||
{
|
||||
var lightningClient = await GetLightningClient(cryptoCode, true);
|
||||
if (request?.NodeURI is null)
|
||||
@ -124,12 +124,12 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
var response = await lightningClient.OpenChannel(new OpenChannelRequest
|
||||
var response = await lightningClient.OpenChannel(new Lightning.OpenChannelRequest()
|
||||
{
|
||||
ChannelAmount = request.ChannelAmount,
|
||||
FeeRate = request.FeeRate,
|
||||
NodeInfo = request.NodeURI
|
||||
}, cancellationToken);
|
||||
});
|
||||
|
||||
string errorCode, errorMessage;
|
||||
switch (response.Result)
|
||||
@ -164,14 +164,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return Ok(new JValue((await lightningClient.GetDepositAddress()).ToString()));
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> GetPayment(string cryptoCode, string paymentHash, CancellationToken cancellationToken = default)
|
||||
public virtual async Task<IActionResult> GetPayment(string cryptoCode, string paymentHash)
|
||||
{
|
||||
var lightningClient = await GetLightningClient(cryptoCode, false);
|
||||
var payment = await lightningClient.GetPayment(paymentHash, cancellationToken);
|
||||
var payment = await lightningClient.GetPayment(paymentHash);
|
||||
return payment == null ? this.CreateAPIError(404, "payment-not-found", "Impossible to find a lightning payment with this payment hash") : Ok(ToModel(payment));
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> PayInvoice(string cryptoCode, PayLightningInvoiceRequest lightningInvoice, CancellationToken cancellationToken = default)
|
||||
public virtual async Task<IActionResult> PayInvoice(string cryptoCode, PayLightningInvoiceRequest lightningInvoice)
|
||||
{
|
||||
var lightningClient = await GetLightningClient(cryptoCode, true);
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
@ -190,7 +190,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
var param = lightningInvoice?.MaxFeeFlat != null || lightningInvoice?.MaxFeePercent != null
|
||||
? new PayInvoiceParams { MaxFeePercent = lightningInvoice.MaxFeePercent, MaxFeeFlat = lightningInvoice.MaxFeeFlat }
|
||||
: null;
|
||||
var result = await lightningClient.Pay(lightningInvoice.BOLT11, param, cancellationToken);
|
||||
var result = await lightningClient.Pay(lightningInvoice.BOLT11, param);
|
||||
|
||||
return result.Result switch
|
||||
{
|
||||
@ -205,14 +205,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
};
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> GetInvoice(string cryptoCode, string id, CancellationToken cancellationToken = default)
|
||||
public virtual async Task<IActionResult> GetInvoice(string cryptoCode, string id)
|
||||
{
|
||||
var lightningClient = await GetLightningClient(cryptoCode, false);
|
||||
var inv = await lightningClient.GetInvoice(id, cancellationToken);
|
||||
var inv = await lightningClient.GetInvoice(id);
|
||||
return inv == null ? this.CreateAPIError(404, "invoice-not-found", "Impossible to find a lightning invoice with this id") : Ok(ToModel(inv));
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> CreateInvoice(string cryptoCode, CreateLightningInvoiceRequest request, CancellationToken cancellationToken = default)
|
||||
public virtual async Task<IActionResult> CreateInvoice(string cryptoCode, CreateLightningInvoiceRequest request)
|
||||
{
|
||||
var lightningClient = await GetLightningClient(cryptoCode, false);
|
||||
if (request.Amount < LightMoney.Zero)
|
||||
@ -241,7 +241,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
PrivateRouteHints = request.PrivateRouteHints, DescriptionHash = request.DescriptionHash
|
||||
};
|
||||
var invoice = await lightningClient.CreateInvoice(param, cancellationToken);
|
||||
var invoice = await lightningClient.CreateInvoice(param, CancellationToken.None);
|
||||
return Ok(ToModel(invoice));
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -1,43 +0,0 @@
|
||||
#nullable enable
|
||||
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;
|
||||
using BTCPayServer.PayoutProcessors;
|
||||
using BTCPayServer.Security;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public class GreenfieldPayoutProcessorsController : ControllerBase
|
||||
{
|
||||
private readonly IEnumerable<IPayoutProcessorFactory> _factories;
|
||||
|
||||
public GreenfieldPayoutProcessorsController(IEnumerable<IPayoutProcessorFactory>factories)
|
||||
{
|
||||
_factories = factories;
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/payout-processors")]
|
||||
public IActionResult GetPayoutProcessors()
|
||||
{
|
||||
return Ok(_factories.Select(factory => new PayoutProcessorData()
|
||||
{
|
||||
Name = factory.Processor,
|
||||
FriendlyName = factory.FriendlyName,
|
||||
PaymentMethods = factory.GetSupportedPaymentMethods().Select(id => id.ToStringNormalized())
|
||||
.ToArray()
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -31,6 +31,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
private readonly CurrencyNameTable _currencyNameTable;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _serializerSettings;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
||||
|
||||
public GreenfieldPullPaymentController(PullPaymentHostedService pullPaymentService,
|
||||
@ -38,6 +39,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
ApplicationDbContextFactory dbContextFactory,
|
||||
CurrencyNameTable currencyNameTable,
|
||||
Services.BTCPayNetworkJsonSerializerSettings serializerSettings,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
IEnumerable<IPayoutHandler> payoutHandlers)
|
||||
{
|
||||
_pullPaymentService = pullPaymentService;
|
||||
@ -45,6 +47,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_currencyNameTable = currencyNameTable;
|
||||
_serializerSettings = serializerSettings;
|
||||
_networkProvider = networkProvider;
|
||||
_payoutHandlers = payoutHandlers;
|
||||
}
|
||||
|
||||
@ -135,8 +138,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
Amount = request.Amount,
|
||||
Currency = request.Currency,
|
||||
StoreId = storeId,
|
||||
PaymentMethodIds = paymentMethods,
|
||||
AutoApproveClaims = request.AutoApproveClaims
|
||||
PaymentMethodIds = paymentMethods
|
||||
});
|
||||
var pp = await _pullPaymentService.GetPullPayment(ppId, false);
|
||||
return this.Ok(CreatePullPaymentData(pp));
|
||||
@ -156,7 +158,6 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
Currency = ppBlob.Currency,
|
||||
Period = ppBlob.Period,
|
||||
Archived = pp.Archived,
|
||||
AutoApproveClaims = ppBlob.AutoApproveClaims,
|
||||
BOLT11Expiration = ppBlob.BOLT11Expiration,
|
||||
ViewLink = _linkGenerator.GetUriByAction(
|
||||
nameof(UIPullPaymentController.ViewPullPayment),
|
||||
@ -190,8 +191,9 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
if (pp is null)
|
||||
return PullPaymentNotFound();
|
||||
var payouts = pp.Payouts.Where(p => p.State != PayoutState.Cancelled || includeCancelled).ToList();
|
||||
var cd = _currencyNameTable.GetCurrencyData(pp.GetBlob().Currency, false);
|
||||
return base.Ok(payouts
|
||||
.Select(ToModel).ToList());
|
||||
.Select(p => ToModel(p, cd)).ToList());
|
||||
}
|
||||
|
||||
[HttpGet("~/api/v1/pull-payments/{pullPaymentId}/payouts/{payoutId}")]
|
||||
@ -207,10 +209,11 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
var payout = pp.Payouts.FirstOrDefault(p => p.Id == payoutId);
|
||||
if (payout is null)
|
||||
return PayoutNotFound();
|
||||
return base.Ok(ToModel(payout));
|
||||
var cd = _currencyNameTable.GetCurrencyData(payout.PullPaymentData.GetBlob().Currency, false);
|
||||
return base.Ok(ToModel(payout, cd));
|
||||
}
|
||||
|
||||
private Client.Models.PayoutData ToModel(Data.PayoutData p)
|
||||
private Client.Models.PayoutData ToModel(Data.PayoutData p, CurrencyData cd)
|
||||
{
|
||||
var blob = p.GetBlob(_serializerSettings);
|
||||
var model = new Client.Models.PayoutData()
|
||||
@ -272,83 +275,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
ModelState.AddModelError(nameof(request.Amount), $"Amount too small (should be at least {ppBlob.MinimumClaim})");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
var result = await _pullPaymentService.Claim(new ClaimRequest()
|
||||
var cd = _currencyNameTable.GetCurrencyData(pp.GetBlob().Currency, false);
|
||||
var result = await _pullPaymentService.Claim(new ClaimRequest()
|
||||
{
|
||||
Destination = destination.destination,
|
||||
PullPaymentId = pullPaymentId,
|
||||
Value = request.Amount,
|
||||
PaymentMethodId = paymentMethodId,
|
||||
PaymentMethodId = paymentMethodId
|
||||
});
|
||||
|
||||
return HandleClaimResult(result);
|
||||
}
|
||||
|
||||
[HttpPost("~/api/v1/stores/{storeId}/payouts")]
|
||||
[Authorize(Policy = Policies.CanManagePullPayments, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> CreatePayoutThroughStore(string storeId, CreatePayoutThroughStoreRequest request)
|
||||
{
|
||||
if (request is null || !PaymentMethodId.TryParse(request?.PaymentMethod, out var paymentMethodId))
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
var payoutHandler = _payoutHandlers.FindPayoutHandler(paymentMethodId);
|
||||
if (payoutHandler is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
await using var ctx = _dbContextFactory.CreateContext();
|
||||
|
||||
|
||||
PullPaymentBlob? ppBlob = null;
|
||||
if (request?.PullPaymentId is not null)
|
||||
{
|
||||
|
||||
var pp = await ctx.PullPayments.FirstOrDefaultAsync(data =>
|
||||
data.Id == request.PullPaymentId && data.StoreId == storeId);
|
||||
if (pp is null)
|
||||
return PullPaymentNotFound();
|
||||
ppBlob = pp.GetBlob();
|
||||
}
|
||||
var destination = await payoutHandler.ParseAndValidateClaimDestination(paymentMethodId, request!.Destination, ppBlob);
|
||||
if (destination.destination is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Destination), destination.error ?? "The destination is invalid for the payment specified");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
if (request.Amount is null && destination.destination.Amount != null)
|
||||
{
|
||||
request.Amount = destination.destination.Amount;
|
||||
}
|
||||
else if (request.Amount != null && destination.destination.Amount != null && request.Amount != destination.destination.Amount)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Amount), $"Amount is implied in destination ({destination.destination.Amount}) that does not match the payout amount provided {request.Amount})");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
if (request.Amount is { } v && (v < ppBlob?.MinimumClaim || v == 0.0m))
|
||||
{
|
||||
var minimumClaim = ppBlob?.MinimumClaim is decimal val ? val : 0.0m;
|
||||
ModelState.AddModelError(nameof(request.Amount), $"Amount too small (should be at least {minimumClaim})");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
var result = await _pullPaymentService.Claim(new ClaimRequest()
|
||||
{
|
||||
Destination = destination.destination,
|
||||
PullPaymentId = request.PullPaymentId,
|
||||
PreApprove = request.Approved,
|
||||
Value = request.Amount,
|
||||
PaymentMethodId = paymentMethodId,
|
||||
StoreId = storeId
|
||||
});
|
||||
return HandleClaimResult(result);
|
||||
}
|
||||
|
||||
private IActionResult HandleClaimResult(ClaimRequest.ClaimResponse result)
|
||||
{
|
||||
switch (result.Result)
|
||||
{
|
||||
case ClaimRequest.ClaimResult.Ok:
|
||||
@ -370,8 +304,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
default:
|
||||
throw new NotSupportedException("Unsupported ClaimResult");
|
||||
}
|
||||
|
||||
return Ok(ToModel(result.PayoutData));
|
||||
return Ok(ToModel(result.PayoutData, cd));
|
||||
}
|
||||
|
||||
[HttpDelete("~/api/v1/stores/{storeId}/pull-payments/{pullPaymentId}")]
|
||||
@ -386,20 +319,6 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payouts")]
|
||||
[Authorize(Policy = Policies.CanManagePullPayments, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> GetStorePayouts(string storeId, bool includeCancelled = false)
|
||||
{
|
||||
await using var ctx = _dbContextFactory.CreateContext();
|
||||
var payouts = await ctx.Payouts
|
||||
.Where(p => p.StoreDataId == storeId && (p.State != PayoutState.Cancelled || includeCancelled))
|
||||
.ToListAsync();
|
||||
return base.Ok(payouts
|
||||
.Select(ToModel).ToList());
|
||||
}
|
||||
|
||||
[HttpDelete("~/api/v1/stores/{storeId}/payouts/{payoutId}")]
|
||||
[Authorize(Policy = Policies.CanManagePullPayments, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> CancelPayout(string storeId, string payoutId)
|
||||
@ -442,6 +361,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
ModelState.AddModelError(nameof(approvePayoutRequest.RateRule), "Invalid RateRule");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
var ppBlob = payout.PullPaymentData.GetBlob();
|
||||
var cd = _currencyNameTable.GetCurrencyData(ppBlob.Currency, false);
|
||||
var result = await _pullPaymentService.Approve(new PullPaymentHostedService.PayoutApproval()
|
||||
{
|
||||
PayoutId = payoutId,
|
||||
@ -452,7 +373,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
switch (result)
|
||||
{
|
||||
case PullPaymentHostedService.PayoutApproval.Result.Ok:
|
||||
return Ok(ToModel(await ctx.Payouts.GetPayout(payoutId, storeId, true)));
|
||||
return Ok(ToModel(await ctx.Payouts.GetPayout(payoutId, storeId, true), cd));
|
||||
case PullPaymentHostedService.PayoutApproval.Result.InvalidState:
|
||||
return this.CreateAPIError("invalid-state", errorMessage);
|
||||
case PullPaymentHostedService.PayoutApproval.Result.TooLowAmount:
|
||||
|
@ -1,94 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data.Data;
|
||||
using BTCPayServer.PayoutProcessors;
|
||||
using BTCPayServer.PayoutProcessors.Lightning;
|
||||
using BTCPayServer.PayoutProcessors.Settings;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PayoutProcessorData = BTCPayServer.Data.Data.PayoutProcessorData;
|
||||
|
||||
namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public class GreenfieldStoreAutomatedLightningPayoutProcessorsController : ControllerBase
|
||||
{
|
||||
private readonly PayoutProcessorService _payoutProcessorService;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
|
||||
public GreenfieldStoreAutomatedLightningPayoutProcessorsController(PayoutProcessorService payoutProcessorService,
|
||||
EventAggregator eventAggregator)
|
||||
{
|
||||
_payoutProcessorService = payoutProcessorService;
|
||||
_eventAggregator = eventAggregator;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payout-processors/" + nameof(LightningAutomatedPayoutSenderFactory))]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payout-processors/" + nameof(LightningAutomatedPayoutSenderFactory) +
|
||||
"/{paymentMethod}")]
|
||||
public async Task<IActionResult> GetStoreLightningAutomatedPayoutProcessors(
|
||||
string storeId, string? paymentMethod)
|
||||
{
|
||||
var configured =
|
||||
await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery()
|
||||
{
|
||||
Stores = new[] {storeId},
|
||||
Processors = new[] {LightningAutomatedPayoutSenderFactory.ProcessorName},
|
||||
PaymentMethods = paymentMethod is null ? null : new[] {paymentMethod}
|
||||
});
|
||||
|
||||
return Ok(configured.Select(ToModel).ToArray());
|
||||
}
|
||||
|
||||
private static LightningAutomatedPayoutSettings ToModel(PayoutProcessorData data)
|
||||
{
|
||||
return new LightningAutomatedPayoutSettings()
|
||||
{
|
||||
PaymentMethod = data.PaymentMethod,
|
||||
IntervalSeconds = InvoiceRepository.FromBytes<AutomatedPayoutBlob>(data.Blob).Interval
|
||||
};
|
||||
}
|
||||
|
||||
private static AutomatedPayoutBlob FromModel(LightningAutomatedPayoutSettings data)
|
||||
{
|
||||
return new AutomatedPayoutBlob() {Interval = data.IntervalSeconds};
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPut("~/api/v1/stores/{storeId}/payout-processors/" + nameof(LightningAutomatedPayoutSenderFactory) +
|
||||
"/{paymentMethod}")]
|
||||
public async Task<IActionResult> UpdateStoreLightningAutomatedPayoutProcessor(
|
||||
string storeId, string paymentMethod, LightningAutomatedPayoutSettings request)
|
||||
{
|
||||
var activeProcessor =
|
||||
(await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery()
|
||||
{
|
||||
Stores = new[] {storeId},
|
||||
Processors = new[] {LightningAutomatedPayoutSenderFactory.ProcessorName},
|
||||
PaymentMethods = new[] {paymentMethod}
|
||||
}))
|
||||
.FirstOrDefault();
|
||||
activeProcessor ??= new PayoutProcessorData();
|
||||
activeProcessor.Blob = InvoiceRepository.ToBytes(FromModel(request));
|
||||
activeProcessor.StoreId = storeId;
|
||||
activeProcessor.PaymentMethod = paymentMethod;
|
||||
activeProcessor.Processor = LightningAutomatedPayoutSenderFactory.ProcessorName;
|
||||
var tcs = new TaskCompletionSource();
|
||||
_eventAggregator.Publish(new PayoutProcessorUpdated()
|
||||
{
|
||||
Data = activeProcessor, Id = activeProcessor.Id, Processed = tcs
|
||||
});
|
||||
await tcs.Task;
|
||||
return Ok(ToModel(activeProcessor));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data.Data;
|
||||
using BTCPayServer.PayoutProcessors;
|
||||
using BTCPayServer.PayoutProcessors.OnChain;
|
||||
using BTCPayServer.PayoutProcessors.Settings;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PayoutProcessorData = BTCPayServer.Data.Data.PayoutProcessorData;
|
||||
|
||||
namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public class GreenfieldStoreAutomatedOnChainPayoutProcessorsController : ControllerBase
|
||||
{
|
||||
private readonly PayoutProcessorService _payoutProcessorService;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
|
||||
public GreenfieldStoreAutomatedOnChainPayoutProcessorsController(PayoutProcessorService payoutProcessorService,
|
||||
EventAggregator eventAggregator)
|
||||
{
|
||||
_payoutProcessorService = payoutProcessorService;
|
||||
_eventAggregator = eventAggregator;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payout-processors/" + nameof(OnChainAutomatedPayoutSenderFactory))]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payout-processors/" + nameof(OnChainAutomatedPayoutSenderFactory) +
|
||||
"/{paymentMethod}")]
|
||||
public async Task<IActionResult> GetStoreOnChainAutomatedPayoutProcessors(
|
||||
string storeId, string? paymentMethod)
|
||||
{
|
||||
var configured =
|
||||
await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery()
|
||||
{
|
||||
Stores = new[] {storeId},
|
||||
Processors = new[] {OnChainAutomatedPayoutSenderFactory.ProcessorName},
|
||||
PaymentMethods = paymentMethod is null ? null : new[] {paymentMethod}
|
||||
});
|
||||
|
||||
return Ok(configured.Select(ToModel).ToArray());
|
||||
}
|
||||
|
||||
private static OnChainAutomatedPayoutSettings ToModel(PayoutProcessorData data)
|
||||
{
|
||||
return new OnChainAutomatedPayoutSettings()
|
||||
{
|
||||
PaymentMethod = data.PaymentMethod,
|
||||
IntervalSeconds = InvoiceRepository.FromBytes<AutomatedPayoutBlob>(data.Blob).Interval
|
||||
};
|
||||
}
|
||||
|
||||
private static AutomatedPayoutBlob FromModel(OnChainAutomatedPayoutSettings data)
|
||||
{
|
||||
return new AutomatedPayoutBlob() {Interval = data.IntervalSeconds};
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPut("~/api/v1/stores/{storeId}/payout-processors/" + nameof(OnChainAutomatedPayoutSenderFactory) +
|
||||
"/{paymentMethod}")]
|
||||
public async Task<IActionResult> UpdateStoreOnchainAutomatedPayoutProcessor(
|
||||
string storeId, string paymentMethod, OnChainAutomatedPayoutSettings request)
|
||||
{
|
||||
var activeProcessor =
|
||||
(await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery()
|
||||
{
|
||||
Stores = new[] {storeId},
|
||||
Processors = new[] {OnChainAutomatedPayoutSenderFactory.ProcessorName},
|
||||
PaymentMethods = new[] {paymentMethod}
|
||||
}))
|
||||
.FirstOrDefault();
|
||||
activeProcessor ??= new PayoutProcessorData();
|
||||
activeProcessor.Blob = InvoiceRepository.ToBytes(FromModel(request));
|
||||
activeProcessor.StoreId = storeId;
|
||||
activeProcessor.PaymentMethod = paymentMethod;
|
||||
activeProcessor.Processor = OnChainAutomatedPayoutSenderFactory.ProcessorName;
|
||||
var tcs = new TaskCompletionSource();
|
||||
_eventAggregator.Publish(new PayoutProcessorUpdated()
|
||||
{
|
||||
Data = activeProcessor, Id = activeProcessor.Id, Processed = tcs
|
||||
});
|
||||
await tcs.Task;
|
||||
return Ok(ToModel(activeProcessor));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -173,7 +173,6 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
string storeId,
|
||||
string cryptoCode,
|
||||
[FromQuery] TransactionStatus[]? statusFilter = null,
|
||||
[FromQuery] string? labelFilter = null,
|
||||
[FromQuery] int skip = 0,
|
||||
[FromQuery] int limit = int.MaxValue
|
||||
)
|
||||
@ -203,23 +202,6 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
filteredFlatList.AddRange(txs.ReplacedTransactions.Transactions);
|
||||
}
|
||||
|
||||
if (labelFilter != null)
|
||||
{
|
||||
filteredFlatList = filteredFlatList.Where(information =>
|
||||
{
|
||||
walletTransactionsInfoAsync.TryGetValue(information.TransactionId.ToString(), out var transactionInfo);
|
||||
|
||||
if (transactionInfo != null)
|
||||
{
|
||||
return transactionInfo.Labels.ContainsKey(labelFilter);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
var result = filteredFlatList.Skip(skip).Take(limit).Select(information =>
|
||||
{
|
||||
walletTransactionsInfoAsync.TryGetValue(information.TransactionId.ToString(), out var transactionInfo);
|
||||
|
@ -1,72 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.PayoutProcessors;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public class GreenfieldStorePayoutProcessorsController : ControllerBase
|
||||
{
|
||||
private readonly PayoutProcessorService _payoutProcessorService;
|
||||
private readonly IEnumerable<IPayoutProcessorFactory> _factories;
|
||||
public GreenfieldStorePayoutProcessorsController(PayoutProcessorService payoutProcessorService, IEnumerable<IPayoutProcessorFactory> factories)
|
||||
{
|
||||
_payoutProcessorService = payoutProcessorService;
|
||||
_factories = factories;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payout-processors")]
|
||||
public async Task<IActionResult> GetStorePayoutProcessors(
|
||||
string storeId)
|
||||
{
|
||||
var configured =
|
||||
(await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery() { Stores = new[] { storeId } }))
|
||||
.GroupBy(data => data.Processor).Select(datas => new PayoutProcessorData()
|
||||
{
|
||||
Name = datas.Key,
|
||||
FriendlyName = _factories.FirstOrDefault(factory => factory.Processor == datas.Key)?.FriendlyName,
|
||||
PaymentMethods = datas.Select(data => data.PaymentMethod).ToArray()
|
||||
});
|
||||
return Ok(configured);
|
||||
|
||||
}
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpDelete("~/api/v1/stores/{storeId}/payout-processors/{processor}/{paymentMethod}")]
|
||||
public async Task<IActionResult> RemoveStorePayoutProcessor(
|
||||
string storeId,string processor,string paymentMethod)
|
||||
{
|
||||
var matched =
|
||||
(await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery()
|
||||
{
|
||||
Stores = new[] { storeId },
|
||||
Processors = new []{ processor},
|
||||
PaymentMethods = new []{paymentMethod}
|
||||
})).FirstOrDefault();
|
||||
if (matched is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource();
|
||||
_payoutProcessorService.EventAggregator.Publish(new PayoutProcessorUpdated()
|
||||
{
|
||||
Id = matched.Id,
|
||||
Processed = tcs
|
||||
});
|
||||
await tcs.Task;
|
||||
return Ok();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -74,8 +74,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return UserNotFound();
|
||||
}
|
||||
[Authorize(Policy = Policies.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/users/{idOrEmail}/lock")]
|
||||
public async Task<IActionResult> LockUser(string idOrEmail, LockUserRequest request )
|
||||
[HttpPost("~/api/v1/users/{idOrEmail}/toggle")]
|
||||
public async Task<IActionResult> ToggleUser(string idOrEmail, bool enabled )
|
||||
{
|
||||
var user = (await _userManager.FindByIdAsync(idOrEmail) ) ?? await _userManager.FindByEmailAsync(idOrEmail);
|
||||
if (user is null)
|
||||
@ -83,10 +83,10 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return UserNotFound();
|
||||
}
|
||||
|
||||
await _userService.ToggleUser(user.Id, request.Locked ? DateTimeOffset.MaxValue : null);
|
||||
await _userService.ToggleUser(user.Id, enabled ? null : DateTimeOffset.MaxValue);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
||||
[Authorize(Policy = Policies.CanViewUsers, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/users/")]
|
||||
public async Task<ActionResult<ApplicationUserData[]>> GetUsers()
|
||||
|
@ -65,10 +65,6 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
private readonly GreenfieldStorePaymentMethodsController _storePaymentMethodsController;
|
||||
private readonly GreenfieldStoreEmailController _greenfieldStoreEmailController;
|
||||
private readonly GreenfieldStoreUsersController _greenfieldStoreUsersController;
|
||||
private readonly GreenfieldStorePayoutProcessorsController _greenfieldStorePayoutProcessorsController;
|
||||
private readonly GreenfieldPayoutProcessorsController _greenfieldPayoutProcessorsController;
|
||||
private readonly GreenfieldStoreAutomatedOnChainPayoutProcessorsController _greenfieldStoreAutomatedOnChainPayoutProcessorsController;
|
||||
private readonly GreenfieldStoreAutomatedLightningPayoutProcessorsController _greenfieldStoreAutomatedLightningPayoutProcessorsController;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public BTCPayServerClientFactory(StoreRepository storeRepository,
|
||||
@ -94,10 +90,6 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
GreenfieldStorePaymentMethodsController storePaymentMethodsController,
|
||||
GreenfieldStoreEmailController greenfieldStoreEmailController,
|
||||
GreenfieldStoreUsersController greenfieldStoreUsersController,
|
||||
GreenfieldStorePayoutProcessorsController greenfieldStorePayoutProcessorsController,
|
||||
GreenfieldPayoutProcessorsController greenfieldPayoutProcessorsController,
|
||||
GreenfieldStoreAutomatedOnChainPayoutProcessorsController greenfieldStoreAutomatedOnChainPayoutProcessorsController,
|
||||
GreenfieldStoreAutomatedLightningPayoutProcessorsController greenfieldStoreAutomatedLightningPayoutProcessorsController,
|
||||
IServiceProvider serviceProvider)
|
||||
{
|
||||
_storeRepository = storeRepository;
|
||||
@ -123,10 +115,6 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
_storePaymentMethodsController = storePaymentMethodsController;
|
||||
_greenfieldStoreEmailController = greenfieldStoreEmailController;
|
||||
_greenfieldStoreUsersController = greenfieldStoreUsersController;
|
||||
_greenfieldStorePayoutProcessorsController = greenfieldStorePayoutProcessorsController;
|
||||
_greenfieldPayoutProcessorsController = greenfieldPayoutProcessorsController;
|
||||
_greenfieldStoreAutomatedOnChainPayoutProcessorsController = greenfieldStoreAutomatedOnChainPayoutProcessorsController;
|
||||
_greenfieldStoreAutomatedLightningPayoutProcessorsController = greenfieldStoreAutomatedLightningPayoutProcessorsController;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
@ -200,18 +188,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
_storePaymentMethodsController,
|
||||
_greenfieldStoreEmailController,
|
||||
_greenfieldStoreUsersController,
|
||||
_greenfieldStorePayoutProcessorsController,
|
||||
_greenfieldPayoutProcessorsController,
|
||||
_greenfieldStoreAutomatedOnChainPayoutProcessorsController,
|
||||
_greenfieldStoreAutomatedLightningPayoutProcessorsController,
|
||||
new LocalHttpContextAccessor() { HttpContext = context }
|
||||
new LocalHttpContextAccessor() {HttpContext = context}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public class LocalHttpContextAccessor : IHttpContextAccessor
|
||||
{
|
||||
public HttpContext HttpContext { get; set; }
|
||||
public HttpContext? HttpContext { get; set; }
|
||||
}
|
||||
|
||||
public class LocalBTCPayServerClient : BTCPayServerClient
|
||||
@ -238,10 +222,6 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
private readonly UIHomeController _homeController;
|
||||
private readonly GreenfieldStorePaymentMethodsController _storePaymentMethodsController;
|
||||
private readonly GreenfieldStoreEmailController _greenfieldStoreEmailController;
|
||||
private readonly GreenfieldStorePayoutProcessorsController _greenfieldStorePayoutProcessorsController;
|
||||
private readonly GreenfieldPayoutProcessorsController _greenfieldPayoutProcessorsController;
|
||||
private readonly GreenfieldStoreAutomatedOnChainPayoutProcessorsController _greenfieldStoreAutomatedOnChainPayoutProcessorsController;
|
||||
private readonly GreenfieldStoreAutomatedLightningPayoutProcessorsController _greenfieldStoreAutomatedLightningPayoutProcessorsController;
|
||||
private readonly GreenfieldStoreUsersController _greenfieldStoreUsersController;
|
||||
|
||||
public LocalBTCPayServerClient(
|
||||
@ -266,10 +246,6 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
GreenfieldStorePaymentMethodsController storePaymentMethodsController,
|
||||
GreenfieldStoreEmailController greenfieldStoreEmailController,
|
||||
GreenfieldStoreUsersController greenfieldStoreUsersController,
|
||||
GreenfieldStorePayoutProcessorsController greenfieldStorePayoutProcessorsController,
|
||||
GreenfieldPayoutProcessorsController greenfieldPayoutProcessorsController,
|
||||
GreenfieldStoreAutomatedOnChainPayoutProcessorsController greenfieldStoreAutomatedOnChainPayoutProcessorsController,
|
||||
GreenfieldStoreAutomatedLightningPayoutProcessorsController greenfieldStoreAutomatedLightningPayoutProcessorsController,
|
||||
IHttpContextAccessor httpContextAccessor) : base(new Uri("https://dummy.local"), "", "")
|
||||
{
|
||||
_chainPaymentMethodsController = chainPaymentMethodsController;
|
||||
@ -292,10 +268,6 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
_storePaymentMethodsController = storePaymentMethodsController;
|
||||
_greenfieldStoreEmailController = greenfieldStoreEmailController;
|
||||
_greenfieldStoreUsersController = greenfieldStoreUsersController;
|
||||
_greenfieldStorePayoutProcessorsController = greenfieldStorePayoutProcessorsController;
|
||||
_greenfieldPayoutProcessorsController = greenfieldPayoutProcessorsController;
|
||||
_greenfieldStoreAutomatedOnChainPayoutProcessorsController = greenfieldStoreAutomatedOnChainPayoutProcessorsController;
|
||||
_greenfieldStoreAutomatedLightningPayoutProcessorsController = greenfieldStoreAutomatedLightningPayoutProcessorsController;
|
||||
|
||||
var controllers = new[]
|
||||
{
|
||||
@ -304,11 +276,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
storeLightningNetworkPaymentMethodsController, greenFieldInvoiceController, storeWebhooksController,
|
||||
greenFieldServerInfoController, greenfieldPullPaymentController, storesController, homeController,
|
||||
lightningNodeApiController, storeLightningNodeApiController as ControllerBase,
|
||||
storePaymentMethodsController, greenfieldStoreEmailController, greenfieldStoreUsersController,
|
||||
lightningNodeApiController, storeLightningNodeApiController as ControllerBase, storePaymentMethodsController,
|
||||
greenfieldStoreEmailController, greenfieldStorePayoutProcessorsController, greenfieldPayoutProcessorsController,
|
||||
greenfieldStoreAutomatedOnChainPayoutProcessorsController,
|
||||
greenfieldStoreAutomatedLightningPayoutProcessorsController,
|
||||
storePaymentMethodsController, greenfieldStoreEmailController, greenfieldStoreUsersController
|
||||
};
|
||||
|
||||
var authoverride = new DefaultAuthorizationService(
|
||||
@ -479,7 +447,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
|
||||
public override async Task<PayoutData> ApprovePayout(string storeId, string payoutId,
|
||||
ApprovePayoutRequest request, CancellationToken cancellationToken = default)
|
||||
ApprovePayoutRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return GetFromActionResult<PayoutData>(
|
||||
await _greenfieldPullPaymentController.ApprovePayout(storeId, payoutId, request, cancellationToken));
|
||||
@ -489,11 +458,12 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LightningNodeInformationData>(
|
||||
await _storeLightningNodeApiController.GetInfo(cryptoCode, token));
|
||||
await _storeLightningNodeApiController.GetInfo(cryptoCode));
|
||||
}
|
||||
|
||||
public override async Task ConnectToLightningNode(string storeId, string cryptoCode,
|
||||
ConnectToNodeRequest request, CancellationToken token = default)
|
||||
ConnectToNodeRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
HandleActionResult(await _storeLightningNodeApiController.ConnectToNode(cryptoCode, request));
|
||||
}
|
||||
@ -502,14 +472,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
string cryptoCode, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<IEnumerable<LightningChannelData>>(
|
||||
await _storeLightningNodeApiController.GetChannels(cryptoCode, token));
|
||||
await _storeLightningNodeApiController.GetChannels(cryptoCode));
|
||||
}
|
||||
|
||||
public override async Task OpenLightningChannel(string storeId, string cryptoCode,
|
||||
OpenLightningChannelRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
HandleActionResult(await _storeLightningNodeApiController.OpenChannel(cryptoCode, request, token));
|
||||
HandleActionResult(await _storeLightningNodeApiController.OpenChannel(cryptoCode, request));
|
||||
}
|
||||
|
||||
public override async Task<string> GetLightningDepositAddress(string storeId, string cryptoCode,
|
||||
@ -520,23 +490,25 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
|
||||
public override async Task PayLightningInvoice(string storeId, string cryptoCode,
|
||||
PayLightningInvoiceRequest request, CancellationToken token = default)
|
||||
PayLightningInvoiceRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
HandleActionResult(await _storeLightningNodeApiController.PayInvoice(cryptoCode, request, token));
|
||||
HandleActionResult(await _storeLightningNodeApiController.PayInvoice(cryptoCode, request));
|
||||
}
|
||||
|
||||
public override async Task<LightningInvoiceData> GetLightningInvoice(string storeId, string cryptoCode,
|
||||
string invoiceId, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LightningInvoiceData>(
|
||||
await _storeLightningNodeApiController.GetInvoice(cryptoCode, invoiceId, token));
|
||||
await _storeLightningNodeApiController.GetInvoice(cryptoCode, invoiceId));
|
||||
}
|
||||
|
||||
public override async Task<LightningInvoiceData> CreateLightningInvoice(string storeId, string cryptoCode,
|
||||
CreateLightningInvoiceRequest request, CancellationToken token = default)
|
||||
CreateLightningInvoiceRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LightningInvoiceData>(
|
||||
await _storeLightningNodeApiController.CreateInvoice(cryptoCode, request, token));
|
||||
await _storeLightningNodeApiController.CreateInvoice(cryptoCode, request));
|
||||
}
|
||||
|
||||
public override async Task<LightningNodeInformationData> GetLightningNodeInfo(string cryptoCode,
|
||||
@ -556,13 +528,13 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<IEnumerable<LightningChannelData>>(
|
||||
await _lightningNodeApiController.GetChannels(cryptoCode, token));
|
||||
await _lightningNodeApiController.GetChannels(cryptoCode));
|
||||
}
|
||||
|
||||
public override async Task OpenLightningChannel(string cryptoCode, OpenLightningChannelRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
HandleActionResult(await _lightningNodeApiController.OpenChannel(cryptoCode, request, token));
|
||||
HandleActionResult(await _lightningNodeApiController.OpenChannel(cryptoCode, request));
|
||||
}
|
||||
|
||||
public override async Task<string> GetLightningDepositAddress(string cryptoCode,
|
||||
@ -573,17 +545,18 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
|
||||
public override async Task<LightningPaymentData> PayLightningInvoice(string cryptoCode,
|
||||
PayLightningInvoiceRequest request, CancellationToken token = default)
|
||||
PayLightningInvoiceRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LightningPaymentData>(
|
||||
await _lightningNodeApiController.PayInvoice(cryptoCode, request, token));
|
||||
await _lightningNodeApiController.PayInvoice(cryptoCode, request));
|
||||
}
|
||||
|
||||
public override async Task<LightningInvoiceData> GetLightningInvoice(string cryptoCode, string invoiceId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LightningInvoiceData>(
|
||||
await _lightningNodeApiController.GetInvoice(cryptoCode, invoiceId, token));
|
||||
await _lightningNodeApiController.GetInvoice(cryptoCode, invoiceId));
|
||||
}
|
||||
|
||||
public override async Task<LightningInvoiceData> CreateLightningInvoice(string cryptoCode,
|
||||
@ -591,18 +564,21 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LightningInvoiceData>(
|
||||
await _lightningNodeApiController.CreateInvoice(cryptoCode, request, token));
|
||||
await _lightningNodeApiController.CreateInvoice(cryptoCode, request));
|
||||
}
|
||||
|
||||
private T GetFromActionResult<T>(IActionResult result)
|
||||
{
|
||||
HandleActionResult(result);
|
||||
return result switch
|
||||
switch (result)
|
||||
{
|
||||
JsonResult jsonResult => (T)jsonResult.Value,
|
||||
OkObjectResult { Value: T res } => res,
|
||||
_ => default
|
||||
};
|
||||
case JsonResult jsonResult:
|
||||
return (T)jsonResult.Value;
|
||||
case OkObjectResult {Value: T res}:
|
||||
return res;
|
||||
default:
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleActionResult(IActionResult result)
|
||||
@ -631,7 +607,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<OnChainPaymentMethodData>> GetStoreOnChainPaymentMethods(string storeId,
|
||||
bool? enabled, CancellationToken token = default)
|
||||
bool? enabled, CancellationToken token)
|
||||
{
|
||||
return Task.FromResult(
|
||||
GetFromActionResult(_chainPaymentMethodsController.GetOnChainPaymentMethods(storeId, enabled)));
|
||||
@ -651,7 +627,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
|
||||
public override async Task<OnChainPaymentMethodData> UpdateStoreOnChainPaymentMethod(string storeId,
|
||||
string cryptoCode, UpdateOnChainPaymentMethodRequest paymentMethod, CancellationToken token = default)
|
||||
string cryptoCode, UpdateOnChainPaymentMethodRequest paymentMethod,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<OnChainPaymentMethodData>(
|
||||
await _chainPaymentMethodsController.UpdateOnChainPaymentMethod(storeId, cryptoCode,
|
||||
@ -674,7 +651,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
|
||||
public override Task<OnChainPaymentMethodPreviewResultData> PreviewStoreOnChainPaymentMethodAddresses(
|
||||
string storeId, string cryptoCode, int offset = 0, int amount = 10, CancellationToken token = default)
|
||||
string storeId, string cryptoCode, int offset = 0, int amount = 10,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(GetFromActionResult<OnChainPaymentMethodPreviewResultData>(
|
||||
_chainPaymentMethodsController.GetOnChainPaymentMethodPreview(storeId, cryptoCode, offset,
|
||||
@ -803,7 +781,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
|
||||
public override async Task<IEnumerable<OnChainWalletTransactionData>> ShowOnChainWalletTransactions(
|
||||
string storeId, string cryptoCode, TransactionStatus[] statusFilter = null, string labelFilter = null,
|
||||
string storeId, string cryptoCode, TransactionStatus[] statusFilter = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<IEnumerable<OnChainWalletTransactionData>>(
|
||||
@ -811,7 +789,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
|
||||
public override async Task<OnChainWalletTransactionData> GetOnChainWalletTransaction(string storeId,
|
||||
string cryptoCode, string transactionId, CancellationToken token = default)
|
||||
string cryptoCode, string transactionId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<OnChainWalletTransactionData>(
|
||||
await _storeOnChainWalletsController.GetOnChainWalletTransaction(storeId, cryptoCode, transactionId));
|
||||
@ -825,7 +804,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
|
||||
public override async Task<OnChainWalletTransactionData> CreateOnChainTransaction(string storeId,
|
||||
string cryptoCode, CreateOnChainTransactionRequest request, CancellationToken token = default)
|
||||
string cryptoCode, CreateOnChainTransactionRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
if (!request.ProceedWithBroadcast)
|
||||
{
|
||||
@ -1120,12 +1100,10 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
return GetFromActionResult<ApplicationUserData>(await _usersController.GetUser(idOrEmail));
|
||||
}
|
||||
public override async Task LockUser(string idOrEmail, bool disabled, CancellationToken token = default)
|
||||
|
||||
public override async Task ToggleUser(string idOrEmail, bool enabled, CancellationToken token = default)
|
||||
{
|
||||
HandleActionResult(await _usersController.LockUser(idOrEmail, new LockUserRequest()
|
||||
{
|
||||
Locked = disabled
|
||||
}));
|
||||
HandleActionResult(await _usersController.ToggleUser(idOrEmail, enabled));
|
||||
}
|
||||
|
||||
public override async Task<OnChainWalletTransactionData> PatchOnChainWalletTransaction(string storeId, string cryptoCode, string transactionId,
|
||||
@ -1136,73 +1114,12 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
|
||||
public override async Task<LightningPaymentData> GetLightningPayment(string cryptoCode, string paymentHash, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LightningPaymentData>(await _lightningNodeApiController.GetPayment(cryptoCode, paymentHash, token));
|
||||
return GetFromActionResult<LightningPaymentData>(await _lightningNodeApiController.GetPayment(cryptoCode, paymentHash));
|
||||
}
|
||||
|
||||
public override async Task<LightningPaymentData> GetLightningPayment(string storeId, string cryptoCode, string paymentHash, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LightningPaymentData>(await _storeLightningNodeApiController.GetPayment(cryptoCode, paymentHash, token));
|
||||
}
|
||||
public override async Task<PayoutData> CreatePayout(string storeId, CreatePayoutThroughStoreRequest payoutRequest,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return GetFromActionResult<PayoutData>(
|
||||
await _greenfieldPullPaymentController.CreatePayoutThroughStore(storeId, payoutRequest));
|
||||
}
|
||||
|
||||
public override async Task<IEnumerable<PayoutProcessorData>> GetPayoutProcessors(string storeId, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<IEnumerable<PayoutProcessorData>>(await _greenfieldStorePayoutProcessorsController.GetStorePayoutProcessors(storeId));
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<PayoutProcessorData>> GetPayoutProcessors(CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(GetFromActionResult<IEnumerable<PayoutProcessorData>>(_greenfieldPayoutProcessorsController.GetPayoutProcessors()));
|
||||
}
|
||||
|
||||
public override async Task RemovePayoutProcessor(string storeId, string processor, string paymentMethod, CancellationToken token = default)
|
||||
{
|
||||
HandleActionResult(await _greenfieldStorePayoutProcessorsController.RemoveStorePayoutProcessor(storeId, processor, paymentMethod));
|
||||
}
|
||||
|
||||
public override async Task<IEnumerable<OnChainAutomatedPayoutSettings>>
|
||||
GetStoreOnChainAutomatedPayoutProcessors(string storeId, string paymentMethod = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<IEnumerable<OnChainAutomatedPayoutSettings>>(
|
||||
await _greenfieldStoreAutomatedOnChainPayoutProcessorsController
|
||||
.GetStoreOnChainAutomatedPayoutProcessors(storeId, paymentMethod));
|
||||
}
|
||||
|
||||
public override async Task<IEnumerable<LightningAutomatedPayoutSettings>> GetStoreLightningAutomatedPayoutProcessors(string storeId, string paymentMethod = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<IEnumerable<LightningAutomatedPayoutSettings>>(
|
||||
await _greenfieldStoreAutomatedLightningPayoutProcessorsController
|
||||
.GetStoreLightningAutomatedPayoutProcessors(storeId, paymentMethod));
|
||||
}
|
||||
|
||||
public override async Task<OnChainAutomatedPayoutSettings> UpdateStoreOnChainAutomatedPayoutProcessors(string storeId, string paymentMethod,
|
||||
OnChainAutomatedPayoutSettings request, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<OnChainAutomatedPayoutSettings>(
|
||||
await _greenfieldStoreAutomatedOnChainPayoutProcessorsController
|
||||
.UpdateStoreOnchainAutomatedPayoutProcessor(storeId, paymentMethod, request));
|
||||
}
|
||||
|
||||
public override async Task<LightningAutomatedPayoutSettings> UpdateStoreLightningAutomatedPayoutProcessors(string storeId, string paymentMethod,
|
||||
LightningAutomatedPayoutSettings request, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LightningAutomatedPayoutSettings>(
|
||||
await _greenfieldStoreAutomatedLightningPayoutProcessorsController
|
||||
.UpdateStoreLightningAutomatedPayoutProcessor(storeId, paymentMethod, request));
|
||||
}
|
||||
|
||||
public override async Task<PayoutData[]> GetStorePayouts(string storeId, bool includeCancelled = false, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return GetFromActionResult<PayoutData[]>(
|
||||
await _greenfieldPullPaymentController
|
||||
.GetStorePayouts(storeId,includeCancelled));
|
||||
return GetFromActionResult<LightningPaymentData>(await _storeLightningNodeApiController.GetPayment(cryptoCode, paymentHash));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,122 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace BTCPayServer;
|
||||
|
||||
public class LightningAddressService
|
||||
{
|
||||
private readonly ApplicationDbContextFactory _applicationDbContextFactory;
|
||||
private readonly IMemoryCache _memoryCache;
|
||||
|
||||
public LightningAddressService(ApplicationDbContextFactory applicationDbContextFactory, IMemoryCache memoryCache)
|
||||
{
|
||||
_applicationDbContextFactory = applicationDbContextFactory;
|
||||
_memoryCache = memoryCache;
|
||||
}
|
||||
|
||||
public async Task<List<LightningAddressData>> Get(LightningAddressQuery query)
|
||||
{
|
||||
await using var context = _applicationDbContextFactory.CreateContext();
|
||||
return await GetCore(context, query);
|
||||
}
|
||||
|
||||
private async Task<List<LightningAddressData>> GetCore(ApplicationDbContext context, LightningAddressQuery query)
|
||||
{
|
||||
IQueryable<LightningAddressData?> queryable = context.LightningAddresses.AsQueryable();
|
||||
query.Usernames = query.Usernames?.Select(NormalizeUsername)?.ToArray();
|
||||
if (query.Usernames is not null)
|
||||
{
|
||||
queryable = queryable.Where(data => query.Usernames.Contains(data!.Username));
|
||||
}
|
||||
|
||||
if (query.StoreIds is not null)
|
||||
{
|
||||
queryable = queryable.Where(data => query.StoreIds.Contains(data!.StoreDataId));
|
||||
}
|
||||
|
||||
#pragma warning disable CS8619 // Nullability of reference types in value doesn't match target type.
|
||||
return await queryable.ToListAsync();
|
||||
#pragma warning restore CS8619 // Nullability of reference types in value doesn't match target type.
|
||||
}
|
||||
|
||||
public async Task<LightningAddressData?> ResolveByAddress(string username)
|
||||
{
|
||||
return await _memoryCache.GetOrCreateAsync(GetKey(username), async entry =>
|
||||
{
|
||||
var result = await Get(new LightningAddressQuery() {Usernames = new[] {username}});
|
||||
return result.FirstOrDefault();
|
||||
});
|
||||
}
|
||||
|
||||
private string NormalizeUsername(string username)
|
||||
{
|
||||
return username.ToLowerInvariant();
|
||||
}
|
||||
|
||||
public async Task<bool> Set(LightningAddressData data)
|
||||
{
|
||||
await using var context = _applicationDbContextFactory.CreateContext();
|
||||
var result = (await GetCore(context, new LightningAddressQuery() {Usernames = new[] {data.Username}}))
|
||||
.FirstOrDefault();
|
||||
if (result is not null)
|
||||
{
|
||||
if (result.StoreDataId != data.StoreDataId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
context.Remove(result);
|
||||
}
|
||||
|
||||
data.Username = NormalizeUsername(data.Username);
|
||||
await context.AddAsync(data);
|
||||
await context.SaveChangesAsync();
|
||||
_memoryCache.Remove(GetKey(data.Username));
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> Remove(string username, string? storeId = null)
|
||||
{
|
||||
await using var context = _applicationDbContextFactory.CreateContext();
|
||||
var x = (await GetCore(context, new LightningAddressQuery() {Usernames = new[] {username}})).FirstOrDefault();
|
||||
if (x is null) return true;
|
||||
if (storeId is not null && x.StoreDataId != storeId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
context.Remove(x);
|
||||
await context.SaveChangesAsync();
|
||||
_memoryCache.Remove(GetKey(username));
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task Set(LightningAddressData data, ApplicationDbContext context)
|
||||
{
|
||||
var result = (await GetCore(context, new LightningAddressQuery() {Usernames = new[] {data.Username}}))
|
||||
.FirstOrDefault();
|
||||
if (result is not null)
|
||||
{
|
||||
if (result.StoreDataId != data.StoreDataId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
context.Remove(result);
|
||||
}
|
||||
|
||||
await context.AddAsync(data);
|
||||
}
|
||||
|
||||
|
||||
private string GetKey(string username)
|
||||
{
|
||||
username = NormalizeUsername(username);
|
||||
return $"{nameof(LightningAddressService)}_{username}";
|
||||
}
|
||||
}
|
@ -144,11 +144,4 @@ namespace BTCPayServer
|
||||
return await context.Fido2Credentials.Where(fDevice => fDevice.ApplicationUserId == userId && fDevice.Type == Fido2Credential.CredentialType.LNURLAuth).AnyAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public class LightningAddressQuery
|
||||
{
|
||||
public string[] StoreIds { get; set; }
|
||||
public string[] Usernames { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ namespace BTCPayServer.Controllers
|
||||
if (result.IsLockedOut)
|
||||
{
|
||||
_logger.LogWarning($"User '{user.Id}' account locked out.");
|
||||
return RedirectToAction(nameof(Lockout), new { user.LockoutEnd});
|
||||
return RedirectToAction(nameof(Lockout));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -428,7 +428,7 @@ namespace BTCPayServer.Controllers
|
||||
else if (result.IsLockedOut)
|
||||
{
|
||||
_logger.LogWarning("User with ID {UserId} account locked out.", user.Id);
|
||||
return RedirectToAction(nameof(Lockout), new { user.LockoutEnd});
|
||||
return RedirectToAction(nameof(Lockout));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -497,8 +497,7 @@ namespace BTCPayServer.Controllers
|
||||
if (result.IsLockedOut)
|
||||
{
|
||||
_logger.LogWarning("User with ID {UserId} account locked out.", user.Id);
|
||||
|
||||
return RedirectToAction(nameof(Lockout), new { user.LockoutEnd});
|
||||
return RedirectToAction(nameof(Lockout));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -510,9 +509,9 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpGet("/login/lockout")]
|
||||
[AllowAnonymous]
|
||||
public IActionResult Lockout(DateTimeOffset? lockoutEnd)
|
||||
public IActionResult Lockout()
|
||||
{
|
||||
return View(lockoutEnd);
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpGet("/register")]
|
||||
|
@ -43,10 +43,8 @@ namespace BTCPayServer.Controllers
|
||||
[HttpGet("/apps/{appId}")]
|
||||
public async Task<IActionResult> RedirectToApp(string appId)
|
||||
{
|
||||
var app = await _AppService.GetApp(appId, null);
|
||||
if (app is null)
|
||||
return NotFound();
|
||||
switch (app.AppType)
|
||||
|
||||
switch ((await _AppService.GetApp(appId, null)).AppType)
|
||||
{
|
||||
case nameof(AppType.Crowdfund):
|
||||
return RedirectToAction("ViewCrowdfund", new { appId });
|
||||
@ -221,7 +219,7 @@ namespace BTCPayServer.Controllers
|
||||
Currency = settings.Currency,
|
||||
Price = price,
|
||||
BuyerEmail = email,
|
||||
OrderId = orderId ?? AppService.GetPosOrderId(appId),
|
||||
OrderId = orderId,
|
||||
NotificationURL =
|
||||
string.IsNullOrEmpty(notificationUrl) ? settings.NotificationUrl : notificationUrl,
|
||||
RedirectURL = !string.IsNullOrEmpty(redirectUrl) ? redirectUrl
|
||||
|
@ -135,12 +135,6 @@ namespace BTCPayServer.Controllers
|
||||
var servers = new JArray();
|
||||
servers.Add(new JObject(new JProperty("url", HttpContext.Request.GetAbsoluteRoot())));
|
||||
json["servers"] = servers;
|
||||
var tags = (JArray)json["tags"];
|
||||
json["tags"] = new JArray(tags
|
||||
.Select(o => (name: ((JObject)o)["name"].Value<string>(), o))
|
||||
.OrderBy(o => o.name)
|
||||
.Select(o => o.o)
|
||||
.ToArray());
|
||||
return Json(json);
|
||||
}
|
||||
[Route("docs")]
|
||||
|
@ -224,7 +224,7 @@ namespace BTCPayServer.Controllers
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> Refund(string invoiceId, RefundModel model, CancellationToken cancellationToken)
|
||||
{
|
||||
await using var ctx = _dbContextFactory.CreateContext();
|
||||
using var ctx = _dbContextFactory.CreateContext();
|
||||
|
||||
var invoice = GetCurrentInvoice();
|
||||
if (invoice == null)
|
||||
@ -288,25 +288,21 @@ namespace BTCPayServer.Controllers
|
||||
Name = $"Refund {invoice.Id}",
|
||||
PaymentMethodIds = new[] { paymentMethodId },
|
||||
StoreId = invoice.StoreId,
|
||||
BOLT11Expiration = store.GetStoreBlob().RefundBOLT11Expiration,
|
||||
//AutoApproveClaims = true
|
||||
BOLT11Expiration = store.GetStoreBlob().RefundBOLT11Expiration
|
||||
};
|
||||
switch (model.SelectedRefundOption)
|
||||
{
|
||||
case "RateThen":
|
||||
createPullPayment.Currency = paymentMethodId.CryptoCode;
|
||||
createPullPayment.Amount = model.CryptoAmountThen;
|
||||
createPullPayment.AutoApproveClaims = true;
|
||||
break;
|
||||
case "CurrentRate":
|
||||
createPullPayment.Currency = paymentMethodId.CryptoCode;
|
||||
createPullPayment.Amount = model.CryptoAmountNow;
|
||||
createPullPayment.AutoApproveClaims = true;
|
||||
break;
|
||||
case "Fiat":
|
||||
createPullPayment.Currency = invoice.Currency;
|
||||
createPullPayment.Amount = model.FiatAmount;
|
||||
createPullPayment.AutoApproveClaims = false;
|
||||
break;
|
||||
case "Custom":
|
||||
model.Title = "How much to refund?";
|
||||
@ -352,11 +348,10 @@ namespace BTCPayServer.Controllers
|
||||
createPullPayment = new CreatePullPayment
|
||||
{
|
||||
Name = $"Refund {invoice.Id}",
|
||||
PaymentMethodIds = new[] {paymentMethodId},
|
||||
PaymentMethodIds = new[] { paymentMethodId },
|
||||
StoreId = invoice.StoreId,
|
||||
Currency = model.CustomCurrency,
|
||||
Amount = model.CustomAmount,
|
||||
AutoApproveClaims = paymentMethodId.CryptoCode == model.CustomCurrency
|
||||
Amount = model.CustomAmount
|
||||
};
|
||||
break;
|
||||
default:
|
||||
@ -573,8 +568,8 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
if (paymentMethodId is null)
|
||||
{
|
||||
paymentMethodId = enabledPaymentIds.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == PaymentTypes.BTCLike) ??
|
||||
enabledPaymentIds.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == PaymentTypes.LightningLike) ??
|
||||
paymentMethodId = enabledPaymentIds.FirstOrDefault(e => e.CryptoCode == "BTC" && e.PaymentType == PaymentTypes.BTCLike) ??
|
||||
enabledPaymentIds.FirstOrDefault(e => e.CryptoCode == "BTC" && e.PaymentType == PaymentTypes.LightningLike) ??
|
||||
enabledPaymentIds.FirstOrDefault();
|
||||
}
|
||||
isDefaultPaymentId = true;
|
||||
@ -694,7 +689,7 @@ namespace BTCPayServer.Controllers
|
||||
})
|
||||
};
|
||||
}).Where(c => c.CryptoImage != "/")
|
||||
.OrderByDescending(a => a.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode).ThenBy(a => a.PaymentMethodName).ThenBy(a => a.IsLightning ? 1 : 0)
|
||||
.OrderByDescending(a => a.CryptoCode == "BTC").ThenBy(a => a.PaymentMethodName).ThenBy(a => a.IsLightning ? 1 : 0)
|
||||
.ToList()
|
||||
};
|
||||
paymentMethodHandler.PreparePaymentModel(model, dto, storeBlob, paymentMethod);
|
||||
|
@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@ -17,6 +18,7 @@ using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
@ -24,6 +26,7 @@ using BTCPayServer.Services.Stores;
|
||||
using LNURL;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Crypto;
|
||||
@ -41,10 +44,9 @@ namespace BTCPayServer
|
||||
private readonly LightningLikePaymentHandler _lightningLikePaymentHandler;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly AppService _appService;
|
||||
|
||||
private readonly UIInvoiceController _invoiceController;
|
||||
private readonly SettingsRepository _settingsRepository;
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
private readonly LightningAddressService _lightningAddressService;
|
||||
|
||||
public UILNURLController(InvoiceRepository invoiceRepository,
|
||||
EventAggregator eventAggregator,
|
||||
@ -53,8 +55,8 @@ namespace BTCPayServer
|
||||
StoreRepository storeRepository,
|
||||
AppService appService,
|
||||
UIInvoiceController invoiceController,
|
||||
LinkGenerator linkGenerator,
|
||||
LightningAddressService lightningAddressService)
|
||||
SettingsRepository settingsRepository,
|
||||
LinkGenerator linkGenerator)
|
||||
{
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_eventAggregator = eventAggregator;
|
||||
@ -63,8 +65,8 @@ namespace BTCPayServer
|
||||
_storeRepository = storeRepository;
|
||||
_appService = appService;
|
||||
_invoiceController = invoiceController;
|
||||
_settingsRepository = settingsRepository;
|
||||
_linkGenerator = linkGenerator;
|
||||
_lightningAddressService = lightningAddressService;
|
||||
}
|
||||
|
||||
|
||||
@ -88,7 +90,6 @@ namespace BTCPayServer
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(itemCode))
|
||||
{
|
||||
return NotFound();
|
||||
@ -132,7 +133,7 @@ namespace BTCPayServer
|
||||
}
|
||||
|
||||
return await GetLNURL(cryptoCode, app.StoreDataId, currencyCode, null, null,
|
||||
() => (null, app, item, new List<string> {AppService.GetAppInternalTag(appId)}, item.Price.Value, true));
|
||||
() => (null, new List<string> { AppService.GetAppInternalTag(appId) }, item.Price.Value, true));
|
||||
}
|
||||
|
||||
public class EditLightningAddressVM
|
||||
@ -145,7 +146,7 @@ namespace BTCPayServer
|
||||
}
|
||||
|
||||
public EditLightningAddressItem Add { get; set; }
|
||||
public List<EditLightningAddressItem> Items { get; set; } = new();
|
||||
public List<EditLightningAddressItem> Items { get; set; } = new List<EditLightningAddressItem>();
|
||||
}
|
||||
|
||||
public class LightningAddressSettings
|
||||
@ -153,7 +154,8 @@ namespace BTCPayServer
|
||||
public class LightningAddressItem
|
||||
{
|
||||
public string StoreId { get; set; }
|
||||
[Display(Name = "Invoice currency")] public string CurrencyCode { get; set; }
|
||||
[Display(Name = "Invoice currency")]
|
||||
public string CurrencyCode { get; set; }
|
||||
|
||||
[Display(Name = "Min sats")]
|
||||
[Range(1, double.PositiveInfinity)]
|
||||
@ -176,24 +178,29 @@ namespace BTCPayServer
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<LightningAddressSettings> GetSettings()
|
||||
{
|
||||
return await _settingsRepository.GetSettingAsync<LightningAddressSettings>(nameof(LightningAddressSettings)) ??
|
||||
new LightningAddressSettings();
|
||||
}
|
||||
|
||||
[HttpGet("~/.well-known/lnurlp/{username}")]
|
||||
public async Task<IActionResult> ResolveLightningAddress(string username)
|
||||
{
|
||||
var lightningAddressSettings = await _lightningAddressService.ResolveByAddress(username);
|
||||
if (lightningAddressSettings is null)
|
||||
var lightningAddressSettings = await GetSettings();
|
||||
if (!lightningAddressSettings.Items.TryGetValue(username.ToLowerInvariant(), out var item))
|
||||
{
|
||||
return NotFound("Unknown username");
|
||||
}
|
||||
|
||||
var blob = lightningAddressSettings.Blob.GetBlob<LightningAddressDataBlob>();
|
||||
return await GetLNURL("BTC", lightningAddressSettings.StoreDataId, blob.CurrencyCode, blob.Min, blob.Max,
|
||||
() => (username, null, null, null, null, true));
|
||||
return await GetLNURL("BTC", item.StoreId, item.CurrencyCode, item.Min, item.Max,
|
||||
() => (username, null, null, true));
|
||||
}
|
||||
|
||||
[HttpGet("pay")]
|
||||
public async Task<IActionResult> GetLNURL(string cryptoCode, string storeId, string currencyCode = null,
|
||||
decimal? min = null, decimal? max = null,
|
||||
Func<(string username, AppData app, ViewPointOfSaleViewModel.Item item, List<string> additionalTags, decimal? invoiceAmount, bool? anyoneCanInvoice)>
|
||||
Func<(string username, List<string> additionalTags, decimal? invoiceAmount, bool? anyoneCanInvoice)>
|
||||
internalDetails = null)
|
||||
{
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
@ -226,8 +233,8 @@ namespace BTCPayServer
|
||||
return NotFound("LNURL or Lightning payment method disabled");
|
||||
}
|
||||
|
||||
(string username, AppData app, ViewPointOfSaleViewModel.Item item, List<string> additionalTags, decimal? invoiceAmount, bool? anyoneCanInvoice) =
|
||||
(internalDetails ?? (() => (null, null, null, null, null, null)))();
|
||||
(string username, List<string> additionalTags, decimal? invoiceAmount, bool? anyoneCanInvoice) =
|
||||
(internalDetails ?? (() => (null, null, null, null)))();
|
||||
|
||||
if ((anyoneCanInvoice ?? blob.AnyoneCanInvoice) is false)
|
||||
{
|
||||
@ -235,34 +242,22 @@ namespace BTCPayServer
|
||||
}
|
||||
|
||||
var lnAddress = username is null ? null : $"{username}@{Request.Host}";
|
||||
List<string[]> lnurlMetadata = new();
|
||||
List<string[]> lnurlMetadata = new List<string[]>();
|
||||
|
||||
var invoiceRequest = new CreateInvoiceRequest
|
||||
{
|
||||
Amount = invoiceAmount,
|
||||
Checkout = new InvoiceDataBase.CheckoutOptions
|
||||
var i = await _invoiceController.CreateInvoiceCoreRaw(
|
||||
new CreateInvoiceRequest
|
||||
{
|
||||
PaymentMethods = new[] { pmi.ToStringNormalized() },
|
||||
Expiration = blob.InvoiceExpiration < TimeSpan.FromMinutes(2)
|
||||
? blob.InvoiceExpiration
|
||||
: TimeSpan.FromMinutes(2)
|
||||
},
|
||||
Currency = currencyCode,
|
||||
Type = invoiceAmount is null ? InvoiceType.TopUp : InvoiceType.Standard,
|
||||
};
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
invoiceRequest.Metadata =
|
||||
new InvoiceMetadata
|
||||
Amount = invoiceAmount,
|
||||
Checkout = new InvoiceDataBase.CheckoutOptions
|
||||
{
|
||||
ItemCode = item.Id,
|
||||
ItemDesc = item.Description,
|
||||
OrderId = AppService.GetPosOrderId(app.Id)
|
||||
}.ToJObject();
|
||||
}
|
||||
|
||||
var i = await _invoiceController.CreateInvoiceCoreRaw(invoiceRequest, store, Request.GetAbsoluteRoot(), additionalTags);
|
||||
PaymentMethods = new[] { pmi.ToStringNormalized() },
|
||||
Expiration = blob.InvoiceExpiration < TimeSpan.FromMinutes(2)
|
||||
? blob.InvoiceExpiration
|
||||
: TimeSpan.FromMinutes(2)
|
||||
},
|
||||
Currency = currencyCode,
|
||||
Type = invoiceAmount is null ? InvoiceType.TopUp : InvoiceType.Standard,
|
||||
}, store, Request.GetAbsoluteRoot(), additionalTags);
|
||||
if (i.Type != InvoiceType.TopUp)
|
||||
{
|
||||
min = i.GetPaymentMethod(pmi).Calculate().Due.ToDecimal(MoneyUnit.Satoshi);
|
||||
@ -277,16 +272,11 @@ namespace BTCPayServer
|
||||
pm.SetPaymentMethodDetails(paymentMethodDetails);
|
||||
await _invoiceRepository.UpdateInvoicePaymentMethod(i.Id, pm);
|
||||
}
|
||||
|
||||
var description = blob.LightningDescriptionTemplate
|
||||
.Replace("{StoreName}", store.StoreName ?? "", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{ItemDescription}", i.Metadata.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{OrderId}", i.Metadata.OrderId ?? "", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
lnurlMetadata.Add(new[] {"text/plain", description});
|
||||
lnurlMetadata.Add(new[] { "text/plain", i.Id });
|
||||
if (!string.IsNullOrEmpty(username))
|
||||
{
|
||||
lnurlMetadata.Add(new[] {"text/identifier", lnAddress});
|
||||
lnurlMetadata.Add(new[] { "text/identifier", lnAddress });
|
||||
}
|
||||
|
||||
return Ok(new LNURLPayRequest
|
||||
@ -302,10 +292,11 @@ namespace BTCPayServer
|
||||
Callback = new Uri(_linkGenerator.GetUriByAction(
|
||||
action: nameof(GetLNURLForInvoice),
|
||||
controller: "UILNURL",
|
||||
values: new {cryptoCode, invoiceId = i.Id}, Request.Scheme, Request.Host, Request.PathBase))
|
||||
values: new { cryptoCode, invoiceId = i.Id }, Request.Scheme, Request.Host, Request.PathBase))
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("pay/i/{invoiceId}")]
|
||||
public async Task<IActionResult> GetLNURLForInvoice(string invoiceId, string cryptoCode,
|
||||
[FromQuery] long? amount = null, string comment = null)
|
||||
@ -315,19 +306,11 @@ namespace BTCPayServer
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (comment is not null)
|
||||
comment = comment.Truncate(2000);
|
||||
|
||||
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
||||
var i = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||
|
||||
var store = await _storeRepository.FindStore(i.StoreId);
|
||||
if (store is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (i.Status == InvoiceStatusLegacy.New)
|
||||
{
|
||||
var isTopup = i.IsUnsetTopUp();
|
||||
@ -352,24 +335,22 @@ namespace BTCPayServer
|
||||
LightMoneyUnit.Satoshi);
|
||||
var max = isTopup ? LightMoney.FromUnit(6.12m, LightMoneyUnit.BTC) : min;
|
||||
|
||||
List<string[]> lnurlMetadata = new();
|
||||
List<string[]> lnurlMetadata = new List<string[]>();
|
||||
|
||||
var blob = store.GetStoreBlob();
|
||||
var description = blob.LightningDescriptionTemplate
|
||||
.Replace("{StoreName}", store.StoreName ?? "", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{ItemDescription}", i.Metadata.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{OrderId}", i.Metadata.OrderId ?? "", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
lnurlMetadata.Add(new[] {"text/plain", description});
|
||||
lnurlMetadata.Add(new[] { "text/plain", i.Id });
|
||||
if (!string.IsNullOrEmpty(paymentMethodDetails.ConsumedLightningAddress))
|
||||
{
|
||||
lnurlMetadata.Add(new[] {"text/identifier", paymentMethodDetails.ConsumedLightningAddress});
|
||||
lnurlMetadata.Add(new[] { "text/identifier", paymentMethodDetails.ConsumedLightningAddress });
|
||||
}
|
||||
|
||||
var metadata = JsonConvert.SerializeObject(lnurlMetadata);
|
||||
if (amount.HasValue && (amount < min || amount > max))
|
||||
{
|
||||
return BadRequest(new LNUrlStatusResponse {Status = "ERROR", Reason = "Amount is out of bounds."});
|
||||
return BadRequest(new LNUrlStatusResponse
|
||||
{
|
||||
Status = "ERROR",
|
||||
Reason = "Amount is out of bounds."
|
||||
});
|
||||
}
|
||||
|
||||
if (amount.HasValue && string.IsNullOrEmpty(paymentMethodDetails.BOLT11) ||
|
||||
@ -398,7 +379,7 @@ namespace BTCPayServer
|
||||
descriptionHash,
|
||||
i.ExpirationTime.ToUniversalTime() - DateTimeOffset.UtcNow));
|
||||
if (!BOLT11PaymentRequest.Parse(invoice.BOLT11, network.NBitcoinNetwork)
|
||||
.VerifyDescriptionHash(metadata))
|
||||
.VerifyDescriptionHash(metadata))
|
||||
{
|
||||
return BadRequest(new LNUrlStatusResponse
|
||||
{
|
||||
@ -432,7 +413,9 @@ namespace BTCPayServer
|
||||
paymentMethodDetails, pmi));
|
||||
return Ok(new LNURLPayRequest.LNURLPayRequestCallbackResponse
|
||||
{
|
||||
Disposable = true, Routes = Array.Empty<string>(), Pr = paymentMethodDetails.BOLT11
|
||||
Disposable = true,
|
||||
Routes = Array.Empty<string>(),
|
||||
Pr = paymentMethodDetails.BOLT11
|
||||
});
|
||||
}
|
||||
|
||||
@ -447,7 +430,9 @@ namespace BTCPayServer
|
||||
|
||||
return Ok(new LNURLPayRequest.LNURLPayRequestCallbackResponse
|
||||
{
|
||||
Disposable = true, Routes = Array.Empty<string>(), Pr = paymentMethodDetails.BOLT11
|
||||
Disposable = true,
|
||||
Routes = Array.Empty<string>(),
|
||||
Pr = paymentMethodDetails.BOLT11
|
||||
});
|
||||
}
|
||||
|
||||
@ -467,7 +452,8 @@ namespace BTCPayServer
|
||||
|
||||
return BadRequest(new LNUrlStatusResponse
|
||||
{
|
||||
Status = "ERROR", Reason = "Invoice not in a valid payable state"
|
||||
Status = "ERROR",
|
||||
Reason = "Invoice not in a valid payable state"
|
||||
});
|
||||
}
|
||||
|
||||
@ -477,39 +463,37 @@ namespace BTCPayServer
|
||||
[HttpGet("~/stores/{storeId}/integrations/lightning-address")]
|
||||
public async Task<IActionResult> EditLightningAddress(string storeId)
|
||||
{
|
||||
if (ControllerContext.HttpContext.GetStoreData().GetEnabledPaymentIds(_btcPayNetworkProvider)
|
||||
.All(id => id.PaymentType != LNURLPayPaymentType.Instance))
|
||||
if (ControllerContext.HttpContext.GetStoreData().GetEnabledPaymentIds(_btcPayNetworkProvider).All(id => id.PaymentType != LNURLPayPaymentType.Instance))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
Message = "LNURL is required for lightning addresses but has not yet been enabled.",
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
});
|
||||
return RedirectToAction(nameof(UIStoresController.GeneralSettings), "UIStores", new {storeId});
|
||||
return RedirectToAction(nameof(UIStoresController.GeneralSettings), "UIStores", new { storeId });
|
||||
}
|
||||
var lightningAddressSettings = await GetSettings();
|
||||
if (lightningAddressSettings.StoreToItemMap.TryGetValue(storeId, out var addresses))
|
||||
{
|
||||
return View(new EditLightningAddressVM
|
||||
{
|
||||
Items = addresses.Select(s => new EditLightningAddressVM.EditLightningAddressItem
|
||||
{
|
||||
Max = lightningAddressSettings.Items[s].Max,
|
||||
Min = lightningAddressSettings.Items[s].Min,
|
||||
CurrencyCode = lightningAddressSettings.Items[s].CurrencyCode,
|
||||
StoreId = lightningAddressSettings.Items[s].StoreId,
|
||||
Username = s,
|
||||
}).ToList()
|
||||
});
|
||||
}
|
||||
|
||||
var addresses =
|
||||
await _lightningAddressService.Get(new LightningAddressQuery() {StoreIds = new[] {storeId}});
|
||||
|
||||
return View(new EditLightningAddressVM
|
||||
{
|
||||
Items = addresses.Select(s =>
|
||||
{
|
||||
var blob = s.Blob.GetBlob<LightningAddressDataBlob>();
|
||||
return new EditLightningAddressVM.EditLightningAddressItem
|
||||
{
|
||||
Max = blob.Max,
|
||||
Min = blob.Min,
|
||||
CurrencyCode = blob.CurrencyCode,
|
||||
StoreId = storeId,
|
||||
Username = s.Username,
|
||||
};
|
||||
}
|
||||
).ToList()
|
||||
Items = new List<EditLightningAddressVM.EditLightningAddressItem>()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[HttpPost("~/stores/{storeId}/integrations/lightning-address")]
|
||||
@ -518,71 +502,69 @@ namespace BTCPayServer
|
||||
{
|
||||
if (command == "add")
|
||||
{
|
||||
if (!string.IsNullOrEmpty(vm.Add.CurrencyCode) &&
|
||||
currencyNameTable.GetCurrencyData(vm.Add.CurrencyCode, false) is null)
|
||||
if (!string.IsNullOrEmpty(vm.Add.CurrencyCode) && currencyNameTable.GetCurrencyData(vm.Add.CurrencyCode, false) is null)
|
||||
{
|
||||
vm.AddModelError(addressVm => addressVm.Add.CurrencyCode, "Currency is invalid", this);
|
||||
}
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(vm);
|
||||
}
|
||||
var lightningAddressSettings = await GetSettings();
|
||||
if (lightningAddressSettings.Items.ContainsKey(vm.Add.Username.ToLowerInvariant()))
|
||||
{
|
||||
vm.AddModelError(addressVm => addressVm.Add.Username, "Username is already taken", this);
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
|
||||
if (await _lightningAddressService.Set(new LightningAddressData()
|
||||
{
|
||||
StoreDataId = storeId,
|
||||
Username = vm.Add.Username,
|
||||
Blob = new LightningAddressDataBlob()
|
||||
{
|
||||
Max = vm.Add.Max, Min = vm.Add.Min, CurrencyCode = vm.Add.CurrencyCode
|
||||
}.SerializeBlob()
|
||||
}))
|
||||
if (lightningAddressSettings.StoreToItemMap.TryGetValue(storeId, out var ids))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
Message = "Lightning address added successfully."
|
||||
});
|
||||
ids = ids.Concat(new[] { vm.Add.Username.ToLowerInvariant() }).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
vm.AddModelError(addressVm => addressVm.Add.Username, "Username is already taken", this);
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(vm);
|
||||
}
|
||||
ids = new[] { vm.Add.Username.ToLowerInvariant() };
|
||||
}
|
||||
|
||||
lightningAddressSettings.StoreToItemMap.AddOrReplace(storeId, ids);
|
||||
vm.Add.StoreId = storeId;
|
||||
lightningAddressSettings.Items.TryAdd(vm.Add.Username.ToLowerInvariant(), vm.Add);
|
||||
await _settingsRepository.UpdateSetting(lightningAddressSettings, nameof(LightningAddressSettings));
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
Message = "Lightning address added successfully."
|
||||
});
|
||||
|
||||
return RedirectToAction("EditLightningAddress");
|
||||
}
|
||||
|
||||
if (command.StartsWith("remove", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var index = command.Substring(command.IndexOf(":", StringComparison.InvariantCultureIgnoreCase) + 1);
|
||||
if (await _lightningAddressService.Remove(index, storeId))
|
||||
var lightningAddressSettings = await GetSettings();
|
||||
var index = int.Parse(
|
||||
command.Substring(command.IndexOf(":", StringComparison.InvariantCultureIgnoreCase) + 1),
|
||||
CultureInfo.InvariantCulture);
|
||||
if (lightningAddressSettings.StoreToItemMap.TryGetValue(storeId, out var addresses))
|
||||
{
|
||||
var addressToRemove = addresses[index];
|
||||
addresses = addresses.Where(s => s != addressToRemove).ToArray();
|
||||
lightningAddressSettings.StoreToItemMap.AddOrReplace(storeId, addresses);
|
||||
lightningAddressSettings.Items.TryRemove(addressToRemove, out _);
|
||||
await _settingsRepository.UpdateSetting(lightningAddressSettings, nameof(LightningAddressSettings));
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
Message = $"Lightning address {index} removed successfully."
|
||||
Message = $"Lightning address {addressToRemove} removed successfully."
|
||||
});
|
||||
return RedirectToAction("EditLightningAddress");
|
||||
}
|
||||
else
|
||||
{
|
||||
vm.AddModelError(addressVm => addressVm.Add.Username, "Username could not be removed", this);
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return View(vm);
|
||||
|
||||
return RedirectToAction("EditLightningAddress");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -186,7 +186,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (redirectToInvoice)
|
||||
{
|
||||
return RedirectToAction("ViewPaymentRequest", new { payReqId });
|
||||
return RedirectToAction("ViewPaymentRequest", new { Id = payReqId });
|
||||
}
|
||||
|
||||
return BadRequest("Payment Request cannot be paid as it has been archived");
|
||||
@ -197,7 +197,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (redirectToInvoice)
|
||||
{
|
||||
return RedirectToAction("ViewPaymentRequest", new { payReqId });
|
||||
return RedirectToAction("ViewPaymentRequest", new { Id = payReqId });
|
||||
}
|
||||
|
||||
return BadRequest("Payment Request has already been settled.");
|
||||
@ -207,7 +207,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (redirectToInvoice)
|
||||
{
|
||||
return RedirectToAction("ViewPaymentRequest", new { payReqId });
|
||||
return RedirectToAction("ViewPaymentRequest", new { Id = payReqId });
|
||||
}
|
||||
|
||||
return BadRequest("Payment Request has expired");
|
||||
@ -265,7 +265,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
if (redirectToInvoice)
|
||||
{
|
||||
return RedirectToAction("Checkout", "UIInvoice", new { invoiceId = newInvoice.Id });
|
||||
return RedirectToAction("Checkout", "UIInvoice", new { newInvoice.Id });
|
||||
}
|
||||
|
||||
return Ok(newInvoice.Id);
|
||||
@ -306,7 +306,7 @@ namespace BTCPayServer.Controllers
|
||||
if (redirect)
|
||||
{
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Payment cancelled";
|
||||
return RedirectToAction(nameof(ViewPaymentRequest), new { payReqId });
|
||||
return RedirectToAction(nameof(ViewPaymentRequest), new { Id = payReqId });
|
||||
}
|
||||
|
||||
return Ok("Payment cancelled");
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
@ -161,7 +162,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = $"Your claim request of {_currencyNameTable.DisplayFormatCurrency(vm.ClaimedAmount, ppBlob.Currency)} to {vm.Destination} has been submitted and is awaiting {(result.PayoutData.State == PayoutState.AwaitingApproval? "approval": "payment")}.",
|
||||
Message = $"Your claim request of {_currencyNameTable.DisplayFormatCurrency(vm.ClaimedAmount, ppBlob.Currency)} to {vm.Destination} has been submitted and is awaiting approval.",
|
||||
Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
[Route("stores/{storeId}/pull-payments")]
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[AutoValidateAntiforgeryToken]
|
||||
public class UIStorePullPaymentsController : Controller
|
||||
@ -43,7 +44,6 @@ namespace BTCPayServer.Controllers
|
||||
return HttpContext.GetStoreData();
|
||||
}
|
||||
}
|
||||
|
||||
public UIStorePullPaymentsController(BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
IEnumerable<IPayoutHandler> payoutHandlers,
|
||||
CurrencyNameTable currencyNameTable,
|
||||
@ -59,7 +59,7 @@ namespace BTCPayServer.Controllers
|
||||
_jsonSerializerSettings = jsonSerializerSettings;
|
||||
}
|
||||
|
||||
[HttpGet("stores/{storeId}/pull-payments/new")]
|
||||
[HttpGet("new")]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> NewPullPayment(string storeId)
|
||||
{
|
||||
@ -76,19 +76,18 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
return RedirectToAction(nameof(UIStoresController.GeneralSettings), "UIStores", new { storeId });
|
||||
}
|
||||
|
||||
|
||||
return View(new NewPullPaymentModel
|
||||
{
|
||||
Name = "",
|
||||
Currency = CurrentStore.GetStoreBlob().DefaultCurrency,
|
||||
CustomCSSLink = "",
|
||||
EmbeddedCSS = "",
|
||||
PaymentMethodItems =
|
||||
paymentMethods.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString(), true))
|
||||
PaymentMethodItems = paymentMethods.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString(), true))
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("stores/{storeId}/pull-payments/new")]
|
||||
[HttpPost("new")]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> NewPullPayment(string storeId, NewPullPaymentModel model)
|
||||
{
|
||||
@ -105,8 +104,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
// Since we assign all payment methods to be selected by default above we need to update
|
||||
// them here to reflect user's selection so that they can correct their mistake
|
||||
model.PaymentMethodItems =
|
||||
paymentMethodOptions.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString(), false));
|
||||
model.PaymentMethodItems = paymentMethodOptions.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString(), false));
|
||||
ModelState.AddModelError(nameof(model.PaymentMethods), "You need at least one payment method");
|
||||
}
|
||||
if (_currencyNameTable.GetCurrencyData(model.Currency, false) is null)
|
||||
@ -139,17 +137,17 @@ namespace BTCPayServer.Controllers
|
||||
PaymentMethodIds = selectedPaymentMethodIds,
|
||||
EmbeddedCSS = model.EmbeddedCSS,
|
||||
CustomCSSLink = model.CustomCSSLink,
|
||||
BOLT11Expiration = TimeSpan.FromDays(model.BOLT11Expiration),
|
||||
AutoApproveClaims = model.AutoApproveClaims
|
||||
BOLT11Expiration = TimeSpan.FromDays(model.BOLT11Expiration)
|
||||
});
|
||||
this.TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Pull payment request created", Severity = StatusMessageModel.StatusSeverity.Success
|
||||
Message = "Pull payment request created",
|
||||
Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
return RedirectToAction(nameof(PullPayments), new { storeId = storeId });
|
||||
}
|
||||
|
||||
[HttpGet("stores/{storeId}/pull-payments")]
|
||||
[HttpGet("")]
|
||||
public async Task<IActionResult> PullPayments(
|
||||
string storeId,
|
||||
PullPaymentState pullPaymentState,
|
||||
@ -192,18 +190,20 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
var vm = this.ParseListQuery(new PullPaymentsModel
|
||||
{
|
||||
Skip = skip, Count = count, Total = await ppsQuery.CountAsync(), ActiveState = pullPaymentState
|
||||
Skip = skip,
|
||||
Count = count,
|
||||
Total = await ppsQuery.CountAsync(),
|
||||
ActiveState = pullPaymentState
|
||||
});
|
||||
|
||||
switch (pullPaymentState)
|
||||
{
|
||||
switch (pullPaymentState) {
|
||||
case PullPaymentState.Active:
|
||||
ppsQuery = ppsQuery
|
||||
.Where(
|
||||
p => !p.Archived &&
|
||||
(p.EndDate != null ? p.EndDate > DateTimeOffset.UtcNow : true) &&
|
||||
p.StartDate <= DateTimeOffset.UtcNow
|
||||
);
|
||||
(p.EndDate != null ? p.EndDate > DateTimeOffset.UtcNow : true) &&
|
||||
p.StartDate <= DateTimeOffset.UtcNow
|
||||
);
|
||||
break;
|
||||
case PullPaymentState.Archived:
|
||||
ppsQuery = ppsQuery.Where(p => p.Archived);
|
||||
@ -225,11 +225,10 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var totalCompleted = pp.Payouts.Where(p => (p.State == PayoutState.Completed ||
|
||||
p.State == PayoutState.InProgress) && p.IsInPeriod(pp, now))
|
||||
.Select(o => o.GetBlob(_jsonSerializerSettings).Amount).Sum();
|
||||
.Select(o => o.GetBlob(_jsonSerializerSettings).Amount).Sum();
|
||||
var totalAwaiting = pp.Payouts.Where(p => (p.State == PayoutState.AwaitingPayment ||
|
||||
p.State == PayoutState.AwaitingApproval) &&
|
||||
p.IsInPeriod(pp, now)).Select(o =>
|
||||
o.GetBlob(_jsonSerializerSettings).Amount).Sum();
|
||||
p.IsInPeriod(pp, now)).Select(o => o.GetBlob(_jsonSerializerSettings).Amount).Sum();
|
||||
;
|
||||
var ppBlob = pp.GetBlob();
|
||||
var ni = _currencyNameTable.GetCurrencyData(ppBlob.Currency, true);
|
||||
@ -263,16 +262,15 @@ namespace BTCPayServer.Controllers
|
||||
return time;
|
||||
}
|
||||
|
||||
[HttpGet("stores/{storeId}/pull-payments/{pullPaymentId}/archive")]
|
||||
[HttpGet("{pullPaymentId}/archive")]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public IActionResult ArchivePullPayment(string storeId,
|
||||
string pullPaymentId)
|
||||
{
|
||||
return View("Confirm",
|
||||
new ConfirmModel("Archive pull payment", "Do you really want to archive the pull payment?", "Archive"));
|
||||
return View("Confirm", new ConfirmModel("Archive pull payment", "Do you really want to archive the pull payment?", "Archive"));
|
||||
}
|
||||
|
||||
[HttpPost("stores/{storeId}/pull-payments/{pullPaymentId}/archive")]
|
||||
[HttpPost("{pullPaymentId}/archive")]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> ArchivePullPaymentPost(string storeId,
|
||||
string pullPaymentId)
|
||||
@ -280,15 +278,14 @@ namespace BTCPayServer.Controllers
|
||||
await _pullPaymentService.Cancel(new HostedServices.PullPaymentHostedService.CancelRequest(pullPaymentId));
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Pull payment archived", Severity = StatusMessageModel.StatusSeverity.Success
|
||||
Message = "Pull payment archived",
|
||||
Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
return RedirectToAction(nameof(PullPayments), new { storeId });
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[HttpPost("stores/{storeId}/pull-payments/payouts")]
|
||||
[HttpPost("stores/{storeId}/pull-payments/{pullPaymentId}/payouts")]
|
||||
[HttpPost("stores/{storeId}/payouts")]
|
||||
[HttpPost("payouts")]
|
||||
public async Task<IActionResult> PayoutsPost(
|
||||
string storeId, PayoutsModel vm, CancellationToken cancellationToken)
|
||||
{
|
||||
@ -305,17 +302,16 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "No payout selected", Severity = StatusMessageModel.StatusSeverity.Error
|
||||
Message = "No payout selected",
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
});
|
||||
return RedirectToAction(nameof(Payouts), new
|
||||
{
|
||||
storeId = storeId,
|
||||
pullPaymentId = vm.PullPaymentId,
|
||||
paymentMethodId = paymentMethodId.ToString()
|
||||
});
|
||||
return RedirectToAction(nameof(Payouts),
|
||||
new
|
||||
{
|
||||
storeId = storeId,
|
||||
pullPaymentId = vm.PullPaymentId,
|
||||
paymentMethodId = paymentMethodId.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
var command = vm.Command.Substring(vm.Command.IndexOf('-', StringComparison.InvariantCulture) + 1);
|
||||
if (handler != null)
|
||||
{
|
||||
@ -325,127 +321,124 @@ namespace BTCPayServer.Controllers
|
||||
TempData.SetStatusMessageModel(result);
|
||||
}
|
||||
}
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case "approve-pay":
|
||||
case "approve":
|
||||
{
|
||||
await using var ctx = this._dbContextFactory.CreateContext();
|
||||
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
var payouts =
|
||||
await GetPayoutsForPaymentMethod(paymentMethodId, ctx, payoutIds, storeId, cancellationToken);
|
||||
|
||||
var failed = false;
|
||||
for (int i = 0; i < payouts.Count; i++)
|
||||
{
|
||||
var payout = payouts[i];
|
||||
if (payout.State != PayoutState.AwaitingApproval)
|
||||
continue;
|
||||
var rateResult = await _pullPaymentService.GetRate(payout, null, cancellationToken);
|
||||
if (rateResult.BidAsk == null)
|
||||
{
|
||||
this.TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = $"Rate unavailable: {rateResult.EvaluatedRule}",
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
});
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
await using var ctx = this._dbContextFactory.CreateContext();
|
||||
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
var payouts = await GetPayoutsForPaymentMethod(paymentMethodId, ctx, payoutIds, storeId, cancellationToken);
|
||||
|
||||
var approveResult = await _pullPaymentService.Approve(
|
||||
new HostedServices.PullPaymentHostedService.PayoutApproval()
|
||||
var failed = false;
|
||||
for (int i = 0; i < payouts.Count; i++)
|
||||
{
|
||||
var payout = payouts[i];
|
||||
if (payout.State != PayoutState.AwaitingApproval)
|
||||
continue;
|
||||
var rateResult = await _pullPaymentService.GetRate(payout, null, cancellationToken);
|
||||
if (rateResult.BidAsk == null)
|
||||
{
|
||||
this.TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = $"Rate unavailable: {rateResult.EvaluatedRule}",
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
});
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
var approveResult = await _pullPaymentService.Approve(new HostedServices.PullPaymentHostedService.PayoutApproval()
|
||||
{
|
||||
PayoutId = payout.Id,
|
||||
Revision = payout.GetBlob(_jsonSerializerSettings).Revision,
|
||||
Rate = rateResult.BidAsk.Ask
|
||||
});
|
||||
if (approveResult != PullPaymentHostedService.PayoutApproval.Result.Ok)
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
if (approveResult != PullPaymentHostedService.PayoutApproval.Result.Ok)
|
||||
{
|
||||
Message = PullPaymentHostedService.PayoutApproval.GetErrorMessage(approveResult),
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
});
|
||||
failed = true;
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = PullPaymentHostedService.PayoutApproval.GetErrorMessage(approveResult),
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
});
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (failed)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (failed)
|
||||
{
|
||||
if (command == "approve-pay")
|
||||
{
|
||||
goto case "pay";
|
||||
}
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Payouts approved",
|
||||
Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
if (command == "approve-pay")
|
||||
case "pay":
|
||||
{
|
||||
goto case "pay";
|
||||
if (handler is { })
|
||||
return await handler?.InitiatePayment(paymentMethodId, payoutIds);
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Paying via this payment method is not supported",
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Payouts approved", Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "pay":
|
||||
{
|
||||
if (handler is { })
|
||||
return await handler?.InitiatePayment(paymentMethodId, payoutIds);
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Paying via this payment method is not supported",
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "mark-paid":
|
||||
{
|
||||
await using var ctx = this._dbContextFactory.CreateContext();
|
||||
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
var payouts =
|
||||
await GetPayoutsForPaymentMethod(paymentMethodId, ctx, payoutIds, storeId, cancellationToken);
|
||||
for (int i = 0; i < payouts.Count; i++)
|
||||
{
|
||||
var payout = payouts[i];
|
||||
if (payout.State != PayoutState.AwaitingPayment)
|
||||
continue;
|
||||
|
||||
var result =
|
||||
await _pullPaymentService.MarkPaid(new PayoutPaidRequest() { PayoutId = payout.Id });
|
||||
if (result != PayoutPaidRequest.PayoutPaidResult.Ok)
|
||||
await using var ctx = this._dbContextFactory.CreateContext();
|
||||
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
var payouts = await GetPayoutsForPaymentMethod(paymentMethodId, ctx, payoutIds, storeId, cancellationToken);
|
||||
for (int i = 0; i < payouts.Count; i++)
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
var payout = payouts[i];
|
||||
if (payout.State != PayoutState.AwaitingPayment)
|
||||
continue;
|
||||
|
||||
var result = await _pullPaymentService.MarkPaid(new PayoutPaidRequest()
|
||||
{
|
||||
Message = PayoutPaidRequest.GetErrorMessage(result),
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
PayoutId = payout.Id
|
||||
});
|
||||
return RedirectToAction(nameof(Payouts),
|
||||
new
|
||||
if (result != PayoutPaidRequest.PayoutPaidResult.Ok)
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = PayoutPaidRequest.GetErrorMessage(result),
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
});
|
||||
return RedirectToAction(nameof(Payouts), new
|
||||
{
|
||||
storeId = storeId,
|
||||
pullPaymentId = vm.PullPaymentId,
|
||||
paymentMethodId = paymentMethodId.ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Payouts marked as paid", Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
break;
|
||||
}
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Payouts marked as paid",
|
||||
Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "cancel":
|
||||
await _pullPaymentService.Cancel(
|
||||
new PullPaymentHostedService.CancelRequest(payoutIds));
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Payouts archived", Severity = StatusMessageModel.StatusSeverity.Success
|
||||
Message = "Payouts archived",
|
||||
Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
break;
|
||||
}
|
||||
@ -465,17 +458,16 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var payouts = (await ctx.Payouts
|
||||
.Include(p => p.PullPaymentData)
|
||||
.Include(p => p.StoreData)
|
||||
.Include(p => p.PullPaymentData.StoreData)
|
||||
.Where(p => payoutIds.Contains(p.Id))
|
||||
.Where(p => p.StoreDataId == storeId && (p.PullPaymentDataId == null || !p.PullPaymentData.Archived))
|
||||
.Where(p => p.PullPaymentData.StoreId == storeId && !p.PullPaymentData.Archived)
|
||||
.ToListAsync(cancellationToken))
|
||||
.Where(p => p.GetPaymentMethodId() == paymentMethodId)
|
||||
.ToList();
|
||||
return payouts;
|
||||
}
|
||||
|
||||
[HttpGet("stores/{storeId}/pull-payments/{pullPaymentId}/payouts")]
|
||||
[HttpGet("stores/{storeId}/payouts")]
|
||||
[HttpGet("payouts")]
|
||||
public async Task<IActionResult> Payouts(
|
||||
string storeId, string pullPaymentId, string paymentMethodId, PayoutState payoutState,
|
||||
int skip = 0, int count = 50)
|
||||
@ -502,8 +494,7 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
vm.Payouts = new List<PayoutsModel.PayoutModel>();
|
||||
await using var ctx = _dbContextFactory.CreateContext();
|
||||
var payoutRequest =
|
||||
ctx.Payouts.Where(p => p.StoreDataId == storeId && (p.PullPaymentDataId == null || !p.PullPaymentData.Archived));
|
||||
var payoutRequest = ctx.Payouts.Where(p => p.PullPaymentData.StoreId == storeId && !p.PullPaymentData.Archived);
|
||||
if (pullPaymentId != null)
|
||||
{
|
||||
payoutRequest = payoutRequest.Where(p => p.PullPaymentDataId == vm.PullPaymentId);
|
||||
@ -516,9 +507,6 @@ namespace BTCPayServer.Controllers
|
||||
payoutRequest = payoutRequest.Where(p => p.PaymentMethodId == pmiStr);
|
||||
}
|
||||
|
||||
vm.PaymentMethodCount = (await payoutRequest.GroupBy(data => data.PaymentMethodId)
|
||||
.Select(datas => new {datas.Key, Count = datas.Count()}).ToListAsync())
|
||||
.ToDictionary(datas => datas.Key, arg => arg.Count);
|
||||
vm.PayoutStateCount = payoutRequest.GroupBy(data => data.State)
|
||||
.Select(e => new { e.Key, Count = e.Count() })
|
||||
.ToDictionary(arg => arg.Key, arg => arg.Count);
|
||||
@ -537,18 +525,22 @@ namespace BTCPayServer.Controllers
|
||||
payoutRequest = payoutRequest.Skip(vm.Skip).Take(vm.Count);
|
||||
|
||||
var payouts = await payoutRequest.OrderByDescending(p => p.Date)
|
||||
.Select(o => new { Payout = o, PullPayment = o.PullPaymentData }).ToListAsync();
|
||||
.Select(o => new
|
||||
{
|
||||
Payout = o,
|
||||
PullPayment = o.PullPaymentData
|
||||
}).ToListAsync();
|
||||
foreach (var item in payouts)
|
||||
{
|
||||
var ppBlob = item.PullPayment?.GetBlob();
|
||||
var ppBlob = item.PullPayment.GetBlob();
|
||||
var payoutBlob = item.Payout.GetBlob(_jsonSerializerSettings);
|
||||
var m = new PayoutsModel.PayoutModel
|
||||
{
|
||||
PullPaymentId = item.PullPayment?.Id,
|
||||
PullPaymentName = ppBlob?.Name ?? item.PullPayment?.Id,
|
||||
PullPaymentId = item.PullPayment.Id,
|
||||
PullPaymentName = ppBlob.Name ?? item.PullPayment.Id,
|
||||
Date = item.Payout.Date,
|
||||
PayoutId = item.Payout.Id,
|
||||
Amount = _currencyNameTable.DisplayFormatCurrency(payoutBlob.Amount, ppBlob?.Currency ?? PaymentMethodId.Parse(item.Payout.PaymentMethodId).CryptoCode),
|
||||
Amount = _currencyNameTable.DisplayFormatCurrency(payoutBlob.Amount, ppBlob.Currency),
|
||||
Destination = payoutBlob.Destination
|
||||
};
|
||||
var handler = _payoutHandlers
|
||||
|
@ -464,8 +464,8 @@ namespace BTCPayServer.Controllers
|
||||
var defaultChoice = defaultPaymentId is not null ? defaultPaymentId.FindNearest(enabled) : null;
|
||||
if (defaultChoice is null)
|
||||
{
|
||||
defaultChoice = enabled.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == PaymentTypes.BTCLike) ??
|
||||
enabled.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == PaymentTypes.LightningLike) ??
|
||||
defaultChoice = enabled.FirstOrDefault(e => e.CryptoCode == "BTC" && e.PaymentType == PaymentTypes.BTCLike) ??
|
||||
enabled.FirstOrDefault(e => e.CryptoCode == "BTC" && e.PaymentType == PaymentTypes.LightningLike) ??
|
||||
enabled.FirstOrDefault();
|
||||
}
|
||||
var choices = GetEnabledPaymentMethodChoices(storeData);
|
||||
|
@ -13,6 +13,7 @@ using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.ModelBinders;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.PayJoin;
|
||||
@ -27,8 +28,6 @@ using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NBitcoin;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Logging;
|
||||
using NBXplorer;
|
||||
using NBXplorer.Client;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
@ -51,6 +50,7 @@ namespace BTCPayServer.Controllers
|
||||
public RateFetcher RateFetcher { get; }
|
||||
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly JsonSerializerSettings _serializerSettings;
|
||||
private readonly NBXplorerDashboard _dashboard;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly IFeeProviderFactory _feeRateProvider;
|
||||
@ -61,7 +61,6 @@ namespace BTCPayServer.Controllers
|
||||
private readonly DelayedTransactionBroadcaster _broadcaster;
|
||||
private readonly PayjoinClient _payjoinClient;
|
||||
private readonly LabelFactory _labelFactory;
|
||||
private readonly PullPaymentHostedService _pullPaymentHostedService;
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _jsonSerializerSettings;
|
||||
private readonly PullPaymentHostedService _pullPaymentService;
|
||||
@ -70,7 +69,6 @@ namespace BTCPayServer.Controllers
|
||||
private readonly WalletHistogramService _walletHistogramService;
|
||||
|
||||
readonly CurrencyNameTable _currencyTable;
|
||||
|
||||
public UIWalletsController(StoreRepository repo,
|
||||
WalletRepository walletRepository,
|
||||
CurrencyNameTable currencyTable,
|
||||
@ -95,8 +93,7 @@ namespace BTCPayServer.Controllers
|
||||
BTCPayNetworkJsonSerializerSettings jsonSerializerSettings,
|
||||
PullPaymentHostedService pullPaymentService,
|
||||
IEnumerable<IPayoutHandler> payoutHandlers,
|
||||
IServiceProvider serviceProvider,
|
||||
PullPaymentHostedService pullPaymentHostedService)
|
||||
IServiceProvider serviceProvider)
|
||||
{
|
||||
_currencyTable = currencyTable;
|
||||
Repository = repo;
|
||||
@ -105,6 +102,7 @@ namespace BTCPayServer.Controllers
|
||||
_authorizationService = authorizationService;
|
||||
NetworkProvider = networkProvider;
|
||||
_userManager = userManager;
|
||||
_serializerSettings = mvcJsonOptions.SerializerSettings;
|
||||
_dashboard = dashboard;
|
||||
ExplorerClientProvider = explorerProvider;
|
||||
_feeRateProvider = feeRateProvider;
|
||||
@ -115,7 +113,6 @@ namespace BTCPayServer.Controllers
|
||||
_broadcaster = broadcaster;
|
||||
_payjoinClient = payjoinClient;
|
||||
_labelFactory = labelFactory;
|
||||
_pullPaymentHostedService = pullPaymentHostedService;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_jsonSerializerSettings = jsonSerializerSettings;
|
||||
_pullPaymentService = pullPaymentService;
|
||||
@ -124,7 +121,7 @@ namespace BTCPayServer.Controllers
|
||||
_connectionFactory = connectionFactory;
|
||||
_walletHistogramService = walletHistogramService;
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[Route("{walletId}")]
|
||||
public async Task<IActionResult> ModifyTransaction(
|
||||
@ -133,10 +130,10 @@ namespace BTCPayServer.Controllers
|
||||
// does not work
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, string transactionId,
|
||||
string addlabel = null,
|
||||
string addlabelclick = null,
|
||||
string addcomment = null,
|
||||
string removelabel = null)
|
||||
string addlabel = null,
|
||||
string addlabelclick = null,
|
||||
string addcomment = null,
|
||||
string removelabel = null)
|
||||
{
|
||||
addlabel = addlabel ?? addlabelclick;
|
||||
// Hack necessary when the user enter a empty comment and submit.
|
||||
@ -187,8 +184,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (walletTransactionInfo.Labels.Remove(removelabel))
|
||||
{
|
||||
var canDeleteColor =
|
||||
!walletTransactionsInfo.Any(txi => txi.Value.Labels.ContainsKey(removelabel));
|
||||
var canDeleteColor = !walletTransactionsInfo.Any(txi => txi.Value.Labels.ContainsKey(removelabel));
|
||||
if (canDeleteColor)
|
||||
{
|
||||
walletBlobInfo.LabelColors.Remove(removelabel);
|
||||
@ -223,18 +219,18 @@ namespace BTCPayServer.Controllers
|
||||
var stores = await Repository.GetStoresByUserId(GetUserId());
|
||||
|
||||
var onChainWallets = stores
|
||||
.SelectMany(s => s.GetSupportedPaymentMethods(NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.Select(d => ((Wallet: _walletProvider.GetWallet(d.Network),
|
||||
DerivationStrategy: d.AccountDerivation,
|
||||
Network: d.Network)))
|
||||
.Where(_ => _.Wallet != null && _.Network.WalletSupported)
|
||||
.Select(_ => (Wallet: _.Wallet,
|
||||
Store: s,
|
||||
Balance: GetBalanceString(_.Wallet, _.DerivationStrategy),
|
||||
DerivationStrategy: _.DerivationStrategy,
|
||||
Network: _.Network)))
|
||||
.ToList();
|
||||
.SelectMany(s => s.GetSupportedPaymentMethods(NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.Select(d => ((Wallet: _walletProvider.GetWallet(d.Network),
|
||||
DerivationStrategy: d.AccountDerivation,
|
||||
Network: d.Network)))
|
||||
.Where(_ => _.Wallet != null && _.Network.WalletSupported)
|
||||
.Select(_ => (Wallet: _.Wallet,
|
||||
Store: s,
|
||||
Balance: GetBalanceString(_.Wallet, _.DerivationStrategy),
|
||||
DerivationStrategy: _.DerivationStrategy,
|
||||
Network: _.Network)))
|
||||
.ToList();
|
||||
|
||||
foreach (var wallet in onChainWallets)
|
||||
{
|
||||
@ -246,7 +242,6 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
walletVm.Balance = "";
|
||||
}
|
||||
|
||||
walletVm.CryptoCode = wallet.Network.CryptoCode;
|
||||
walletVm.StoreId = wallet.Store.Id;
|
||||
walletVm.Id = new WalletId(wallet.Store.Id, wallet.Network.CryptoCode);
|
||||
@ -281,10 +276,18 @@ namespace BTCPayServer.Controllers
|
||||
var transactions = await wallet.FetchTransactions(paymentMethod.AccountDerivation);
|
||||
var walletBlob = await walletBlobAsync;
|
||||
var walletTransactionsInfo = await walletTransactionsInfoAsync;
|
||||
var model = new ListTransactionsViewModel { Skip = skip, Count = count, Total = 0 };
|
||||
var model = new ListTransactionsViewModel
|
||||
{
|
||||
Skip = skip,
|
||||
Count = count,
|
||||
Total = 0
|
||||
};
|
||||
if (labelFilter != null)
|
||||
{
|
||||
model.PaginationQuery = new Dictionary<string, object> { { "labelFilter", labelFilter } };
|
||||
model.PaginationQuery = new Dictionary<string, object>
|
||||
{
|
||||
{"labelFilter", labelFilter}
|
||||
};
|
||||
}
|
||||
if (transactions == null)
|
||||
{
|
||||
@ -299,7 +302,7 @@ namespace BTCPayServer.Controllers
|
||||
else
|
||||
{
|
||||
foreach (var tx in transactions.UnconfirmedTransactions.Transactions
|
||||
.Concat(transactions.ConfirmedTransactions.Transactions).ToArray())
|
||||
.Concat(transactions.ConfirmedTransactions.Transactions).ToArray())
|
||||
{
|
||||
var vm = new ListTransactionsViewModel.TransactionViewModel();
|
||||
vm.Id = tx.TransactionId.ToString();
|
||||
@ -324,8 +327,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
model.Total = model.Transactions.Count;
|
||||
model.Transactions = model.Transactions.OrderByDescending(t => t.Timestamp).Skip(skip).Take(count)
|
||||
.ToList();
|
||||
model.Transactions = model.Transactions.OrderByDescending(t => t.Timestamp).Skip(skip).Take(count).ToList();
|
||||
}
|
||||
|
||||
model.CryptoCode = walletId.CryptoCode;
|
||||
@ -352,7 +354,8 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[HttpGet("{walletId}/receive")]
|
||||
public IActionResult WalletReceive([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId)
|
||||
public IActionResult WalletReceive([ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId)
|
||||
{
|
||||
if (walletId?.StoreId == null)
|
||||
return NotFound();
|
||||
@ -368,9 +371,7 @@ namespace BTCPayServer.Controllers
|
||||
var bip21 = network.GenerateBIP21(address?.ToString(), null);
|
||||
if (allowedPayjoin)
|
||||
{
|
||||
bip21.QueryParams.Add(PayjoinClient.BIP21EndpointKey,
|
||||
Request.GetAbsoluteUri(Url.Action(nameof(PayJoinEndpointController.Submit), "PayJoinEndpoint",
|
||||
new { walletId.CryptoCode })));
|
||||
bip21.QueryParams.Add(PayjoinClient.BIP21EndpointKey, Request.GetAbsoluteUri(Url.Action(nameof(PayJoinEndpointController.Submit), "PayJoinEndpoint", new { walletId.CryptoCode })));
|
||||
}
|
||||
return View(new WalletReceiveViewModel()
|
||||
{
|
||||
@ -383,8 +384,8 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[Route("{walletId}/receive")]
|
||||
public async Task<IActionResult> WalletReceive([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId,
|
||||
WalletReceiveViewModel viewModel, string command)
|
||||
public async Task<IActionResult> WalletReceive([ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, WalletReceiveViewModel viewModel, string command)
|
||||
{
|
||||
if (walletId?.StoreId == null)
|
||||
return NotFound();
|
||||
@ -478,13 +479,17 @@ namespace BTCPayServer.Controllers
|
||||
rateRules.Spread = 0.0m;
|
||||
var currencyPair = new Rating.CurrencyPair(paymentMethod.PaymentId.CryptoCode, storeData.DefaultCurrency);
|
||||
double.TryParse(defaultAmount, out var amount);
|
||||
var model = new WalletSendModel() { CryptoCode = walletId.CryptoCode };
|
||||
var model = new WalletSendModel()
|
||||
{
|
||||
CryptoCode = walletId.CryptoCode
|
||||
};
|
||||
if (bip21?.Any() is true)
|
||||
{
|
||||
foreach (var link in bip21)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(link))
|
||||
{
|
||||
|
||||
LoadFromBIP21(model, link, network);
|
||||
}
|
||||
}
|
||||
@ -512,10 +517,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var result = await feeProvider.GetFeeRateAsync(
|
||||
(int)network.NBitcoinNetwork.Consensus.GetExpectedBlocksFor(time));
|
||||
return new WalletSendModel.FeeRateOption()
|
||||
{
|
||||
Target = time, FeeRate = result.SatoshiPerByte
|
||||
};
|
||||
return new WalletSendModel.FeeRateOption() { Target = time, FeeRate = result.SatoshiPerByte };
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@ -545,46 +547,37 @@ namespace BTCPayServer.Controllers
|
||||
try
|
||||
{
|
||||
cts.CancelAfter(TimeSpan.FromSeconds(5));
|
||||
var result = await RateFetcher.FetchRate(currencyPair, rateRules, cts.Token)
|
||||
.WithCancellation(cts.Token);
|
||||
var result = await RateFetcher.FetchRate(currencyPair, rateRules, cts.Token).WithCancellation(cts.Token);
|
||||
if (result.BidAsk != null)
|
||||
{
|
||||
model.Rate = result.BidAsk.Center;
|
||||
model.FiatDivisibility = _currencyTable.GetNumberFormatInfo(currencyPair.Right, true)
|
||||
.CurrencyDecimalDigits;
|
||||
model.FiatDivisibility = _currencyTable.GetNumberFormatInfo(currencyPair.Right, true).CurrencyDecimalDigits;
|
||||
model.Fiat = currencyPair.Right;
|
||||
}
|
||||
else
|
||||
{
|
||||
model.RateError =
|
||||
$"{result.EvaluatedRule} ({string.Join(", ", result.Errors.OfType<object>().ToArray())})";
|
||||
model.RateError = $"{result.EvaluatedRule} ({string.Join(", ", result.Errors.OfType<object>().ToArray())})";
|
||||
}
|
||||
}
|
||||
catch (Exception ex) { model.RateError = ex.Message; }
|
||||
}
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
private async Task<string> GetSeed(WalletId walletId, BTCPayNetwork network)
|
||||
{
|
||||
return await CanUseHotWallet() &&
|
||||
GetDerivationSchemeSettings(walletId) is DerivationSchemeSettings s &&
|
||||
s.IsHotWallet &&
|
||||
ExplorerClientProvider.GetExplorerClient(network) is ExplorerClient client &&
|
||||
await client.GetMetadataAsync<string>(s.AccountDerivation, WellknownMetadataKeys.MasterHDKey) is
|
||||
string seed &&
|
||||
!string.IsNullOrEmpty(seed)
|
||||
? seed
|
||||
: null;
|
||||
GetDerivationSchemeSettings(walletId) is DerivationSchemeSettings s &&
|
||||
s.IsHotWallet &&
|
||||
ExplorerClientProvider.GetExplorerClient(network) is ExplorerClient client &&
|
||||
await client.GetMetadataAsync<string>(s.AccountDerivation, WellknownMetadataKeys.MasterHDKey) is string seed &&
|
||||
!string.IsNullOrEmpty(seed) ? seed : null;
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("{walletId}/send")]
|
||||
public async Task<IActionResult> WalletSend(
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, WalletSendModel vm, string command = "", CancellationToken cancellation = default,
|
||||
string bip21 = "")
|
||||
WalletId walletId, WalletSendModel vm, string command = "", CancellationToken cancellation = default, string bip21 = "")
|
||||
{
|
||||
if (walletId?.StoreId == null)
|
||||
return NotFound();
|
||||
@ -613,8 +606,7 @@ namespace BTCPayServer.Controllers
|
||||
var walletBlobAsync = await WalletRepository.GetWalletInfo(walletId);
|
||||
var walletTransactionsInfoAsync = await WalletRepository.GetWalletTransactionsInfo(walletId);
|
||||
|
||||
var utxos = await _walletProvider.GetWallet(network)
|
||||
.GetUnspentCoins(schemeSettings.AccountDerivation, cancellation);
|
||||
var utxos = await _walletProvider.GetWallet(network).GetUnspentCoins(schemeSettings.AccountDerivation, cancellation);
|
||||
vm.InputsAvailable = utxos.Select(coin =>
|
||||
{
|
||||
walletTransactionsInfoAsync.TryGetValue(coin.OutPoint.Hash.ToString(), out var info);
|
||||
@ -623,12 +615,8 @@ namespace BTCPayServer.Controllers
|
||||
Outpoint = coin.OutPoint.ToString(),
|
||||
Amount = coin.Value.GetValue(network),
|
||||
Comment = info?.Comment,
|
||||
Labels =
|
||||
info == null
|
||||
? null
|
||||
: _labelFactory.ColorizeTransactionLabels(walletBlobAsync, info, Request),
|
||||
Link = string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink,
|
||||
coin.OutPoint.Hash.ToString()),
|
||||
Labels = info == null ? null : _labelFactory.ColorizeTransactionLabels(walletBlobAsync, info, Request),
|
||||
Link = string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, coin.OutPoint.Hash.ToString()),
|
||||
Confirmations = coin.Confirmations
|
||||
};
|
||||
}).ToArray();
|
||||
@ -657,9 +645,7 @@ namespace BTCPayServer.Controllers
|
||||
if (command.StartsWith("remove-output", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
ModelState.Clear();
|
||||
var index = int.Parse(
|
||||
command.Substring(command.IndexOf(":", StringComparison.InvariantCultureIgnoreCase) + 1),
|
||||
CultureInfo.InvariantCulture);
|
||||
var index = int.Parse(command.Substring(command.IndexOf(":", StringComparison.InvariantCultureIgnoreCase) + 1), CultureInfo.InvariantCulture);
|
||||
vm.Outputs.RemoveAt(index);
|
||||
return View(vm);
|
||||
}
|
||||
@ -671,8 +657,6 @@ namespace BTCPayServer.Controllers
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
var bypassBalanceChecks = command == "schedule";
|
||||
|
||||
var subtractFeesOutputsCount = new List<int>();
|
||||
var substractFees = vm.Outputs.Any(o => o.SubtractFeesFromOutput);
|
||||
for (var i = 0; i < vm.Outputs.Count; i++)
|
||||
@ -685,20 +669,17 @@ namespace BTCPayServer.Controllers
|
||||
transactionOutput.DestinationAddress = transactionOutput.DestinationAddress?.Trim() ?? string.Empty;
|
||||
|
||||
var inputName =
|
||||
string.Format(CultureInfo.InvariantCulture, "Outputs[{0}].",
|
||||
i.ToString(CultureInfo.InvariantCulture)) +
|
||||
nameof(transactionOutput.DestinationAddress);
|
||||
string.Format(CultureInfo.InvariantCulture, "Outputs[{0}].", i.ToString(CultureInfo.InvariantCulture)) +
|
||||
nameof(transactionOutput.DestinationAddress);
|
||||
try
|
||||
{
|
||||
var address = BitcoinAddress.Create(transactionOutput.DestinationAddress, network.NBitcoinNetwork);
|
||||
if (address is TaprootAddress)
|
||||
{
|
||||
var supportTaproot = _dashboard.Get(network.CryptoCode)?.Status?.BitcoinStatus?.Capabilities
|
||||
?.CanSupportTaproot;
|
||||
var supportTaproot = _dashboard.Get(network.CryptoCode)?.Status?.BitcoinStatus?.Capabilities?.CanSupportTaproot;
|
||||
if (!(supportTaproot is true))
|
||||
{
|
||||
ModelState.AddModelError(inputName,
|
||||
"You need to update your full node, and/or NBXplorer (Version >= 2.1.56) to be able to send to a taproot address.");
|
||||
ModelState.AddModelError(inputName, "You need to update your full node, and/or NBXplorer (Version >= 2.1.56) to be able to send to a taproot address.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -707,7 +688,7 @@ namespace BTCPayServer.Controllers
|
||||
ModelState.AddModelError(inputName, "Invalid address");
|
||||
}
|
||||
|
||||
if (!bypassBalanceChecks && transactionOutput.Amount.HasValue)
|
||||
if (transactionOutput.Amount.HasValue)
|
||||
{
|
||||
transactionAmountSum += transactionOutput.Amount.Value;
|
||||
|
||||
@ -719,120 +700,41 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
if (!bypassBalanceChecks)
|
||||
if (subtractFeesOutputsCount.Count > 1)
|
||||
{
|
||||
if (subtractFeesOutputsCount.Count > 1)
|
||||
foreach (var subtractFeesOutput in subtractFeesOutputsCount)
|
||||
{
|
||||
foreach (var subtractFeesOutput in subtractFeesOutputsCount)
|
||||
{
|
||||
vm.AddModelError(model => model.Outputs[subtractFeesOutput].SubtractFeesFromOutput,
|
||||
"You can only subtract fees from one output", this);
|
||||
}
|
||||
}
|
||||
else if (vm.CurrentBalance == transactionAmountSum && !substractFees)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty,
|
||||
"You are sending your entire balance, you should subtract the fees from an output");
|
||||
}
|
||||
|
||||
if (vm.CurrentBalance < transactionAmountSum)
|
||||
{
|
||||
for (var i = 0; i < vm.Outputs.Count; i++)
|
||||
{
|
||||
vm.AddModelError(model => model.Outputs[i].Amount,
|
||||
"You are sending more than what you own", this);
|
||||
}
|
||||
}
|
||||
|
||||
if (vm.FeeSatoshiPerByte is decimal fee)
|
||||
{
|
||||
if (fee < 0)
|
||||
{
|
||||
vm.AddModelError(model => model.FeeSatoshiPerByte,
|
||||
"The fee rate should be above 0", this);
|
||||
}
|
||||
vm.AddModelError(model => model.Outputs[subtractFeesOutput].SubtractFeesFromOutput,
|
||||
"You can only subtract fees from one output", this);
|
||||
}
|
||||
}
|
||||
else if (vm.CurrentBalance == transactionAmountSum && !substractFees)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty,
|
||||
"You are sending your entire balance, you should subtract the fees from an output");
|
||||
}
|
||||
|
||||
if (vm.CurrentBalance < transactionAmountSum)
|
||||
{
|
||||
for (var i = 0; i < vm.Outputs.Count; i++)
|
||||
{
|
||||
vm.AddModelError(model => model.Outputs[i].Amount,
|
||||
"You are sending more than what you own", this);
|
||||
}
|
||||
}
|
||||
if (vm.FeeSatoshiPerByte is decimal fee)
|
||||
{
|
||||
if (fee < 0)
|
||||
{
|
||||
vm.AddModelError(model => model.FeeSatoshiPerByte,
|
||||
"The fee rate should be above 0", this);
|
||||
}
|
||||
}
|
||||
if (!ModelState.IsValid)
|
||||
return View(vm);
|
||||
|
||||
DerivationSchemeSettings derivationScheme = GetDerivationSchemeSettings(walletId);
|
||||
CreatePSBTResponse psbtResponse;
|
||||
if (command == "schedule")
|
||||
{
|
||||
var pmi = new PaymentMethodId(walletId.CryptoCode, BitcoinPaymentType.Instance);
|
||||
var claims =
|
||||
vm.Outputs.Where(output => string.IsNullOrEmpty(output.PayoutId)).Select(output => new ClaimRequest()
|
||||
{
|
||||
Destination = new AddressClaimDestination(
|
||||
BitcoinAddress.Create(output.DestinationAddress, network.NBitcoinNetwork)),
|
||||
Value = output.Amount.Value,
|
||||
PaymentMethodId = pmi,
|
||||
StoreId = walletId.StoreId,
|
||||
PreApprove = true,
|
||||
}).ToArray();
|
||||
var someFailed = false;
|
||||
string message = null;
|
||||
string errorMessage = null;
|
||||
var result = new Dictionary<ClaimRequest, ClaimRequest.ClaimResult>();
|
||||
foreach (ClaimRequest claimRequest in claims)
|
||||
{
|
||||
var response = await _pullPaymentHostedService.Claim(claimRequest);
|
||||
result.Add(claimRequest, response.Result);
|
||||
if (response.Result == ClaimRequest.ClaimResult.Ok)
|
||||
{
|
||||
if (message is null)
|
||||
{
|
||||
message = "Payouts scheduled:<br/>";
|
||||
}
|
||||
|
||||
message += $"{claimRequest.Value} to {claimRequest.Destination.ToString()}<br/>";
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
someFailed = true;
|
||||
if (errorMessage is null)
|
||||
{
|
||||
errorMessage = "Payouts failed to be scheduled:<br/>";
|
||||
}
|
||||
|
||||
switch (response.Result)
|
||||
{
|
||||
case ClaimRequest.ClaimResult.Duplicate:
|
||||
errorMessage += $"{claimRequest.Value} to {claimRequest.Destination.ToString() } - address reuse<br/>";
|
||||
break;
|
||||
case ClaimRequest.ClaimResult.AmountTooLow:
|
||||
errorMessage += $"{claimRequest.Value} to {claimRequest.Destination.ToString() } - amount too low<br/>";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (message is not null && errorMessage is not null)
|
||||
{
|
||||
message += $"<br/><br/>{errorMessage}";
|
||||
}
|
||||
else if(message is null && errorMessage is not null)
|
||||
{
|
||||
message = errorMessage;
|
||||
}
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity =someFailed? StatusMessageModel.StatusSeverity.Warning:
|
||||
StatusMessageModel.StatusSeverity.Success,
|
||||
Html = message
|
||||
});
|
||||
return RedirectToAction("Payouts", "UIStorePullPayments",
|
||||
new
|
||||
{
|
||||
storeId = walletId.StoreId,
|
||||
PaymentMethodId = pmi.ToString(),
|
||||
payoutState = PayoutState.AwaitingPayment,
|
||||
});
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
psbtResponse = await CreatePSBT(network, derivationScheme, vm, cancellation);
|
||||
@ -861,17 +763,23 @@ namespace BTCPayServer.Controllers
|
||||
switch (command)
|
||||
{
|
||||
case "sign":
|
||||
return await WalletSign(walletId, new WalletPSBTViewModel() { SigningContext = signingContext });
|
||||
return await WalletSign(walletId, new WalletPSBTViewModel()
|
||||
{
|
||||
SigningContext = signingContext
|
||||
});
|
||||
case "analyze-psbt":
|
||||
var name =
|
||||
$"Send-{string.Join('_', vm.Outputs.Select(output => $"{output.Amount}->{output.DestinationAddress}{(output.SubtractFeesFromOutput ? "-Fees" : string.Empty)}"))}.psbt";
|
||||
return RedirectToWalletPSBT(new WalletPSBTViewModel { PSBT = psbt.ToBase64(), FileName = name });
|
||||
return RedirectToWalletPSBT(new WalletPSBTViewModel
|
||||
{
|
||||
PSBT = psbt.ToBase64(),
|
||||
FileName = name
|
||||
});
|
||||
default:
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void LoadFromBIP21(WalletSendModel vm, string bip21, BTCPayNetwork network)
|
||||
{
|
||||
vm.Outputs ??= new List<WalletSendModel.TransactionOutput>();
|
||||
@ -883,10 +791,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
Amount = uriBuilder.Amount?.ToDecimal(MoneyUnit.BTC),
|
||||
DestinationAddress = uriBuilder.Address.ToString(),
|
||||
SubtractFeesFromOutput = false,
|
||||
PayoutId = uriBuilder.UnknownParameters.ContainsKey("payout")
|
||||
? uriBuilder.UnknownParameters["payout"]
|
||||
: null
|
||||
SubtractFeesFromOutput = false
|
||||
});
|
||||
if (!string.IsNullOrEmpty(uriBuilder.Label) || !string.IsNullOrEmpty(uriBuilder.Message))
|
||||
{
|
||||
@ -906,9 +811,9 @@ namespace BTCPayServer.Controllers
|
||||
try
|
||||
{
|
||||
vm.Outputs.Add(new WalletSendModel.TransactionOutput()
|
||||
{
|
||||
DestinationAddress = BitcoinAddress.Create(bip21, network.NBitcoinNetwork).ToString()
|
||||
}
|
||||
{
|
||||
DestinationAddress = BitcoinAddress.Create(bip21, network.NBitcoinNetwork).ToString()
|
||||
}
|
||||
);
|
||||
}
|
||||
catch
|
||||
@ -926,22 +831,23 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
private IActionResult ViewVault(WalletId walletId, SigningContextModel signingContext)
|
||||
{
|
||||
return View(nameof(WalletSendVault),
|
||||
new WalletSendVaultModel()
|
||||
{
|
||||
SigningContext = signingContext,
|
||||
WalletId = walletId.ToString(),
|
||||
WebsocketPath = this.Url.Action(nameof(UIVaultController.VaultBridgeConnection), "UIVault",
|
||||
new { walletId = walletId.ToString() })
|
||||
});
|
||||
return View(nameof(WalletSendVault), new WalletSendVaultModel()
|
||||
{
|
||||
SigningContext = signingContext,
|
||||
WalletId = walletId.ToString(),
|
||||
WebsocketPath = this.Url.Action(nameof(UIVaultController.VaultBridgeConnection), "UIVault", new { walletId = walletId.ToString() })
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{walletId}/vault")]
|
||||
public IActionResult WalletSendVault([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId,
|
||||
WalletSendVaultModel model)
|
||||
public IActionResult WalletSendVault([ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, WalletSendVaultModel model)
|
||||
{
|
||||
return RedirectToWalletPSBTReady(new WalletPSBTReadyViewModel() { SigningContext = model.SigningContext });
|
||||
return RedirectToWalletPSBTReady(new WalletPSBTReadyViewModel()
|
||||
{
|
||||
SigningContext = model.SigningContext
|
||||
});
|
||||
}
|
||||
|
||||
private IActionResult RedirectToWalletPSBTReady(WalletPSBTReadyViewModel vm)
|
||||
@ -980,8 +886,7 @@ namespace BTCPayServer.Controllers
|
||||
redirectVm.FormParameters.Add("SigningContext.PSBT", signingContext.PSBT);
|
||||
redirectVm.FormParameters.Add("SigningContext.OriginalPSBT", signingContext.OriginalPSBT);
|
||||
redirectVm.FormParameters.Add("SigningContext.PayJoinBIP21", signingContext.PayJoinBIP21);
|
||||
redirectVm.FormParameters.Add("SigningContext.EnforceLowR",
|
||||
signingContext.EnforceLowR?.ToString(CultureInfo.InvariantCulture));
|
||||
redirectVm.FormParameters.Add("SigningContext.EnforceLowR", signingContext.EnforceLowR?.ToString(CultureInfo.InvariantCulture));
|
||||
redirectVm.FormParameters.Add("SigningContext.ChangeAddress", signingContext.ChangeAddress);
|
||||
}
|
||||
|
||||
@ -992,21 +897,29 @@ namespace BTCPayServer.Controllers
|
||||
AspController = "UIWallets",
|
||||
AspAction = nameof(WalletPSBT),
|
||||
RouteParameters = { { "walletId", this.RouteData?.Values["walletId"]?.ToString() } },
|
||||
FormParameters = { { "psbt", vm.PSBT }, { "fileName", vm.FileName }, { "command", "decode" }, }
|
||||
FormParameters =
|
||||
{
|
||||
{ "psbt", vm.PSBT },
|
||||
{ "fileName", vm.FileName },
|
||||
{ "command", "decode" },
|
||||
}
|
||||
};
|
||||
return View("PostRedirect", redirectVm);
|
||||
}
|
||||
|
||||
[HttpGet("{walletId}/psbt/seed")]
|
||||
public IActionResult SignWithSeed([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId,
|
||||
SigningContextModel signingContext)
|
||||
public IActionResult SignWithSeed([ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, SigningContextModel signingContext)
|
||||
{
|
||||
return View(nameof(SignWithSeed), new SignWithSeedViewModel { SigningContext = signingContext });
|
||||
return View(nameof(SignWithSeed), new SignWithSeedViewModel
|
||||
{
|
||||
SigningContext = signingContext
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("{walletId}/psbt/seed")]
|
||||
public async Task<IActionResult> SignWithSeed([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId,
|
||||
SignWithSeedViewModel viewModel)
|
||||
public async Task<IActionResult> SignWithSeed([ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, SignWithSeedViewModel viewModel)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
@ -1044,8 +957,7 @@ namespace BTCPayServer.Controllers
|
||||
RootedKeyPath rootedKeyPath = signingKeySettings.GetRootedKeyPath();
|
||||
if (rootedKeyPath == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.SeedOrKey),
|
||||
"The master fingerprint and/or account key path of your seed are not set in the wallet settings.");
|
||||
ModelState.AddModelError(nameof(viewModel.SeedOrKey), "The master fingerprint and/or account key path of your seed are not set in the wallet settings.");
|
||||
return View(nameof(SignWithSeed), viewModel);
|
||||
}
|
||||
// The user gave the root key, let's try to rebase the PSBT, and derive the account private key
|
||||
@ -1056,8 +968,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.SeedOrKey),
|
||||
"The master fingerprint does not match the one set in your wallet settings. Probable causes are: wrong seed, wrong passphrase or wrong fingerprint in your wallet settings.");
|
||||
ModelState.AddModelError(nameof(viewModel.SeedOrKey), "The master fingerprint does not match the one set in your wallet settings. Probable causes are: wrong seed, wrong passphrase or wrong fingerprint in your wallet settings.");
|
||||
return View(nameof(SignWithSeed), viewModel);
|
||||
}
|
||||
|
||||
@ -1068,15 +979,17 @@ namespace BTCPayServer.Controllers
|
||||
var changed = psbt.PSBTChanged(() => psbt.SignAll(settings.AccountDerivation, signingKey, rootedKeyPath));
|
||||
if (!changed)
|
||||
{
|
||||
var update = new UpdatePSBTRequest() { PSBT = psbt, DerivationScheme = settings.AccountDerivation };
|
||||
var update = new UpdatePSBTRequest()
|
||||
{
|
||||
PSBT = psbt,
|
||||
DerivationScheme = settings.AccountDerivation
|
||||
};
|
||||
update.RebaseKeyPaths = settings.GetPSBTRebaseKeyRules().ToList();
|
||||
psbt = (await ExplorerClientProvider.GetExplorerClient(network).UpdatePSBTAsync(update))?.PSBT;
|
||||
changed = psbt is not null && psbt.PSBTChanged(() =>
|
||||
psbt.SignAll(settings.AccountDerivation, signingKey, rootedKeyPath));
|
||||
changed = psbt is not null && psbt.PSBTChanged(() => psbt.SignAll(settings.AccountDerivation, signingKey, rootedKeyPath));
|
||||
if (!changed)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.SeedOrKey),
|
||||
"Impossible to sign the transaction. Probable causes: Incorrect account key path in wallet settings or PSBT already signed.");
|
||||
ModelState.AddModelError(nameof(viewModel.SeedOrKey), "Impossible to sign the transaction. Probable causes: Incorrect account key path in wallet settings or PSBT already signed.");
|
||||
return View(nameof(SignWithSeed), viewModel);
|
||||
}
|
||||
}
|
||||
@ -1108,10 +1021,8 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
var vm = new RescanWalletModel();
|
||||
vm.IsFullySync = _dashboard.IsFullySynched(walletId.CryptoCode, out var unused);
|
||||
vm.IsServerAdmin = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings))
|
||||
.Succeeded;
|
||||
vm.IsSupportedByCurrency =
|
||||
_dashboard.Get(walletId.CryptoCode)?.Status?.BitcoinStatus?.Capabilities?.CanScanTxoutSet == true;
|
||||
vm.IsServerAdmin = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings)).Succeeded;
|
||||
vm.IsSupportedByCurrency = _dashboard.Get(walletId.CryptoCode)?.Status?.BitcoinStatus?.Capabilities?.CanScanTxoutSet == true;
|
||||
var explorer = ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode);
|
||||
var scanProgress = await explorer.GetScanUTXOSetInformationAsync(paymentMethod.AccountDerivation);
|
||||
if (scanProgress != null)
|
||||
@ -1129,15 +1040,12 @@ namespace BTCPayServer.Controllers
|
||||
vm.RemainingTime = TimeSpan.FromSeconds(scanProgress.Progress.RemainingSeconds).PrettyPrint();
|
||||
}
|
||||
}
|
||||
|
||||
if (scanProgress.Status == ScanUTXOStatus.Complete)
|
||||
{
|
||||
vm.LastSuccess = scanProgress.Progress;
|
||||
vm.TimeOfScan = (scanProgress.Progress.CompletedAt.Value - scanProgress.Progress.StartedAt)
|
||||
.PrettyPrint();
|
||||
vm.TimeOfScan = (scanProgress.Progress.CompletedAt.Value - scanProgress.Progress.StartedAt).PrettyPrint();
|
||||
}
|
||||
}
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
@ -1155,13 +1063,12 @@ namespace BTCPayServer.Controllers
|
||||
var explorer = ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode);
|
||||
try
|
||||
{
|
||||
await explorer.ScanUTXOSetAsync(paymentMethod.AccountDerivation, vm.BatchSize, vm.GapLimit,
|
||||
vm.StartingIndex);
|
||||
await explorer.ScanUTXOSetAsync(paymentMethod.AccountDerivation, vm.BatchSize, vm.GapLimit, vm.StartingIndex);
|
||||
}
|
||||
catch (NBXplorerException ex) when (ex.Error.Code == "scanutxoset-in-progress")
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
return RedirectToAction();
|
||||
}
|
||||
|
||||
@ -1170,8 +1077,7 @@ namespace BTCPayServer.Controllers
|
||||
return GetCurrentStore().GetDerivationSchemeSettings(NetworkProvider, walletId.CryptoCode);
|
||||
}
|
||||
|
||||
private static async Task<IMoney> GetBalanceAsMoney(BTCPayWallet wallet,
|
||||
DerivationStrategyBase derivationStrategy)
|
||||
private static async Task<IMoney> GetBalanceAsMoney(BTCPayWallet wallet, DerivationStrategyBase derivationStrategy)
|
||||
{
|
||||
using CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||
try
|
||||
@ -1211,68 +1117,60 @@ namespace BTCPayServer.Controllers
|
||||
switch (command)
|
||||
{
|
||||
case "cpfp":
|
||||
{
|
||||
selectedTransactions ??= Array.Empty<string>();
|
||||
if (selectedTransactions.Length == 0)
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = $"No transaction selected";
|
||||
return RedirectToAction(nameof(WalletTransactions), new { walletId });
|
||||
}
|
||||
|
||||
var parameters = new MultiValueDictionary<string, string>();
|
||||
parameters.Add("walletId", walletId.ToString());
|
||||
int i = 0;
|
||||
foreach (var tx in selectedTransactions)
|
||||
{
|
||||
parameters.Add($"transactionHashes[{i}]", tx);
|
||||
i++;
|
||||
}
|
||||
|
||||
parameters.Add("returnUrl", Url.Action(nameof(WalletTransactions), new { walletId }));
|
||||
return View("PostRedirect",
|
||||
new PostRedirectViewModel
|
||||
selectedTransactions ??= Array.Empty<string>();
|
||||
if (selectedTransactions.Length == 0)
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = $"No transaction selected";
|
||||
return RedirectToAction(nameof(WalletTransactions), new { walletId });
|
||||
}
|
||||
var parameters = new MultiValueDictionary<string, string>();
|
||||
parameters.Add("walletId", walletId.ToString());
|
||||
int i = 0;
|
||||
foreach (var tx in selectedTransactions)
|
||||
{
|
||||
parameters.Add($"transactionHashes[{i}]", tx);
|
||||
i++;
|
||||
}
|
||||
parameters.Add("returnUrl", Url.Action(nameof(WalletTransactions), new { walletId }));
|
||||
return View("PostRedirect", new PostRedirectViewModel
|
||||
{
|
||||
AspController = "UIWallets",
|
||||
AspAction = nameof(UIWalletsController.WalletCPFP),
|
||||
RouteParameters = { { "walletId", walletId.ToString() } },
|
||||
FormParameters = parameters
|
||||
});
|
||||
}
|
||||
}
|
||||
case "prune":
|
||||
{
|
||||
var result = await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode)
|
||||
.PruneAsync(derivationScheme.AccountDerivation, new PruneRequest(), cancellationToken);
|
||||
if (result.TotalPruned == 0)
|
||||
{
|
||||
TempData[WellKnownTempData.SuccessMessage] = "The wallet is already pruned";
|
||||
}
|
||||
else
|
||||
{
|
||||
TempData[WellKnownTempData.SuccessMessage] =
|
||||
$"The wallet has been successfully pruned ({result.TotalPruned} transactions have been removed from the history)";
|
||||
}
|
||||
var result = await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode).PruneAsync(derivationScheme.AccountDerivation, new PruneRequest(), cancellationToken);
|
||||
if (result.TotalPruned == 0)
|
||||
{
|
||||
TempData[WellKnownTempData.SuccessMessage] = "The wallet is already pruned";
|
||||
}
|
||||
else
|
||||
{
|
||||
TempData[WellKnownTempData.SuccessMessage] =
|
||||
$"The wallet has been successfully pruned ({result.TotalPruned} transactions have been removed from the history)";
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(WalletTransactions), new { walletId });
|
||||
}
|
||||
return RedirectToAction(nameof(WalletTransactions), new { walletId });
|
||||
}
|
||||
case "clear" when User.IsInRole(Roles.ServerAdmin):
|
||||
{
|
||||
if (Version.TryParse(_dashboard.Get(walletId.CryptoCode)?.Status?.Version ?? "0.0.0.0",
|
||||
out var v) &&
|
||||
v < new Version(2, 2, 4))
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] =
|
||||
"This version of NBXplorer doesn't support this operation, please upgrade to 2.2.4 or above";
|
||||
if (Version.TryParse(_dashboard.Get(walletId.CryptoCode)?.Status?.Version ?? "0.0.0.0", out var v) &&
|
||||
v < new Version(2, 2, 4))
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = "This version of NBXplorer doesn't support this operation, please upgrade to 2.2.4 or above";
|
||||
}
|
||||
else
|
||||
{
|
||||
await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode)
|
||||
.WipeAsync(derivationScheme.AccountDerivation, cancellationToken);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "The transactions have been wiped out, to restore your balance, rescan the wallet.";
|
||||
}
|
||||
return RedirectToAction(nameof(WalletTransactions), new { walletId });
|
||||
}
|
||||
else
|
||||
{
|
||||
await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode)
|
||||
.WipeAsync(derivationScheme.AccountDerivation, cancellationToken);
|
||||
TempData[WellKnownTempData.SuccessMessage] =
|
||||
"The transactions have been wiped out, to restore your balance, rescan the wallet.";
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(WalletTransactions), new { walletId });
|
||||
}
|
||||
default:
|
||||
return NotFound();
|
||||
}
|
||||
@ -1301,6 +1199,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
public class SendToAddressResult
|
||||
{
|
||||
[JsonProperty("psbt")] public string PSBT { get; set; }
|
||||
[JsonProperty("psbt")]
|
||||
public string PSBT { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System.Linq;
|
||||
using NBXplorer;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
@ -8,30 +7,20 @@ namespace BTCPayServer.Data
|
||||
{
|
||||
public static APIKeyBlob GetBlob(this APIKeyData apiKeyData)
|
||||
{
|
||||
return GetBlob<APIKeyBlob>(apiKeyData.Blob);
|
||||
var result = apiKeyData.Blob == null
|
||||
? new APIKeyBlob()
|
||||
: JObject.Parse(ZipUtils.Unzip(apiKeyData.Blob)).ToObject<APIKeyBlob>();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool SetBlob(this APIKeyData apiKeyData, APIKeyBlob blob)
|
||||
{
|
||||
var newBlob = SerializeBlob(blob);
|
||||
if (apiKeyData?.Blob?.SequenceEqual(newBlob) is true)
|
||||
return false;
|
||||
apiKeyData.Blob = newBlob;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static T GetBlob<T>(this byte[] data)
|
||||
{
|
||||
var result = data == null
|
||||
? default
|
||||
: JObject.Parse(ZipUtils.Unzip(data)).ToObject<T>();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte[] SerializeBlob<T>(this T blob)
|
||||
{
|
||||
var original = new Serializer(null).ToString(apiKeyData.GetBlob());
|
||||
var newBlob = new Serializer(null).ToString(blob);
|
||||
return ZipUtils.Zip(newBlob);
|
||||
if (original == newBlob)
|
||||
return false;
|
||||
apiKeyData.Blob = ZipUtils.Zip(newBlob);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,10 +4,9 @@ namespace BTCPayServer.Data
|
||||
{
|
||||
public static class InvoiceDataExtensions
|
||||
{
|
||||
public static InvoiceEntity GetBlob(this InvoiceData invoiceData, BTCPayNetworkProvider networks)
|
||||
public static InvoiceEntity GetBlob(this Data.InvoiceData invoiceData, BTCPayNetworkProvider networks)
|
||||
{
|
||||
|
||||
var entity = InvoiceRepository.FromBytes<InvoiceEntity>(invoiceData.Blob);
|
||||
var entity = NBitcoin.JsonConverters.Serializer.ToObject<InvoiceEntity>(ZipUtils.Unzip(invoiceData.Blob), null);
|
||||
entity.Networks = networks;
|
||||
if (entity.Metadata is null)
|
||||
{
|
||||
|
@ -6,7 +6,7 @@ namespace BTCPayServer.Data
|
||||
{
|
||||
public static class PaymentDataExtensions
|
||||
{
|
||||
public static PaymentEntity GetBlob(this PaymentData paymentData, BTCPayNetworkProvider networks)
|
||||
public static PaymentEntity GetBlob(this Data.PaymentData paymentData, BTCPayNetworkProvider networks)
|
||||
{
|
||||
var unziped = ZipUtils.Unzip(paymentData.Blob);
|
||||
var cryptoCode = "BTC";
|
||||
|
@ -5,7 +5,6 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
@ -244,8 +243,8 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
&& data.State == PayoutState.AwaitingPayment)
|
||||
.ToListAsync();
|
||||
|
||||
var pullPaymentIds = payouts.Select(data => data.PullPaymentDataId).Distinct().Where(s => s!= null).ToArray();
|
||||
var storeId = payouts.First().StoreDataId;
|
||||
var pullPaymentIds = payouts.Select(data => data.PullPaymentDataId).Distinct().ToArray();
|
||||
var storeId = payouts.First().PullPaymentData.StoreId;
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
List<string> bip21 = new List<string>();
|
||||
foreach (var payout in payouts)
|
||||
@ -262,14 +261,10 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
{
|
||||
case UriClaimDestination uriClaimDestination:
|
||||
uriClaimDestination.BitcoinUrl.Amount = new Money(blob.CryptoAmount.Value, MoneyUnit.BTC);
|
||||
var newUri = new UriBuilder(uriClaimDestination.BitcoinUrl.Uri);
|
||||
BTCPayServerClient.AppendPayloadToQuery(newUri, new KeyValuePair<string, object>("payout", payout.Id));
|
||||
bip21.Add(newUri.Uri.ToString());
|
||||
bip21.Add(uriClaimDestination.ToString());
|
||||
break;
|
||||
case AddressClaimDestination addressClaimDestination:
|
||||
var bip21New = network.GenerateBIP21(addressClaimDestination.Address.ToString(), new Money(blob.CryptoAmount.Value, MoneyUnit.BTC));
|
||||
bip21New.QueryParams.Add("payout", payout.Id);
|
||||
bip21.Add(bip21New.ToString());
|
||||
bip21.Add(network.GenerateBIP21(addressClaimDestination.Address.ToString(), new Money(blob.CryptoAmount.Value, MoneyUnit.BTC)).ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -331,7 +326,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
}
|
||||
}
|
||||
|
||||
if (proof.TransactionId is not null && !proof.Candidates.Contains(proof.TransactionId))
|
||||
if (proof.TransactionId is null && !proof.Candidates.Contains(proof.TransactionId))
|
||||
{
|
||||
proof.TransactionId = null;
|
||||
}
|
||||
@ -371,8 +366,8 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
|
||||
await using var ctx = _dbContextFactory.CreateContext();
|
||||
var payouts = await ctx.Payouts
|
||||
.Include(o => o.StoreData)
|
||||
.Include(o => o.PullPaymentData)
|
||||
.ThenInclude(o => o.StoreData)
|
||||
.Where(p => p.State == PayoutState.AwaitingPayment)
|
||||
.Where(p => p.PaymentMethodId == paymentMethodId.ToString())
|
||||
#pragma warning disable CA1307 // Specify StringComparison
|
||||
@ -391,7 +386,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
BTCPayServer.Extensions.RoundUp(payoutBlob.CryptoAmount.Value, network.Divisibility))
|
||||
return;
|
||||
|
||||
var derivationSchemeSettings = payout.StoreData
|
||||
var derivationSchemeSettings = payout.PullPaymentData.StoreData
|
||||
.GetDerivationSchemeSettings(_btcPayNetworkProvider, newTransaction.CryptoCode).AccountDerivation;
|
||||
|
||||
var storeWalletMatched = (await _explorerClientProvider.GetExplorerClient(newTransaction.CryptoCode)
|
||||
@ -408,19 +403,19 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
if (isInternal)
|
||||
{
|
||||
payout.State = PayoutState.InProgress;
|
||||
var walletId = new WalletId(payout.StoreDataId, newTransaction.CryptoCode);
|
||||
var walletId = new WalletId(payout.PullPaymentData.StoreId, newTransaction.CryptoCode);
|
||||
_eventAggregator.Publish(new UpdateTransactionLabel(walletId,
|
||||
newTransaction.NewTransactionEvent.TransactionData.TransactionHash,
|
||||
UpdateTransactionLabel.PayoutTemplate(payout.Id, payout.PullPaymentDataId, walletId.ToString())));
|
||||
}
|
||||
else
|
||||
{
|
||||
await _notificationSender.SendNotification(new StoreScope(payout.StoreDataId),
|
||||
await _notificationSender.SendNotification(new StoreScope(payout.PullPaymentData.StoreId),
|
||||
new ExternalPayoutTransactionNotification()
|
||||
{
|
||||
PaymentMethod = payout.PaymentMethodId,
|
||||
PayoutId = payout.Id,
|
||||
StoreId = payout.StoreDataId
|
||||
StoreId = payout.PullPaymentData.StoreId
|
||||
});
|
||||
}
|
||||
|
||||
@ -436,7 +431,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
}
|
||||
}
|
||||
|
||||
public void SetProofBlob(PayoutData data, PayoutTransactionOnChainBlob blob)
|
||||
private void SetProofBlob(PayoutData data, PayoutTransactionOnChainBlob blob)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(blob, _jsonSerializerSettings.GetSerializer(data.GetPaymentMethodId().CryptoCode)));
|
||||
// We only update the property if the bytes actually changed, this prevent from hammering the DB too much
|
||||
|
@ -1,4 +1,3 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
@ -16,8 +15,8 @@ public interface IPayoutHandler
|
||||
public Task TrackClaim(PaymentMethodId paymentMethodId, IClaimDestination claimDestination);
|
||||
//Allows payout handler to parse payout destinations on its own
|
||||
public Task<(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination);
|
||||
public (bool valid, string? error) ValidateClaimDestination(IClaimDestination claimDestination, PullPaymentBlob? pullPaymentBlob);
|
||||
public async Task<(IClaimDestination? destination, string? error)> ParseAndValidateClaimDestination(PaymentMethodId paymentMethodId, string destination, PullPaymentBlob? pullPaymentBlob)
|
||||
public (bool valid, string error) ValidateClaimDestination(IClaimDestination claimDestination, PullPaymentBlob pullPaymentBlob);
|
||||
public async Task<(IClaimDestination destination, string error)> ParseAndValidateClaimDestination(PaymentMethodId paymentMethodId, string destination, PullPaymentBlob pullPaymentBlob)
|
||||
{
|
||||
var res = await ParseClaimDestination(paymentMethodId, destination);
|
||||
if (res.destination is null)
|
||||
|
@ -108,7 +108,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||
if (claimDestination is not BoltInvoiceClaimDestination bolt)
|
||||
return (true, null);
|
||||
var invoice = bolt.PaymentRequest;
|
||||
if (pullPaymentBlob is not null && (invoice.ExpiryDate.UtcDateTime - DateTime.UtcNow) < pullPaymentBlob.BOLT11Expiration)
|
||||
if ((invoice.ExpiryDate.UtcDateTime - DateTime.UtcNow) < pullPaymentBlob.BOLT11Expiration)
|
||||
{
|
||||
return (false,
|
||||
$"The BOLT11 invoice must have an expiry date of at least {(long)pullPaymentBlob.BOLT11Expiration.TotalDays} days from submission (Provided was only {(invoice.ExpiryDate.UtcDateTime - DateTime.UtcNow).Days}).");
|
||||
|
@ -19,9 +19,9 @@ namespace BTCPayServer.Data
|
||||
if (includePullPayment)
|
||||
query = query.Include(p => p.PullPaymentData);
|
||||
if (includeStore)
|
||||
query = query.Include(p => p.StoreData);
|
||||
query = query.Include(p => p.PullPaymentData.StoreData);
|
||||
var payout = await query.Where(p => p.Id == payoutId &&
|
||||
p.StoreDataId == storeId).FirstOrDefaultAsync();
|
||||
p.PullPaymentData.StoreId == storeId).FirstOrDefaultAsync();
|
||||
if (payout is null)
|
||||
return null;
|
||||
return payout;
|
||||
|
@ -30,8 +30,6 @@ namespace BTCPayServer.Data
|
||||
[JsonProperty(ItemConverterType = typeof(PaymentMethodIdJsonConverter))]
|
||||
public PaymentMethodId[] SupportedPaymentMethods { get; set; }
|
||||
|
||||
public bool AutoApproveClaims { get; set; }
|
||||
|
||||
public class PullPaymentView
|
||||
{
|
||||
public string Title { get; set; }
|
||||
|
@ -2,7 +2,6 @@ namespace BTCPayServer.Events
|
||||
{
|
||||
public class SettingsChanged<T>
|
||||
{
|
||||
public string SettingsName { get; set; }
|
||||
public T Settings { get; set; }
|
||||
public override string ToString()
|
||||
{
|
||||
|
@ -1,6 +1,5 @@
|
||||
|
||||
using BTCPayServer;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
@ -33,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||
return urlHelper.GetUriByAction(
|
||||
action: nameof(UIPaymentRequestController.ViewPaymentRequest),
|
||||
controller: "UIPaymentRequest",
|
||||
values: new { payReqId = paymentRequestId },
|
||||
values: new { id = paymentRequestId },
|
||||
scheme, host, pathbase);
|
||||
}
|
||||
|
||||
@ -51,7 +50,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||
return urlHelper.GetUriByAction(
|
||||
action: nameof(UIInvoiceController.Invoice),
|
||||
controller: "UIInvoice",
|
||||
values: new { invoiceId },
|
||||
values: new { invoiceId = invoiceId },
|
||||
scheme, host, pathbase);
|
||||
}
|
||||
|
||||
@ -60,17 +59,17 @@ namespace Microsoft.AspNetCore.Mvc
|
||||
return urlHelper.GetUriByAction(
|
||||
action: nameof(UIInvoiceController.Checkout),
|
||||
controller: "UIInvoice",
|
||||
values: new { invoiceId },
|
||||
values: new { invoiceId = invoiceId },
|
||||
scheme, host, pathbase);
|
||||
}
|
||||
|
||||
public static string PayoutLink(this LinkGenerator urlHelper, string walletIdOrStoreId, string pullPaymentId, PayoutState payoutState,string scheme, HostString host, string pathbase)
|
||||
public static string PayoutLink(this LinkGenerator urlHelper, string walletIdOrStoreId, string pullPaymentId, string scheme, HostString host, string pathbase)
|
||||
{
|
||||
WalletId.TryParse(walletIdOrStoreId, out var wallet);
|
||||
return urlHelper.GetUriByAction(
|
||||
action: nameof(UIStorePullPaymentsController.Payouts),
|
||||
controller: "UIStorePullPayments",
|
||||
values: new { storeId = wallet?.StoreId ?? walletIdOrStoreId, pullPaymentId, payoutState },
|
||||
values: new { storeId = wallet?.StoreId ?? walletIdOrStoreId, pullPaymentId },
|
||||
scheme, host, pathbase);
|
||||
}
|
||||
}
|
||||
|
@ -13,17 +13,11 @@ namespace BTCPayServer.HostedServices
|
||||
private CancellationTokenSource _Cts = new CancellationTokenSource();
|
||||
protected Task[] _Tasks;
|
||||
public readonly Logs Logs;
|
||||
|
||||
protected BaseAsyncService(Logs logs)
|
||||
public BaseAsyncService(Logs logs)
|
||||
{
|
||||
Logs = logs;
|
||||
}
|
||||
|
||||
protected BaseAsyncService(ILogger logger)
|
||||
{
|
||||
Logs = new Logs() { PayServer = logger, Events = logger, Configuration = logger};
|
||||
}
|
||||
|
||||
public virtual Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_Tasks = InitializeTasks();
|
||||
|
@ -25,12 +25,6 @@ namespace BTCPayServer.HostedServices
|
||||
_EventAggregator = eventAggregator;
|
||||
Logs = logs;
|
||||
}
|
||||
|
||||
public EventHostedServiceBase(EventAggregator eventAggregator, ILogger logger)
|
||||
{
|
||||
_EventAggregator = eventAggregator;
|
||||
Logs = new Logs() { PayServer = logger, Events = logger, Configuration = logger};
|
||||
}
|
||||
|
||||
readonly Channel<object> _Events = Channel.CreateUnbounded<object>();
|
||||
public async Task ProcessEvents(CancellationToken cancellationToken)
|
||||
|
@ -35,10 +35,8 @@ namespace BTCPayServer.HostedServices
|
||||
public string EmbeddedCSS { get; set; }
|
||||
public PaymentMethodId[] PaymentMethodIds { get; set; }
|
||||
public TimeSpan? Period { get; set; }
|
||||
public bool AutoApproveClaims { get; set; }
|
||||
public TimeSpan? BOLT11Expiration { get; set; }
|
||||
}
|
||||
|
||||
public class PullPaymentHostedService : BaseAsyncService
|
||||
{
|
||||
public class CancelRequest
|
||||
@ -48,18 +46,15 @@ namespace BTCPayServer.HostedServices
|
||||
ArgumentNullException.ThrowIfNull(pullPaymentId);
|
||||
PullPaymentId = pullPaymentId;
|
||||
}
|
||||
|
||||
public CancelRequest(string[] payoutIds)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(payoutIds);
|
||||
PayoutIds = payoutIds;
|
||||
}
|
||||
|
||||
public string PullPaymentId { get; set; }
|
||||
public string[] PayoutIds { get; set; }
|
||||
internal TaskCompletionSource<bool> Completion { get; set; }
|
||||
}
|
||||
|
||||
public class PayoutApproval
|
||||
{
|
||||
public enum Result
|
||||
@ -70,7 +65,6 @@ namespace BTCPayServer.HostedServices
|
||||
TooLowAmount,
|
||||
OldRevision
|
||||
}
|
||||
|
||||
public string PayoutId { get; set; }
|
||||
public int Revision { get; set; }
|
||||
public decimal Rate { get; set; }
|
||||
@ -95,7 +89,6 @@ namespace BTCPayServer.HostedServices
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> CreatePullPayment(CreatePullPayment create)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(create);
|
||||
@ -103,9 +96,7 @@ namespace BTCPayServer.HostedServices
|
||||
throw new ArgumentException("Amount out of bound", nameof(create));
|
||||
using var ctx = this._dbContextFactory.CreateContext();
|
||||
var o = new Data.PullPaymentData();
|
||||
o.StartDate = create.StartsAt is DateTimeOffset date
|
||||
? date
|
||||
: DateTimeOffset.UtcNow - TimeSpan.FromSeconds(1.0);
|
||||
o.StartDate = create.StartsAt is DateTimeOffset date ? date : DateTimeOffset.UtcNow - TimeSpan.FromSeconds(1.0);
|
||||
o.EndDate = create.ExpiresAt is DateTimeOffset date2 ? new DateTimeOffset?(date2) : null;
|
||||
o.Period = create.Period is TimeSpan period ? (long?)period.TotalSeconds : null;
|
||||
o.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(20));
|
||||
@ -118,7 +109,6 @@ namespace BTCPayServer.HostedServices
|
||||
Limit = create.Amount,
|
||||
Period = o.Period is long periodSeconds ? (TimeSpan?)TimeSpan.FromSeconds(periodSeconds) : null,
|
||||
SupportedPaymentMethods = create.PaymentMethodIds,
|
||||
AutoApproveClaims = create.AutoApproveClaims,
|
||||
View = new PullPaymentBlob.PullPaymentView()
|
||||
{
|
||||
Title = create.Name ?? string.Empty,
|
||||
@ -146,21 +136,19 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
class PayoutRequest
|
||||
{
|
||||
public PayoutRequest(TaskCompletionSource<ClaimRequest.ClaimResponse> completionSource,
|
||||
ClaimRequest request)
|
||||
public PayoutRequest(TaskCompletionSource<ClaimRequest.ClaimResponse> completionSource, ClaimRequest request)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentNullException.ThrowIfNull(completionSource);
|
||||
Completion = completionSource;
|
||||
ClaimRequest = request;
|
||||
}
|
||||
|
||||
public TaskCompletionSource<ClaimRequest.ClaimResponse> Completion { get; set; }
|
||||
public ClaimRequest ClaimRequest { get; }
|
||||
}
|
||||
|
||||
public PullPaymentHostedService(ApplicationDbContextFactory dbContextFactory,
|
||||
BTCPayNetworkJsonSerializerSettings jsonSerializerSettings,
|
||||
CurrencyNameTable currencyNameTable,
|
||||
EventAggregator eventAggregator,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
NotificationSender notificationSender,
|
||||
@ -171,6 +159,7 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_jsonSerializerSettings = jsonSerializerSettings;
|
||||
_currencyNameTable = currencyNameTable;
|
||||
_eventAggregator = eventAggregator;
|
||||
_networkProvider = networkProvider;
|
||||
_notificationSender = notificationSender;
|
||||
@ -182,6 +171,7 @@ namespace BTCPayServer.HostedServices
|
||||
Channel<object> _Channel;
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _jsonSerializerSettings;
|
||||
private readonly CurrencyNameTable _currencyNameTable;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly NotificationSender _notificationSender;
|
||||
@ -197,7 +187,6 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
payoutHandler.StartBackgroundCheck(Subscribe);
|
||||
}
|
||||
|
||||
return new[] { Loop() };
|
||||
}
|
||||
|
||||
@ -222,17 +211,14 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
await HandleApproval(approv);
|
||||
}
|
||||
|
||||
if (o is CancelRequest cancel)
|
||||
{
|
||||
await HandleCancel(cancel);
|
||||
}
|
||||
|
||||
if (o is InternalPayoutPaidRequest paid)
|
||||
{
|
||||
await HandleMarkPaid(paid);
|
||||
}
|
||||
|
||||
foreach (IPayoutHandler payoutHandler in _payoutHandlers)
|
||||
{
|
||||
try
|
||||
@ -249,16 +235,14 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
public Task<RateResult> GetRate(PayoutData payout, string explicitRateRule, CancellationToken cancellationToken)
|
||||
{
|
||||
var ppBlob = payout.PullPaymentData?.GetBlob();
|
||||
var payoutPaymentMethod = payout.GetPaymentMethodId();
|
||||
var currencyPair = new Rating.CurrencyPair(payoutPaymentMethod.CryptoCode,
|
||||
ppBlob?.Currency ?? payoutPaymentMethod.CryptoCode);
|
||||
var ppBlob = payout.PullPaymentData.GetBlob();
|
||||
var currencyPair = new Rating.CurrencyPair(payout.GetPaymentMethodId().CryptoCode, ppBlob.Currency);
|
||||
Rating.RateRule rule = null;
|
||||
try
|
||||
{
|
||||
if (explicitRateRule is null)
|
||||
{
|
||||
var storeBlob = payout.StoreData.GetStoreBlob();
|
||||
var storeBlob = payout.PullPaymentData.StoreData.GetStoreBlob();
|
||||
var rules = storeBlob.GetRateRules(_networkProvider);
|
||||
rules.Spread = 0.0m;
|
||||
rule = rules.GetRuleFor(currencyPair);
|
||||
@ -272,73 +256,60 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
throw new FormatException("Invalid RateRule");
|
||||
}
|
||||
|
||||
return _rateFetcher.FetchRate(rule, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<PayoutApproval.Result> Approve(PayoutApproval approval)
|
||||
{
|
||||
approval.Completion =
|
||||
new TaskCompletionSource<PayoutApproval.Result>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
approval.Completion = new TaskCompletionSource<PayoutApproval.Result>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
if (!_Channel.Writer.TryWrite(approval))
|
||||
throw new ObjectDisposedException(nameof(PullPaymentHostedService));
|
||||
return approval.Completion.Task;
|
||||
}
|
||||
|
||||
private async Task HandleApproval(PayoutApproval req)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var ctx = _dbContextFactory.CreateContext();
|
||||
var payout = await ctx.Payouts.Include(p => p.PullPaymentData).Where(p => p.Id == req.PayoutId)
|
||||
.FirstOrDefaultAsync();
|
||||
var payout = await ctx.Payouts.Include(p => p.PullPaymentData).Where(p => p.Id == req.PayoutId).FirstOrDefaultAsync();
|
||||
if (payout is null)
|
||||
{
|
||||
req.Completion.SetResult(PayoutApproval.Result.NotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
if (payout.State != PayoutState.AwaitingApproval)
|
||||
{
|
||||
req.Completion.SetResult(PayoutApproval.Result.InvalidState);
|
||||
return;
|
||||
}
|
||||
|
||||
var payoutBlob = payout.GetBlob(this._jsonSerializerSettings);
|
||||
if (payoutBlob.Revision != req.Revision)
|
||||
{
|
||||
req.Completion.SetResult(PayoutApproval.Result.OldRevision);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PaymentMethodId.TryParse(payout.PaymentMethodId, out var paymentMethod))
|
||||
{
|
||||
req.Completion.SetResult(PayoutApproval.Result.NotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
payout.State = PayoutState.AwaitingPayment;
|
||||
|
||||
if (payout.PullPaymentData is null || paymentMethod.CryptoCode == payout.PullPaymentData.GetBlob().Currency)
|
||||
if (paymentMethod.CryptoCode == payout.PullPaymentData.GetBlob().Currency)
|
||||
req.Rate = 1.0m;
|
||||
var cryptoAmount = payoutBlob.Amount / req.Rate;
|
||||
var payoutHandler = _payoutHandlers.FindPayoutHandler(paymentMethod);
|
||||
if (payoutHandler is null)
|
||||
throw new InvalidOperationException($"No payout handler for {paymentMethod}");
|
||||
var dest = await payoutHandler.ParseClaimDestination(paymentMethod, payoutBlob.Destination);
|
||||
decimal minimumCryptoAmount =
|
||||
await payoutHandler.GetMinimumPayoutAmount(paymentMethod, dest.destination);
|
||||
decimal minimumCryptoAmount = await payoutHandler.GetMinimumPayoutAmount(paymentMethod, dest.destination);
|
||||
if (cryptoAmount < minimumCryptoAmount)
|
||||
{
|
||||
req.Completion.TrySetResult(PayoutApproval.Result.TooLowAmount);
|
||||
return;
|
||||
}
|
||||
|
||||
payoutBlob.CryptoAmount = Extensions.RoundUp(cryptoAmount,
|
||||
_networkProvider.GetNetwork(paymentMethod.CryptoCode).Divisibility);
|
||||
payoutBlob.CryptoAmount = BTCPayServer.Extensions.RoundUp(cryptoAmount, _networkProvider.GetNetwork(paymentMethod.CryptoCode).Divisibility);
|
||||
payout.SetBlob(payoutBlob, _jsonSerializerSettings);
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
req.Completion.SetResult(PayoutApproval.Result.Ok);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -346,31 +317,26 @@ namespace BTCPayServer.HostedServices
|
||||
req.Completion.TrySetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleMarkPaid(InternalPayoutPaidRequest req)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var ctx = _dbContextFactory.CreateContext();
|
||||
var payout = await ctx.Payouts.Include(p => p.PullPaymentData).Where(p => p.Id == req.Request.PayoutId)
|
||||
.FirstOrDefaultAsync();
|
||||
var payout = await ctx.Payouts.Include(p => p.PullPaymentData).Where(p => p.Id == req.Request.PayoutId).FirstOrDefaultAsync();
|
||||
if (payout is null)
|
||||
{
|
||||
req.Completion.SetResult(PayoutPaidRequest.PayoutPaidResult.NotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
if (payout.State != PayoutState.AwaitingPayment)
|
||||
{
|
||||
req.Completion.SetResult(PayoutPaidRequest.PayoutPaidResult.InvalidState);
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.Request.Proof != null)
|
||||
{
|
||||
payout.SetProofBlob(req.Request.Proof);
|
||||
}
|
||||
|
||||
payout.State = PayoutState.Completed;
|
||||
await ctx.SaveChangesAsync();
|
||||
req.Completion.SetResult(PayoutPaidRequest.PayoutPaidResult.Ok);
|
||||
@ -387,59 +353,40 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||
await using var ctx = _dbContextFactory.CreateContext();
|
||||
var withoutPullPayment = req.ClaimRequest.PullPaymentId is null;
|
||||
var pp = string.IsNullOrEmpty(req.ClaimRequest.PullPaymentId)
|
||||
? null
|
||||
: await ctx.PullPayments.FindAsync(req.ClaimRequest.PullPaymentId);
|
||||
var pp = await ctx.PullPayments.FindAsync(req.ClaimRequest.PullPaymentId);
|
||||
|
||||
if (!withoutPullPayment && (pp is null || pp.Archived))
|
||||
if (pp is null || pp.Archived)
|
||||
{
|
||||
req.Completion.TrySetResult(new ClaimRequest.ClaimResponse(ClaimRequest.ClaimResult.Archived));
|
||||
return;
|
||||
}
|
||||
|
||||
PullPaymentBlob ppBlob = null;
|
||||
if (!withoutPullPayment)
|
||||
if (pp.IsExpired(now))
|
||||
{
|
||||
if (pp.IsExpired(now))
|
||||
{
|
||||
req.Completion.TrySetResult(new ClaimRequest.ClaimResponse(ClaimRequest.ClaimResult.Expired));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pp.HasStarted(now))
|
||||
{
|
||||
req.Completion.TrySetResult(
|
||||
new ClaimRequest.ClaimResponse(ClaimRequest.ClaimResult.NotStarted));
|
||||
return;
|
||||
}
|
||||
|
||||
ppBlob = pp.GetBlob();
|
||||
|
||||
if (!ppBlob.SupportedPaymentMethods.Contains(req.ClaimRequest.PaymentMethodId))
|
||||
{
|
||||
req.Completion.TrySetResult(
|
||||
new ClaimRequest.ClaimResponse(ClaimRequest.ClaimResult.PaymentMethodNotSupported));
|
||||
return;
|
||||
}
|
||||
req.Completion.TrySetResult(new ClaimRequest.ClaimResponse(ClaimRequest.ClaimResult.Expired));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pp.HasStarted(now))
|
||||
{
|
||||
req.Completion.TrySetResult(new ClaimRequest.ClaimResponse(ClaimRequest.ClaimResult.NotStarted));
|
||||
return;
|
||||
}
|
||||
var ppBlob = pp.GetBlob();
|
||||
var payoutHandler =
|
||||
_payoutHandlers.FindPayoutHandler(req.ClaimRequest.PaymentMethodId);
|
||||
if (payoutHandler is null)
|
||||
if (!ppBlob.SupportedPaymentMethods.Contains(req.ClaimRequest.PaymentMethodId) || payoutHandler is null)
|
||||
{
|
||||
req.Completion.TrySetResult(
|
||||
new ClaimRequest.ClaimResponse(ClaimRequest.ClaimResult.PaymentMethodNotSupported));
|
||||
req.Completion.TrySetResult(new ClaimRequest.ClaimResponse(ClaimRequest.ClaimResult.PaymentMethodNotSupported));
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.ClaimRequest.Destination.Id != null)
|
||||
{
|
||||
if (await ctx.Payouts.AnyAsync(data =>
|
||||
data.Destination.Equals(req.ClaimRequest.Destination.Id) &&
|
||||
data.State != PayoutState.Completed && data.State != PayoutState.Cancelled
|
||||
data.Destination.Equals(req.ClaimRequest.Destination.Id) &&
|
||||
data.State != PayoutState.Completed && data.State != PayoutState.Cancelled
|
||||
))
|
||||
{
|
||||
|
||||
req.Completion.TrySetResult(new ClaimRequest.ClaimResponse(ClaimRequest.ClaimResult.Duplicate));
|
||||
return;
|
||||
}
|
||||
@ -453,27 +400,22 @@ namespace BTCPayServer.HostedServices
|
||||
return;
|
||||
}
|
||||
|
||||
var payoutsRaw = withoutPullPayment
|
||||
? null
|
||||
: await ctx.Payouts.GetPayoutInPeriod(pp, now)
|
||||
.Where(p => p.State != PayoutState.Cancelled).ToListAsync();
|
||||
|
||||
var payouts = payoutsRaw?.Select(o => new { Entity = o, Blob = o.GetBlob(_jsonSerializerSettings) });
|
||||
var limit = ppBlob?.Limit ?? 0;
|
||||
var totalPayout = payouts?.Select(p => p.Blob.Amount)?.Sum();
|
||||
var claimed = req.ClaimRequest.Value is decimal v ? v : limit - (totalPayout ?? 0);
|
||||
if (totalPayout is not null && totalPayout + claimed > limit)
|
||||
var payouts = (await ctx.Payouts.GetPayoutInPeriod(pp, now)
|
||||
.Where(p => p.State != PayoutState.Cancelled)
|
||||
.ToListAsync())
|
||||
.Select(o => new
|
||||
{
|
||||
Entity = o,
|
||||
Blob = o.GetBlob(_jsonSerializerSettings)
|
||||
});
|
||||
var limit = ppBlob.Limit;
|
||||
var totalPayout = payouts.Select(p => p.Blob.Amount).Sum();
|
||||
var claimed = req.ClaimRequest.Value is decimal v ? v : limit - totalPayout;
|
||||
if (totalPayout + claimed > limit)
|
||||
{
|
||||
req.Completion.TrySetResult(new ClaimRequest.ClaimResponse(ClaimRequest.ClaimResult.Overdraft));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!withoutPullPayment && (claimed < ppBlob.MinimumClaim || claimed == 0.0m))
|
||||
{
|
||||
req.Completion.TrySetResult(new ClaimRequest.ClaimResponse(ClaimRequest.ClaimResult.AmountTooLow));
|
||||
return;
|
||||
}
|
||||
|
||||
var payout = new PayoutData()
|
||||
{
|
||||
Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(20)),
|
||||
@ -481,9 +423,13 @@ namespace BTCPayServer.HostedServices
|
||||
State = PayoutState.AwaitingApproval,
|
||||
PullPaymentDataId = req.ClaimRequest.PullPaymentId,
|
||||
PaymentMethodId = req.ClaimRequest.PaymentMethodId.ToString(),
|
||||
Destination = req.ClaimRequest.Destination.Id,
|
||||
StoreDataId = req.ClaimRequest.StoreId ?? pp?.StoreId
|
||||
Destination = req.ClaimRequest.Destination.Id
|
||||
};
|
||||
if (claimed < ppBlob.MinimumClaim || claimed == 0.0m)
|
||||
{
|
||||
req.Completion.TrySetResult(new ClaimRequest.ClaimResponse(ClaimRequest.ClaimResult.AmountTooLow));
|
||||
return;
|
||||
}
|
||||
var payoutBlob = new PayoutBlob()
|
||||
{
|
||||
Amount = claimed,
|
||||
@ -495,34 +441,14 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
await payoutHandler.TrackClaim(req.ClaimRequest.PaymentMethodId, req.ClaimRequest.Destination);
|
||||
await ctx.SaveChangesAsync();
|
||||
if (req.ClaimRequest.PreApprove.GetValueOrDefault(ppBlob?.AutoApproveClaims is true) )
|
||||
{
|
||||
payout.StoreData = await ctx.Stores.FindAsync(payout.StoreDataId);
|
||||
var rateResult = await GetRate(payout, null, CancellationToken.None);
|
||||
if (rateResult.BidAsk != null)
|
||||
{
|
||||
var approveResult = new TaskCompletionSource<PayoutApproval.Result>();
|
||||
await HandleApproval(new PayoutApproval()
|
||||
{
|
||||
PayoutId = payout.Id, Revision = payoutBlob.Revision, Rate = rateResult.BidAsk.Ask, Completion =approveResult
|
||||
});
|
||||
|
||||
if ((await approveResult.Task) == PayoutApproval.Result.Ok)
|
||||
{
|
||||
payout.State = PayoutState.AwaitingPayment;
|
||||
}
|
||||
}
|
||||
}
|
||||
req.Completion.TrySetResult(new ClaimRequest.ClaimResponse(ClaimRequest.ClaimResult.Ok, payout));
|
||||
await _notificationSender.SendNotification(new StoreScope(payout.StoreDataId),
|
||||
new PayoutNotification()
|
||||
{
|
||||
StoreId = payout.StoreDataId,
|
||||
Currency = ppBlob?.Currency ?? req.ClaimRequest.PaymentMethodId.CryptoCode,
|
||||
Status = payout.State,
|
||||
PaymentMethod = payout.PaymentMethodId,
|
||||
PayoutId = payout.Id
|
||||
});
|
||||
await _notificationSender.SendNotification(new StoreScope(pp.StoreId), new PayoutNotification()
|
||||
{
|
||||
StoreId = pp.StoreId,
|
||||
Currency = ppBlob.Currency,
|
||||
PaymentMethod = payout.PaymentMethodId,
|
||||
PayoutId = pp.Id
|
||||
});
|
||||
}
|
||||
catch (DbUpdateException)
|
||||
{
|
||||
@ -534,7 +460,6 @@ namespace BTCPayServer.HostedServices
|
||||
req.Completion.TrySetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleCancel(CancelRequest cancel)
|
||||
{
|
||||
try
|
||||
@ -546,15 +471,15 @@ namespace BTCPayServer.HostedServices
|
||||
ctx.PullPayments.Attach(new Data.PullPaymentData() { Id = cancel.PullPaymentId, Archived = true })
|
||||
.Property(o => o.Archived).IsModified = true;
|
||||
payouts = await ctx.Payouts
|
||||
.Where(p => p.PullPaymentDataId == cancel.PullPaymentId)
|
||||
.ToListAsync();
|
||||
.Where(p => p.PullPaymentDataId == cancel.PullPaymentId)
|
||||
.ToListAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
var payoutIds = cancel.PayoutIds.ToHashSet();
|
||||
payouts = await ctx.Payouts
|
||||
.Where(p => payoutIds.Contains(p.Id))
|
||||
.ToListAsync();
|
||||
.Where(p => payoutIds.Contains(p.Id))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
foreach (var payout in payouts)
|
||||
@ -562,7 +487,6 @@ namespace BTCPayServer.HostedServices
|
||||
if (payout.State != PayoutState.Completed && payout.State != PayoutState.InProgress)
|
||||
payout.State = PayoutState.Cancelled;
|
||||
}
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
cancel.Completion.TrySetResult(true);
|
||||
}
|
||||
@ -571,7 +495,6 @@ namespace BTCPayServer.HostedServices
|
||||
cancel.Completion.TrySetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public Task Cancel(CancelRequest cancelRequest)
|
||||
{
|
||||
CancellationToken.ThrowIfCancellationRequested();
|
||||
@ -585,8 +508,7 @@ namespace BTCPayServer.HostedServices
|
||||
public Task<ClaimRequest.ClaimResponse> Claim(ClaimRequest request)
|
||||
{
|
||||
CancellationToken.ThrowIfCancellationRequested();
|
||||
var cts = new TaskCompletionSource<ClaimRequest.ClaimResponse>(TaskCreationOptions
|
||||
.RunContinuationsAsynchronously);
|
||||
var cts = new TaskCompletionSource<ClaimRequest.ClaimResponse>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
if (!_Channel.Writer.TryWrite(new PayoutRequest(cts, request)))
|
||||
throw new ObjectDisposedException(nameof(PullPaymentHostedService));
|
||||
return cts.Task;
|
||||
@ -602,8 +524,7 @@ namespace BTCPayServer.HostedServices
|
||||
public Task<PayoutPaidRequest.PayoutPaidResult> MarkPaid(PayoutPaidRequest request)
|
||||
{
|
||||
CancellationToken.ThrowIfCancellationRequested();
|
||||
var cts = new TaskCompletionSource<PayoutPaidRequest.PayoutPaidResult>(TaskCreationOptions
|
||||
.RunContinuationsAsynchronously);
|
||||
var cts = new TaskCompletionSource<PayoutPaidRequest.PayoutPaidResult>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
if (!_Channel.Writer.TryWrite(new InternalPayoutPaidRequest(cts, request)))
|
||||
throw new ObjectDisposedException(nameof(PullPaymentHostedService));
|
||||
return cts.Task;
|
||||
@ -612,18 +533,17 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
class InternalPayoutPaidRequest
|
||||
{
|
||||
public InternalPayoutPaidRequest(TaskCompletionSource<PayoutPaidRequest.PayoutPaidResult> completionSource,
|
||||
PayoutPaidRequest request)
|
||||
public InternalPayoutPaidRequest(TaskCompletionSource<PayoutPaidRequest.PayoutPaidResult> completionSource, PayoutPaidRequest request)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentNullException.ThrowIfNull(completionSource);
|
||||
Completion = completionSource;
|
||||
Request = request;
|
||||
}
|
||||
|
||||
public TaskCompletionSource<PayoutPaidRequest.PayoutPaidResult> Completion { get; set; }
|
||||
public PayoutPaidRequest Request { get; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class PayoutPaidRequest
|
||||
@ -634,7 +554,6 @@ namespace BTCPayServer.HostedServices
|
||||
NotFound,
|
||||
InvalidState
|
||||
}
|
||||
|
||||
public string PayoutId { get; set; }
|
||||
public ManualPayoutProof Proof { get; set; }
|
||||
|
||||
@ -652,6 +571,7 @@ namespace BTCPayServer.HostedServices
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class ClaimRequest
|
||||
@ -679,10 +599,8 @@ namespace BTCPayServer.HostedServices
|
||||
default:
|
||||
throw new NotSupportedException("Unsupported ClaimResult");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public class ClaimResponse
|
||||
{
|
||||
public ClaimResponse(ClaimResult result, PayoutData payoutData = null)
|
||||
@ -690,11 +608,9 @@ namespace BTCPayServer.HostedServices
|
||||
Result = result;
|
||||
PayoutData = payoutData;
|
||||
}
|
||||
|
||||
public ClaimResult Result { get; set; }
|
||||
public PayoutData PayoutData { get; set; }
|
||||
}
|
||||
|
||||
public enum ClaimResult
|
||||
{
|
||||
Ok,
|
||||
@ -711,7 +627,6 @@ namespace BTCPayServer.HostedServices
|
||||
public string PullPaymentId { get; set; }
|
||||
public decimal? Value { get; set; }
|
||||
public IClaimDestination Destination { get; set; }
|
||||
public string StoreId { get; set; }
|
||||
public bool? PreApprove { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Payments.PayJoin;
|
||||
using BTCPayServer.PayoutProcessors;
|
||||
using BTCPayServer.Plugins;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
@ -323,8 +322,7 @@ namespace BTCPayServer.Hosting
|
||||
.ConfigurePrimaryHttpMessageHandler<Socks5HttpClientHandler>();
|
||||
|
||||
|
||||
services.AddSingleton<BitcoinLikePayoutHandler>();
|
||||
services.AddSingleton<IPayoutHandler>(provider => provider.GetRequiredService<BitcoinLikePayoutHandler>());
|
||||
services.AddSingleton<IPayoutHandler, BitcoinLikePayoutHandler>();
|
||||
services.AddSingleton<IPayoutHandler, LightningLikePayoutHandler>();
|
||||
|
||||
services.AddHttpClient(LightningLikePayoutHandler.LightningLikePayoutHandlerOnionNamedClient)
|
||||
@ -404,7 +402,6 @@ namespace BTCPayServer.Hosting
|
||||
services.AddScoped<BTCPayServerClient, LocalBTCPayServerClient>();
|
||||
//also provide a factory that can impersonate user/store id
|
||||
services.AddSingleton<IBTCPayServerClientFactory, BTCPayServerClientFactory>();
|
||||
services.AddPayoutProcesors();
|
||||
|
||||
services.AddAPIKeyAuthentication();
|
||||
services.AddBtcPayServerAuthenticationSchemes();
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -24,14 +23,9 @@ using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBXplorer;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PeterO.Cbor;
|
||||
using PayoutData = BTCPayServer.Data.PayoutData;
|
||||
using PullPaymentData = BTCPayServer.Data.PullPaymentData;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
{
|
||||
@ -46,8 +40,6 @@ namespace BTCPayServer.Hosting
|
||||
private readonly AppService _appService;
|
||||
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
|
||||
private readonly LightningAddressService _lightningAddressService;
|
||||
private readonly ILogger<MigrationStartupTask> _logger;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
|
||||
public IOptions<LightningNetworkOptions> LightningOptions { get; }
|
||||
@ -62,9 +54,9 @@ namespace BTCPayServer.Hosting
|
||||
AppService appService,
|
||||
IEnumerable<IPayoutHandler> payoutHandlers,
|
||||
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
|
||||
LightningAddressService lightningAddressService,
|
||||
ILogger<MigrationStartupTask> logger)
|
||||
Logs logs)
|
||||
{
|
||||
Logs = logs;
|
||||
_DBContextFactory = dbContextFactory;
|
||||
_StoreRepository = storeRepository;
|
||||
_NetworkProvider = networkProvider;
|
||||
@ -72,8 +64,6 @@ namespace BTCPayServer.Hosting
|
||||
_appService = appService;
|
||||
_payoutHandlers = payoutHandlers;
|
||||
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
|
||||
_lightningAddressService = lightningAddressService;
|
||||
_logger = logger;
|
||||
_userManager = userManager;
|
||||
LightningOptions = lightningOptions;
|
||||
}
|
||||
@ -185,68 +175,14 @@ namespace BTCPayServer.Hosting
|
||||
settings.LighingAddressSettingRename = true;
|
||||
await _Settings.UpdateSetting(settings);
|
||||
}
|
||||
if (!settings.LighingAddressDatabaseMigration)
|
||||
{
|
||||
await MigrateLighingAddressDatabaseMigration();
|
||||
settings.LighingAddressDatabaseMigration = true;
|
||||
}
|
||||
if (!settings.AddStoreToPayout)
|
||||
{
|
||||
await MigrateAddStoreToPayout();
|
||||
settings.AddStoreToPayout = true;
|
||||
await _Settings.UpdateSetting(settings);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error on the MigrationStartupTask");
|
||||
Logs.PayServer.LogError(ex, "Error on the MigrationStartupTask");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task MigrateLighingAddressDatabaseMigration()
|
||||
{
|
||||
await using var ctx = _DBContextFactory.CreateContext();
|
||||
|
||||
var lightningAddressSettings =
|
||||
await _Settings.GetSettingAsync<UILNURLController.LightningAddressSettings>(
|
||||
nameof(UILNURLController.LightningAddressSettings));
|
||||
|
||||
if (lightningAddressSettings is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var storeids = lightningAddressSettings.StoreToItemMap.Keys.ToArray();
|
||||
var existingStores = (await ctx.Stores.Where(data => storeids.Contains(data.Id)).Select(data => data.Id ).ToArrayAsync()).ToHashSet();
|
||||
|
||||
foreach (var storeMap in lightningAddressSettings.StoreToItemMap)
|
||||
{
|
||||
if (!existingStores.Contains(storeMap.Key)) continue;
|
||||
foreach (var storeitem in storeMap.Value)
|
||||
{
|
||||
if (lightningAddressSettings.Items.TryGetValue(storeitem, out var val))
|
||||
{
|
||||
await _lightningAddressService.Set(
|
||||
new LightningAddressData()
|
||||
{
|
||||
StoreDataId = storeMap.Key,
|
||||
Username = storeitem,
|
||||
Blob = new LightningAddressDataBlob()
|
||||
{
|
||||
Max = val.Max,
|
||||
Min = val.Min,
|
||||
CurrencyCode = val.CurrencyCode
|
||||
}.SerializeBlob()
|
||||
}, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
await _Settings.UpdateSetting<object>(null, nameof(UILNURLController.LightningAddressSettings));
|
||||
}
|
||||
|
||||
private async Task MigrateLighingAddressSettingRename()
|
||||
{
|
||||
var old = await _Settings.GetSettingAsync<UILNURLController.LightningAddressSettings>("BTCPayServer.LNURLController+LightningAddressSettings");
|
||||
@ -256,41 +192,6 @@ namespace BTCPayServer.Hosting
|
||||
}
|
||||
}
|
||||
|
||||
private async Task MigrateAddStoreToPayout()
|
||||
{
|
||||
await using var ctx = _DBContextFactory.CreateContext();
|
||||
|
||||
if (ctx.Database.IsNpgsql())
|
||||
{
|
||||
await ctx.Database.ExecuteSqlRawAsync(@"
|
||||
WITH cte AS (
|
||||
SELECT DISTINCT p.""Id"", pp.""StoreId"" FROM ""Payouts"" p
|
||||
JOIN ""PullPayments"" pp ON pp.""Id"" = p.""PullPaymentDataId""
|
||||
WHERE p.""StoreDataId"" IS NULL
|
||||
)
|
||||
UPDATE ""Payouts"" p
|
||||
SET ""StoreDataId""=cte.""StoreId""
|
||||
FROM cte
|
||||
WHERE cte.""Id""=p.""Id""
|
||||
");
|
||||
}
|
||||
else
|
||||
{
|
||||
var queryable = ctx.Payouts.Where(data => data.StoreDataId == null);
|
||||
var count = await queryable.CountAsync();
|
||||
_logger.LogInformation($"Migrating {count} payouts to have a store id explicitly");
|
||||
for (int i = 0; i < count; i+=1000)
|
||||
{
|
||||
await queryable.Include(data => data.PullPaymentData).Skip(i).Take(1000)
|
||||
.ForEachAsync(data => data.StoreDataId = data.PullPaymentData.StoreId);
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation($"Migrated {i+1000}/{count} payouts to have a store id explicitly");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddInitialUserBlob()
|
||||
{
|
||||
await using var ctx = _DBContextFactory.CreateContext();
|
||||
|
@ -113,7 +113,6 @@ namespace BTCPayServer.Hosting
|
||||
services.AddScoped<Fido2Service>();
|
||||
services.AddSingleton<UserLoginCodeService>();
|
||||
services.AddSingleton<LnurlAuthService>();
|
||||
services.AddSingleton<LightningAddressService>();
|
||||
var mvcBuilder = services.AddMvc(o =>
|
||||
{
|
||||
o.Filters.Add(new XFrameOptionsAttribute("DENY"));
|
||||
|
@ -32,7 +32,7 @@ namespace BTCPayServer.ModelBinders
|
||||
|
||||
var networkProvider = (BTCPayNetworkProvider)bindingContext.HttpContext.RequestServices.GetService(typeof(BTCPayNetworkProvider));
|
||||
var cryptoCode = bindingContext.ValueProvider.GetValue("cryptoCode").FirstValue;
|
||||
var network = networkProvider.GetNetwork<BTCPayNetwork>(cryptoCode ?? networkProvider.DefaultNetwork.CryptoCode);
|
||||
var network = networkProvider.GetNetwork<BTCPayNetwork>(cryptoCode ?? "BTC");
|
||||
try
|
||||
{
|
||||
var data = network.NBXplorerNetwork.DerivationStrategyFactory.Parse(key);
|
||||
|
@ -11,7 +11,6 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||
public string PullPaymentId { get; set; }
|
||||
public string Command { get; set; }
|
||||
public Dictionary<PayoutState, int> PayoutStateCount { get; set; }
|
||||
public Dictionary<string, int> PaymentMethodCount { get; set; }
|
||||
public string PaymentMethodId { get; set; }
|
||||
|
||||
public List<PayoutModel> Payouts { get; set; }
|
||||
|
@ -28,7 +28,6 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||
public ProgressModel Progress { get; set; }
|
||||
public DateTimeOffset StartDate { get; set; }
|
||||
public DateTimeOffset? EndDate { get; set; }
|
||||
public bool AutoApproveClaims { get; set; }
|
||||
public bool Archived { get; set; } = false;
|
||||
}
|
||||
|
||||
@ -63,7 +62,5 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||
[Display(Name = "Minimum acceptable expiration time for BOLT11 for refunds")]
|
||||
[Range(1, 365 * 10)]
|
||||
public long BOLT11Expiration { get; set; } = 30;
|
||||
[Display(Name = "Automatically approve claims")]
|
||||
public bool AutoApproveClaims { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,8 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||
public TimeSpan Target { get; set; }
|
||||
public decimal FeeRate { get; set; }
|
||||
}
|
||||
public List<TransactionOutput> Outputs { get; set; } = new();
|
||||
public List<TransactionOutput> Outputs { get; set; } = new List<TransactionOutput>();
|
||||
|
||||
public class TransactionOutput
|
||||
{
|
||||
[Display(Name = "Destination Address")]
|
||||
@ -32,8 +33,6 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||
|
||||
[Display(Name = "Subtract fees from amount")]
|
||||
public bool SubtractFeesFromOutput { get; set; }
|
||||
|
||||
public string PayoutId { get; set; }
|
||||
}
|
||||
public decimal CurrentBalance { get; set; }
|
||||
public decimal ImmatureBalance { get; set; }
|
||||
|
@ -1,84 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Data.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.PayoutProcessors.Settings;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PayoutData = BTCPayServer.Data.PayoutData;
|
||||
using PayoutProcessorData = BTCPayServer.Data.Data.PayoutProcessorData;
|
||||
|
||||
namespace BTCPayServer.PayoutProcessors;
|
||||
|
||||
public abstract class BaseAutomatedPayoutProcessor<T> : BaseAsyncService where T:AutomatedPayoutBlob
|
||||
{
|
||||
protected readonly StoreRepository _storeRepository;
|
||||
protected readonly PayoutProcessorData _PayoutProcesserSettings;
|
||||
protected readonly ApplicationDbContextFactory _applicationDbContextFactory;
|
||||
protected readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
protected readonly PaymentMethodId PaymentMethodId;
|
||||
|
||||
protected BaseAutomatedPayoutProcessor(
|
||||
ILoggerFactory logger,
|
||||
StoreRepository storeRepository,
|
||||
PayoutProcessorData payoutProcesserSettings,
|
||||
ApplicationDbContextFactory applicationDbContextFactory,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider) : base(logger.CreateLogger($"{payoutProcesserSettings.Processor}:{payoutProcesserSettings.StoreId}:{payoutProcesserSettings.PaymentMethod}"))
|
||||
{
|
||||
_storeRepository = storeRepository;
|
||||
_PayoutProcesserSettings = payoutProcesserSettings;
|
||||
PaymentMethodId = _PayoutProcesserSettings.GetPaymentMethodId();
|
||||
_applicationDbContextFactory = applicationDbContextFactory;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
}
|
||||
|
||||
internal override Task[] InitializeTasks()
|
||||
{
|
||||
return new[] { CreateLoopTask(Act) };
|
||||
}
|
||||
|
||||
protected abstract Task Process(ISupportedPaymentMethod paymentMethod, PayoutData[] payouts);
|
||||
|
||||
private async Task Act()
|
||||
{
|
||||
var store = await _storeRepository.FindStore(_PayoutProcesserSettings.StoreId);
|
||||
var paymentMethod = store?.GetEnabledPaymentMethods(_btcPayNetworkProvider)?.FirstOrDefault(
|
||||
method =>
|
||||
method.PaymentId == PaymentMethodId);
|
||||
|
||||
var blob = GetBlob(_PayoutProcesserSettings);
|
||||
if (paymentMethod is not null)
|
||||
{
|
||||
var payouts = await GetRelevantPayouts();
|
||||
if (payouts.Length > 0)
|
||||
{
|
||||
Logs.PayServer.LogInformation($"{payouts.Length} found to process. Starting (and after will sleep for {blob.Interval})");
|
||||
await Process(paymentMethod, payouts);
|
||||
}
|
||||
}
|
||||
await Task.Delay(blob.Interval, CancellationToken);
|
||||
}
|
||||
|
||||
|
||||
public static T GetBlob(PayoutProcessorData data)
|
||||
{
|
||||
return InvoiceRepository.FromBytes<T>(data.Blob);
|
||||
}
|
||||
|
||||
private async Task<PayoutData[]> GetRelevantPayouts()
|
||||
{
|
||||
await using var context = _applicationDbContextFactory.CreateContext();
|
||||
var pmi = _PayoutProcesserSettings.PaymentMethod;
|
||||
return await context.Payouts
|
||||
.Where(data => data.State == PayoutState.AwaitingPayment)
|
||||
.Where(data => data.PaymentMethodId == pmi)
|
||||
.Where(data => data.StoreDataId == _PayoutProcesserSettings.StoreId)
|
||||
.OrderBy(data => data.Date)
|
||||
.ToArrayAsync();
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data.Data;
|
||||
using BTCPayServer.Payments;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace BTCPayServer.PayoutProcessors;
|
||||
|
||||
public interface IPayoutProcessorFactory
|
||||
{
|
||||
public string Processor { get;}
|
||||
public string FriendlyName { get;}
|
||||
public string ConfigureLink(string storeId, PaymentMethodId paymentMethodId, HttpRequest request);
|
||||
public IEnumerable<PaymentMethodId> GetSupportedPaymentMethods();
|
||||
public Task<IHostedService> ConstructProcessor(PayoutProcessorData settings);
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Data.Data;
|
||||
using BTCPayServer.Data.Payouts.LightningLike;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.PayoutProcessors.Settings;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using LNURL;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using PayoutData = BTCPayServer.Data.PayoutData;
|
||||
using PayoutProcessorData = BTCPayServer.Data.Data.PayoutProcessorData;
|
||||
|
||||
namespace BTCPayServer.PayoutProcessors.Lightning;
|
||||
|
||||
public class LightningAutomatedPayoutProcessor : BaseAutomatedPayoutProcessor<AutomatedPayoutBlob>
|
||||
{
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
|
||||
private readonly LightningClientFactoryService _lightningClientFactoryService;
|
||||
private readonly UserService _userService;
|
||||
private readonly IOptions<LightningNetworkOptions> _options;
|
||||
private readonly LightningLikePayoutHandler _payoutHandler;
|
||||
private readonly BTCPayNetwork _network;
|
||||
|
||||
public LightningAutomatedPayoutProcessor(
|
||||
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
|
||||
LightningClientFactoryService lightningClientFactoryService,
|
||||
IEnumerable<IPayoutHandler> payoutHandlers,
|
||||
UserService userService,
|
||||
ILoggerFactory logger, IOptions<LightningNetworkOptions> options,
|
||||
StoreRepository storeRepository, PayoutProcessorData payoutProcesserSettings,
|
||||
ApplicationDbContextFactory applicationDbContextFactory, BTCPayNetworkProvider btcPayNetworkProvider) :
|
||||
base(logger, storeRepository, payoutProcesserSettings, applicationDbContextFactory,
|
||||
btcPayNetworkProvider)
|
||||
{
|
||||
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
|
||||
_lightningClientFactoryService = lightningClientFactoryService;
|
||||
_userService = userService;
|
||||
_options = options;
|
||||
_payoutHandler = (LightningLikePayoutHandler)payoutHandlers.FindPayoutHandler(PaymentMethodId);
|
||||
|
||||
_network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(_PayoutProcesserSettings.GetPaymentMethodId().CryptoCode);
|
||||
}
|
||||
|
||||
protected override async Task Process(ISupportedPaymentMethod paymentMethod, PayoutData[] payouts)
|
||||
{
|
||||
await using var ctx = _applicationDbContextFactory.CreateContext();
|
||||
|
||||
|
||||
var lightningSupportedPaymentMethod = (LightningSupportedPaymentMethod)paymentMethod;
|
||||
|
||||
if (lightningSupportedPaymentMethod.IsInternalNode &&
|
||||
!(await Task.WhenAll((await _storeRepository.GetStoreUsers(_PayoutProcesserSettings.StoreId))
|
||||
.Where(user => user.Role == StoreRoles.Owner).Select(user => user.Id)
|
||||
.Select(s => _userService.IsAdminUser(s)))).Any(b => b))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var client =
|
||||
lightningSupportedPaymentMethod.CreateLightningClient(_network, _options.Value,
|
||||
_lightningClientFactoryService);
|
||||
|
||||
|
||||
|
||||
foreach (var payoutData in payouts)
|
||||
{
|
||||
var blob = payoutData.GetBlob(_btcPayNetworkJsonSerializerSettings);
|
||||
var claim = await _payoutHandler.ParseClaimDestination(PaymentMethodId, blob.Destination);
|
||||
try
|
||||
{
|
||||
switch (claim.destination)
|
||||
{
|
||||
case LNURLPayClaimDestinaton lnurlPayClaimDestinaton:
|
||||
var endpoint = LNURL.LNURL.Parse(lnurlPayClaimDestinaton.LNURL, out var tag);
|
||||
var httpClient = _payoutHandler.CreateClient(endpoint);
|
||||
var lnurlInfo =
|
||||
(LNURLPayRequest)await LNURL.LNURL.FetchInformation(endpoint, "payRequest",
|
||||
httpClient);
|
||||
var lm = new LightMoney(blob.CryptoAmount.Value, LightMoneyUnit.BTC);
|
||||
if (lm > lnurlInfo.MaxSendable || lm < lnurlInfo.MinSendable)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var lnurlPayRequestCallbackResponse =
|
||||
await lnurlInfo.SendRequest(lm, _network.NBitcoinNetwork, httpClient);
|
||||
|
||||
if (await TrypayBolt(client, blob, payoutData,
|
||||
lnurlPayRequestCallbackResponse
|
||||
.GetPaymentRequest(_network.NBitcoinNetwork)))
|
||||
{
|
||||
ctx.Attach(payoutData);
|
||||
payoutData.State = PayoutState.Completed;
|
||||
}
|
||||
}
|
||||
catch (LNUrlException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case BoltInvoiceClaimDestination item1:
|
||||
if (await TrypayBolt(client, blob, payoutData, item1.PaymentRequest))
|
||||
{
|
||||
ctx.Attach(payoutData);
|
||||
payoutData.State = PayoutState.Completed;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logs.PayServer.LogError(e, $"Could not process payout {payoutData.Id}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
//we group per store and init the transfers by each
|
||||
async Task<bool> TrypayBolt(ILightningClient lightningClient, PayoutBlob payoutBlob, PayoutData payoutData,
|
||||
BOLT11PaymentRequest bolt11PaymentRequest)
|
||||
{
|
||||
var boltAmount = bolt11PaymentRequest.MinimumAmount.ToDecimal(LightMoneyUnit.BTC);
|
||||
if (boltAmount != payoutBlob.CryptoAmount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = await lightningClient.Pay(bolt11PaymentRequest.ToString());
|
||||
return result.Result == PayResult.Ok;
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data.Data;
|
||||
using BTCPayServer.Payments;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace BTCPayServer.PayoutProcessors.Lightning;
|
||||
|
||||
public class LightningAutomatedPayoutSenderFactory : IPayoutProcessorFactory
|
||||
{
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
|
||||
public LightningAutomatedPayoutSenderFactory(BTCPayNetworkProvider btcPayNetworkProvider, IServiceProvider serviceProvider, LinkGenerator linkGenerator)
|
||||
{
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_serviceProvider = serviceProvider;
|
||||
_linkGenerator = linkGenerator;
|
||||
}
|
||||
|
||||
public string FriendlyName { get; } = "Automated Lightning Sender";
|
||||
|
||||
public string ConfigureLink(string storeId, PaymentMethodId paymentMethodId, HttpRequest request)
|
||||
{
|
||||
return _linkGenerator.GetUriByAction("Configure",
|
||||
"UILightningAutomatedPayoutProcessors",new
|
||||
{
|
||||
storeId,
|
||||
cryptoCode = paymentMethodId.CryptoCode
|
||||
}, request.Scheme, request.Host, request.PathBase);
|
||||
}
|
||||
public string Processor => ProcessorName;
|
||||
public static string ProcessorName => nameof(LightningAutomatedPayoutSenderFactory);
|
||||
public IEnumerable<PaymentMethodId> GetSupportedPaymentMethods()
|
||||
{
|
||||
return _btcPayNetworkProvider.GetAll().OfType<BTCPayNetwork>()
|
||||
.Where(network => network.SupportLightning)
|
||||
.Select(network =>
|
||||
new PaymentMethodId(network.CryptoCode, LightningPaymentType.Instance));
|
||||
}
|
||||
|
||||
public Task<IHostedService> ConstructProcessor(PayoutProcessorData settings)
|
||||
{
|
||||
if (settings.Processor != Processor)
|
||||
{
|
||||
throw new NotSupportedException("This processor cannot handle the provided requirements");
|
||||
}
|
||||
|
||||
return Task.FromResult<IHostedService>(ActivatorUtilities.CreateInstance<LightningAutomatedPayoutProcessor>(_serviceProvider, settings));
|
||||
|
||||
}
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data.Data;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.PayoutProcessors.OnChain;
|
||||
using BTCPayServer.PayoutProcessors.Settings;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.PayoutProcessors.Lightning;
|
||||
|
||||
public class UILightningAutomatedPayoutProcessorsController : Controller
|
||||
{
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly LightningAutomatedPayoutSenderFactory _lightningAutomatedPayoutSenderFactory;
|
||||
private readonly PayoutProcessorService _payoutProcessorService;
|
||||
|
||||
public UILightningAutomatedPayoutProcessorsController(
|
||||
EventAggregator eventAggregator,
|
||||
LightningAutomatedPayoutSenderFactory lightningAutomatedPayoutSenderFactory,
|
||||
PayoutProcessorService payoutProcessorService)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
_lightningAutomatedPayoutSenderFactory = lightningAutomatedPayoutSenderFactory;
|
||||
_payoutProcessorService = payoutProcessorService;
|
||||
}
|
||||
[HttpGet("~/stores/{storeId}/payout-processors/lightning-automated/{cryptocode}")]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> Configure(string storeId, string cryptoCode)
|
||||
{
|
||||
if (!_lightningAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(id =>
|
||||
id.CryptoCode.Equals(cryptoCode, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = $"This processor cannot handle {cryptoCode}."
|
||||
});
|
||||
return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors");
|
||||
}
|
||||
var activeProcessor =
|
||||
(await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery()
|
||||
{
|
||||
Stores = new[] { storeId },
|
||||
Processors = new []{ _lightningAutomatedPayoutSenderFactory.Processor},
|
||||
PaymentMethods = new[]
|
||||
{
|
||||
new PaymentMethodId(cryptoCode, LightningPaymentType.Instance).ToString()
|
||||
}
|
||||
}))
|
||||
.FirstOrDefault();
|
||||
|
||||
return View (new LightningTransferViewModel(activeProcessor is null? new AutomatedPayoutBlob() : OnChainAutomatedPayoutProcessor.GetBlob(activeProcessor)));
|
||||
}
|
||||
|
||||
[HttpPost("~/stores/{storeId}/payout-processors/lightning-automated/{cryptocode}")]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> Configure(string storeId, string cryptoCode, LightningTransferViewModel automatedTransferBlob)
|
||||
{
|
||||
if (!_lightningAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(id =>
|
||||
id.CryptoCode.Equals(cryptoCode, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = $"This processor cannot handle {cryptoCode}."
|
||||
});
|
||||
return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors");
|
||||
}
|
||||
var activeProcessor =
|
||||
(await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery()
|
||||
{
|
||||
Stores = new[] { storeId },
|
||||
Processors = new []{ _lightningAutomatedPayoutSenderFactory.Processor},
|
||||
PaymentMethods = new[]
|
||||
{
|
||||
new PaymentMethodId(cryptoCode, LightningPaymentType.Instance).ToString()
|
||||
}
|
||||
}))
|
||||
.FirstOrDefault();
|
||||
activeProcessor ??= new PayoutProcessorData();
|
||||
activeProcessor.Blob = InvoiceRepository.ToBytes(automatedTransferBlob.ToBlob());
|
||||
activeProcessor.StoreId = storeId;
|
||||
activeProcessor.PaymentMethod = new PaymentMethodId(cryptoCode, LightningPaymentType.Instance).ToString();
|
||||
activeProcessor.Processor = _lightningAutomatedPayoutSenderFactory.Processor;
|
||||
var tcs = new TaskCompletionSource();
|
||||
_eventAggregator.Publish(new PayoutProcessorUpdated()
|
||||
{
|
||||
Data = activeProcessor,
|
||||
Id = activeProcessor.Id,
|
||||
Processed = tcs
|
||||
});
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
Message = $"Processor updated."
|
||||
});
|
||||
await tcs.Task;
|
||||
return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors", new {storeId});
|
||||
}
|
||||
|
||||
public class LightningTransferViewModel
|
||||
{
|
||||
public LightningTransferViewModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public LightningTransferViewModel(AutomatedPayoutBlob blob)
|
||||
{
|
||||
IntervalMinutes = blob.Interval.TotalMinutes;
|
||||
}
|
||||
public double IntervalMinutes { get; set; }
|
||||
|
||||
public AutomatedPayoutBlob ToBlob()
|
||||
{
|
||||
return new AutomatedPayoutBlob() { Interval = TimeSpan.FromMinutes(IntervalMinutes) };
|
||||
}
|
||||
}
|
||||
}
|
@ -1,197 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Data.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.PayoutProcessors.Settings;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using PayoutData = BTCPayServer.Data.PayoutData;
|
||||
using PayoutProcessorData = BTCPayServer.Data.Data.PayoutProcessorData;
|
||||
|
||||
namespace BTCPayServer.PayoutProcessors.OnChain
|
||||
{
|
||||
public class OnChainAutomatedPayoutProcessor : BaseAutomatedPayoutProcessor<AutomatedPayoutBlob>
|
||||
{
|
||||
private readonly ExplorerClientProvider _explorerClientProvider;
|
||||
private readonly BTCPayWalletProvider _btcPayWalletProvider;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
|
||||
private readonly BitcoinLikePayoutHandler _bitcoinLikePayoutHandler;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
|
||||
public OnChainAutomatedPayoutProcessor(
|
||||
ApplicationDbContextFactory applicationDbContextFactory,
|
||||
ExplorerClientProvider explorerClientProvider,
|
||||
BTCPayWalletProvider btcPayWalletProvider,
|
||||
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
|
||||
ILoggerFactory logger,
|
||||
BitcoinLikePayoutHandler bitcoinLikePayoutHandler,
|
||||
EventAggregator eventAggregator,
|
||||
StoreRepository storeRepository,
|
||||
PayoutProcessorData payoutProcesserSettings,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider) :
|
||||
base(logger, storeRepository, payoutProcesserSettings, applicationDbContextFactory,
|
||||
btcPayNetworkProvider)
|
||||
{
|
||||
_explorerClientProvider = explorerClientProvider;
|
||||
_btcPayWalletProvider = btcPayWalletProvider;
|
||||
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
|
||||
_bitcoinLikePayoutHandler = bitcoinLikePayoutHandler;
|
||||
_eventAggregator = eventAggregator;
|
||||
}
|
||||
|
||||
protected override async Task Process(ISupportedPaymentMethod paymentMethod, PayoutData[] payouts)
|
||||
{
|
||||
var storePaymentMethod = paymentMethod as DerivationSchemeSettings;
|
||||
if (storePaymentMethod?.IsHotWallet is not true)
|
||||
{
|
||||
|
||||
Logs.PayServer.LogInformation($"Wallet is not a hot wallet.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_explorerClientProvider.IsAvailable(PaymentMethodId.CryptoCode))
|
||||
{
|
||||
Logs.PayServer.LogInformation($"{paymentMethod.PaymentId.CryptoCode} node is not available");
|
||||
return;
|
||||
}
|
||||
var explorerClient = _explorerClientProvider.GetExplorerClient(PaymentMethodId.CryptoCode);
|
||||
var paymentMethodId = PaymentMethodId.Parse(PaymentMethodId.CryptoCode);
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
|
||||
var extKeyStr = await explorerClient.GetMetadataAsync<string>(
|
||||
storePaymentMethod.AccountDerivation,
|
||||
WellknownMetadataKeys.AccountHDKey);
|
||||
if (extKeyStr == null)
|
||||
{
|
||||
Logs.PayServer.LogInformation($"Wallet keys not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
var wallet = _btcPayWalletProvider.GetWallet(PaymentMethodId.CryptoCode);
|
||||
|
||||
var reccoins = (await wallet.GetUnspentCoins(storePaymentMethod.AccountDerivation)).ToArray();
|
||||
var coins = reccoins.Select(coin => coin.Coin).ToArray();
|
||||
|
||||
var accountKey = ExtKey.Parse(extKeyStr, network.NBitcoinNetwork);
|
||||
var keys = reccoins.Select(coin => accountKey.Derive(coin.KeyPath).PrivateKey).ToArray();
|
||||
Transaction workingTx = null;
|
||||
decimal? failedAmount = null;
|
||||
var changeAddress = await explorerClient.GetUnusedAsync(
|
||||
storePaymentMethod.AccountDerivation, DerivationFeature.Change, 0, true);
|
||||
|
||||
var feeRate = await explorerClient.GetFeeRateAsync(1, new FeeRate(1m));
|
||||
|
||||
var transfersProcessing = new List<PayoutData>();
|
||||
foreach (var transferRequest in payouts)
|
||||
{
|
||||
var blob = transferRequest.GetBlob(_btcPayNetworkJsonSerializerSettings);
|
||||
if (failedAmount.HasValue && blob.CryptoAmount >= failedAmount)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var claimDestination =
|
||||
await _bitcoinLikePayoutHandler.ParseClaimDestination(paymentMethodId, blob.Destination);
|
||||
if (!string.IsNullOrEmpty(claimDestination.error))
|
||||
{
|
||||
Logs.PayServer.LogInformation($"Could not process payout {transferRequest.Id} because {claimDestination.error}.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var bitcoinClaimDestination = (IBitcoinLikeClaimDestination)claimDestination.destination;
|
||||
var txBuilder = network.NBitcoinNetwork.CreateTransactionBuilder()
|
||||
.AddCoins(coins)
|
||||
.AddKeys(keys);
|
||||
|
||||
if (workingTx is not null)
|
||||
{
|
||||
foreach (var txout in workingTx.Outputs.Where(txout =>
|
||||
!txout.IsTo(changeAddress.Address)))
|
||||
{
|
||||
txBuilder.Send(txout.ScriptPubKey, txout.Value);
|
||||
}
|
||||
}
|
||||
|
||||
txBuilder.Send(bitcoinClaimDestination.Address,
|
||||
new Money(blob.CryptoAmount.Value, MoneyUnit.BTC));
|
||||
|
||||
try
|
||||
{
|
||||
txBuilder.SetChange(changeAddress.Address);
|
||||
txBuilder.SendEstimatedFees(feeRate.FeeRate);
|
||||
workingTx = txBuilder.BuildTransaction(true);
|
||||
transfersProcessing.Add(transferRequest);
|
||||
}
|
||||
catch (NotEnoughFundsException e)
|
||||
{
|
||||
|
||||
Logs.PayServer.LogInformation($"Could not process payout {transferRequest.Id} because of not enough funds. ({e.Missing.GetValue(network)})");
|
||||
failedAmount = blob.CryptoAmount;
|
||||
//keep going, we prioritize withdraws by time but if there is some other we can fit, we should
|
||||
}
|
||||
}
|
||||
|
||||
if (workingTx is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var context = _applicationDbContextFactory.CreateContext();
|
||||
var txHash = workingTx.GetHash();
|
||||
Logs.PayServer.LogInformation($"Processing {transfersProcessing.Count} payouts in tx {txHash}");
|
||||
foreach (PayoutData payoutData in transfersProcessing)
|
||||
{
|
||||
context.Attach(payoutData);
|
||||
payoutData.State = PayoutState.InProgress;
|
||||
_bitcoinLikePayoutHandler.SetProofBlob(payoutData,
|
||||
new PayoutTransactionOnChainBlob()
|
||||
{
|
||||
Accounted = true,
|
||||
TransactionId = txHash,
|
||||
Candidates = new HashSet<uint256>() { txHash }
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
TaskCompletionSource<bool> tcs = new();
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(TimeSpan.FromSeconds(20));
|
||||
var task = _eventAggregator.WaitNext<NewOnChainTransactionEvent>(
|
||||
e => e.NewTransactionEvent.TransactionData.TransactionHash == txHash,
|
||||
cts.Token);
|
||||
var broadcastResult = await explorerClient.BroadcastAsync(workingTx, cts.Token);
|
||||
if (!broadcastResult.Success)
|
||||
{
|
||||
tcs.SetResult(false);
|
||||
}
|
||||
var walletId = new WalletId(_PayoutProcesserSettings.StoreId, PaymentMethodId.CryptoCode);
|
||||
foreach (PayoutData payoutData in transfersProcessing)
|
||||
{
|
||||
_eventAggregator.Publish(new UpdateTransactionLabel(walletId,
|
||||
txHash,
|
||||
UpdateTransactionLabel.PayoutTemplate(payoutData.Id, payoutData.PullPaymentDataId,
|
||||
walletId.ToString())));
|
||||
}
|
||||
await Task.WhenAny(tcs.Task, task);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Logs.PayServer.LogError(e, "Could not finalize and broadcast");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BTCPayServer.PayoutProcessors.OnChain;
|
||||
|
||||
public class OnChainAutomatedPayoutSenderFactory : EventHostedServiceBase, IPayoutProcessorFactory
|
||||
{
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
|
||||
public string FriendlyName { get; } = "Automated Bitcoin Sender";
|
||||
public OnChainAutomatedPayoutSenderFactory(EventAggregator eventAggregator,
|
||||
ILogger<OnChainAutomatedPayoutSenderFactory> logger,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider, IServiceProvider serviceProvider, LinkGenerator linkGenerator) : base(eventAggregator, logger)
|
||||
{
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_serviceProvider = serviceProvider;
|
||||
_linkGenerator = linkGenerator;
|
||||
}
|
||||
|
||||
public string Processor => ProcessorName;
|
||||
public static string ProcessorName => nameof(OnChainAutomatedPayoutSenderFactory);
|
||||
|
||||
public string ConfigureLink(string storeId, PaymentMethodId paymentMethodId, HttpRequest request)
|
||||
{
|
||||
return _linkGenerator.GetUriByAction("Configure",
|
||||
"UIOnChainAutomatedPayoutProcessors",new
|
||||
{
|
||||
storeId,
|
||||
cryptoCode = paymentMethodId.CryptoCode
|
||||
}, request.Scheme, request.Host, request.PathBase);
|
||||
}
|
||||
|
||||
public IEnumerable<PaymentMethodId> GetSupportedPaymentMethods()
|
||||
{
|
||||
return _btcPayNetworkProvider.GetAll().OfType<BTCPayNetwork>()
|
||||
.Where(network => !network.ReadonlyWallet && network.WalletSupported)
|
||||
.Select(network =>
|
||||
new PaymentMethodId(network.CryptoCode, BitcoinPaymentType.Instance));
|
||||
}
|
||||
|
||||
public Task<IHostedService> ConstructProcessor(PayoutProcessorData settings)
|
||||
{
|
||||
if (settings.Processor != Processor)
|
||||
{
|
||||
throw new NotSupportedException("This processor cannot handle the provided requirements");
|
||||
}
|
||||
|
||||
return Task.FromResult<IHostedService>(ActivatorUtilities.CreateInstance<OnChainAutomatedPayoutProcessor>(_serviceProvider, settings));
|
||||
}
|
||||
}
|
@ -1,144 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data.Data;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.PayoutProcessors.Settings;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.PayoutProcessors.OnChain;
|
||||
|
||||
public class UIOnChainAutomatedPayoutProcessorsController : Controller
|
||||
{
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly OnChainAutomatedPayoutSenderFactory _onChainAutomatedPayoutSenderFactory;
|
||||
private readonly PayoutProcessorService _payoutProcessorService;
|
||||
|
||||
public UIOnChainAutomatedPayoutProcessorsController(
|
||||
EventAggregator eventAggregator,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
OnChainAutomatedPayoutSenderFactory onChainAutomatedPayoutSenderFactory,
|
||||
PayoutProcessorService payoutProcessorService)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_onChainAutomatedPayoutSenderFactory = onChainAutomatedPayoutSenderFactory;
|
||||
_payoutProcessorService = payoutProcessorService;
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("~/stores/{storeId}/payout-processors/onchain-automated/{cryptocode}")]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> Configure(string storeId, string cryptoCode)
|
||||
{
|
||||
if (!_onChainAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(id =>
|
||||
id.CryptoCode.Equals(cryptoCode, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = $"This processor cannot handle {cryptoCode}."
|
||||
});
|
||||
return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors");
|
||||
}
|
||||
var wallet = HttpContext.GetStoreData().GetDerivationSchemeSettings(_btcPayNetworkProvider, cryptoCode);
|
||||
if (wallet?.IsHotWallet is not true)
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = $"Either your {cryptoCode} wallet is not configured, or it is not a hot wallet. This processor cannot function until a hot wallet is configured in your store."
|
||||
});
|
||||
}
|
||||
var activeProcessor =
|
||||
(await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery()
|
||||
{
|
||||
Stores = new[] { storeId },
|
||||
Processors = new []{ _onChainAutomatedPayoutSenderFactory.Processor},
|
||||
PaymentMethods = new[]
|
||||
{
|
||||
new PaymentMethodId(cryptoCode, BitcoinPaymentType.Instance).ToString()
|
||||
}
|
||||
}))
|
||||
.FirstOrDefault();
|
||||
|
||||
return View (new OnChainTransferViewModel(activeProcessor is null? new AutomatedPayoutBlob() : OnChainAutomatedPayoutProcessor.GetBlob(activeProcessor)));
|
||||
}
|
||||
|
||||
[HttpPost("~/stores/{storeId}/payout-processors/onchain-automated/{cryptocode}")]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> Configure(string storeId, string cryptoCode, OnChainTransferViewModel automatedTransferBlob)
|
||||
{
|
||||
if (!_onChainAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(id =>
|
||||
id.CryptoCode.Equals(cryptoCode, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = $"This processor cannot handle {cryptoCode}."
|
||||
});
|
||||
return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors");
|
||||
}
|
||||
var activeProcessor =
|
||||
(await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery()
|
||||
{
|
||||
Stores = new[] { storeId },
|
||||
Processors = new []{ OnChainAutomatedPayoutSenderFactory.ProcessorName},
|
||||
PaymentMethods = new[]
|
||||
{
|
||||
new PaymentMethodId(cryptoCode, BitcoinPaymentType.Instance).ToString()
|
||||
}
|
||||
}))
|
||||
.FirstOrDefault();
|
||||
activeProcessor ??= new PayoutProcessorData();
|
||||
activeProcessor.Blob = InvoiceRepository.ToBytes(automatedTransferBlob.ToBlob());
|
||||
activeProcessor.StoreId = storeId;
|
||||
activeProcessor.PaymentMethod = new PaymentMethodId(cryptoCode, BitcoinPaymentType.Instance).ToString();
|
||||
activeProcessor.Processor = _onChainAutomatedPayoutSenderFactory.Processor;
|
||||
var tcs = new TaskCompletionSource();
|
||||
_eventAggregator.Publish(new PayoutProcessorUpdated()
|
||||
{
|
||||
Data = activeProcessor,
|
||||
Id = activeProcessor.Id,
|
||||
Processed = tcs
|
||||
});
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
Message = $"Processor updated."
|
||||
});
|
||||
await tcs.Task;
|
||||
return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors", new {storeId});
|
||||
}
|
||||
|
||||
public class OnChainTransferViewModel
|
||||
{
|
||||
public OnChainTransferViewModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public OnChainTransferViewModel(AutomatedPayoutBlob blob)
|
||||
{
|
||||
IntervalMinutes = blob.Interval.TotalMinutes;
|
||||
}
|
||||
public double IntervalMinutes { get; set; }
|
||||
|
||||
public AutomatedPayoutBlob ToBlob()
|
||||
{
|
||||
return new AutomatedPayoutBlob() { Interval = TimeSpan.FromMinutes(IntervalMinutes) };
|
||||
}
|
||||
}
|
||||
}
|
@ -1,165 +0,0 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Data.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Logging;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace BTCPayServer.PayoutProcessors;
|
||||
|
||||
public class PayoutProcessorUpdated
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public PayoutProcessorData Data { get; set; }
|
||||
|
||||
public TaskCompletionSource Processed { get; set; }
|
||||
}
|
||||
|
||||
public class PayoutProcessorService : EventHostedServiceBase
|
||||
{
|
||||
private readonly ApplicationDbContextFactory _applicationDbContextFactory;
|
||||
private readonly IEnumerable<IPayoutProcessorFactory> _payoutProcessorFactories;
|
||||
|
||||
|
||||
private ConcurrentDictionary<string, IHostedService> Services { get; set; } = new();
|
||||
public PayoutProcessorService(
|
||||
ApplicationDbContextFactory applicationDbContextFactory,
|
||||
EventAggregator eventAggregator,
|
||||
Logs logs,
|
||||
IEnumerable<IPayoutProcessorFactory> payoutProcessorFactories) : base(eventAggregator, logs)
|
||||
{
|
||||
_applicationDbContextFactory = applicationDbContextFactory;
|
||||
_payoutProcessorFactories = payoutProcessorFactories;
|
||||
}
|
||||
|
||||
public class PayoutProcessorQuery
|
||||
{
|
||||
public string[] Stores { get; set; }
|
||||
public string[] Processors { get; set; }
|
||||
public string[] PaymentMethods { get; set; }
|
||||
}
|
||||
|
||||
public async Task<List<PayoutProcessorData>> GetProcessors(PayoutProcessorQuery query)
|
||||
{
|
||||
|
||||
await using var context = _applicationDbContextFactory.CreateContext();
|
||||
var queryable = context.PayoutProcessors.AsQueryable();
|
||||
if (query.Processors is not null)
|
||||
{
|
||||
queryable = queryable.Where(data => query.Processors.Contains(data.Processor));
|
||||
}
|
||||
if (query.Stores is not null)
|
||||
{
|
||||
queryable = queryable.Where(data => query.Stores.Contains(data.StoreId));
|
||||
}
|
||||
if (query.PaymentMethods is not null)
|
||||
{
|
||||
queryable = queryable.Where(data => query.PaymentMethods.Contains(data.PaymentMethod));
|
||||
}
|
||||
|
||||
return await queryable.ToListAsync();
|
||||
}
|
||||
|
||||
private async Task RemoveProcessor(string id)
|
||||
{
|
||||
await using var context = _applicationDbContextFactory.CreateContext();
|
||||
var item = await context.FindAsync<PayoutProcessorData>(id);
|
||||
if (item is not null)
|
||||
context.Remove(item);
|
||||
await context.SaveChangesAsync();
|
||||
await StopProcessor(id, CancellationToken.None);
|
||||
}
|
||||
|
||||
private async Task AddOrUpdateProcessor(PayoutProcessorData data)
|
||||
{
|
||||
|
||||
await using var context = _applicationDbContextFactory.CreateContext();
|
||||
if (string.IsNullOrEmpty(data.Id))
|
||||
{
|
||||
await context.AddAsync(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Update(data);
|
||||
}
|
||||
await context.SaveChangesAsync();
|
||||
await StartOrUpdateProcessor(data, CancellationToken.None);
|
||||
}
|
||||
|
||||
protected override void SubscribeToEvents()
|
||||
{
|
||||
base.SubscribeToEvents();
|
||||
Subscribe<PayoutProcessorUpdated>();
|
||||
}
|
||||
|
||||
public override async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await base.StartAsync(cancellationToken);
|
||||
var activeProcessors = await GetProcessors(new PayoutProcessorQuery());
|
||||
var tasks = activeProcessors.Select(data => StartOrUpdateProcessor(data, cancellationToken));
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
private async Task StopProcessor(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
if (Services.Remove(id, out var currentService))
|
||||
{
|
||||
await currentService.StopAsync(cancellationToken);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task StartOrUpdateProcessor(PayoutProcessorData data, CancellationToken cancellationToken)
|
||||
{
|
||||
var matchedProcessor = _payoutProcessorFactories.FirstOrDefault(factory =>
|
||||
factory.Processor == data.Processor);
|
||||
|
||||
if (matchedProcessor is not null)
|
||||
{
|
||||
await StopProcessor(data.Id, cancellationToken);
|
||||
var processor = await matchedProcessor.ConstructProcessor(data);
|
||||
await processor.StartAsync(cancellationToken);
|
||||
Services.TryAdd(data.Id, processor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await base.StopAsync(cancellationToken);
|
||||
await StopAllService(cancellationToken);
|
||||
}
|
||||
|
||||
private async Task StopAllService(CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (KeyValuePair<string,IHostedService> service in Services)
|
||||
{
|
||||
await service.Value.StopAsync(cancellationToken);
|
||||
}
|
||||
Services.Clear();
|
||||
}
|
||||
|
||||
protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken)
|
||||
{
|
||||
await base.ProcessEvent(evt, cancellationToken);
|
||||
|
||||
if (evt is PayoutProcessorUpdated processorUpdated)
|
||||
{
|
||||
if (processorUpdated.Data is null)
|
||||
{
|
||||
await RemoveProcessor(processorUpdated.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
await AddOrUpdateProcessor(processorUpdated.Data);
|
||||
}
|
||||
|
||||
processorUpdated.Processed?.SetResult();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
using BTCPayServer.Data.Data;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.PayoutProcessors.Lightning;
|
||||
using BTCPayServer.PayoutProcessors.OnChain;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BTCPayServer.PayoutProcessors;
|
||||
|
||||
public static class PayoutProcessorsExtensions
|
||||
{
|
||||
public static void AddPayoutProcesors(this IServiceCollection serviceCollection)
|
||||
{
|
||||
serviceCollection.AddSingleton<OnChainAutomatedPayoutSenderFactory>();
|
||||
serviceCollection.AddSingleton<IPayoutProcessorFactory>(provider => provider.GetRequiredService<OnChainAutomatedPayoutSenderFactory>());
|
||||
serviceCollection.AddSingleton<LightningAutomatedPayoutSenderFactory>();
|
||||
serviceCollection.AddSingleton<IPayoutProcessorFactory>(provider => provider.GetRequiredService<LightningAutomatedPayoutSenderFactory>());
|
||||
serviceCollection.AddHostedService<PayoutProcessorService>();
|
||||
serviceCollection.AddSingleton<PayoutProcessorService>();
|
||||
serviceCollection.AddHostedService(s=> s.GetRequiredService<PayoutProcessorService>());
|
||||
}
|
||||
|
||||
public static PaymentMethodId GetPaymentMethodId(this PayoutProcessorData data)
|
||||
{
|
||||
return PaymentMethodId.Parse(data.PaymentMethod);
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer.PayoutProcessors.Settings;
|
||||
|
||||
public class AutomatedPayoutBlob
|
||||
{
|
||||
public TimeSpan Interval { get; set; } = TimeSpan.FromHours(1);
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Data.Data;
|
||||
using BTCPayServer.Payments;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.PayoutProcessors;
|
||||
|
||||
public class UIPayoutProcessorsController : Controller
|
||||
{
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly IEnumerable<IPayoutProcessorFactory> _payoutProcessorFactories;
|
||||
private readonly PayoutProcessorService _payoutProcessorService;
|
||||
|
||||
public UIPayoutProcessorsController(
|
||||
EventAggregator eventAggregator,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
IEnumerable<IPayoutProcessorFactory> payoutProcessorFactories,
|
||||
PayoutProcessorService payoutProcessorService)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_payoutProcessorFactories = payoutProcessorFactories;
|
||||
_payoutProcessorService = payoutProcessorService;
|
||||
;
|
||||
}
|
||||
|
||||
[HttpGet("~/stores/{storeId}/payout-processors")]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> ConfigureStorePayoutProcessors(string storeId)
|
||||
{
|
||||
var activeProcessors =
|
||||
(await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery() { Stores = new[] { storeId } }))
|
||||
.GroupBy(data => data.Processor);
|
||||
|
||||
var paymentMethods = HttpContext.GetStoreData().GetEnabledPaymentMethods(_btcPayNetworkProvider)
|
||||
.Select(method => method.PaymentId).ToList();
|
||||
|
||||
return View(_payoutProcessorFactories.Select(factory =>
|
||||
{
|
||||
var conf = activeProcessors.FirstOrDefault(datas => datas.Key == factory.Processor)
|
||||
?.ToDictionary(data => data.GetPaymentMethodId(), data => data) ??
|
||||
new Dictionary<PaymentMethodId, PayoutProcessorData>();
|
||||
foreach (PaymentMethodId supportedPaymentMethod in factory.GetSupportedPaymentMethods())
|
||||
{
|
||||
conf.TryAdd(supportedPaymentMethod, null);
|
||||
}
|
||||
|
||||
return new StorePayoutProcessorsView() { Factory = factory, Configured = conf };
|
||||
}).ToList());
|
||||
}
|
||||
|
||||
[HttpPost("~/stores/{storeId}/payout-processors/{id}/remove")]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> Remove(string storeId, string id)
|
||||
{
|
||||
var tcs = new TaskCompletionSource();
|
||||
_eventAggregator.Publish(new PayoutProcessorUpdated()
|
||||
{
|
||||
Data = null,
|
||||
Id = id,
|
||||
Processed = tcs
|
||||
});
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
Message = "Payout Processor removed"
|
||||
});
|
||||
await tcs.Task;
|
||||
return RedirectToAction("ConfigureStorePayoutProcessors",new {storeId});
|
||||
|
||||
}
|
||||
|
||||
public class StorePayoutProcessorsView
|
||||
{
|
||||
public Dictionary<PaymentMethodId, PayoutProcessorData> Configured { get; set; }
|
||||
public IPayoutProcessorFactory Factory { get; set; }
|
||||
}
|
||||
}
|
@ -59,7 +59,7 @@ namespace BTCPayServer.Plugins
|
||||
|
||||
var respObj = JObject.Parse(resp)["tree"] as JArray;
|
||||
|
||||
var detectedPlugins = respObj.Where(token => token["path"].ToString().EndsWith(".btcpay", StringComparison.OrdinalIgnoreCase));
|
||||
var detectedPlugins = respObj.Where(token => token["path"].ToString().EndsWith(".btcpay"));
|
||||
|
||||
List<Task<AvailablePlugin>> result = new List<Task<AvailablePlugin>>();
|
||||
foreach (JToken detectedPlugin in detectedPlugins)
|
||||
|
@ -39,7 +39,7 @@ namespace BTCPayServer.Security.Greenfield
|
||||
|
||||
var key = await _apiKeyRepository.GetKey(apiKey, true);
|
||||
|
||||
if (key == null || await _userManager.IsLockedOutAsync(key.User))
|
||||
if (key == null)
|
||||
{
|
||||
return AuthenticateResult.Fail("ApiKey authentication failed");
|
||||
}
|
||||
|
@ -280,9 +280,7 @@ namespace BTCPayServer.Services.Altcoins.Zcash.UI
|
||||
}
|
||||
};
|
||||
|
||||
#pragma warning disable CA1416 // Validate platform compatibility
|
||||
process.Start();
|
||||
#pragma warning restore CA1416 // Validate platform compatibility
|
||||
process.WaitForExit();
|
||||
}
|
||||
|
||||
|
@ -214,12 +214,12 @@ namespace BTCPayServer.Services.Apps
|
||||
return rate * value;
|
||||
}));
|
||||
var itemCode = entities.Key;
|
||||
var perk = perks.FirstOrDefault(p => p.Id == itemCode);
|
||||
var perk = perks.First(p => p.Id == itemCode);
|
||||
return new ItemStats
|
||||
{
|
||||
ItemCode = itemCode,
|
||||
Title = perk?.Title ?? itemCode,
|
||||
SalesCount = entities.Count(),
|
||||
Title = perk.Title,
|
||||
SalesCount = entities.Count(),
|
||||
Total = total,
|
||||
TotalFormatted = $"{total.ShowMoney(currencyData.Divisibility)} {settings.TargetCurrency}"
|
||||
};
|
||||
@ -270,7 +270,6 @@ namespace BTCPayServer.Services.Apps
|
||||
return entity.Status == InvoiceStatusLegacy.Complete || entity.Status == InvoiceStatusLegacy.Confirmed || entity.Status == InvoiceStatusLegacy.Paid;
|
||||
}
|
||||
|
||||
public static string GetPosOrderId(string appId) => $"pos-app_{appId}";
|
||||
public static string GetCrowdfundOrderId(string appId) => $"crowdfund-app_{appId}";
|
||||
public static string GetAppInternalTag(string appId) => $"APP#{appId}";
|
||||
public static string[] GetAppInternalTags(InvoiceEntity invoice)
|
||||
|
@ -66,6 +66,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
|
||||
public async Task<bool> RemovePendingInvoice(string invoiceId)
|
||||
{
|
||||
Logs.PayServer.LogInformation($"Remove pending invoice {invoiceId}");
|
||||
using var ctx = _applicationDbContextFactory.CreateContext();
|
||||
ctx.PendingInvoices.Remove(new PendingInvoiceData() { Id = invoiceId });
|
||||
try
|
||||
@ -646,18 +647,8 @@ namespace BTCPayServer.Services.Invoices
|
||||
|
||||
if (queryObject.StoreId != null && queryObject.StoreId.Length > 0)
|
||||
{
|
||||
if (queryObject.StoreId.Length > 1)
|
||||
{
|
||||
var stores = queryObject.StoreId.ToHashSet().ToArray();
|
||||
query = query.Where(i => stores.Contains(i.StoreDataId));
|
||||
}
|
||||
// Big performant improvement to use Where rather than Contains when possible
|
||||
// In our test, the first gives 720.173 ms vs 40.735 ms
|
||||
else
|
||||
{
|
||||
var storeId = queryObject.StoreId.First();
|
||||
query = query.Where(i => i.StoreDataId == storeId);
|
||||
}
|
||||
var stores = queryObject.StoreId.ToHashSet().ToArray();
|
||||
query = query.Where(i => stores.Contains(i.StoreDataId));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(queryObject.TextSearch))
|
||||
@ -779,21 +770,18 @@ namespace BTCPayServer.Services.Invoices
|
||||
return status;
|
||||
}
|
||||
|
||||
public static byte[] ToBytes<T>(T obj, BTCPayNetworkBase network = null)
|
||||
internal static byte[] ToBytes<T>(T obj, BTCPayNetworkBase network = null)
|
||||
{
|
||||
return ZipUtils.Zip(ToJsonString(obj, network));
|
||||
}
|
||||
|
||||
public static T FromBytes<T>(byte[] blob, BTCPayNetworkBase network = null)
|
||||
{
|
||||
return network == null
|
||||
? JsonConvert.DeserializeObject<T>(ZipUtils.Unzip(blob), DefaultSerializerSettings)
|
||||
: network.ToObject<T>(ZipUtils.Unzip(blob));
|
||||
}
|
||||
|
||||
public static string ToJsonString<T>(T data, BTCPayNetworkBase network)
|
||||
{
|
||||
return network == null ? JsonConvert.SerializeObject(data, DefaultSerializerSettings) : network.ToString(data);
|
||||
if (network == null)
|
||||
{
|
||||
return JsonConvert.SerializeObject(data, DefaultSerializerSettings);
|
||||
}
|
||||
return network.ToString(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user