Refactor the InvoiceAddresses table (#6232)

This commit is contained in:
Nicolas Dorier
2024-09-19 22:15:02 +09:00
committed by GitHub
parent df651a2157
commit ba2301ebfe
20 changed files with 135 additions and 126 deletions

View File

@@ -133,7 +133,7 @@ namespace BTCPayServer
public string GetTrackedDestination(Script scriptPubKey) public string GetTrackedDestination(Script scriptPubKey)
{ {
return scriptPubKey.Hash.ToString() + "#" + CryptoCode.ToUpperInvariant(); return scriptPubKey.Hash.ToString();
} }
} }

View File

@@ -84,7 +84,6 @@ namespace BTCPayServer.Data
PaymentRequestData.OnModelCreating(builder, Database); PaymentRequestData.OnModelCreating(builder, Database);
PaymentData.OnModelCreating(builder, Database); PaymentData.OnModelCreating(builder, Database);
PayoutData.OnModelCreating(builder, Database); PayoutData.OnModelCreating(builder, Database);
PendingInvoiceData.OnModelCreating(builder);
//PlannedTransaction.OnModelCreating(builder); //PlannedTransaction.OnModelCreating(builder);
PullPaymentData.OnModelCreating(builder, Database); PullPaymentData.OnModelCreating(builder, Database);
RefundData.OnModelCreating(builder); RefundData.OnModelCreating(builder);

View File

@@ -9,6 +9,7 @@ namespace BTCPayServer.Data
public string Address { get; set; } public string Address { get; set; }
public InvoiceData InvoiceData { get; set; } public InvoiceData InvoiceData { get; set; }
public string InvoiceDataId { get; set; } public string InvoiceDataId { get; set; }
public string PaymentMethodId { get; set; }
internal static void OnModelCreating(ModelBuilder builder) internal static void OnModelCreating(ModelBuilder builder)
@@ -18,7 +19,7 @@ namespace BTCPayServer.Data
.WithMany(i => i.AddressInvoices).OnDelete(DeleteBehavior.Cascade); .WithMany(i => i.AddressInvoices).OnDelete(DeleteBehavior.Cascade);
builder.Entity<AddressInvoiceData>() builder.Entity<AddressInvoiceData>()
#pragma warning disable CS0618 #pragma warning disable CS0618
.HasKey(o => o.Address); .HasKey(o => new { o.PaymentMethodId, o.Address });
#pragma warning restore CS0618 #pragma warning restore CS0618
} }
} }

View File

@@ -26,7 +26,6 @@ namespace BTCPayServer.Data
public string ExceptionStatus { get; set; } public string ExceptionStatus { get; set; }
public List<AddressInvoiceData> AddressInvoices { get; set; } public List<AddressInvoiceData> AddressInvoices { get; set; }
public bool Archived { get; set; } public bool Archived { get; set; }
public List<PendingInvoiceData> PendingInvoices { get; set; }
public List<InvoiceSearchData> InvoiceSearchData { get; set; } public List<InvoiceSearchData> InvoiceSearchData { get; set; }
public List<RefundData> Refunds { get; set; } public List<RefundData> Refunds { get; set; }

View File

@@ -1,18 +0,0 @@
using Microsoft.EntityFrameworkCore;
namespace BTCPayServer.Data
{
public class PendingInvoiceData
{
public string Id { get; set; }
public InvoiceData InvoiceData { get; set; }
internal static void OnModelCreating(ModelBuilder builder)
{
builder.Entity<PendingInvoiceData>()
.HasOne(o => o.InvoiceData)
.WithMany(o => o.PendingInvoices)
.HasForeignKey(o => o.Id).OnDelete(DeleteBehavior.Cascade);
}
}
}

View File

