mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-13 11:35:51 +01:00
Payments should use composite key (#6240)
* Payments should use composite key * Invert PK for InvoiceAddress
This commit is contained in:
parent
36a5d0ee3f
commit
3cf1aa00fa
16 changed files with 98 additions and 36 deletions
|
@ -82,7 +82,7 @@ namespace BTCPayServer.Data
|
|||
PairingCodeData.OnModelCreating(builder);
|
||||
//PayjoinLock.OnModelCreating(builder);
|
||||
PaymentRequestData.OnModelCreating(builder, Database);
|
||||
PaymentData.OnModelCreating(builder, Database);
|
||||
PaymentData.OnModelCreating(builder);
|
||||
PayoutData.OnModelCreating(builder, Database);
|
||||
//PlannedTransaction.OnModelCreating(builder);
|
||||
PullPaymentData.OnModelCreating(builder, Database);
|
||||
|
|
|
@ -22,5 +22,6 @@
|
|||
<None Remove="DBScripts\002.RefactorPayouts.sql" />
|
||||
<None Remove="DBScripts\003.RefactorPendingInvoicesPayments.sql" />
|
||||
<None Remove="DBScripts\004.MonitoredInvoices.sql" />
|
||||
<None Remove="DBScripts\005.PaymentsRenaming.sql" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
17
BTCPayServer.Data/DBScripts/005.PaymentsRenaming.sql
Normal file
17
BTCPayServer.Data/DBScripts/005.PaymentsRenaming.sql
Normal file
|
@ -0,0 +1,17 @@
|
|||
DROP FUNCTION get_monitored_invoices;
|
||||
CREATE OR REPLACE FUNCTION get_monitored_invoices(payment_method_id TEXT)
|
||||
RETURNS TABLE (invoice_id TEXT, payment_id TEXT, payment_method_id TEXT) AS $$
|
||||
WITH cte AS (
|
||||
-- Get all the invoices which are pending. Even if no payments.
|
||||
SELECT i."Id" invoice_id, p."Id" payment_id, p."PaymentMethodId" payment_method_id FROM "Invoices" i LEFT JOIN "Payments" p ON i."Id" = p."InvoiceDataId"
|
||||
WHERE is_pending(i."Status")
|
||||
UNION ALL
|
||||
-- For invoices not pending, take all of those which have pending payments
|
||||
SELECT i."Id", p."Id", p."PaymentMethodId" payment_method_id FROM "Invoices" i INNER JOIN "Payments" p ON i."Id" = p."InvoiceDataId"
|
||||
WHERE is_pending(p."Status") AND NOT is_pending(i."Status"))
|
||||
SELECT cte.* FROM cte
|
||||
LEFT JOIN "Payments" p ON cte.payment_id=p."Id" AND cte.payment_id=p."PaymentMethodId"
|
||||
LEFT JOIN "Invoices" i ON cte.invoice_id=i."Id"
|
||||
WHERE (p."PaymentMethodId" IS NOT NULL AND p."PaymentMethodId" = payment_method_id) OR
|
||||
(p."PaymentMethodId" IS NULL AND get_prompt(i."Blob2", payment_method_id) IS NOT NULL AND (get_prompt(i."Blob2", payment_method_id)->'activated')::BOOLEAN IS NOT FALSE);
|
||||
$$ LANGUAGE SQL STABLE;
|
|
@ -19,7 +19,7 @@ namespace BTCPayServer.Data
|
|||
.WithMany(i => i.AddressInvoices).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.Entity<AddressInvoiceData>()
|
||||
#pragma warning disable CS0618
|
||||
.HasKey(o => new { o.PaymentMethodId, o.Address });
|
||||
.HasKey(o => new { o.Address, o.PaymentMethodId });
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,9 +44,9 @@ namespace BTCPayServer.Data
|
|||
}
|
||||
|
||||
var cryptoCode = blob["cryptoCode"].Value<string>();
|
||||
Type = cryptoCode + "_" + blob["cryptoPaymentDataType"].Value<string>();
|
||||
Type = MigrationExtensions.MigratePaymentMethodId(Type);
|
||||
var divisibility = MigrationExtensions.GetDivisibility(Type);
|
||||
PaymentMethodId = cryptoCode + "_" + blob["cryptoPaymentDataType"].Value<string>();
|
||||
PaymentMethodId = MigrationExtensions.MigratePaymentMethodId(PaymentMethodId);
|
||||
var divisibility = MigrationExtensions.GetDivisibility(PaymentMethodId);
|
||||
Currency = blob["cryptoCode"].Value<string>();
|
||||
blob.Remove("cryptoCode");
|
||||
blob.Remove("cryptoPaymentDataType");
|
||||
|
|
|
@ -27,13 +27,15 @@ namespace BTCPayServer.Data
|
|||
[Obsolete("Use Blob2 instead")]
|
||||
public byte[] Blob { get; set; }
|
||||
public string Blob2 { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string PaymentMethodId { get; set; }
|
||||
[Obsolete("Use Status instead")]
|
||||
public bool? Accounted { get; set; }
|
||||
public PaymentStatus? Status { get; set; }
|
||||
public static bool IsPending(PaymentStatus? status) => throw new NotSupportedException();
|
||||
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
|
||||
internal static void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
builder.Entity<PaymentData>()
|
||||
.HasKey(o => new { o.Id, o.PaymentMethodId });
|
||||
builder.Entity<PaymentData>()
|
||||
.HasOne(o => o.InvoiceData)
|
||||
.WithMany(i => i.Payments).OnDelete(DeleteBehavior.Cascade);
|
||||
|
|
|
@ -33,13 +33,13 @@ namespace BTCPayServer.Migrations
|
|||
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;
|
||||
|
||||
ALTER TABLE "AddressInvoices" DROP COLUMN IF EXISTS "CreatedTime";
|
||||
DELETE FROM "AddressInvoices" WHERE "PaymentMethodId" = '';
|
||||
""");
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_AddressInvoices",
|
||||
table: "AddressInvoices",
|
||||
columns: new[] { "PaymentMethodId", "Address" });
|
||||
columns: new[] { "Address", "PaymentMethodId" });
|
||||
migrationBuilder.Sql("VACUUM (ANALYZE) \"AddressInvoices\";", true);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20240923065254_refactorpayments")]
|
||||
[DBScript("005.PaymentsRenaming.sql")]
|
||||
public partial class refactorpayments : DBScriptsMigration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_Payments",
|
||||
table: "Payments");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Type",
|
||||
table: "Payments",
|
||||
newName: "PaymentMethodId");
|
||||
migrationBuilder.Sql("UPDATE \"Payments\" SET \"PaymentMethodId\"='' WHERE \"PaymentMethodId\" IS NULL;");
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_Payments",
|
||||
table: "Payments",
|
||||
columns: new[] { "Id", "PaymentMethodId" });
|
||||
base.Up(migrationBuilder);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
|
@ -9,17 +7,20 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
|||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20240405052858_cleanup_address_invoices")]
|
||||
public partial class cleanup_address_invoices : Migration
|
||||
[Migration("20240923071444_temprefactor2")]
|
||||
public partial class temprefactor2 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(@"
|
||||
DELETE FROM ""AddressInvoices"" WHERE ""Address"" LIKE '%_LightningLike';
|
||||
ALTER TABLE ""AddressInvoices"" DROP COLUMN IF EXISTS ""CreatedTime"";
|
||||
");
|
||||
migrationBuilder.Sql(@"VACUUM (FULL, ANALYZE) ""AddressInvoices"";", true);
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_AddressInvoices",
|
||||
table: "AddressInvoices");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_AddressInvoices",
|
||||
table: "AddressInvoices",
|
||||
columns: new[] { "Address", "PaymentMethodId" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
|
@ -60,16 +60,16 @@ namespace BTCPayServer.Migrations
|
|||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("PaymentMethodId")
|
||||
b.Property<string>("Address")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Address")
|
||||
b.Property<string>("PaymentMethodId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("InvoiceDataId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("PaymentMethodId", "Address");
|
||||
b.HasKey("Address", "PaymentMethodId");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
|
@ -482,6 +482,9 @@ namespace BTCPayServer.Migrations
|
|||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PaymentMethodId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool?>("Accounted")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
|
@ -506,10 +509,7 @@ namespace BTCPayServer.Migrations
|
|||
b.Property<string>("Status")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
b.HasKey("Id", "PaymentMethodId");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
|
|
|
@ -698,11 +698,13 @@ retry:
|
|||
await writer.FlushAsync();
|
||||
}
|
||||
isHeader = true;
|
||||
using (var writer = db.BeginTextImport("COPY \"Payments\" (\"Id\",\"Blob\",\"InvoiceDataId\",\"Accounted\",\"Blob2\",\"Type\") FROM STDIN DELIMITER ',' CSV HEADER"))
|
||||
using (var writer = db.BeginTextImport("COPY \"Payments\" (\"Id\",\"Blob\",\"InvoiceDataId\",\"Accounted\",\"Blob2\",\"PaymentMethodId\") FROM STDIN DELIMITER ',' CSV HEADER"))
|
||||
{
|
||||
foreach (var invoice in oldPayments)
|
||||
{
|
||||
var localPayment = invoice.Replace("3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd", storeId);
|
||||
// Old data could have Type to null.
|
||||
localPayment += "BTC-CHAIN";
|
||||
await writer.WriteLineAsync(localPayment);
|
||||
}
|
||||
await writer.FlushAsync();
|
||||
|
|
|
@ -604,7 +604,7 @@
|
|||
},
|
||||
"expectedProperties": {
|
||||
"Created": "04/23/2019 18:27:56 +00:00",
|
||||
"Type": "BTC-CHAIN",
|
||||
"PaymentMethodId": "BTC-CHAIN",
|
||||
"Currency": "BTC",
|
||||
"Status": "Settled",
|
||||
"Amount": "0.07299962",
|
||||
|
@ -634,7 +634,7 @@
|
|||
},
|
||||
"expectedProperties": {
|
||||
"Created": "10/01/2018 14:13:22 +00:00",
|
||||
"Type": "BTC-CHAIN",
|
||||
"PaymentMethodId": "BTC-CHAIN",
|
||||
"Currency": "BTC",
|
||||
"Status": "Settled",
|
||||
"Amount": "0.00017863",
|
||||
|
@ -666,7 +666,7 @@
|
|||
"Created": "03/21/2024 07:24:35 +00:00",
|
||||
"CreatedInMs": "1711005875969",
|
||||
"Amount": "0.00000001",
|
||||
"Type": "BTC-LNURL",
|
||||
"PaymentMethodId": "BTC-LNURL",
|
||||
"Currency": "BTC"
|
||||
}
|
||||
},
|
||||
|
@ -697,7 +697,7 @@
|
|||
"Created": "03/20/2024 22:39:08 +00:00",
|
||||
"CreatedInMs": "1710974348741",
|
||||
"Amount": "0.00197864",
|
||||
"Type": "BTC-CHAIN",
|
||||
"PaymentMethodId": "BTC-CHAIN",
|
||||
"Currency": "BTC",
|
||||
"Status": "Settled",
|
||||
"Accounted": null
|
||||
|
|
|
@ -674,7 +674,7 @@ namespace BTCPayServer.Controllers
|
|||
private async Task<HashSet<string>> GetAddresses(PaymentMethodId paymentMethodId, string[] selectedItems)
|
||||
{
|
||||
using var ctx = _dbContextFactory.CreateContext();
|
||||
return new HashSet<string>(await ctx.AddressInvoices.Where(i => i.PaymentMethodId == paymentMethodId.ToString() && selectedItems.Contains(i.InvoiceDataId)).Select(i => i.Address).ToArrayAsync());
|
||||
return new HashSet<string>(await ctx.AddressInvoices.Where(i => selectedItems.Contains(i.InvoiceDataId) && i.PaymentMethodId == paymentMethodId.ToString()).Select(i => i.Address).ToArrayAsync());
|
||||
}
|
||||
|
||||
[HttpGet("i/{invoiceId}")]
|
||||
|
|
|
@ -34,13 +34,13 @@ namespace BTCPayServer.Data
|
|||
}
|
||||
public static PaymentData SetBlob(this PaymentData paymentData, PaymentMethodId paymentMethodId, PaymentBlob blob)
|
||||
{
|
||||
paymentData.Type = paymentMethodId.ToString();
|
||||
paymentData.PaymentMethodId = paymentMethodId.ToString();
|
||||
paymentData.Blob2 = JToken.FromObject(blob, InvoiceDataExtensions.DefaultSerializer).ToString(Newtonsoft.Json.Formatting.None);
|
||||
return paymentData;
|
||||
}
|
||||
public static PaymentMethodId GetPaymentMethodId(this PaymentData paymentData)
|
||||
{
|
||||
return PaymentMethodId.Parse(paymentData.Type);
|
||||
return PaymentMethodId.Parse(paymentData.PaymentMethodId);
|
||||
}
|
||||
public static PaymentEntity GetBlob(this PaymentData paymentData)
|
||||
{
|
||||
|
|
|
@ -72,7 +72,7 @@ namespace BTCPayServer.Services.Invoices
|
|||
using var db = _applicationDbContextFactory.CreateContext();
|
||||
var row = (await db.AddressInvoices
|
||||
.Include(a => a.InvoiceData.Payments)
|
||||
.Where(a => a.PaymentMethodId == paymentMethodId.ToString() && a.Address == address)
|
||||
.Where(a => a.Address == address && a.PaymentMethodId == paymentMethodId.ToString())
|
||||
.Select(a => a.InvoiceData)
|
||||
.FirstOrDefaultAsync());
|
||||
return row is null ? null : ToEntity(row);
|
||||
|
@ -101,7 +101,7 @@ namespace BTCPayServer.Services.Invoices
|
|||
COALESCE(array_agg(to_jsonb(p)) FILTER (WHERE p."Id" IS NOT NULL), '{}') as payments,
|
||||
(array_agg(to_jsonb(i)))[1] as invoice
|
||||
FROM get_monitored_invoices(@pmi) m
|
||||
LEFT JOIN "Payments" p ON p."Id" = m.payment_id
|
||||
LEFT JOIN "Payments" p ON p."Id" = m.payment_id AND p."PaymentMethodId" = m.payment_method_id
|
||||
LEFT JOIN "Invoices" i ON i."Id" = m.invoice_id
|
||||
LEFT JOIN "AddressInvoices" ai ON i."Id" = ai."InvoiceDataId"
|
||||
WHERE ai."PaymentMethodId" = @pmi
|
||||
|
|
|
@ -49,7 +49,7 @@ namespace BTCPayServer.Services.Invoices
|
|||
if (invoice == null)
|
||||
return null;
|
||||
invoiceEntity = invoice.GetBlob();
|
||||
var pmi = PaymentMethodId.Parse(paymentData.Type);
|
||||
var pmi = PaymentMethodId.Parse(paymentData.PaymentMethodId);
|
||||
PaymentPrompt paymentMethod = invoiceEntity.GetPaymentPrompt(pmi);
|
||||
if (paymentMethod is null || !_handlers.TryGetValue(pmi, out var handler))
|
||||
return null;
|
||||
|
|
Loading…
Add table
Reference in a new issue