diff --git a/BTCPayServer.Data/Data/InvoiceData.Migration.cs b/BTCPayServer.Data/Data/InvoiceData.Migration.cs
index 32ada35c3..f26e4a26e 100644
--- a/BTCPayServer.Data/Data/InvoiceData.Migration.cs
+++ b/BTCPayServer.Data/Data/InvoiceData.Migration.cs
@@ -37,6 +37,10 @@ namespace BTCPayServer.Data
{
paymentData.Migrate();
}
+ else if (entity is PayoutData payoutData && payoutData.Currency is null)
+ {
+ payoutData.Migrate();
+ }
return entity;
}
}
diff --git a/BTCPayServer.Data/Data/PayoutData.Migration.cs b/BTCPayServer.Data/Data/PayoutData.Migration.cs
new file mode 100644
index 000000000..bafdf8286
--- /dev/null
+++ b/BTCPayServer.Data/Data/PayoutData.Migration.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BTCPayServer.Data
+{
+ public partial class PayoutData
+ {
+ public void Migrate()
+ {
+ PayoutMethodId = MigrationExtensions.MigratePaymentMethodId(PayoutMethodId);
+ // Could only be BTC-LN or BTC-CHAIN, so we extract the crypto currency
+ Currency = PayoutMethodId.Split('-')[0];
+ }
+ }
+}
diff --git a/BTCPayServer.Data/Data/PayoutData.cs b/BTCPayServer.Data/Data/PayoutData.cs
index c79cc9109..4d86b75f3 100644
--- a/BTCPayServer.Data/Data/PayoutData.cs
+++ b/BTCPayServer.Data/Data/PayoutData.cs
@@ -10,7 +10,7 @@ using NBitcoin;
namespace BTCPayServer.Data
{
- public class PayoutData
+ public partial class PayoutData
{
[Key]
[MaxLength(30)]
@@ -18,12 +18,13 @@ namespace BTCPayServer.Data
public DateTimeOffset Date { get; set; }
public string PullPaymentDataId { get; set; }
public string StoreDataId { get; set; }
+ public string Currency { get; set; }
public PullPaymentData PullPaymentData { get; set; }
[MaxLength(20)]
public PayoutState State { get; set; }
[MaxLength(20)]
[Required]
- public string PaymentMethodId { get; set; }
+ public string PayoutMethodId { get; set; }
public string Blob { get; set; }
public string Proof { get; set; }
#nullable enable
diff --git a/BTCPayServer.Data/Migrations/20240520042729_payoutsmigration.cs b/BTCPayServer.Data/Migrations/20240520042729_payoutsmigration.cs
new file mode 100644
index 000000000..ee1067289
--- /dev/null
+++ b/BTCPayServer.Data/Migrations/20240520042729_payoutsmigration.cs
@@ -0,0 +1,29 @@
+using BTCPayServer.Data;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace BTCPayServer.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20240520042729_payoutsmigration")]
+ public partial class payoutsmigration : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "Currency",
+ table: "Payouts",
+ type: "text",
+ nullable: true);
+ migrationBuilder.RenameColumn("PaymentMethodId", "Payouts", "PayoutMethodId");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ }
+ }
+}
diff --git a/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs b/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs
index e7a6d6c68..758587f2a 100644
--- a/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs
+++ b/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs
@@ -1,4 +1,4 @@
-//
+//
using System;
using System.Collections.Generic;
using BTCPayServer.Data;
@@ -567,13 +567,16 @@ namespace BTCPayServer.Migrations
b.Property("Blob")
.HasColumnType("JSONB");
+ b.Property("Currency")
+ .HasColumnType("text");
+
b.Property("Date")
.HasColumnType("timestamp with time zone");
b.Property("Destination")
.HasColumnType("text");
- b.Property("PaymentMethodId")
+ b.Property("PayoutMethodId")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldPullPaymentController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldPullPaymentController.cs
index a6d2e5298..3734250a7 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldPullPaymentController.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldPullPaymentController.cs
@@ -372,9 +372,8 @@ namespace BTCPayServer.Controllers.Greenfield
Metadata = blob.Metadata?? new JObject(),
};
model.Destination = blob.Destination;
- model.PaymentMethod = p.PaymentMethodId;
- var currency = this._payoutHandlers.TryGet(p.GetPayoutMethodId())?.Currency;
- model.CryptoCode = currency;
+ model.PaymentMethod = p.PayoutMethodId;
+ model.CryptoCode = p.Currency;
model.PaymentProof = p.GetProofBlobJson();
return model;
}
diff --git a/BTCPayServer/Controllers/UILNURLController.cs b/BTCPayServer/Controllers/UILNURLController.cs
index 66a52d088..fbb557771 100644
--- a/BTCPayServer/Controllers/UILNURLController.cs
+++ b/BTCPayServer/Controllers/UILNURLController.cs
@@ -179,7 +179,7 @@ namespace BTCPayServer
lightningHandler.CreateLightningClient(pm);
var payResult = await UILightningLikePayoutController.TrypayBolt(client,
claimResponse.PayoutData.GetBlob(_btcPayNetworkJsonSerializerSettings),
- claimResponse.PayoutData, result, payoutHandler.Currency, cancellationToken);
+ claimResponse.PayoutData, result, cancellationToken);
switch (payResult.Result)
{
diff --git a/BTCPayServer/Controllers/UIPullPaymentController.cs b/BTCPayServer/Controllers/UIPullPaymentController.cs
index 0d8d220e0..ecc4bcc1f 100644
--- a/BTCPayServer/Controllers/UIPullPaymentController.cs
+++ b/BTCPayServer/Controllers/UIPullPaymentController.cs
@@ -118,7 +118,7 @@ namespace BTCPayServer.Controllers
Currency = blob.Currency,
Status = entity.Entity.State,
Destination = entity.Blob.Destination,
- PaymentMethod = PaymentMethodId.Parse(entity.Entity.PaymentMethodId),
+ PaymentMethod = PaymentMethodId.Parse(entity.Entity.PayoutMethodId),
Link = entity.ProofBlob?.Link,
TransactionId = entity.ProofBlob?.Id
}).ToList()
diff --git a/BTCPayServer/Controllers/UIStorePullPaymentsController.PullPayments.cs b/BTCPayServer/Controllers/UIStorePullPaymentsController.PullPayments.cs
index e67b31348..f3a34dcbb 100644
--- a/BTCPayServer/Controllers/UIStorePullPaymentsController.PullPayments.cs
+++ b/BTCPayServer/Controllers/UIStorePullPaymentsController.PullPayments.cs
@@ -509,14 +509,14 @@ namespace BTCPayServer.Controllers
vm.PullPaymentName = (await ctx.PullPayments.FindAsync(pullPaymentId)).GetBlob().Name;
}
- vm.PayoutMethodCount = (await payoutRequest.GroupBy(data => data.PaymentMethodId)
+ vm.PayoutMethodCount = (await payoutRequest.GroupBy(data => data.PayoutMethodId)
.Select(datas => new { datas.Key, Count = datas.Count() }).ToListAsync())
.ToDictionary(datas => datas.Key, arg => arg.Count);
if (vm.PayoutMethodId != null)
{
var pmiStr = vm.PayoutMethodId;
- payoutRequest = payoutRequest.Where(p => p.PaymentMethodId == pmiStr);
+ payoutRequest = payoutRequest.Where(p => p.PayoutMethodId == pmiStr);
}
vm.PayoutStateCount = payoutRequest.GroupBy(data => data.State)
.Select(e => new { e.Key, Count = e.Count() })
@@ -563,7 +563,6 @@ namespace BTCPayServer.Controllers
{
payoutSourceLink = Url.Action("ViewPullPayment", "UIPullPayment", new { pullPaymentId = item.PullPayment?.Id });
}
- var pCurrency = _payoutHandlers.TryGet(PayoutMethodId.Parse(item.Payout.PaymentMethodId))?.Currency;
var m = new PayoutsModel.PayoutModel
{
@@ -572,7 +571,7 @@ namespace BTCPayServer.Controllers
SourceLink = payoutSourceLink,
Date = item.Payout.Date,
PayoutId = item.Payout.Id,
- Amount = _displayFormatter.Currency(payoutBlob.Amount, ppBlob?.Currency ?? pCurrency),
+ Amount = _displayFormatter.Currency(payoutBlob.Amount, ppBlob?.Currency ?? item.Payout.Currency),
Destination = payoutBlob.Destination
};
var handler = _payoutHandlers
diff --git a/BTCPayServer/Data/Payouts/BitcoinLike/BitcoinLikePayoutHandler.cs b/BTCPayServer/Data/Payouts/BitcoinLike/BitcoinLikePayoutHandler.cs
index 3c82ca1ec..f954b0bf4 100644
--- a/BTCPayServer/Data/Payouts/BitcoinLike/BitcoinLikePayoutHandler.cs
+++ b/BTCPayServer/Data/Payouts/BitcoinLike/BitcoinLikePayoutHandler.cs
@@ -227,7 +227,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler, IHasNetwork
Stores = new[] { storeId },
PayoutIds = payoutIds
}, context)).Where(data =>
- PayoutMethodId.TryParse(data.PaymentMethodId, out var payoutMethodId) &&
+ PayoutMethodId.TryParse(data.PayoutMethodId, out var payoutMethodId) &&
payoutMethodId == PayoutMethodId)
.Select(data => (data, ParseProof(data) as PayoutTransactionOnChainBlob)).Where(tuple => tuple.Item2 != null && tuple.Item2.TransactionId != null && tuple.Item2.Accounted == false);
foreach (var valueTuple in payouts)
@@ -252,7 +252,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler, IHasNetwork
Stores = new[] { storeId },
PayoutIds = payoutIds
}, context)).Where(data =>
- PayoutMethodId.TryParse(data.PaymentMethodId, out var payoutMethodId) &&
+ PayoutMethodId.TryParse(data.PayoutMethodId, out var payoutMethodId) &&
payoutMethodId == PayoutMethodId)
.Select(data => (data, ParseProof(data) as PayoutTransactionOnChainBlob)).Where(tuple => tuple.Item2 != null && tuple.Item2.TransactionId != null && tuple.Item2.Accounted == true);
foreach (var valueTuple in payouts)
@@ -285,7 +285,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler, IHasNetwork
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var payouts = await ctx.Payouts.Include(data => data.PullPaymentData)
.Where(data => payoutIds.Contains(data.Id)
- && PayoutMethodId.ToString() == data.PaymentMethodId
+ && PayoutMethodId.ToString() == data.PayoutMethodId
&& data.State == PayoutState.AwaitingPayment)
.ToListAsync();
@@ -428,7 +428,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler, IHasNetwork
.Include(o => o.StoreData)
.Include(o => o.PullPaymentData)
.Where(p => p.State == PayoutState.AwaitingPayment)
- .Where(p => p.PaymentMethodId == paymentMethodId.ToString())
+ .Where(p => p.PayoutMethodId == paymentMethodId.ToString())
#pragma warning disable CA1307 // Specify StringComparison
.Where(p => destination.Equals(p.Destination))
#pragma warning restore CA1307 // Specify StringComparison
@@ -474,7 +474,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler, IHasNetwork
await _notificationSender.SendNotification(new StoreScope(payout.StoreDataId),
new ExternalPayoutTransactionNotification()
{
- PaymentMethod = payout.PaymentMethodId,
+ PaymentMethod = payout.PayoutMethodId,
PayoutId = payout.Id,
StoreId = payout.StoreDataId
});
diff --git a/BTCPayServer/Data/Payouts/LightningLike/UILightningLikePayoutController.cs b/BTCPayServer/Data/Payouts/LightningLike/UILightningLikePayoutController.cs
index 00894dc42..a5b471253 100644
--- a/BTCPayServer/Data/Payouts/LightningLike/UILightningLikePayoutController.cs
+++ b/BTCPayServer/Data/Payouts/LightningLike/UILightningLikePayoutController.cs
@@ -86,7 +86,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
.Where(data =>
payoutIds.Contains(data.Id) &&
data.State == PayoutState.AwaitingPayment &&
- data.PaymentMethodId == pmiStr)
+ data.PayoutMethodId == pmiStr)
.ToListAsync())
.Where(payout =>
{
@@ -185,13 +185,13 @@ namespace BTCPayServer.Data.Payouts.LightningLike
}
else
{
- result = await TrypayBolt(client, blob, payoutData, lnurlResult.Item1, payoutHandler.Currency, cancellationToken);
+ result = await TrypayBolt(client, blob, payoutData, lnurlResult.Item1, cancellationToken);
}
break;
case BoltInvoiceClaimDestination item1:
- result = await TrypayBolt(client, blob, payoutData, item1.PaymentRequest, payoutHandler.Currency, cancellationToken);
+ result = await TrypayBolt(client, blob, payoutData, item1.PaymentRequest, cancellationToken);
break;
default:
@@ -276,8 +276,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
}
public static async Task TrypayBolt(
- ILightningClient lightningClient, PayoutBlob payoutBlob, PayoutData payoutData, BOLT11PaymentRequest bolt11PaymentRequest,
- string payoutCurrency, CancellationToken cancellationToken)
+ ILightningClient lightningClient, PayoutBlob payoutBlob, PayoutData payoutData, BOLT11PaymentRequest bolt11PaymentRequest, CancellationToken cancellationToken)
{
var boltAmount = bolt11PaymentRequest.MinimumAmount.ToDecimal(LightMoneyUnit.BTC);
if (boltAmount > payoutBlob.CryptoAmount)
@@ -287,7 +286,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
{
PayoutId = payoutData.Id,
Result = PayResult.Error,
- Message = $"The BOLT11 invoice amount ({boltAmount} {payoutCurrency}) did not match the payout's amount ({payoutBlob.CryptoAmount.GetValueOrDefault()} {payoutCurrency})",
+ Message = $"The BOLT11 invoice amount ({boltAmount} {payoutData.Currency}) did not match the payout's amount ({payoutBlob.CryptoAmount.GetValueOrDefault()} {payoutData.Currency})",
Destination = payoutBlob.Destination
};
}
diff --git a/BTCPayServer/Data/Payouts/PayoutExtensions.cs b/BTCPayServer/Data/Payouts/PayoutExtensions.cs
index a209eebda..257eb5684 100644
--- a/BTCPayServer/Data/Payouts/PayoutExtensions.cs
+++ b/BTCPayServer/Data/Payouts/PayoutExtensions.cs
@@ -31,7 +31,7 @@ namespace BTCPayServer.Data
public static PayoutMethodId GetPayoutMethodId(this PayoutData data)
{
- return PayoutMethodId.TryParse(data.PaymentMethodId, out var pmi) ? pmi : null;
+ return PayoutMethodId.TryParse(data.PayoutMethodId, out var pmi) ? pmi : null;
}
public static string GetPayoutSource(this PayoutData data, BTCPayNetworkJsonSerializerSettings jsonSerializerSettings)
diff --git a/BTCPayServer/HostedServices/BlobMigratorHostedService.cs b/BTCPayServer/HostedServices/BlobMigratorHostedService.cs
new file mode 100644
index 000000000..ae170d8bf
--- /dev/null
+++ b/BTCPayServer/HostedServices/BlobMigratorHostedService.cs
@@ -0,0 +1,118 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection.Metadata;
+using System.Threading;
+using System.Threading.Tasks;
+using BTCPayServer.Abstractions.Contracts;
+using BTCPayServer.Data;
+using BTCPayServer.Services.Invoices;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.WindowsAzure.Storage.Table;
+using NBitcoin;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using static BTCPayServer.Controllers.UIInvoiceController;
+
+namespace BTCPayServer.HostedServices;
+
+public abstract class BlobMigratorHostedService : IHostedService
+{
+ public abstract string SettingsKey { get; }
+ internal class Settings
+ {
+ [JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
+ public DateTimeOffset? Progress { get; set; }
+ public bool Complete { get; set; }
+ }
+ Task? _Migrating;
+ TaskCompletionSource _Cts = new TaskCompletionSource();
+ public BlobMigratorHostedService(
+ ILogger logs,
+ ISettingsRepository settingsRepository,
+ ApplicationDbContextFactory applicationDbContextFactory)
+ {
+ Logs = logs;
+ SettingsRepository = settingsRepository;
+ ApplicationDbContextFactory = applicationDbContextFactory;
+ }
+
+ public ILogger Logs { get; }
+ public ISettingsRepository SettingsRepository { get; }
+ public ApplicationDbContextFactory ApplicationDbContextFactory { get; }
+
+ public Task StartAsync(CancellationToken cancellationToken)
+ {
+ _Migrating = Migrate(cancellationToken);
+ return Task.CompletedTask;
+ }
+ public int BatchSize { get; set; } = 1000;
+
+ private async Task Migrate(CancellationToken cancellationToken)
+ {
+ var settings = await SettingsRepository.GetSettingAsync(SettingsKey) ?? new Settings();
+ if (settings.Complete is true)
+ return;
+ if (settings.Progress is DateTimeOffset last)
+ Logs.LogInformation($"Migrating from {last}");
+ else
+ Logs.LogInformation("Migrating from the beginning");
+
+ int batchSize = BatchSize;
+ while (!cancellationToken.IsCancellationRequested)
+ {
+retry:
+ List entities;
+ DateTimeOffset progress;
+ await using (var ctx = ApplicationDbContextFactory.CreateContext())
+ {
+ var query = GetQuery(ctx, settings?.Progress).Take(batchSize);
+ entities = await query.ToListAsync(cancellationToken);
+ if (entities.Count == 0)
+ {
+ await SettingsRepository.UpdateSetting(new Settings() { Complete = true }, SettingsKey);
+ Logs.LogInformation("Migration completed");
+ return;
+ }
+
+ try
+ {
+ progress = ProcessEntities(ctx, entities);
+ await ctx.SaveChangesAsync();
+ batchSize = BatchSize;
+ }
+ catch (DbUpdateConcurrencyException)
+ {
+ batchSize /= 2;
+ batchSize = Math.Max(1, batchSize);
+ goto retry;
+ }
+ }
+ settings = new Settings() { Progress = progress };
+ await SettingsRepository.UpdateSetting(settings, SettingsKey);
+ }
+ }
+ protected abstract IQueryable GetQuery(ApplicationDbContext ctx, DateTimeOffset? progress);
+ protected abstract DateTimeOffset ProcessEntities(ApplicationDbContext ctx, List entities);
+ public async Task ResetMigration()
+ {
+ await SettingsRepository.UpdateSetting(new Settings(), SettingsKey);
+ }
+ public async Task IsComplete()
+ {
+ return (await SettingsRepository.GetSettingAsync(SettingsKey)) is { Complete: true };
+ }
+
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ _Cts.TrySetCanceled();
+ return (_Migrating ?? Task.CompletedTask).ContinueWith(t =>
+ {
+ if (t.IsFaulted)
+ Logs.LogError(t.Exception, "Error while migrating");
+ });
+ }
+}
diff --git a/BTCPayServer/HostedServices/InvoiceBlobMigratorHostedService.cs b/BTCPayServer/HostedServices/InvoiceBlobMigratorHostedService.cs
index 93a607697..1833d3522 100644
--- a/BTCPayServer/HostedServices/InvoiceBlobMigratorHostedService.cs
+++ b/BTCPayServer/HostedServices/InvoiceBlobMigratorHostedService.cs
@@ -20,136 +20,62 @@ using static BTCPayServer.Controllers.UIInvoiceController;
namespace BTCPayServer.HostedServices;
-public class InvoiceBlobMigratorHostedService : IHostedService
+public class InvoiceBlobMigratorHostedService : BlobMigratorHostedService
{
- const string SettingsKey = "InvoiceBlobMigratorHostedService.Settings";
+
private readonly PaymentMethodHandlerDictionary _handlers;
- internal class Settings
- {
- [JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
- public DateTimeOffset? Progress { get; set; }
- public bool Complete { get; set; }
- }
- Task? _Migrating;
- TaskCompletionSource _Cts = new TaskCompletionSource();
public InvoiceBlobMigratorHostedService(
ILogger logs,
ISettingsRepository settingsRepository,
ApplicationDbContextFactory applicationDbContextFactory,
- PaymentMethodHandlerDictionary handlers)
+ PaymentMethodHandlerDictionary handlers) : base(logs, settingsRepository, applicationDbContextFactory)
{
- Logs = logs;
- SettingsRepository = settingsRepository;
- ApplicationDbContextFactory = applicationDbContextFactory;
_handlers = handlers;
}
- public ILogger Logs { get; }
- public ISettingsRepository SettingsRepository { get; }
- public ApplicationDbContextFactory ApplicationDbContextFactory { get; }
-
- public Task StartAsync(CancellationToken cancellationToken)
+ public override string SettingsKey => "InvoicesMigration";
+ protected override IQueryable GetQuery(ApplicationDbContext ctx, DateTimeOffset? progress)
{
- _Migrating = Migrate(cancellationToken);
- return Task.CompletedTask;
+ var query = progress is DateTimeOffset last2 ?
+ ctx.Invoices.Include(o => o.Payments).Where(i => i.Created < last2 && i.Currency == null) :
+ ctx.Invoices.Include(o => o.Payments).Where(i => i.Currency == null);
+ return query.OrderByDescending(i => i.Created);
}
- public int BatchSize { get; set; } = 1000;
-
- private async Task Migrate(CancellationToken cancellationToken)
+ protected override DateTimeOffset ProcessEntities(ApplicationDbContext ctx, List invoices)
{
- var settings = await SettingsRepository.GetSettingAsync(SettingsKey) ?? new Settings();
- if (settings.Complete is true)
- return;
- if (settings.Progress is DateTimeOffset last)
- Logs.LogInformation($"Migrating invoices JSON Blobs from {last}");
- else
- Logs.LogInformation("Migrating invoices JSON Blobs from the beginning");
-
- int batchSize = BatchSize;
- while (!cancellationToken.IsCancellationRequested)
+ // Those clean up the JSON blobs, and mark entities as modified
+ foreach (var inv in invoices)
{
-retry:
- List invoices;
- await using (var ctx = ApplicationDbContextFactory.CreateContext())
+ var blob = inv.GetBlob();
+ var prompts = blob.GetPaymentPrompts();
+ foreach (var p in prompts)
{
- var query = settings.Progress is DateTimeOffset last2 ?
- ctx.Invoices.Include(o => o.Payments).Where(i => i.Created < last2 && i.Currency == null) :
- ctx.Invoices.Include(o => o.Payments).Where(i => i.Currency == null);
- query = query.OrderByDescending(i => i.Created).Take(batchSize);
- invoices = await query.ToListAsync(cancellationToken);
- if (invoices.Count == 0)
+ if (_handlers.TryGetValue(p.PaymentMethodId, out var handler) && p.Details is not (null or { Type: JTokenType.Null }))
{
- await SettingsRepository.UpdateSetting(new Settings() { Complete = true }, SettingsKey);
- Logs.LogInformation("Migration of invoices JSON Blobs completed");
- return;
- }
-
- try
- {
- // Those clean up the JSON blobs, and mark entities as modified
- foreach (var inv in invoices)
- {
- var blob = inv.GetBlob();
- var prompts = blob.GetPaymentPrompts();
- foreach (var p in prompts)
- {
- if (_handlers.TryGetValue(p.PaymentMethodId, out var handler) && p.Details is not (null or { Type: JTokenType.Null }))
- {
- p.Details = JToken.FromObject(handler.ParsePaymentPromptDetails(p.Details), handler.Serializer);
- }
- }
- blob.SetPaymentPrompts(prompts);
- inv.SetBlob(blob);
- foreach (var pay in inv.Payments)
- {
- var paymentEntity = pay.GetBlob();
- if (_handlers.TryGetValue(paymentEntity.PaymentMethodId, out var handler) && paymentEntity.Details is not (null or { Type: JTokenType.Null }))
- {
- paymentEntity.Details = JToken.FromObject(handler.ParsePaymentDetails(paymentEntity.Details), handler.Serializer);
- }
- pay.SetBlob(paymentEntity);
- }
- }
- foreach (var entry in ctx.ChangeTracker.Entries())
- {
- entry.State = EntityState.Modified;
- }
- foreach (var entry in ctx.ChangeTracker.Entries())
- {
- entry.State = EntityState.Modified;
- }
- await ctx.SaveChangesAsync();
- batchSize = BatchSize;
- }
- catch (DbUpdateConcurrencyException)
- {
- batchSize /= 2;
- batchSize = Math.Max(1, batchSize);
- goto retry;
+ p.Details = JToken.FromObject(handler.ParsePaymentPromptDetails(p.Details), handler.Serializer);
}
}
- settings = new Settings() { Progress = invoices[^1].Created };
- await SettingsRepository.UpdateSetting(settings, SettingsKey);
+ blob.SetPaymentPrompts(prompts);
+ inv.SetBlob(blob);
+ foreach (var pay in inv.Payments)
+ {
+ var paymentEntity = pay.GetBlob();
+ if (_handlers.TryGetValue(paymentEntity.PaymentMethodId, out var handler) && paymentEntity.Details is not (null or { Type: JTokenType.Null }))
+ {
+ paymentEntity.Details = JToken.FromObject(handler.ParsePaymentDetails(paymentEntity.Details), handler.Serializer);
+ }
+ pay.SetBlob(paymentEntity);
+ }
}
- }
-
- public async Task ResetMigration()
- {
- await SettingsRepository.UpdateSetting(new Settings(), SettingsKey);
- }
- public async Task IsComplete()
- {
- return (await SettingsRepository.GetSettingAsync(SettingsKey)) is { Complete: true };
- }
-
- public Task StopAsync(CancellationToken cancellationToken)
- {
- _Cts.TrySetCanceled();
- return (_Migrating ?? Task.CompletedTask).ContinueWith(t =>
+ foreach (var entry in ctx.ChangeTracker.Entries())
{
- if (t.IsFaulted)
- Logs.LogError(t.Exception, "Error while migrating invoices JSON Blobs");
- });
+ entry.State = EntityState.Modified;
+ }
+ foreach (var entry in ctx.ChangeTracker.Entries())
+ {
+ entry.State = EntityState.Modified;
+ }
+ return invoices[^1].Created;
}
}
diff --git a/BTCPayServer/HostedServices/PayoutBlobMigratorHostedService.cs b/BTCPayServer/HostedServices/PayoutBlobMigratorHostedService.cs
new file mode 100644
index 000000000..229ea1a25
--- /dev/null
+++ b/BTCPayServer/HostedServices/PayoutBlobMigratorHostedService.cs
@@ -0,0 +1,53 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection.Metadata;
+using System.Threading;
+using System.Threading.Tasks;
+using AngleSharp.Dom;
+using BTCPayServer.Abstractions.Contracts;
+using BTCPayServer.Data;
+using BTCPayServer.Services.Invoices;
+using Google.Apis.Logging;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using NBitcoin;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using static BTCPayServer.Controllers.UIInvoiceController;
+
+namespace BTCPayServer.HostedServices;
+
+public class PayoutBlobMigratorHostedService : BlobMigratorHostedService
+{
+
+ private readonly PaymentMethodHandlerDictionary _handlers;
+
+ public PayoutBlobMigratorHostedService(
+ ILogger logs,
+ ISettingsRepository settingsRepository,
+ ApplicationDbContextFactory applicationDbContextFactory,
+ PaymentMethodHandlerDictionary handlers) : base(logs, settingsRepository, applicationDbContextFactory)
+ {
+ _handlers = handlers;
+ }
+
+ public override string SettingsKey => "PayoutsMigration";
+ protected override IQueryable GetQuery(ApplicationDbContext ctx, DateTimeOffset? progress)
+ {
+ var query = progress is DateTimeOffset last2 ?
+ ctx.Payouts.Where(i => i.Date < last2 && i.Currency == null) :
+ ctx.Payouts.Where(i => i.Currency == null);
+ return query.OrderByDescending(i => i);
+ }
+ protected override DateTimeOffset ProcessEntities(ApplicationDbContext ctx, List payouts)
+ {
+ foreach (var entry in ctx.ChangeTracker.Entries())
+ {
+ entry.State = EntityState.Modified;
+ }
+ return payouts[^1].Date;
+ }
+}
diff --git a/BTCPayServer/HostedServices/PullPaymentHostedService.cs b/BTCPayServer/HostedServices/PullPaymentHostedService.cs
index efd93f14c..fe508c551 100644
--- a/BTCPayServer/HostedServices/PullPaymentHostedService.cs
+++ b/BTCPayServer/HostedServices/PullPaymentHostedService.cs
@@ -217,11 +217,11 @@ namespace BTCPayServer.HostedServices
if (payoutQuery.PayoutMethods.Length == 1)
{
var pm = payoutQuery.PayoutMethods[0];
- query = query.Where(data => pm == data.PaymentMethodId);
+ query = query.Where(data => pm == data.PayoutMethodId);
}
else
{
- query = query.Where(data => payoutQuery.PayoutMethods.Contains(data.PaymentMethodId));
+ query = query.Where(data => payoutQuery.PayoutMethods.Contains(data.PayoutMethodId));
}
}
@@ -459,7 +459,7 @@ namespace BTCPayServer.HostedServices
return;
}
- if (!PayoutMethodId.TryParse(payout.PaymentMethodId, out var paymentMethod))
+ if (!PayoutMethodId.TryParse(payout.PayoutMethodId, out var paymentMethod))
{
req.Completion.SetResult(new PayoutApproval.ApprovalResult(PayoutApproval.Result.NotFound, null));
return;
@@ -644,9 +644,10 @@ namespace BTCPayServer.HostedServices
Date = now,
State = PayoutState.AwaitingApproval,
PullPaymentDataId = req.ClaimRequest.PullPaymentId,
- PaymentMethodId = req.ClaimRequest.PayoutMethodId.ToString(),
+ PayoutMethodId = req.ClaimRequest.PayoutMethodId.ToString(),
Destination = req.ClaimRequest.Destination.Id,
- StoreDataId = req.ClaimRequest.StoreId ?? pp?.StoreId
+ StoreDataId = req.ClaimRequest.StoreId ?? pp?.StoreId,
+ Currency = payoutHandler.Currency
};
var payoutBlob = new PayoutBlob()
{
@@ -693,7 +694,7 @@ namespace BTCPayServer.HostedServices
StoreId = payout.StoreDataId,
Currency = ppBlob?.Currency ?? _handlers.TryGetNetwork(req.ClaimRequest.PayoutMethodId)?.NBXplorerNetwork.CryptoCode,
Status = payout.State,
- PaymentMethod = payout.PaymentMethodId,
+ PaymentMethod = payout.PayoutMethodId,
PayoutId = payout.Id
});
}
diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs
index a0770ae6b..fab607e2f 100644
--- a/BTCPayServer/Hosting/BTCPayServerServices.cs
+++ b/BTCPayServer/Hosting/BTCPayServerServices.cs
@@ -577,6 +577,9 @@ o.GetRequiredService>().ToDictionary(o => o.P
services.AddSingleton();
services.AddSingleton(o => o.GetRequiredService());
+ services.AddSingleton();
+ services.AddSingleton(o => o.GetRequiredService());
+
// Broken
// Providers.Add("argoneum", new ArgoneumRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_ARGONEUM")));
diff --git a/BTCPayServer/Payments/Lightning/LightningPendingPayoutListener.cs b/BTCPayServer/Payments/Lightning/LightningPendingPayoutListener.cs
index 39b74f77d..0829f197b 100644
--- a/BTCPayServer/Payments/Lightning/LightningPendingPayoutListener.cs
+++ b/BTCPayServer/Payments/Lightning/LightningPendingPayoutListener.cs
@@ -85,7 +85,7 @@ public class LightningPendingPayoutListener : BaseAsyncService
}
foreach (IGrouping payoutByStoreByPaymentMethod in payoutByStore.GroupBy(data =>
- data.PaymentMethodId))
+ data.PayoutMethodId))
{
var pmi = PaymentMethodId.Parse(payoutByStoreByPaymentMethod.Key);
var pm = store.GetPaymentMethodConfigs(_handlers)
diff --git a/BTCPayServer/PayoutProcessors/Lightning/LightningAutomatedPayoutProcessor.cs b/BTCPayServer/PayoutProcessors/Lightning/LightningAutomatedPayoutProcessor.cs
index 695e9c363..3cbe97a70 100644
--- a/BTCPayServer/PayoutProcessors/Lightning/LightningAutomatedPayoutProcessor.cs
+++ b/BTCPayServer/PayoutProcessors/Lightning/LightningAutomatedPayoutProcessor.cs
@@ -146,6 +146,6 @@ public class LightningAutomatedPayoutProcessor : BaseAutomatedPayoutProcessor