@@ -0,0 +1,80 @@
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20240919085726_refactorinvoiceaddress")]
public partial class refactorinvoiceaddress : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropPrimaryKey(
name: "PK_AddressInvoices",
table: "AddressInvoices");
migrationBuilder.AddColumn<string>(
name: "PaymentMethodId",
table: "AddressInvoices",
type: "text",
nullable: false,
defaultValue: "");
migrationBuilder.Sql("""
UPDATE "AddressInvoices"
SET
"Address" = (string_to_array("Address", '#'))[1],
"PaymentMethodId" = CASE WHEN (string_to_array("Address", '#'))[2] IS NULL THEN 'BTC-CHAIN'
WHEN STRPOS((string_to_array("Address", '#'))[2], '_') = 0 THEN (string_to_array("Address", '#'))[2] || '-CHAIN'
WHEN STRPOS((string_to_array("Address", '#'))[2], '_MoneroLike') > 0 THEN replace((string_to_array("Address", '#'))[2],'_MoneroLike','-CHAIN')
WHEN STRPOS((string_to_array("Address", '#'))[2], '_ZcashLike') > 0 THEN replace((string_to_array("Address", '#'))[2],'_ZcashLike','-CHAIN')
ELSE '' END;
DELETE FROM "AddressInvoices" WHERE "PaymentMethodId" = '';
""");
migrationBuilder.AddPrimaryKey(
name: "PK_AddressInvoices",
table: "AddressInvoices",
columns: new[] { "PaymentMethodId", "Address" });
migrationBuilder.Sql("VACUUM (ANALYZE) \"AddressInvoices\";", true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropPrimaryKey(
name: "PK_AddressInvoices",
table: "AddressInvoices");
migrationBuilder.DropColumn(
name: "PaymentMethodId",
table: "AddressInvoices");
migrationBuilder.AddPrimaryKey(
name: "PK_AddressInvoices",
table: "AddressInvoices",
column: "Address");
migrationBuilder.CreateTable(
name: "PendingInvoices",
columns: table => new
{
Id = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PendingInvoices", x => x.Id);
table.ForeignKey(
name: "FK_PendingInvoices_Invoices_Id",
column: x => x.Id,
principalTable: "Invoices",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
}
}
}

View File

@@ -60,13 +60,16 @@ namespace BTCPayServer.Migrations
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b => modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
{ {
b.Property<string>("PaymentMethodId")
.HasColumnType("text");
b.Property<string>("Address") b.Property<string>("Address")
.HasColumnType("text"); .HasColumnType("text");
b.Property<string>("InvoiceDataId") b.Property<string>("InvoiceDataId")
.HasColumnType("text"); .HasColumnType("text");
b.HasKey("Address"); b.HasKey("PaymentMethodId", "Address");
b.HasIndex("InvoiceDataId"); b.HasIndex("InvoiceDataId");
@@ -634,16 +637,6 @@ namespace BTCPayServer.Migrations
b.ToTable("PayoutProcessors"); b.ToTable("PayoutProcessors");
}); });
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("PendingInvoices");
});
modelBuilder.Entity("BTCPayServer.Data.PlannedTransaction", b => modelBuilder.Entity("BTCPayServer.Data.PlannedTransaction", b =>
{ {
b.Property<string>("Id") b.Property<string>("Id")
@@ -1331,17 +1324,6 @@ namespace BTCPayServer.Migrations
b.Navigation("Store"); b.Navigation("Store");
}); });
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
.WithMany("PendingInvoices")
.HasForeignKey("Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("InvoiceData");
});
modelBuilder.Entity("BTCPayServer.Data.PullPaymentData", b => modelBuilder.Entity("BTCPayServer.Data.PullPaymentData", b =>
{ {
b.HasOne("BTCPayServer.Data.StoreData", "StoreData") b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
@@ -1572,8 +1554,6 @@ namespace BTCPayServer.Migrations
b.Navigation("Payments"); b.Navigation("Payments");
b.Navigation("PendingInvoices");
b.Navigation("Refunds"); b.Navigation("Refunds");
}); });

View File

@@ -18,6 +18,26 @@ namespace BTCPayServer.Tests
{ {
} }
[Fact]
public async Task CanMigrateInvoiceAddresses()
{
var tester = CreateDBTester();
await tester.MigrateUntil("20240919085726_refactorinvoiceaddress");
using var ctx = tester.CreateContext();
var conn = ctx.Database.GetDbConnection();
await conn.ExecuteAsync("INSERT INTO \"Invoices\" (\"Id\", \"Created\") VALUES ('i', NOW())");
await conn.ExecuteAsync(
"INSERT INTO \"AddressInvoices\" VALUES ('aaa#BTC', 'i'),('bbb','i'),('ccc#BTC_LNU', 'i'),('ddd#XMR_MoneroLike', 'i'),('eee#ZEC_ZcashLike', 'i')");
await tester.ContinueMigration();
foreach (var v in new[] { ("aaa", "BTC-CHAIN"), ("bbb", "BTC-CHAIN"), ("ddd", "XMR-CHAIN") , ("eee", "ZEC-CHAIN") })
{
var ok = await conn.ExecuteScalarAsync<bool>("SELECT 't'::BOOLEAN FROM \"AddressInvoices\" WHERE \"Address\"=@a AND \"PaymentMethodId\"=@b", new { a = v.Item1, b = v.Item2 });
Assert.True(ok);
}
var notok = await conn.ExecuteScalarAsync<bool>("SELECT 't'::BOOLEAN FROM \"AddressInvoices\" WHERE \"Address\"='ccc'");
Assert.False(notok);
}
[Fact] [Fact]
public async Task CanMigratePayoutsAndPullPayments() public async Task CanMigratePayoutsAndPullPayments()
{ {

View File

@@ -16,7 +16,7 @@ namespace BTCPayServer.Tests
public static class TestUtils public static class TestUtils
{ {
#if DEBUG && !SHORT_TIMEOUT #if DEBUG && !SHORT_TIMEOUT
public const int TestTimeout = 600_000; public const int TestTimeout = 60_000;
#else #else
public const int TestTimeout = 90_000; public const int TestTimeout = 90_000;
#endif #endif

View File

@@ -2449,9 +2449,10 @@ namespace BTCPayServer.Tests
private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx) private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx)
{ {
var h = BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest).ScriptPubKey.Hash.ToString(); var h = BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest).ScriptPubKey.Hash.ToString();
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
return (ctx.AddressInvoices.Where(i => i.InvoiceDataId == invoice.Id).ToArrayAsync().GetAwaiter() return (ctx.AddressInvoices.Where(i => i.InvoiceDataId == invoice.Id).ToArrayAsync().GetAwaiter()
.GetResult()) .GetResult())
.Where(i => i.GetAddress() == h).Any(); .Where(i => i.Address == h && i.PaymentMethodId == pmi.ToString()).Any();
} }

View File

@@ -649,9 +649,7 @@ namespace BTCPayServer.Controllers
if (derivationScheme is null) if (derivationScheme is null)
return NotSupported("This feature is only available to BTC wallets"); return NotSupported("This feature is only available to BTC wallets");
var btc = PaymentTypes.CHAIN.GetPaymentMethodId("BTC"); var btc = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
var bumpableAddresses = (await GetAddresses(selectedItems)) var bumpableAddresses = await GetAddresses(btc, selectedItems);
.Where(p => p.GetPaymentMethodId() == btc)
.Select(p => p.GetAddress()).ToHashSet();
var utxos = await explorer.GetUTXOsAsync(derivationScheme); var utxos = await explorer.GetUTXOsAsync(derivationScheme);
var bumpableUTXOs = utxos.GetUnspentUTXOs().Where(u => u.Confirmations == 0 && bumpableAddresses.Contains(u.ScriptPubKey.Hash.ToString())).ToArray(); var bumpableUTXOs = utxos.GetUnspentUTXOs().Where(u => u.Confirmations == 0 && bumpableAddresses.Contains(u.ScriptPubKey.Hash.ToString())).ToArray();
var parameters = new MultiValueDictionary<string, string>(); var parameters = new MultiValueDictionary<string, string>();
@@ -673,10 +671,10 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(ListInvoices), new { storeId }); return RedirectToAction(nameof(ListInvoices), new { storeId });
} }
private async Task<AddressInvoiceData[]> GetAddresses(string[] selectedItems) private async Task<HashSet<string>> GetAddresses(PaymentMethodId paymentMethodId, string[] selectedItems)
{ {
using var ctx = _dbContextFactory.CreateContext(); using var ctx = _dbContextFactory.CreateContext();
return await ctx.AddressInvoices.Where(i => selectedItems.Contains(i.InvoiceDataId)).ToArrayAsync(); return new HashSet<string>(await ctx.AddressInvoices.Where(i => i.PaymentMethodId == paymentMethodId.ToString() && selectedItems.Contains(i.InvoiceDataId)).Select(i => i.Address).ToArrayAsync());
} }
[HttpGet("i/{invoiceId}")] [HttpGet("i/{invoiceId}")]

View File

@@ -1,31 +0,0 @@
using System;
using BTCPayServer.Payments;
namespace BTCPayServer.Data
{
public static class AddressInvoiceDataExtensions
{
#pragma warning disable CS0618
public static string GetAddress(this AddressInvoiceData addressInvoiceData)
{
if (addressInvoiceData.Address == null)
return null;
var index = addressInvoiceData.Address.LastIndexOf("#", StringComparison.InvariantCulture);
if (index == -1)
return addressInvoiceData.Address;
return addressInvoiceData.Address.Substring(0, index);
}
public static PaymentMethodId GetPaymentMethodId(this AddressInvoiceData addressInvoiceData)
{
if (addressInvoiceData.Address == null)
return null;
var index = addressInvoiceData.Address.LastIndexOf("#", StringComparison.InvariantCulture);
// Legacy AddressInvoiceData does not have the paymentMethodId attached to the Address
if (index == -1)
return PaymentMethodId.Parse("BTC");
/////////////////////////
return PaymentMethodId.TryParse(addressInvoiceData.Address.Substring(index + 1));
}
#pragma warning restore CS0618
}
}

View File

@@ -73,7 +73,7 @@ namespace BTCPayServer.Data
entity.Status = state.Status; entity.Status = state.Status;
if (invoiceData.AddressInvoices != null) if (invoiceData.AddressInvoices != null)
{ {
entity.AvailableAddressHashes = invoiceData.AddressInvoices.Select(a => a.GetAddress() + a.GetPaymentMethodId()).ToHashSet(); entity.Addresses = invoiceData.AddressInvoices.Select(a => (PaymentMethodId.Parse(a.PaymentMethodId), a.Address)).ToHashSet();
} }
if (invoiceData.Refunds != null) if (invoiceData.Refunds != null)
{ {

View File

@@ -144,6 +144,7 @@ namespace BTCPayServer.Payments.Bitcoin
Logs.PayServer.LogInformation($"{network.CryptoCode}: {paymentCount} payments happened while offline"); Logs.PayServer.LogInformation($"{network.CryptoCode}: {paymentCount} payments happened while offline");
Logs.PayServer.LogInformation($"Connected to WebSocket of NBXplorer ({network.CryptoCode})"); Logs.PayServer.LogInformation($"Connected to WebSocket of NBXplorer ({network.CryptoCode})");
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode);
while (!_Cts.IsCancellationRequested) while (!_Cts.IsCancellationRequested)
{ {
var newEvent = await session.NextEventAsync(_Cts.Token).ConfigureAwait(false); var newEvent = await session.NextEventAsync(_Cts.Token).ConfigureAwait(false);
@@ -163,13 +164,11 @@ namespace BTCPayServer.Payments.Bitcoin
foreach (var output in validOutputs) foreach (var output in validOutputs)
{ {
var key = network.GetTrackedDestination(output.Item1.ScriptPubKey); var key = network.GetTrackedDestination(output.Item1.ScriptPubKey);
var invoice = (await _InvoiceRepository.GetInvoicesFromAddresses(new[] { key })) var invoice = await _InvoiceRepository.GetInvoiceFromAddress(pmi, key);
.FirstOrDefault();
if (invoice != null) if (invoice != null)
{ {
var address = output.matchedOutput.Address ?? network.NBXplorerNetwork.CreateAddress(evt.DerivationStrategy, var address = output.matchedOutput.Address ?? network.NBXplorerNetwork.CreateAddress(evt.DerivationStrategy,
output.Item1.KeyPath, output.Item1.ScriptPubKey); output.Item1.KeyPath, output.Item1.ScriptPubKey);
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode);
var handler = _handlers[pmi]; var handler = _handlers[pmi];
var details = new BitcoinLikePaymentData(output.outPoint, evt.TransactionData.Transaction.RBF, output.matchedOutput.KeyPath); var details = new BitcoinLikePaymentData(output.outPoint, evt.TransactionData.Transaction.RBF, output.matchedOutput.KeyPath);
@@ -198,7 +197,6 @@ namespace BTCPayServer.Payments.Bitcoin
await UpdatePaymentStates(wallet, invoice.Id); await UpdatePaymentStates(wallet, invoice.Id);
} }
} }
} }
} }
@@ -406,8 +404,7 @@ namespace BTCPayServer.Payments.Bitcoin
coins = await wallet.GetUnspentCoins(strategy); coins = await wallet.GetUnspentCoins(strategy);
coinsPerDerivationStrategy.Add(strategy, coins); coinsPerDerivationStrategy.Add(strategy, coins);
} }
coins = coins.Where(c => invoice.AvailableAddressHashes.Contains(c.ScriptPubKey.Hash.ToString() + cryptoId)) coins = coins.Where(c => invoice.Addresses.Contains((cryptoId, network.GetTrackedDestination(c.ScriptPubKey)))).ToArray();
.ToArray();
foreach (var coin in coins.Where(c => !alreadyAccounted.Contains(c.OutPoint))) foreach (var coin in coins.Where(c => !alreadyAccounted.Contains(c.OutPoint)))
{ {
var transaction = await wallet.GetTransactionAsync(coin.OutPoint.Hash); var transaction = await wallet.GetTransactionAsync(coin.OutPoint.Hash);

View File

@@ -280,7 +280,7 @@ namespace BTCPayServer.Payments
/// <summary> /// <summary>
/// This string can be used to query AddressInvoice to find the invoiceId /// This string can be used to query AddressInvoice to find the invoiceId
/// </summary> /// </summary>
public List<string> TrackedDestinations { get; } = new List<string>(); public List<string> TrackedDestinations { get; } = new();
internal async Task BeforeFetchingRates() internal async Task BeforeFetchingRates()
{ {

View File

@@ -265,8 +265,8 @@ namespace BTCPayServer.Payments.PayJoin
if (walletReceiveMatch is null) if (walletReceiveMatch is null)
{ {
var key = output.ScriptPubKey.Hash + "#" + network.CryptoCode.ToUpperInvariant(); var key = network.GetTrackedDestination(output.ScriptPubKey);
invoice = (await _invoiceRepository.GetInvoicesFromAddresses(new[] { key })).FirstOrDefault(); invoice = await _invoiceRepository.GetInvoiceFromAddress(paymentMethodId, key);
if (invoice is null) if (invoice is null)
continue; continue;
accountDerivation = _handlers.GetDerivationStrategy(invoice, network); accountDerivation = _handlers.GetDerivationStrategy(invoice, network);

View File

@@ -284,12 +284,9 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services
foreach (var destination in transfer.Transfers.GroupBy(destination => destination.Address)) foreach (var destination in transfer.Transfers.GroupBy(destination => destination.Address))
{ {
//find the invoice corresponding to this address, else skip //find the invoice corresponding to this address, else skip
var address = destination.Key + "#" + paymentMethodId; var invoice = await _invoiceRepository.GetInvoiceFromAddress(paymentMethodId, destination.Key);
var invoice = (await _invoiceRepository.GetInvoicesFromAddresses(new[] { address })).FirstOrDefault();
if (invoice == null) if (invoice == null)
{
continue; continue;
}
var index = destination.First().SubaddrIndex; var index = destination.First().SubaddrIndex;

View File

@@ -282,12 +282,9 @@ namespace BTCPayServer.Services.Altcoins.Zcash.Services
foreach (var destination in transfer.Transfers.GroupBy(destination => destination.Address)) foreach (var destination in transfer.Transfers.GroupBy(destination => destination.Address))
{ {
//find the invoice corresponding to this address, else skip //find the invoice corresponding to this address, else skip
var address = destination.Key + "#" + paymentMethodId; var invoice = await _invoiceRepository.GetInvoiceFromAddress(paymentMethodId, destination.Key);
var invoice = (await _invoiceRepository.GetInvoicesFromAddresses(new[] { address })).FirstOrDefault();
if (invoice == null) if (invoice == null)
{
continue; continue;
}
var index = destination.First().SubaddrIndex; var index = destination.First().SubaddrIndex;

View File

@@ -495,7 +495,7 @@ namespace BTCPayServer.Services.Invoices
public DateTimeOffset MonitoringExpiration { get; set; } public DateTimeOffset MonitoringExpiration { get; set; }
[JsonIgnore] [JsonIgnore]
public HashSet<string> AvailableAddressHashes { get; set; } public HashSet<(PaymentMethodId PaymentMethodId, string Address)> Addresses { get; set; }
[JsonProperty] [JsonProperty]
public bool ExtendedNotifications { get; set; } public bool ExtendedNotifications { get; set; }

View File

@@ -67,29 +67,16 @@ namespace BTCPayServer.Services.Invoices
}; };
} }
public async Task<IEnumerable<InvoiceEntity>> GetInvoicesFromAddresses(string[] addresses) public async Task<InvoiceEntity> GetInvoiceFromAddress(PaymentMethodId paymentMethodId, string address)
{ {
if (addresses.Length is 0)
return Array.Empty<InvoiceEntity>();
using var db = _applicationDbContextFactory.CreateContext(); using var db = _applicationDbContextFactory.CreateContext();
if (addresses.Length == 1) var row = (await db.AddressInvoices
{ .Include(a => a.InvoiceData.Payments)
var address = addresses[0]; .Where(a => a.PaymentMethodId == paymentMethodId.ToString() && a.Address == address)
return (await db.AddressInvoices .Select(a => a.InvoiceData)
.Include(a => a.InvoiceData.Payments) .FirstOrDefaultAsync());
.Where(a => a.Address == address) return row is null ? null : ToEntity(row);
.Select(a => a.InvoiceData) }
.ToListAsync()).Select(ToEntity);
}
else
{
return (await db.AddressInvoices
.Include(a => a.InvoiceData.Payments)
.Where(a => addresses.Contains(a.Address))
.Select(a => a.InvoiceData)
.ToListAsync()).Select(ToEntity);
}
}
public async Task<InvoiceEntity[]> GetInvoicesWithPendingPayments(PaymentMethodId paymentMethodId, bool includeAddresses = false) public async Task<InvoiceEntity[]> GetInvoicesWithPendingPayments(PaymentMethodId paymentMethodId, bool includeAddresses = false)
{ {
@@ -190,7 +177,8 @@ retry:
await context.AddressInvoices.AddAsync(new AddressInvoiceData() await context.AddressInvoices.AddAsync(new AddressInvoiceData()
{ {
InvoiceDataId = invoice.Id, InvoiceDataId = invoice.Id,
Address = trackedDestination Address = trackedDestination,
PaymentMethodId = ctx.Key.ToString()
}); });
} }
} }
@@ -311,7 +299,8 @@ retry:
await context.AddressInvoices.AddAsync(new AddressInvoiceData() await context.AddressInvoices.AddAsync(new AddressInvoiceData()
{ {
InvoiceDataId = invoiceId, InvoiceDataId = invoiceId,
Address = tracked Address = tracked,
PaymentMethodId = paymentPromptContext.PaymentMethodId.ToString()
}); });
} }
AddToTextSearch(context, invoice, prompt.Destination); AddToTextSearch(context, invoice, prompt.Destination);