mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-21 22:11:48 +01:00
Migrate Payouts to new format (#5989)
* Migrate Payouts to new format * Rename PayoutData column to PayoutMethodId
This commit is contained in:
parent
c56b660c92
commit
a295e123bc
21 changed files with 303 additions and 151 deletions
|
@ -37,6 +37,10 @@ namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
paymentData.Migrate();
|
paymentData.Migrate();
|
||||||
}
|
}
|
||||||
|
else if (entity is PayoutData payoutData && payoutData.Currency is null)
|
||||||
|
{
|
||||||
|
payoutData.Migrate();
|
||||||
|
}
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
18
BTCPayServer.Data/Data/PayoutData.Migration.cs
Normal file
18
BTCPayServer.Data/Data/PayoutData.Migration.cs
Normal file
|
@ -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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ using NBitcoin;
|
||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
public class PayoutData
|
public partial class PayoutData
|
||||||
{
|
{
|
||||||
[Key]
|
[Key]
|
||||||
[MaxLength(30)]
|
[MaxLength(30)]
|
||||||
|
@ -18,12 +18,13 @@ namespace BTCPayServer.Data
|
||||||
public DateTimeOffset Date { get; set; }
|
public DateTimeOffset Date { get; set; }
|
||||||
public string PullPaymentDataId { get; set; }
|
public string PullPaymentDataId { get; set; }
|
||||||
public string StoreDataId { get; set; }
|
public string StoreDataId { get; set; }
|
||||||
|
public string Currency { get; set; }
|
||||||
public PullPaymentData PullPaymentData { get; set; }
|
public PullPaymentData PullPaymentData { get; set; }
|
||||||
[MaxLength(20)]
|
[MaxLength(20)]
|
||||||
public PayoutState State { get; set; }
|
public PayoutState State { get; set; }
|
||||||
[MaxLength(20)]
|
[MaxLength(20)]
|
||||||
[Required]
|
[Required]
|
||||||
public string PaymentMethodId { get; set; }
|
public string PayoutMethodId { get; set; }
|
||||||
public string Blob { get; set; }
|
public string Blob { get; set; }
|
||||||
public string Proof { get; set; }
|
public string Proof { get; set; }
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Currency",
|
||||||
|
table: "Payouts",
|
||||||
|
type: "text",
|
||||||
|
nullable: true);
|
||||||
|
migrationBuilder.RenameColumn("PaymentMethodId", "Payouts", "PayoutMethodId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
|
@ -567,13 +567,16 @@ namespace BTCPayServer.Migrations
|
||||||
b.Property<string>("Blob")
|
b.Property<string>("Blob")
|
||||||
.HasColumnType("JSONB");
|
.HasColumnType("JSONB");
|
||||||
|
|
||||||
|
b.Property<string>("Currency")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("Date")
|
b.Property<DateTimeOffset>("Date")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<string>("Destination")
|
b.Property<string>("Destination")
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<string>("PaymentMethodId")
|
b.Property<string>("PayoutMethodId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(20)
|
.HasMaxLength(20)
|
||||||
.HasColumnType("character varying(20)");
|
.HasColumnType("character varying(20)");
|
||||||
|
|
|
@ -372,9 +372,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
Metadata = blob.Metadata?? new JObject(),
|
Metadata = blob.Metadata?? new JObject(),
|
||||||
};
|
};
|
||||||
model.Destination = blob.Destination;
|
model.Destination = blob.Destination;
|
||||||
model.PaymentMethod = p.PaymentMethodId;
|
model.PaymentMethod = p.PayoutMethodId;
|
||||||
var currency = this._payoutHandlers.TryGet(p.GetPayoutMethodId())?.Currency;
|
model.CryptoCode = p.Currency;
|
||||||
model.CryptoCode = currency;
|
|
||||||
model.PaymentProof = p.GetProofBlobJson();
|
model.PaymentProof = p.GetProofBlobJson();
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,7 +179,7 @@ namespace BTCPayServer
|
||||||
lightningHandler.CreateLightningClient(pm);
|
lightningHandler.CreateLightningClient(pm);
|
||||||
var payResult = await UILightningLikePayoutController.TrypayBolt(client,
|
var payResult = await UILightningLikePayoutController.TrypayBolt(client,
|
||||||
claimResponse.PayoutData.GetBlob(_btcPayNetworkJsonSerializerSettings),
|
claimResponse.PayoutData.GetBlob(_btcPayNetworkJsonSerializerSettings),
|
||||||
claimResponse.PayoutData, result, payoutHandler.Currency, cancellationToken);
|
claimResponse.PayoutData, result, cancellationToken);
|
||||||
|
|
||||||
switch (payResult.Result)
|
switch (payResult.Result)
|
||||||
{
|
{
|
||||||
|
|
|
@ -118,7 +118,7 @@ namespace BTCPayServer.Controllers
|
||||||
Currency = blob.Currency,
|
Currency = blob.Currency,
|
||||||
Status = entity.Entity.State,
|
Status = entity.Entity.State,
|
||||||
Destination = entity.Blob.Destination,
|
Destination = entity.Blob.Destination,
|
||||||
PaymentMethod = PaymentMethodId.Parse(entity.Entity.PaymentMethodId),
|
PaymentMethod = PaymentMethodId.Parse(entity.Entity.PayoutMethodId),
|
||||||
Link = entity.ProofBlob?.Link,
|
Link = entity.ProofBlob?.Link,
|
||||||
TransactionId = entity.ProofBlob?.Id
|
TransactionId = entity.ProofBlob?.Id
|
||||||
}).ToList()
|
}).ToList()
|
||||||
|
|
|
@ -509,14 +509,14 @@ namespace BTCPayServer.Controllers
|
||||||
vm.PullPaymentName = (await ctx.PullPayments.FindAsync(pullPaymentId)).GetBlob().Name;
|
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())
|
.Select(datas => new { datas.Key, Count = datas.Count() }).ToListAsync())
|
||||||
.ToDictionary(datas => datas.Key, arg => arg.Count);
|
.ToDictionary(datas => datas.Key, arg => arg.Count);
|
||||||
|
|
||||||
if (vm.PayoutMethodId != null)
|
if (vm.PayoutMethodId != null)
|
||||||
{
|
{
|
||||||
var pmiStr = vm.PayoutMethodId;
|
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)
|
vm.PayoutStateCount = payoutRequest.GroupBy(data => data.State)
|
||||||
.Select(e => new { e.Key, Count = e.Count() })
|
.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 });
|
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
|
var m = new PayoutsModel.PayoutModel
|
||||||
{
|
{
|
||||||
|
@ -572,7 +571,7 @@ namespace BTCPayServer.Controllers
|
||||||
SourceLink = payoutSourceLink,
|
SourceLink = payoutSourceLink,
|
||||||
Date = item.Payout.Date,
|
Date = item.Payout.Date,
|
||||||
PayoutId = item.Payout.Id,
|
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
|
Destination = payoutBlob.Destination
|
||||||
};
|
};
|
||||||
var handler = _payoutHandlers
|
var handler = _payoutHandlers
|
||||||
|
|
|
@ -227,7 +227,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler, IHasNetwork
|
||||||
Stores = new[] { storeId },
|
Stores = new[] { storeId },
|
||||||
PayoutIds = payoutIds
|
PayoutIds = payoutIds
|
||||||
}, context)).Where(data =>
|
}, context)).Where(data =>
|
||||||
PayoutMethodId.TryParse(data.PaymentMethodId, out var payoutMethodId) &&
|
PayoutMethodId.TryParse(data.PayoutMethodId, out var payoutMethodId) &&
|
||||||
payoutMethodId == PayoutMethodId)
|
payoutMethodId == PayoutMethodId)
|
||||||
.Select(data => (data, ParseProof(data) as PayoutTransactionOnChainBlob)).Where(tuple => tuple.Item2 != null && tuple.Item2.TransactionId != null && tuple.Item2.Accounted == false);
|
.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)
|
foreach (var valueTuple in payouts)
|
||||||
|
@ -252,7 +252,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler, IHasNetwork
|
||||||
Stores = new[] { storeId },
|
Stores = new[] { storeId },
|
||||||
PayoutIds = payoutIds
|
PayoutIds = payoutIds
|
||||||
}, context)).Where(data =>
|
}, context)).Where(data =>
|
||||||
PayoutMethodId.TryParse(data.PaymentMethodId, out var payoutMethodId) &&
|
PayoutMethodId.TryParse(data.PayoutMethodId, out var payoutMethodId) &&
|
||||||
payoutMethodId == PayoutMethodId)
|
payoutMethodId == PayoutMethodId)
|
||||||
.Select(data => (data, ParseProof(data) as PayoutTransactionOnChainBlob)).Where(tuple => tuple.Item2 != null && tuple.Item2.TransactionId != null && tuple.Item2.Accounted == true);
|
.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)
|
foreach (var valueTuple in payouts)
|
||||||
|
@ -285,7 +285,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler, IHasNetwork
|
||||||
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||||
var payouts = await ctx.Payouts.Include(data => data.PullPaymentData)
|
var payouts = await ctx.Payouts.Include(data => data.PullPaymentData)
|
||||||
.Where(data => payoutIds.Contains(data.Id)
|
.Where(data => payoutIds.Contains(data.Id)
|
||||||
&& PayoutMethodId.ToString() == data.PaymentMethodId
|
&& PayoutMethodId.ToString() == data.PayoutMethodId
|
||||||
&& data.State == PayoutState.AwaitingPayment)
|
&& data.State == PayoutState.AwaitingPayment)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
@ -428,7 +428,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler, IHasNetwork
|
||||||
.Include(o => o.StoreData)
|
.Include(o => o.StoreData)
|
||||||
.Include(o => o.PullPaymentData)
|
.Include(o => o.PullPaymentData)
|
||||||
.Where(p => p.State == PayoutState.AwaitingPayment)
|
.Where(p => p.State == PayoutState.AwaitingPayment)
|
||||||
.Where(p => p.PaymentMethodId == paymentMethodId.ToString())
|
.Where(p => p.PayoutMethodId == paymentMethodId.ToString())
|
||||||
#pragma warning disable CA1307 // Specify StringComparison
|
#pragma warning disable CA1307 // Specify StringComparison
|
||||||
.Where(p => destination.Equals(p.Destination))
|
.Where(p => destination.Equals(p.Destination))
|
||||||
#pragma warning restore CA1307 // Specify StringComparison
|
#pragma warning restore CA1307 // Specify StringComparison
|
||||||
|
@ -474,7 +474,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler, IHasNetwork
|
||||||
await _notificationSender.SendNotification(new StoreScope(payout.StoreDataId),
|
await _notificationSender.SendNotification(new StoreScope(payout.StoreDataId),
|
||||||
new ExternalPayoutTransactionNotification()
|
new ExternalPayoutTransactionNotification()
|
||||||
{
|
{
|
||||||
PaymentMethod = payout.PaymentMethodId,
|
PaymentMethod = payout.PayoutMethodId,
|
||||||
PayoutId = payout.Id,
|
PayoutId = payout.Id,
|
||||||
StoreId = payout.StoreDataId
|
StoreId = payout.StoreDataId
|
||||||
});
|
});
|
||||||
|
|
|
@ -86,7 +86,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||||
.Where(data =>
|
.Where(data =>
|
||||||
payoutIds.Contains(data.Id) &&
|
payoutIds.Contains(data.Id) &&
|
||||||
data.State == PayoutState.AwaitingPayment &&
|
data.State == PayoutState.AwaitingPayment &&
|
||||||
data.PaymentMethodId == pmiStr)
|
data.PayoutMethodId == pmiStr)
|
||||||
.ToListAsync())
|
.ToListAsync())
|
||||||
.Where(payout =>
|
.Where(payout =>
|
||||||
{
|
{
|
||||||
|
@ -185,13 +185,13 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result = await TrypayBolt(client, blob, payoutData, lnurlResult.Item1, payoutHandler.Currency, cancellationToken);
|
result = await TrypayBolt(client, blob, payoutData, lnurlResult.Item1, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BoltInvoiceClaimDestination item1:
|
case BoltInvoiceClaimDestination item1:
|
||||||
result = await TrypayBolt(client, blob, payoutData, item1.PaymentRequest, payoutHandler.Currency, cancellationToken);
|
result = await TrypayBolt(client, blob, payoutData, item1.PaymentRequest, cancellationToken);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -276,8 +276,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<ResultVM> TrypayBolt(
|
public static async Task<ResultVM> TrypayBolt(
|
||||||
ILightningClient lightningClient, PayoutBlob payoutBlob, PayoutData payoutData, BOLT11PaymentRequest bolt11PaymentRequest,
|
ILightningClient lightningClient, PayoutBlob payoutBlob, PayoutData payoutData, BOLT11PaymentRequest bolt11PaymentRequest, CancellationToken cancellationToken)
|
||||||
string payoutCurrency, CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
var boltAmount = bolt11PaymentRequest.MinimumAmount.ToDecimal(LightMoneyUnit.BTC);
|
var boltAmount = bolt11PaymentRequest.MinimumAmount.ToDecimal(LightMoneyUnit.BTC);
|
||||||
if (boltAmount > payoutBlob.CryptoAmount)
|
if (boltAmount > payoutBlob.CryptoAmount)
|
||||||
|
@ -287,7 +286,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||||
{
|
{
|
||||||
PayoutId = payoutData.Id,
|
PayoutId = payoutData.Id,
|
||||||
Result = PayResult.Error,
|
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
|
Destination = payoutBlob.Destination
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ namespace BTCPayServer.Data
|
||||||
|
|
||||||
public static PayoutMethodId GetPayoutMethodId(this PayoutData 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)
|
public static string GetPayoutSource(this PayoutData data, BTCPayNetworkJsonSerializerSettings jsonSerializerSettings)
|
||||||
|
|
118
BTCPayServer/HostedServices/BlobMigratorHostedService.cs
Normal file
118
BTCPayServer/HostedServices/BlobMigratorHostedService.cs
Normal file
|
@ -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<TEntity> : 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<Settings>(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<TEntity> 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<Settings>(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>(settings, SettingsKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected abstract IQueryable<TEntity> GetQuery(ApplicationDbContext ctx, DateTimeOffset? progress);
|
||||||
|
protected abstract DateTimeOffset ProcessEntities(ApplicationDbContext ctx, List<TEntity> entities);
|
||||||
|
public async Task ResetMigration()
|
||||||
|
{
|
||||||
|
await SettingsRepository.UpdateSetting<Settings>(new Settings(), SettingsKey);
|
||||||
|
}
|
||||||
|
public async Task<bool> IsComplete()
|
||||||
|
{
|
||||||
|
return (await SettingsRepository.GetSettingAsync<Settings>(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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,136 +20,62 @@ using static BTCPayServer.Controllers.UIInvoiceController;
|
||||||
|
|
||||||
namespace BTCPayServer.HostedServices;
|
namespace BTCPayServer.HostedServices;
|
||||||
|
|
||||||
public class InvoiceBlobMigratorHostedService : IHostedService
|
public class InvoiceBlobMigratorHostedService : BlobMigratorHostedService<InvoiceData>
|
||||||
{
|
{
|
||||||
const string SettingsKey = "InvoiceBlobMigratorHostedService.Settings";
|
|
||||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
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(
|
public InvoiceBlobMigratorHostedService(
|
||||||
ILogger<InvoiceBlobMigratorHostedService> logs,
|
ILogger<InvoiceBlobMigratorHostedService> logs,
|
||||||
ISettingsRepository settingsRepository,
|
ISettingsRepository settingsRepository,
|
||||||
ApplicationDbContextFactory applicationDbContextFactory,
|
ApplicationDbContextFactory applicationDbContextFactory,
|
||||||
PaymentMethodHandlerDictionary handlers)
|
PaymentMethodHandlerDictionary handlers) : base(logs, settingsRepository, applicationDbContextFactory)
|
||||||
{
|
{
|
||||||
Logs = logs;
|
|
||||||
SettingsRepository = settingsRepository;
|
|
||||||
ApplicationDbContextFactory = applicationDbContextFactory;
|
|
||||||
_handlers = handlers;
|
_handlers = handlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ILogger<InvoiceBlobMigratorHostedService> Logs { get; }
|
public override string SettingsKey => "InvoicesMigration";
|
||||||
public ISettingsRepository SettingsRepository { get; }
|
protected override IQueryable<InvoiceData> GetQuery(ApplicationDbContext ctx, DateTimeOffset? progress)
|
||||||
public ApplicationDbContextFactory ApplicationDbContextFactory { get; }
|
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
_Migrating = Migrate(cancellationToken);
|
var query = progress is DateTimeOffset last2 ?
|
||||||
return Task.CompletedTask;
|
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;
|
protected override DateTimeOffset ProcessEntities(ApplicationDbContext ctx, List<InvoiceData> invoices)
|
||||||
|
|
||||||
private async Task Migrate(CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
var settings = await SettingsRepository.GetSettingAsync<Settings>(SettingsKey) ?? new Settings();
|
// Those clean up the JSON blobs, and mark entities as modified
|
||||||
if (settings.Complete is true)
|
foreach (var inv in invoices)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
retry:
|
var blob = inv.GetBlob();
|
||||||
List<InvoiceData> invoices;
|
var prompts = blob.GetPaymentPrompts();
|
||||||
await using (var ctx = ApplicationDbContextFactory.CreateContext())
|
foreach (var p in prompts)
|
||||||
{
|
{
|
||||||
var query = settings.Progress is DateTimeOffset last2 ?
|
if (_handlers.TryGetValue(p.PaymentMethodId, out var handler) && p.Details is not (null or { Type: JTokenType.Null }))
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
await SettingsRepository.UpdateSetting<Settings>(new Settings() { Complete = true }, SettingsKey);
|
p.Details = JToken.FromObject(handler.ParsePaymentPromptDetails(p.Details), handler.Serializer);
|
||||||
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<InvoiceData>())
|
|
||||||
{
|
|
||||||
entry.State = EntityState.Modified;
|
|
||||||
}
|
|
||||||
foreach (var entry in ctx.ChangeTracker.Entries<PaymentData>())
|
|
||||||
{
|
|
||||||
entry.State = EntityState.Modified;
|
|
||||||
}
|
|
||||||
await ctx.SaveChangesAsync();
|
|
||||||
batchSize = BatchSize;
|
|
||||||
}
|
|
||||||
catch (DbUpdateConcurrencyException)
|
|
||||||
{
|
|
||||||
batchSize /= 2;
|
|
||||||
batchSize = Math.Max(1, batchSize);
|
|
||||||
goto retry;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
settings = new Settings() { Progress = invoices[^1].Created };
|
blob.SetPaymentPrompts(prompts);
|
||||||
await SettingsRepository.UpdateSetting<Settings>(settings, SettingsKey);
|
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<InvoiceData>())
|
||||||
|
|
||||||
public async Task ResetMigration()
|
|
||||||
{
|
|
||||||
await SettingsRepository.UpdateSetting<Settings>(new Settings(), SettingsKey);
|
|
||||||
}
|
|
||||||
public async Task<bool> IsComplete()
|
|
||||||
{
|
|
||||||
return (await SettingsRepository.GetSettingAsync<Settings>(SettingsKey)) is { Complete: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
_Cts.TrySetCanceled();
|
|
||||||
return (_Migrating ?? Task.CompletedTask).ContinueWith(t =>
|
|
||||||
{
|
{
|
||||||
if (t.IsFaulted)
|
entry.State = EntityState.Modified;
|
||||||
Logs.LogError(t.Exception, "Error while migrating invoices JSON Blobs");
|
}
|
||||||
});
|
foreach (var entry in ctx.ChangeTracker.Entries<PaymentData>())
|
||||||
|
{
|
||||||
|
entry.State = EntityState.Modified;
|
||||||
|
}
|
||||||
|
return invoices[^1].Created;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<PayoutData>
|
||||||
|
{
|
||||||
|
|
||||||
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
|
|
||||||
|
public PayoutBlobMigratorHostedService(
|
||||||
|
ILogger<PayoutBlobMigratorHostedService> logs,
|
||||||
|
ISettingsRepository settingsRepository,
|
||||||
|
ApplicationDbContextFactory applicationDbContextFactory,
|
||||||
|
PaymentMethodHandlerDictionary handlers) : base(logs, settingsRepository, applicationDbContextFactory)
|
||||||
|
{
|
||||||
|
_handlers = handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string SettingsKey => "PayoutsMigration";
|
||||||
|
protected override IQueryable<PayoutData> 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<PayoutData> payouts)
|
||||||
|
{
|
||||||
|
foreach (var entry in ctx.ChangeTracker.Entries<PayoutData>())
|
||||||
|
{
|
||||||
|
entry.State = EntityState.Modified;
|
||||||
|
}
|
||||||
|
return payouts[^1].Date;
|
||||||
|
}
|
||||||
|
}
|
|
@ -217,11 +217,11 @@ namespace BTCPayServer.HostedServices
|
||||||
if (payoutQuery.PayoutMethods.Length == 1)
|
if (payoutQuery.PayoutMethods.Length == 1)
|
||||||
{
|
{
|
||||||
var pm = payoutQuery.PayoutMethods[0];
|
var pm = payoutQuery.PayoutMethods[0];
|
||||||
query = query.Where(data => pm == data.PaymentMethodId);
|
query = query.Where(data => pm == data.PayoutMethodId);
|
||||||
}
|
}
|
||||||
else
|
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;
|
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));
|
req.Completion.SetResult(new PayoutApproval.ApprovalResult(PayoutApproval.Result.NotFound, null));
|
||||||
return;
|
return;
|
||||||
|
@ -644,9 +644,10 @@ namespace BTCPayServer.HostedServices
|
||||||
Date = now,
|
Date = now,
|
||||||
State = PayoutState.AwaitingApproval,
|
State = PayoutState.AwaitingApproval,
|
||||||
PullPaymentDataId = req.ClaimRequest.PullPaymentId,
|
PullPaymentDataId = req.ClaimRequest.PullPaymentId,
|
||||||
PaymentMethodId = req.ClaimRequest.PayoutMethodId.ToString(),
|
PayoutMethodId = req.ClaimRequest.PayoutMethodId.ToString(),
|
||||||
Destination = req.ClaimRequest.Destination.Id,
|
Destination = req.ClaimRequest.Destination.Id,
|
||||||
StoreDataId = req.ClaimRequest.StoreId ?? pp?.StoreId
|
StoreDataId = req.ClaimRequest.StoreId ?? pp?.StoreId,
|
||||||
|
Currency = payoutHandler.Currency
|
||||||
};
|
};
|
||||||
var payoutBlob = new PayoutBlob()
|
var payoutBlob = new PayoutBlob()
|
||||||
{
|
{
|
||||||
|
@ -693,7 +694,7 @@ namespace BTCPayServer.HostedServices
|
||||||
StoreId = payout.StoreDataId,
|
StoreId = payout.StoreDataId,
|
||||||
Currency = ppBlob?.Currency ?? _handlers.TryGetNetwork(req.ClaimRequest.PayoutMethodId)?.NBXplorerNetwork.CryptoCode,
|
Currency = ppBlob?.Currency ?? _handlers.TryGetNetwork(req.ClaimRequest.PayoutMethodId)?.NBXplorerNetwork.CryptoCode,
|
||||||
Status = payout.State,
|
Status = payout.State,
|
||||||
PaymentMethod = payout.PaymentMethodId,
|
PaymentMethod = payout.PayoutMethodId,
|
||||||
PayoutId = payout.Id
|
PayoutId = payout.Id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -577,6 +577,9 @@ o.GetRequiredService<IEnumerable<IPaymentLinkExtension>>().ToDictionary(o => o.P
|
||||||
services.AddSingleton<InvoiceBlobMigratorHostedService>();
|
services.AddSingleton<InvoiceBlobMigratorHostedService>();
|
||||||
services.AddSingleton<IHostedService, InvoiceBlobMigratorHostedService>(o => o.GetRequiredService<InvoiceBlobMigratorHostedService>());
|
services.AddSingleton<IHostedService, InvoiceBlobMigratorHostedService>(o => o.GetRequiredService<InvoiceBlobMigratorHostedService>());
|
||||||
|
|
||||||
|
services.AddSingleton<PayoutBlobMigratorHostedService>();
|
||||||
|
services.AddSingleton<IHostedService, PayoutBlobMigratorHostedService>(o => o.GetRequiredService<PayoutBlobMigratorHostedService>());
|
||||||
|
|
||||||
// Broken
|
// Broken
|
||||||
// Providers.Add("argoneum", new ArgoneumRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_ARGONEUM")));
|
// Providers.Add("argoneum", new ArgoneumRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_ARGONEUM")));
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,7 @@ public class LightningPendingPayoutListener : BaseAsyncService
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (IGrouping<string, PayoutData> payoutByStoreByPaymentMethod in payoutByStore.GroupBy(data =>
|
foreach (IGrouping<string, PayoutData> payoutByStoreByPaymentMethod in payoutByStore.GroupBy(data =>
|
||||||
data.PaymentMethodId))
|
data.PayoutMethodId))
|
||||||
{
|
{
|
||||||
var pmi = PaymentMethodId.Parse(payoutByStoreByPaymentMethod.Key);
|
var pmi = PaymentMethodId.Parse(payoutByStoreByPaymentMethod.Key);
|
||||||
var pm = store.GetPaymentMethodConfigs(_handlers)
|
var pm = store.GetPaymentMethodConfigs(_handlers)
|
||||||
|
|
|
@ -146,6 +146,6 @@ public class LightningAutomatedPayoutProcessor : BaseAutomatedPayoutProcessor<Li
|
||||||
{
|
{
|
||||||
return (await UILightningLikePayoutController.TrypayBolt(lightningClient, payoutBlob, payoutData,
|
return (await UILightningLikePayoutController.TrypayBolt(lightningClient, payoutBlob, payoutData,
|
||||||
bolt11PaymentRequest,
|
bolt11PaymentRequest,
|
||||||
_payoutHandler.Currency, CancellationToken)).Result is PayResult.Ok ;
|
CancellationToken)).Result is PayResult.Ok ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,8 +54,7 @@ public class PayoutsReportProvider : ReportProvider
|
||||||
data.Add(payout.Date);
|
data.Add(payout.Date);
|
||||||
data.Add(payout.GetPayoutSource(_btcPayNetworkJsonSerializerSettings));
|
data.Add(payout.GetPayoutSource(_btcPayNetworkJsonSerializerSettings));
|
||||||
data.Add(payout.State.ToString());
|
data.Add(payout.State.ToString());
|
||||||
string? payoutCurrency;
|
if (PayoutMethodId.TryParse(payout.PayoutMethodId, out var pmi))
|
||||||
if (PayoutMethodId.TryParse(payout.PaymentMethodId, out var pmi))
|
|
||||||
{
|
{
|
||||||
var handler = _handlers.TryGet(pmi);
|
var handler = _handlers.TryGet(pmi);
|
||||||
if (handler is LightningLikePayoutHandler)
|
if (handler is LightningLikePayoutHandler)
|
||||||
|
@ -64,17 +63,16 @@ public class PayoutsReportProvider : ReportProvider
|
||||||
data.Add("On-Chain");
|
data.Add("On-Chain");
|
||||||
else
|
else
|
||||||
data.Add(pmi.ToString());
|
data.Add(pmi.ToString());
|
||||||
payoutCurrency = handler?.Currency;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var ppBlob = payout.PullPaymentData?.GetBlob();
|
var ppBlob = payout.PullPaymentData?.GetBlob();
|
||||||
var currency = ppBlob?.Currency ?? payoutCurrency;
|
var currency = ppBlob?.Currency ?? payout.Currency;
|
||||||
if (currency is null)
|
if (currency is null)
|
||||||
continue;
|
continue;
|
||||||
data.Add(payoutCurrency);
|
data.Add(payout.Currency);
|
||||||
data.Add(blob.CryptoAmount.HasValue && payoutCurrency is not null ? _displayFormatter.ToFormattedAmount(blob.CryptoAmount.Value, payoutCurrency) : null);
|
data.Add(blob.CryptoAmount is decimal v ? _displayFormatter.ToFormattedAmount(v, payout.Currency) : null);
|
||||||
data.Add(currency);
|
data.Add(currency);
|
||||||
data.Add(_displayFormatter.ToFormattedAmount(blob.Amount, currency));
|
data.Add(_displayFormatter.ToFormattedAmount(blob.Amount, currency));
|
||||||
data.Add(blob.Destination);
|
data.Add(blob.Destination);
|
||||||
|
|
|
@ -67,7 +67,7 @@ namespace BTCPayServer.Services.Reporting
|
||||||
var conn = ctx.Database.GetDbConnection();
|
var conn = ctx.Database.GetDbConnection();
|
||||||
var rows = await conn.QueryAsync(
|
var rows = await conn.QueryAsync(
|
||||||
"""
|
"""
|
||||||
SELECT i."Created", i."Id" AS "InvoiceId", p."State", p."PaymentMethodId", pp."Id" AS "PullPaymentId", pp."Blob" AS "ppBlob", p."Blob" AS "pBlob" FROM "Invoices" i
|
SELECT i."Created", i."Id" AS "InvoiceId", p."State", p."PayoutMethodId", p."Currency" AS "PayoutCurrency", pp."Id" AS "PullPaymentId", pp."Blob" AS "ppBlob", p."Blob" AS "pBlob" FROM "Invoices" i
|
||||||
JOIN "Refunds" r ON r."InvoiceDataId"= i."Id"
|
JOIN "Refunds" r ON r."InvoiceDataId"= i."Id"
|
||||||
JOIN "PullPayments" pp ON r."PullPaymentDataId"=pp."Id"
|
JOIN "PullPayments" pp ON r."PullPaymentDataId"=pp."Id"
|
||||||
LEFT JOIN "Payouts" p ON p."PullPaymentDataId"=pp."Id"
|
LEFT JOIN "Payouts" p ON p."PullPaymentDataId"=pp."Id"
|
||||||
|
@ -104,7 +104,8 @@ namespace BTCPayServer.Services.Reporting
|
||||||
if (r.pBlob is null)
|
if (r.pBlob is null)
|
||||||
return null;
|
return null;
|
||||||
Data.PayoutData p = new Data.PayoutData();
|
Data.PayoutData p = new Data.PayoutData();
|
||||||
p.PaymentMethodId = r.PaymentMethodId;
|
p.PayoutMethodId = r.PayoutMethodId;
|
||||||
|
p.Currency = (string)r.PayoutCurrency;
|
||||||
p.Blob = (string)r.pBlob;
|
p.Blob = (string)r.pBlob;
|
||||||
return p.GetBlob(_serializerSettings);
|
return p.GetBlob(_serializerSettings);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue