Compare commits
14 Commits
debugtopup
...
v1.12.1
Author | SHA1 | Date | |
---|---|---|---|
ebc053aca5 | |||
96da7f0322 | |||
8ae9e59d9d | |||
c94dc87cb8 | |||
20512a59b3 | |||
b3f9216c54 | |||
1cda0360e9 | |||
7f75117bfa | |||
5a70345499 | |||
5114a3a2ea | |||
93ab219124 | |||
61bf6d33b2 | |||
3fc687a2d4 | |||
8da04fd7e2 |
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
@ -31,7 +32,9 @@ namespace BTCPayServer.Data
|
||||
public List<InvoiceSearchData> InvoiceSearchData { get; set; }
|
||||
public List<RefundData> Refunds { get; set; }
|
||||
|
||||
|
||||
[Timestamp]
|
||||
// With this, update of InvoiceData will fail if the row was modified by another process
|
||||
public uint XMin { get; set; }
|
||||
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
|
||||
{
|
||||
builder.Entity<InvoiceData>()
|
||||
|
@ -1,4 +1,4 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@ -16,25 +16,7 @@ namespace BTCPayServer.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.9");
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Address")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("CreatedTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("InvoiceDataId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Address");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("AddressInvoices");
|
||||
});
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b =>
|
||||
{
|
||||
@ -71,6 +53,24 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("ApiKeys");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Address")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("CreatedTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("InvoiceDataId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Address");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("AddressInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AppData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -89,7 +89,7 @@ namespace BTCPayServer.Migrations
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Settings")
|
||||
.HasColumnType("JSONB");
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StoreDataId")
|
||||
.HasColumnType("TEXT");
|
||||
@ -305,6 +305,11 @@ namespace BTCPayServer.Migrations
|
||||
b.Property<string>("StoreDataId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("XMin")
|
||||
.IsConcurrencyToken()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Created");
|
||||
@ -781,31 +786,6 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("Stores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoredFile", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StorageFileName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("Timestamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId");
|
||||
|
||||
b.ToTable("Files");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -863,6 +843,31 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("StoreWebhooks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoredFile", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StorageFileName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("Timestamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId");
|
||||
|
||||
b.ToTable("Files");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.U2FDevice", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -1171,16 +1176,6 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("AddressInvoices")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("InvoiceData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
@ -1198,6 +1193,16 @@ namespace BTCPayServer.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("AddressInvoices")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("InvoiceData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AppData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
@ -1408,15 +1413,6 @@ namespace BTCPayServer.Migrations
|
||||
b.Navigation("PullPaymentData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoredFile", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("StoredFiles")
|
||||
.HasForeignKey("ApplicationUserId");
|
||||
|
||||
b.Navigation("ApplicationUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreRole", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
@ -1457,6 +1453,15 @@ namespace BTCPayServer.Migrations
|
||||
b.Navigation("Webhook");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoredFile", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("StoredFiles")
|
||||
.HasForeignKey("ApplicationUserId");
|
||||
|
||||
b.Navigation("ApplicationUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.U2FDevice", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.ApplicationUser", "ApplicationUser")
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@ -99,14 +98,25 @@ namespace BTCPayServer.Tests
|
||||
Driver.FindElement(By.Id("FakePayment")).Click();
|
||||
if (mine)
|
||||
{
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
Driver.WaitForElement(By.Id("CheatSuccessMessage"));
|
||||
});
|
||||
MineBlockOnInvoiceCheckout();
|
||||
}
|
||||
}
|
||||
|
||||
public void MineBlockOnInvoiceCheckout()
|
||||
{
|
||||
Driver.FindElement(By.CssSelector("#mine-block button")).Click();
|
||||
|
||||
retry:
|
||||
try
|
||||
{
|
||||
Driver.FindElement(By.CssSelector("#mine-block button")).Click();
|
||||
}
|
||||
catch (StaleElementReferenceException)
|
||||
{
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -164,7 +164,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("CustomFormInputTest", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Name("buyerEmail")).SendKeys("aa@aa.com");
|
||||
s.Driver.FindElement(By.CssSelector("input[type='submit']")).Click();
|
||||
s.PayInvoice(true);
|
||||
s.PayInvoice(true, 0.001m);
|
||||
var result = await s.Server.PayTester.HttpClient.GetAsync(formurl);
|
||||
Assert.Equal(HttpStatusCode.NotFound, result.StatusCode);
|
||||
|
||||
@ -1149,7 +1149,6 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// Contribute
|
||||
s.Driver.FindElement(By.Id("crowdfund-body-header-cta")).Click();
|
||||
Thread.Sleep(1000);
|
||||
s.Driver.WaitUntilAvailable(By.Name("btcpay"));
|
||||
|
||||
var frameElement = s.Driver.FindElement(By.Name("btcpay"));
|
||||
@ -2152,7 +2151,7 @@ namespace BTCPayServer.Tests
|
||||
var ppid = lnurl.AbsoluteUri.Split("/").Last();
|
||||
var issuerKey = new IssuerKey(SettingsRepositoryExtensions.FixedKey());
|
||||
var uid = RandomNumberGenerator.GetBytes(7);
|
||||
var cardKey = issuerKey.CreateCardKey(uid, 0);
|
||||
var cardKey = issuerKey.CreatePullPaymentCardKey(uid, 0, ppid);
|
||||
var keys = cardKey.DeriveBoltcardKeys(issuerKey);
|
||||
await db.LinkBoltcardToPullPayment(ppid, issuerKey, uid);
|
||||
var piccData = new byte[] { 0xc7 }.Concat(uid).Concat(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0 }).ToArray();
|
||||
@ -2190,6 +2189,10 @@ namespace BTCPayServer.Tests
|
||||
// Relink should bump Version
|
||||
reg = await db.GetBoltcardRegistration(issuerKey, uid);
|
||||
Assert.Equal((ppid, 0, 1), (reg.PullPaymentId, reg.Counter, reg.Version));
|
||||
|
||||
await db.LinkBoltcardToPullPayment(ppid, issuerKey, uid);
|
||||
reg = await db.GetBoltcardRegistration(issuerKey, uid);
|
||||
Assert.Equal((ppid, 0, 2), (reg.PullPaymentId, reg.Counter, reg.Version));
|
||||
}
|
||||
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
|
@ -395,7 +395,7 @@ namespace BTCPayServer.Tests
|
||||
BOLT11PaymentRequest.Parse(newBolt11, Network.RegTest).MinimumAmount.ToDecimal(LightMoneyUnit.BTC));
|
||||
}, 40000);
|
||||
|
||||
TestLogs.LogInformation($"Paying invoice {newInvoice.Id} remaining due amount {newInvoice.BtcDue.GetValue((BTCPayNetwork) tester.DefaultNetwork)} via lightning");
|
||||
TestLogs.LogInformation($"Paying invoice {newInvoice.Id} remaining due amount {newInvoice.BtcDue.GetValue((BTCPayNetwork)tester.DefaultNetwork)} via lightning");
|
||||
var evt = await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
|
||||
{
|
||||
await tester.SendLightningPaymentAsync(newInvoice);
|
||||
@ -1301,11 +1301,8 @@ namespace BTCPayServer.Tests
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
|
||||
await tester.ExplorerNode.EnsureGenerateAsync(1);
|
||||
var rng = new Random();
|
||||
var seed = rng.Next();
|
||||
rng = new Random(seed);
|
||||
TestLogs.LogInformation("Seed: " + seed);
|
||||
foreach (var networkFeeMode in Enum.GetValues(typeof(NetworkFeeMode)).Cast<NetworkFeeMode>())
|
||||
{
|
||||
await user.SetNetworkFeeMode(networkFeeMode);
|
||||
@ -1318,7 +1315,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task AssertTopUpBtcPrice(ServerTester tester, TestAccount user, Money btcSent, decimal expectedPriceWithoutNetworkFee, NetworkFeeMode networkFeeMode)
|
||||
private async Task AssertTopUpBtcPrice(ServerTester tester, TestAccount user, Money btcSent, decimal expectedPriceWithoutNetworkFee, NetworkFeeMode networkFeeMode)
|
||||
{
|
||||
var cashCow = tester.ExplorerNode;
|
||||
// First we try payment with a merchant having only BTC
|
||||
@ -1343,7 +1340,6 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
networkFee = 0.0m;
|
||||
}
|
||||
|
||||
await cashCow.SendToAddressAsync(invoiceAddress, paid);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
@ -1822,7 +1818,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Empty(appList2.Apps);
|
||||
Assert.Equal("test", appList.Apps[0].AppName);
|
||||
Assert.Equal(apps.CreatedAppId, appList.Apps[0].Id);
|
||||
|
||||
|
||||
Assert.True(app.Role.ToPermissionSet(app.StoreId).Contains(Policies.CanModifyStoreSettings, app.StoreId));
|
||||
Assert.Equal(user.StoreId, appList.Apps[0].StoreId);
|
||||
Assert.IsType<NotFoundResult>(apps2.DeleteApp(appList.Apps[0].Id));
|
||||
@ -2399,11 +2395,11 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var url = lnMethod.GetExternalLightningUrl();
|
||||
var kv = LightningConnectionStringHelper.ExtractValues(url, out var connType);
|
||||
Assert.Equal(LightningConnectionType.Charge,connType);
|
||||
Assert.Equal(LightningConnectionType.Charge, connType);
|
||||
var client = Assert.IsType<ChargeClient>(tester.PayTester.GetService<LightningClientFactoryService>()
|
||||
.Create(url, tester.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC")));
|
||||
var auth = Assert.IsType<ChargeAuthentication.UserPasswordAuthentication>(client.ChargeAuthentication);
|
||||
|
||||
|
||||
Assert.Equal("pass", auth.NetworkCredential.Password);
|
||||
Assert.Equal("usr", auth.NetworkCredential.UserName);
|
||||
|
||||
@ -2829,7 +2825,7 @@ namespace BTCPayServer.Tests
|
||||
var app = await client.CreatePointOfSaleApp(acc.StoreId, new CreatePointOfSaleAppRequest()
|
||||
{
|
||||
AppName = "Static",
|
||||
DefaultView = Client.Models.PosViewType.Static,
|
||||
DefaultView = Client.Models.PosViewType.Static,
|
||||
Template = new PointOfSaleSettings().Template
|
||||
});
|
||||
var resp = await posController.ViewPointOfSale(app.Id, choiceKey: "green-tea");
|
||||
@ -2839,7 +2835,7 @@ namespace BTCPayServer.Tests
|
||||
app = await client.CreatePointOfSaleApp(acc.StoreId, new CreatePointOfSaleAppRequest()
|
||||
{
|
||||
AppName = "Cart",
|
||||
DefaultView = Client.Models.PosViewType.Cart,
|
||||
DefaultView = Client.Models.PosViewType.Cart,
|
||||
Template = new PointOfSaleSettings().Template
|
||||
});
|
||||
resp = await posController.ViewPointOfSale(app.Id, posData: new JObject()
|
||||
|
@ -46,11 +46,11 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BTCPayServer.NTag424" Version="1.0.18" />
|
||||
<PackageReference Include="BTCPayServer.NTag424" Version="1.0.19" />
|
||||
<PackageReference Include="YamlDotNet" Version="8.0.0" />
|
||||
<PackageReference Include="BIP78.Sender" Version="0.2.2" />
|
||||
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.2" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.5.2" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.5.3" />
|
||||
<PackageReference Include="CsvHelper" Version="15.0.5" />
|
||||
<PackageReference Include="Dapper" Version="2.1.24" />
|
||||
<PackageReference Include="Fido2" Version="2.0.2" />
|
||||
|
@ -41,14 +41,13 @@ namespace BTCPayServer.Components.StoreSelector
|
||||
.FirstOrDefault()?
|
||||
.Network.CryptoCode;
|
||||
var walletId = cryptoCode != null ? new WalletId(store.Id, cryptoCode) : null;
|
||||
var role = store.GetStoreRoleOfUser(userId);
|
||||
return new StoreSelectorOption
|
||||
{
|
||||
Text = store.StoreName,
|
||||
Value = store.Id,
|
||||
Selected = store.Id == currentStore?.Id,
|
||||
WalletId = walletId,
|
||||
Store = store,
|
||||
Store = store
|
||||
};
|
||||
})
|
||||
.OrderBy(s => s.Text)
|
||||
|
@ -1,6 +1,7 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.IsolatedStorage;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
@ -218,7 +219,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
|
||||
var issuerKey = await _settingsRepository.GetIssuerKey(_env);
|
||||
var version = await _dbContextFactory.LinkBoltcardToPullPayment(pullPaymentId, issuerKey, request.UID, request.OnExisting);
|
||||
var keys = issuerKey.CreateCardKey(request.UID, version).DeriveBoltcardKeys(issuerKey);
|
||||
var keys = issuerKey.CreatePullPaymentCardKey(request.UID, version, pullPaymentId).DeriveBoltcardKeys(issuerKey);
|
||||
|
||||
var boltcardUrl = Url.Action(nameof(UIBoltcardController.GetWithdrawRequest), "UIBoltcard");
|
||||
boltcardUrl = Request.GetAbsoluteUri(boltcardUrl);
|
||||
|
@ -48,16 +48,11 @@ public class LightningAddressService
|
||||
{
|
||||
return await _memoryCache.GetOrCreateAsync(GetKey(username), async entry =>
|
||||
{
|
||||
var result = await Get(new LightningAddressQuery { Usernames = new[] { username } });
|
||||
var result = await Get(new LightningAddressQuery { Usernames = new[] { NormalizeUsername(username) } });
|
||||
return result.FirstOrDefault();
|
||||
});
|
||||
}
|
||||
|
||||
private string NormalizeUsername(string username)
|
||||
{
|
||||
return username.ToLowerInvariant();
|
||||
}
|
||||
|
||||
public async Task<bool> Set(LightningAddressData data)
|
||||
{
|
||||
data.Username = NormalizeUsername(data.Username);
|
||||
@ -115,8 +110,12 @@ public class LightningAddressService
|
||||
await context.AddAsync(data);
|
||||
}
|
||||
|
||||
public static string NormalizeUsername(string username)
|
||||
{
|
||||
return username.ToLowerInvariant();
|
||||
}
|
||||
|
||||
private string GetKey(string username)
|
||||
private static string GetKey(string username)
|
||||
{
|
||||
username = NormalizeUsername(username);
|
||||
return $"{nameof(LightningAddressService)}_{username}";
|
||||
|
@ -61,7 +61,7 @@ public class UIBoltcardController : Controller
|
||||
var registration = await ContextFactory.GetBoltcardRegistration(issuerKey, piccData, updateCounter: pr is not null);
|
||||
if (registration?.PullPaymentId is null)
|
||||
return BadRequest(new LNUrlStatusResponse { Status = "ERROR", Reason = "Replayed or expired query" });
|
||||
var cardKey = issuerKey.CreateCardKey(piccData.Uid, registration.Version);
|
||||
var cardKey = issuerKey.CreatePullPaymentCardKey(piccData.Uid, registration.Version, registration.PullPaymentId);
|
||||
if (!cardKey.CheckSunMac(c, piccData))
|
||||
return BadRequest(new LNUrlStatusResponse { Status = "ERROR", Reason = "Replayed or expired query" });
|
||||
LNURLController.ControllerContext.HttpContext = HttpContext;
|
||||
|
@ -84,8 +84,9 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
var stores = await _storeRepository.GetStoresByUserId(userId);
|
||||
return stores.Any()
|
||||
? RedirectToStore(userId, stores.First())
|
||||
var activeStore = stores.FirstOrDefault(s => !s.Archived);
|
||||
return activeStore != null
|
||||
? RedirectToStore(userId, activeStore)
|
||||
: RedirectToAction(nameof(UIUserStoresController.CreateStore), "UIUserStores");
|
||||
}
|
||||
|
||||
|
@ -373,7 +373,7 @@ namespace BTCPayServer
|
||||
if (string.IsNullOrEmpty(username))
|
||||
return NotFound("Unknown username");
|
||||
|
||||
LNURLPayRequest lnurlRequest = null;
|
||||
LNURLPayRequest lnurlRequest;
|
||||
|
||||
// Check core and fall back to lookup Lightning Address via plugins
|
||||
var lightningAddressSettings = await _lightningAddressService.ResolveByAddress(username);
|
||||
|
@ -111,7 +111,7 @@ next:
|
||||
try
|
||||
{
|
||||
var version = await _dbContextFactory.LinkBoltcardToPullPayment(pullPaymentId, issuerKey, uid);
|
||||
var cardKey = issuerKey.CreateCardKey(uid, version);
|
||||
var cardKey = issuerKey.CreatePullPaymentCardKey(uid, version, pullPaymentId);
|
||||
await ntag.SetupBoltcard(boltcardUrl, BoltcardKeys.Default, cardKey.DeriveBoltcardKeys(issuerKey));
|
||||
}
|
||||
catch
|
||||
@ -135,7 +135,7 @@ next:
|
||||
}
|
||||
else if (cardOrigin is CardOrigin.ThisIssuer thisIssuer)
|
||||
{
|
||||
var cardKey = issuerKey.CreateCardKey(thisIssuer.Registration.UId, thisIssuer.Registration.Version);
|
||||
var cardKey = issuerKey.CreatePullPaymentCardKey(thisIssuer.Registration.UId, thisIssuer.Registration.Version, pullPaymentId);
|
||||
await ntag.ResetCard(issuerKey, cardKey);
|
||||
await _dbContextFactory.SetBoltcardResetState(issuerKey, thisIssuer.Registration.UId);
|
||||
await vaultClient.Show(VaultMessageType.Ok, "Card reset succeed", cts.Token);
|
||||
|
@ -310,10 +310,11 @@ askdevice:
|
||||
await websocketHelper.Send("{ \"error\": \"no-device\"}", cancellationToken);
|
||||
continue;
|
||||
}
|
||||
device = new HwiDeviceClient(hwi, deviceEntry.DeviceSelector, deviceEntry.Model, deviceEntry.Fingerprint);
|
||||
var model = deviceEntry.Model ?? "Unsupported hardware wallet, try to update BTCPay Server Vault";
|
||||
device = new HwiDeviceClient(hwi, deviceEntry.DeviceSelector, model, deviceEntry.Fingerprint);
|
||||
fingerprint = device.Fingerprint;
|
||||
JObject json = new JObject();
|
||||
json.Add("model", device.Model);
|
||||
json.Add("model", model);
|
||||
json.Add("fingerprint", device.Fingerprint?.ToString());
|
||||
await websocketHelper.Send(json.ToString(), cancellationToken);
|
||||
break;
|
||||
|
@ -21,7 +21,7 @@ public static class BoltcardDataExtensions
|
||||
string onConflict = onExisting switch
|
||||
{
|
||||
OnExistingBehavior.KeepVersion => "UPDATE SET ppid=excluded.ppid, version=boltcards.version",
|
||||
OnExistingBehavior.UpdateVersion => "UPDATE SET ppid=excluded.ppid, version=excluded.version+1",
|
||||
OnExistingBehavior.UpdateVersion => "UPDATE SET ppid=excluded.ppid, version=boltcards.version+1",
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
return await conn.QueryFirstOrDefaultAsync<int>(
|
||||
|
@ -50,6 +50,7 @@ namespace BTCPayServer.Data
|
||||
|
||||
public static StoreBlob GetStoreBlob(this StoreData storeData)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(storeData);
|
||||
var result = storeData.StoreBlob == null ? new StoreBlob() : new Serializer(null).ToObject<StoreBlob>(storeData.StoreBlob);
|
||||
if (result.PreferredExchange == null)
|
||||
result.PreferredExchange = result.GetRecommendedExchange();
|
||||
|
@ -19,6 +19,7 @@ using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.NTag424;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Security;
|
||||
@ -41,6 +42,11 @@ namespace BTCPayServer
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static CardKey CreatePullPaymentCardKey(this IssuerKey issuerKey, byte[] uid, int version, string pullPaymentId)
|
||||
{
|
||||
var data = Encoding.UTF8.GetBytes(pullPaymentId);
|
||||
return issuerKey.CreateCardKey(uid, version, data);
|
||||
}
|
||||
public static DateTimeOffset TruncateMilliSeconds(this DateTimeOffset dt) => new (dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, 0, dt.Offset);
|
||||
public static decimal? GetDue(this InvoiceCryptoInfo invoiceCryptoInfo)
|
||||
{
|
||||
@ -94,20 +100,21 @@ namespace BTCPayServer
|
||||
public static string GetDisplayName(this ILightningClient client)
|
||||
{
|
||||
LightningConnectionStringHelper.ExtractValues(client.ToString(), out var type);
|
||||
|
||||
var field = typeof(LightningConnectionType).GetField(type, BindingFlags.Public | BindingFlags.Static);
|
||||
|
||||
var lncType = typeof(LightningConnectionType);
|
||||
var fields = lncType.GetFields(BindingFlags.Public | BindingFlags.Static);
|
||||
var field = fields.FirstOrDefault(f => f.GetValue(lncType)?.ToString() == type);
|
||||
if (field == null) return type;
|
||||
DisplayAttribute attr = field.GetCustomAttribute<DisplayAttribute>();
|
||||
return attr?.Name ?? type;
|
||||
|
||||
}
|
||||
|
||||
public static bool IsSafe(this ILightningClient connectionString)
|
||||
public static bool IsSafe(this ILightningClient client)
|
||||
{
|
||||
var kv = LightningConnectionStringHelper.ExtractValues(connectionString.ToString(), out var type);
|
||||
if (kv.TryGetValue("cookiefilepath", out var cookieFilePath) ||
|
||||
kv.TryGetValue("macaroondirectorypath", out var macaroonDirectoryPath) ||
|
||||
kv.TryGetValue("macaroonfilepath", out var macaroonFilePath) )
|
||||
var kv = LightningConnectionStringHelper.ExtractValues(client.ToString(), out var type);
|
||||
if (kv.TryGetValue("cookiefilepath", out _) ||
|
||||
kv.TryGetValue("macaroondirectorypath", out _) ||
|
||||
kv.TryGetValue("macaroonfilepath", out _) )
|
||||
return false;
|
||||
|
||||
if (!kv.TryGetValue("server", out var server))
|
||||
@ -117,7 +124,7 @@ namespace BTCPayServer
|
||||
var uri = new Uri(server, UriKind.Absolute);
|
||||
if (uri.Scheme.Equals("unix", StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
if (!Utils.TryParseEndpoint(uri.DnsSafeHost, 80, out var endpoint))
|
||||
if (!Utils.TryParseEndpoint(uri.DnsSafeHost, 80, out _))
|
||||
return false;
|
||||
return !IsLocalNetwork(uri.DnsSafeHost);
|
||||
}
|
||||
|
@ -38,11 +38,10 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
public bool Dirty => _dirty;
|
||||
|
||||
bool _isBlobUpdated;
|
||||
public bool IsBlobUpdated => _isBlobUpdated;
|
||||
public void BlobUpdated()
|
||||
public bool IsPriceUpdated { get; private set; }
|
||||
public void PriceUpdated()
|
||||
{
|
||||
_isBlobUpdated = true;
|
||||
IsPriceUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,7 +103,7 @@ namespace BTCPayServer.HostedServices
|
||||
var payment = invoice.GetPayments(true).First();
|
||||
invoice.Price = payment.InvoicePaidAmount.Net;
|
||||
invoice.UpdateTotals();
|
||||
context.BlobUpdated();
|
||||
context.PriceUpdated();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -291,9 +290,9 @@ namespace BTCPayServer.HostedServices
|
||||
await _invoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.GetInvoiceState());
|
||||
updateContext.Events.Insert(0, new InvoiceDataChangedEvent(invoice));
|
||||
}
|
||||
if (updateContext.IsBlobUpdated)
|
||||
if (updateContext.IsPriceUpdated)
|
||||
{
|
||||
await _invoiceRepository.UpdateInvoicePrice(invoice.Id, invoice);
|
||||
await _invoiceRepository.UpdateInvoicePrice(invoice.Id, invoice.Price);
|
||||
}
|
||||
|
||||
foreach (var evt in updateContext.Events)
|
||||
|
@ -61,7 +61,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
|
||||
if (preparePaymentObject is null)
|
||||
{
|
||||
return new LightningLikePaymentMethodDetails()
|
||||
return new LightningLikePaymentMethodDetails
|
||||
{
|
||||
Activated = false
|
||||
};
|
||||
@ -144,6 +144,12 @@ namespace BTCPayServer.Payments.Lightning
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
// LNDhub, LNbits and others might not support this call, yet we can create invoices.
|
||||
return new NodeInfo[] {};
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
// LND might return this with restricted macaroon, support this nevertheless..
|
||||
return new NodeInfo[] {};
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -237,7 +243,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
|
||||
public override CheckoutUIPaymentMethodSettings GetCheckoutUISettings()
|
||||
{
|
||||
return new CheckoutUIPaymentMethodSettings()
|
||||
return new CheckoutUIPaymentMethodSettings
|
||||
{
|
||||
ExtensionPartial = "Lightning/LightningLikeMethodCheckout",
|
||||
CheckoutBodyVueComponentName = "LightningLikeMethodCheckout",
|
||||
|
@ -30,11 +30,11 @@ namespace BTCPayServer.Payments.Lightning
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
public void SetLightningUrl(ILightningClient connectionString)
|
||||
public void SetLightningUrl(ILightningClient client)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(connectionString);
|
||||
ArgumentNullException.ThrowIfNull(client);
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
LightningConnectionString = connectionString.ToString();
|
||||
LightningConnectionString = client.ToString();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
|
9
BTCPayServer/Plugins/LightningAddressResolver.cs
Normal file
9
BTCPayServer/Plugins/LightningAddressResolver.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using LNURL;
|
||||
|
||||
namespace BTCPayServer.Plugins;
|
||||
|
||||
public class LightningAddressResolver(string username)
|
||||
{
|
||||
public string Username { get; set; } = LightningAddressService.NormalizeUsername(username);
|
||||
public LNURLPayRequest LNURLPayRequest { get; set; }
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
using LNURL;
|
||||
|
||||
namespace BTCPayServer.Plugins;
|
||||
|
||||
public class LightningAddressResolver
|
||||
{
|
||||
public string Username { get; set; }
|
||||
public LNURLPayRequest LNURLPayRequest { get; set; }
|
||||
|
||||
public LightningAddressResolver(string username)
|
||||
{
|
||||
Username = username;
|
||||
}
|
||||
}
|
@ -79,13 +79,13 @@ namespace BTCPayServer.Services.Apps
|
||||
|
||||
public async Task<object?> GetInfo(string appId)
|
||||
{
|
||||
var appData = await GetApp(appId, null);
|
||||
var appData = await GetApp(appId, null, includeStore: true);
|
||||
if (appData is null)
|
||||
return null;
|
||||
var appType = GetAppType(appData.AppType);
|
||||
if (appType is null)
|
||||
return null;
|
||||
return appType.GetInfo(appData);
|
||||
return await appType.GetInfo(appData);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ItemStats>> GetItemStats(AppData appData)
|
||||
|
@ -11,6 +11,7 @@ using BTCPayServer.Events;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments;
|
||||
using Dapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
@ -130,32 +131,50 @@ namespace BTCPayServer.Services.Invoices
|
||||
|
||||
public async Task UpdateInvoice(string invoiceId, UpdateCustomerModel data)
|
||||
{
|
||||
using var ctx = _applicationDbContextFactory.CreateContext();
|
||||
var invoiceData = await ctx.Invoices.FindAsync(invoiceId).ConfigureAwait(false);
|
||||
if (invoiceData == null)
|
||||
return;
|
||||
if (invoiceData.CustomerEmail == null && data.Email != null)
|
||||
retry:
|
||||
using (var ctx = _applicationDbContextFactory.CreateContext())
|
||||
{
|
||||
invoiceData.CustomerEmail = data.Email;
|
||||
AddToTextSearch(ctx, invoiceData, invoiceData.CustomerEmail);
|
||||
var invoiceData = await ctx.Invoices.FindAsync(invoiceId);
|
||||
if (invoiceData == null)
|
||||
return;
|
||||
if (invoiceData.CustomerEmail == null && data.Email != null)
|
||||
{
|
||||
invoiceData.CustomerEmail = data.Email;
|
||||
AddToTextSearch(ctx, invoiceData, invoiceData.CustomerEmail);
|
||||
}
|
||||
try
|
||||
{
|
||||
await ctx.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
await ctx.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task UpdateInvoiceExpiry(string invoiceId, TimeSpan seconds)
|
||||
{
|
||||
await using var ctx = _applicationDbContextFactory.CreateContext();
|
||||
var invoiceData = await ctx.Invoices.FindAsync(invoiceId);
|
||||
var invoice = invoiceData.GetBlob(_btcPayNetworkProvider);
|
||||
var expiry = DateTimeOffset.Now + seconds;
|
||||
invoice.ExpirationTime = expiry;
|
||||
invoice.MonitoringExpiration = expiry.AddHours(1);
|
||||
invoiceData.SetBlob(invoice);
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
_eventAggregator.Publish(new InvoiceDataChangedEvent(invoice));
|
||||
_ = InvoiceNeedUpdateEventLater(invoiceId, seconds);
|
||||
retry:
|
||||
await using (var ctx = _applicationDbContextFactory.CreateContext())
|
||||
{
|
||||
var invoiceData = await ctx.Invoices.FindAsync(invoiceId);
|
||||
var invoice = invoiceData.GetBlob(_btcPayNetworkProvider);
|
||||
var expiry = DateTimeOffset.Now + seconds;
|
||||
invoice.ExpirationTime = expiry;
|
||||
invoice.MonitoringExpiration = expiry.AddHours(1);
|
||||
invoiceData.SetBlob(invoice);
|
||||
try
|
||||
{
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
goto retry;
|
||||
}
|
||||
_eventAggregator.Publish(new InvoiceDataChangedEvent(invoice));
|
||||
_ = InvoiceNeedUpdateEventLater(invoiceId, seconds);
|
||||
}
|
||||
}
|
||||
|
||||
async Task InvoiceNeedUpdateEventLater(string invoiceId, TimeSpan expirationIn)
|
||||
@ -166,13 +185,23 @@ namespace BTCPayServer.Services.Invoices
|
||||
|
||||
public async Task ExtendInvoiceMonitor(string invoiceId)
|
||||
{
|
||||
using var ctx = _applicationDbContextFactory.CreateContext();
|
||||
var invoiceData = await ctx.Invoices.FindAsync(invoiceId);
|
||||
retry:
|
||||
using (var ctx = _applicationDbContextFactory.CreateContext())
|
||||
{
|
||||
var invoiceData = await ctx.Invoices.FindAsync(invoiceId);
|
||||
|
||||
var invoice = invoiceData.GetBlob(_btcPayNetworkProvider);
|
||||
invoice.MonitoringExpiration = invoice.MonitoringExpiration.AddHours(1);
|
||||
invoiceData.SetBlob(invoice);
|
||||
await ctx.SaveChangesAsync();
|
||||
var invoice = invoiceData.GetBlob(_btcPayNetworkProvider);
|
||||
invoice.MonitoringExpiration = invoice.MonitoringExpiration.AddHours(1);
|
||||
invoiceData.SetBlob(invoice);
|
||||
try
|
||||
{
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CreateInvoiceAsync(InvoiceEntity invoice, string[] additionalSearchTerms = null)
|
||||
@ -279,62 +308,81 @@ namespace BTCPayServer.Services.Invoices
|
||||
|
||||
public async Task<bool> NewPaymentDetails(string invoiceId, IPaymentMethodDetails paymentMethodDetails, BTCPayNetworkBase network)
|
||||
{
|
||||
await using var context = _applicationDbContextFactory.CreateContext();
|
||||
var invoice = (await context.Invoices.Where(i => i.Id == invoiceId).ToListAsync()).FirstOrDefault();
|
||||
if (invoice == null)
|
||||
return false;
|
||||
retry:
|
||||
await using (var context = _applicationDbContextFactory.CreateContext())
|
||||
{
|
||||
var invoice = (await context.Invoices.Where(i => i.Id == invoiceId).ToListAsync()).FirstOrDefault();
|
||||
if (invoice == null)
|
||||
return false;
|
||||
|
||||
var invoiceEntity = invoice.GetBlob(_btcPayNetworkProvider);
|
||||
var paymentMethod = invoiceEntity.GetPaymentMethod(network, paymentMethodDetails.GetPaymentType());
|
||||
if (paymentMethod == null)
|
||||
return false;
|
||||
var invoiceEntity = invoice.GetBlob(_btcPayNetworkProvider);
|
||||
var paymentMethod = invoiceEntity.GetPaymentMethod(network, paymentMethodDetails.GetPaymentType());
|
||||
if (paymentMethod == null)
|
||||
return false;
|
||||
|
||||
var existingPaymentMethod = paymentMethod.GetPaymentMethodDetails();
|
||||
paymentMethod.SetPaymentMethodDetails(paymentMethodDetails);
|
||||
var existingPaymentMethod = paymentMethod.GetPaymentMethodDetails();
|
||||
paymentMethod.SetPaymentMethodDetails(paymentMethodDetails);
|
||||
#pragma warning disable CS0618
|
||||
if (network.IsBTC)
|
||||
{
|
||||
invoiceEntity.DepositAddress = paymentMethod.DepositAddress;
|
||||
}
|
||||
if (network.IsBTC)
|
||||
{
|
||||
invoiceEntity.DepositAddress = paymentMethod.DepositAddress;
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
invoiceEntity.SetPaymentMethod(paymentMethod);
|
||||
invoice.SetBlob(invoiceEntity);
|
||||
|
||||
await context.AddressInvoices.AddAsync(new AddressInvoiceData()
|
||||
{
|
||||
InvoiceDataId = invoiceId,
|
||||
CreatedTime = DateTimeOffset.UtcNow
|
||||
}
|
||||
.Set(GetDestination(paymentMethod), paymentMethod.GetId()));
|
||||
|
||||
AddToTextSearch(context, invoice, paymentMethodDetails.GetPaymentDestination());
|
||||
await context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task UpdateInvoicePaymentMethod(string invoiceId, PaymentMethod paymentMethod)
|
||||
{
|
||||
using var context = _applicationDbContextFactory.CreateContext();
|
||||
var invoice = await context.Invoices.FindAsync(invoiceId);
|
||||
if (invoice == null)
|
||||
return;
|
||||
var network = paymentMethod.Network;
|
||||
var invoiceEntity = invoice.GetBlob(_btcPayNetworkProvider);
|
||||
var newDetails = paymentMethod.GetPaymentMethodDetails();
|
||||
var existing = invoiceEntity.GetPaymentMethod(paymentMethod.GetId());
|
||||
if (existing.GetPaymentMethodDetails().GetPaymentDestination() != newDetails.GetPaymentDestination() && newDetails.Activated)
|
||||
{
|
||||
invoiceEntity.SetPaymentMethod(paymentMethod);
|
||||
invoice.SetBlob(invoiceEntity);
|
||||
await context.AddressInvoices.AddAsync(new AddressInvoiceData()
|
||||
{
|
||||
InvoiceDataId = invoiceId,
|
||||
CreatedTime = DateTimeOffset.UtcNow
|
||||
}
|
||||
.Set(GetDestination(paymentMethod), paymentMethod.GetId()));
|
||||
.Set(GetDestination(paymentMethod), paymentMethod.GetId()));
|
||||
|
||||
AddToTextSearch(context, invoice, paymentMethodDetails.GetPaymentDestination());
|
||||
try
|
||||
{
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
goto retry;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateInvoicePaymentMethod(string invoiceId, PaymentMethod paymentMethod)
|
||||
{
|
||||
retry:
|
||||
using (var context = _applicationDbContextFactory.CreateContext())
|
||||
{
|
||||
var invoice = await context.Invoices.FindAsync(invoiceId);
|
||||
if (invoice == null)
|
||||
return;
|
||||
var network = paymentMethod.Network;
|
||||
var invoiceEntity = invoice.GetBlob(_btcPayNetworkProvider);
|
||||
var newDetails = paymentMethod.GetPaymentMethodDetails();
|
||||
var existing = invoiceEntity.GetPaymentMethod(paymentMethod.GetId());
|
||||
if (existing.GetPaymentMethodDetails().GetPaymentDestination() != newDetails.GetPaymentDestination() && newDetails.Activated)
|
||||
{
|
||||
await context.AddressInvoices.AddAsync(new AddressInvoiceData()
|
||||
{
|
||||
InvoiceDataId = invoiceId,
|
||||
CreatedTime = DateTimeOffset.UtcNow
|
||||
}
|
||||
.Set(GetDestination(paymentMethod), paymentMethod.GetId()));
|
||||
}
|
||||
invoiceEntity.SetPaymentMethod(paymentMethod);
|
||||
invoice.SetBlob(invoiceEntity);
|
||||
AddToTextSearch(context, invoice, paymentMethod.GetPaymentMethodDetails().GetPaymentDestination());
|
||||
try
|
||||
{
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
invoiceEntity.SetPaymentMethod(paymentMethod);
|
||||
invoice.SetBlob(invoiceEntity);
|
||||
AddToTextSearch(context, invoice, paymentMethod.GetPaymentMethodDetails().GetPaymentDestination());
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task AddPendingInvoiceIfNotPresent(string invoiceId)
|
||||
@ -389,26 +437,38 @@ namespace BTCPayServer.Services.Invoices
|
||||
public async Task UpdateInvoiceStatus(string invoiceId, InvoiceState invoiceState)
|
||||
{
|
||||
using var context = _applicationDbContextFactory.CreateContext();
|
||||
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||
if (invoiceData == null)
|
||||
return;
|
||||
invoiceData.Status = InvoiceState.ToString(invoiceState.Status);
|
||||
invoiceData.ExceptionStatus = InvoiceState.ToString(invoiceState.ExceptionStatus);
|
||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||
await context.Database.GetDbConnection()
|
||||
.ExecuteAsync("UPDATE \"Invoices\" SET \"Status\"=@status, \"ExceptionStatus\"=@exstatus WHERE \"Id\"=@id",
|
||||
new
|
||||
{
|
||||
id = invoiceId,
|
||||
status = InvoiceState.ToString(invoiceState.Status),
|
||||
exstatus = InvoiceState.ToString(invoiceState.ExceptionStatus)
|
||||
});
|
||||
}
|
||||
internal async Task UpdateInvoicePrice(string invoiceId, InvoiceEntity invoice)
|
||||
internal async Task UpdateInvoicePrice(string invoiceId, decimal price)
|
||||
{
|
||||
if (invoice.Type != InvoiceType.TopUp)
|
||||
throw new ArgumentException("The invoice type should be TopUp to be able to update invoice price", nameof(invoice));
|
||||
using var context = _applicationDbContextFactory.CreateContext();
|
||||
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||
if (invoiceData == null)
|
||||
return;
|
||||
var blob = invoiceData.GetBlob(_btcPayNetworkProvider);
|
||||
blob.Price = invoice.Price;
|
||||
AddToTextSearch(context, invoiceData, new[] { invoice.Price.ToString(CultureInfo.InvariantCulture) });
|
||||
invoiceData.SetBlob(blob);
|
||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||
retry:
|
||||
using (var context = _applicationDbContextFactory.CreateContext())
|
||||
{
|
||||
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||
if (invoiceData == null)
|
||||
return;
|
||||
var blob = invoiceData.GetBlob(_btcPayNetworkProvider);
|
||||
if (blob.Type != InvoiceType.TopUp)
|
||||
throw new ArgumentException("The invoice type should be TopUp to be able to update invoice price", nameof(invoiceId));
|
||||
blob.Price = price;
|
||||
AddToTextSearch(context, invoiceData, new[] { price.ToString(CultureInfo.InvariantCulture) });
|
||||
invoiceData.SetBlob(blob);
|
||||
try
|
||||
{
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task MassArchive(string[] invoiceIds, bool archive = true)
|
||||
@ -436,37 +496,47 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
public async Task<InvoiceEntity> UpdateInvoiceMetadata(string invoiceId, string storeId, JObject metadata)
|
||||
{
|
||||
using var context = _applicationDbContextFactory.CreateContext();
|
||||
var invoiceData = await GetInvoiceRaw(invoiceId, context);
|
||||
if (invoiceData == null || (storeId != null &&
|
||||
!invoiceData.StoreDataId.Equals(storeId,
|
||||
StringComparison.InvariantCultureIgnoreCase)))
|
||||
return null;
|
||||
var blob = invoiceData.GetBlob(_btcPayNetworkProvider);
|
||||
|
||||
var newMetadata = InvoiceMetadata.FromJObject(metadata);
|
||||
var oldOrderId = blob.Metadata.OrderId;
|
||||
var newOrderId = newMetadata.OrderId;
|
||||
|
||||
if (newOrderId != oldOrderId)
|
||||
retry:
|
||||
using (var context = _applicationDbContextFactory.CreateContext())
|
||||
{
|
||||
// OrderId is saved in 2 places: (1) the invoice table and (2) in the metadata field. We are updating both for consistency.
|
||||
invoiceData.OrderId = newOrderId;
|
||||
var invoiceData = await GetInvoiceRaw(invoiceId, context);
|
||||
if (invoiceData == null || (storeId != null &&
|
||||
!invoiceData.StoreDataId.Equals(storeId,
|
||||
StringComparison.InvariantCultureIgnoreCase)))
|
||||
return null;
|
||||
var blob = invoiceData.GetBlob(_btcPayNetworkProvider);
|
||||
|
||||
if (oldOrderId != null && (newOrderId is null || !newOrderId.Equals(oldOrderId, StringComparison.InvariantCulture)))
|
||||
var newMetadata = InvoiceMetadata.FromJObject(metadata);
|
||||
var oldOrderId = blob.Metadata.OrderId;
|
||||
var newOrderId = newMetadata.OrderId;
|
||||
|
||||
if (newOrderId != oldOrderId)
|
||||
{
|
||||
RemoveFromTextSearch(context, invoiceData, oldOrderId);
|
||||
// OrderId is saved in 2 places: (1) the invoice table and (2) in the metadata field. We are updating both for consistency.
|
||||
invoiceData.OrderId = newOrderId;
|
||||
|
||||
if (oldOrderId != null && (newOrderId is null || !newOrderId.Equals(oldOrderId, StringComparison.InvariantCulture)))
|
||||
{
|
||||
RemoveFromTextSearch(context, invoiceData, oldOrderId);
|
||||
}
|
||||
if (newOrderId != null)
|
||||
{
|
||||
AddToTextSearch(context, invoiceData, new[] { newOrderId });
|
||||
}
|
||||
}
|
||||
if (newOrderId != null)
|
||||
|
||||
blob.Metadata = newMetadata;
|
||||
invoiceData.SetBlob(blob);
|
||||
try
|
||||
{
|
||||
AddToTextSearch(context, invoiceData, new[] { newOrderId });
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
goto retry;
|
||||
}
|
||||
return ToEntity(invoiceData);
|
||||
}
|
||||
|
||||
blob.Metadata = newMetadata;
|
||||
invoiceData.SetBlob(blob);
|
||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||
return ToEntity(invoiceData);
|
||||
}
|
||||
public async Task<bool> MarkInvoiceStatus(string invoiceId, InvoiceStatus status)
|
||||
{
|
||||
|
@ -31,7 +31,7 @@
|
||||
<div class="row">
|
||||
<div class="col-xxl-constrain">
|
||||
<form method="post">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div asp-validation-summary="ModelOnly"></div>
|
||||
|
||||
<div class="form-group" style="max-width:320px">
|
||||
<label asp-for="Role" class="form-label"></label>
|
||||
|
@ -42,7 +42,7 @@
|
||||
|
||||
<input type="hidden" asp-for="StoreId" />
|
||||
<input type="hidden" asp-for="Archived" />
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div asp-validation-summary="ModelOnly"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-10 col-md-9 col-xl-7 col-xxl-6">
|
||||
|
@ -27,7 +27,7 @@
|
||||
<div class="col-xl-10 col-xxl-constrain">
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
<div class="form-group">
|
||||
<label asp-for="Settings.Server" class="form-label">SMTP Server</label>
|
||||
|
@ -43,7 +43,7 @@
|
||||
|
||||
<input type="hidden" asp-for="StoreId" />
|
||||
<input type="hidden" asp-for="Archived" />
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div asp-validation-summary="ModelOnly"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-10 col-md-9 col-xl-7 col-xxl-6">
|
||||
|
@ -10,7 +10,7 @@
|
||||
</p>
|
||||
|
||||
<form asp-action="ForgotPassword" method="post">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Email" class="form-label"></label>
|
||||
<input asp-for="Email" class="form-control" />
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
<form asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" id="login-form" asp-action="Login">
|
||||
<fieldset disabled="@(ViewData.ContainsKey("disabled") ? "disabled" : null)">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div asp-validation-summary="ModelOnly"></div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Email" class="form-label"></label>
|
||||
<input asp-for="Email" class="form-control" required autofocus/>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="twoFaBox">
|
||||
<h2 class="h3 mb-3">Two-Factor Authentication</h2>
|
||||
<form method="post" asp-route-returnUrl="@ViewData["ReturnUrl"]" asp-action="LoginWith2fa">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div asp-validation-summary="ModelOnly"></div>
|
||||
<input asp-for="RememberMe" type="hidden"/>
|
||||
<div class="form-group">
|
||||
<label asp-for="TwoFactorCode" class="form-label"></label>
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
<form asp-route-returnUrl="@ViewData["ReturnUrl"]" asp-route-logon="true" method="post">
|
||||
<fieldset disabled="@(ViewData.ContainsKey("disabled") ? "disabled" : null)" >
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div asp-validation-summary="ModelOnly"></div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Email" class="form-label"></label>
|
||||
<input asp-for="Email" class="form-control" required autofocus />
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
@if (Model.LoginWith2FaViewModel != null && Model.LoginWithFido2ViewModel != null&& Model.LoginWithLNURLAuthViewModel != null)
|
||||
{
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div asp-validation-summary="ModelOnly"></div>
|
||||
}
|
||||
else if (Model.LoginWith2FaViewModel == null && Model.LoginWithFido2ViewModel == null && Model.LoginWithLNURLAuthViewModel == null)
|
||||
{
|
||||
|
@ -5,7 +5,7 @@
|
||||
}
|
||||
|
||||
<form method="post" asp-action="SetPassword">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
<input asp-for="Code" type="hidden"/>
|
||||
<input asp-for="EmailSetInternally" type="hidden"/>
|
||||
@if (Model.EmailSetInternally)
|
||||
|
@ -14,7 +14,7 @@
|
||||
<div class="row">
|
||||
<div class="col-xl-8 col-xxl-constrain">
|
||||
<form asp-action="CreateApp" asp-route-appType="@Model.AppType">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div asp-validation-summary="ModelOnly"></div>
|
||||
@if (string.IsNullOrEmpty(Model.AppType))
|
||||
{
|
||||
<div class="form-group">
|
||||
|
@ -20,7 +20,7 @@
|
||||
<input asp-for="Config" type="hidden" />
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div asp-validation-summary="ModelOnly"></div>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Model.SelectedCustodian))
|
||||
{
|
||||
|
@ -21,7 +21,7 @@
|
||||
<input asp-for="Config" type="hidden" />
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div asp-validation-summary="ModelOnly"></div>
|
||||
}
|
||||
<partial name="_FormTopMessages" model="Model.ConfigForm"/>
|
||||
|
||||
|
@ -211,7 +211,7 @@
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
<div class="form-group" style="max-width: 27rem;">
|
||||
<label asp-for="Name" class="form-label" data-required></label>
|
||||
<input asp-for="Name" class="form-control" required />
|
||||
|
@ -26,7 +26,7 @@
|
||||
<main class="flex-grow-1">
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div asp-validation-summary="ModelOnly"></div>
|
||||
}
|
||||
<partial name="_FormTopMessages" model="@Model.Form" />
|
||||
<div class="d-flex flex-column justify-content-center gap-4">
|
||||
|
@ -38,7 +38,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xl-8 col-xxl-constrain">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div asp-validation-summary="ModelOnly"></div>
|
||||
@if (Model.StoreId != null)
|
||||
{
|
||||
<input type="hidden" asp-for="StoreId" />
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
|
||||
@switch (Model.RefundStep)
|
||||
|
@ -15,7 +15,7 @@
|
||||
<p>Set a schedule for automated Lightning Network Payouts.</p>
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
<form method="post">
|
||||
<div class="form-check">
|
||||
|
@ -32,7 +32,7 @@
|
||||
<p>Generate a new api key to use BTCPay through its API.</p>
|
||||
|
||||
<form method="post" asp-action="AddApiKey" id="Permissions">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="Label" class="form-label"></label>
|
||||
|
@ -45,7 +45,7 @@
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
<p class="lead text-secondary mt-3">@(displayName ?? "An application") is requesting access to your BTCPay Server account.</p>
|
||||
</header>
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
|
||||
@if (Model.NeedsStorePermission && store == null)
|
||||
{
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="col-xl-8 col-xxl-constrain">
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
|
@ -53,7 +53,7 @@
|
||||
<span asp-validation-for="Code" class="text-danger"></span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mt-2">Verify</button>
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div asp-validation-summary="ModelOnly"></div>
|
||||
</form>
|
||||
</li>
|
||||
</ol>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<form method="post">
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
<div class="form-group">
|
||||
<div class="col-md-6">
|
||||
|
@ -12,7 +12,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<form method="post">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
<div class="form-group">
|
||||
<label asp-for="NewPassword" class="form-label"></label>
|
||||
<input asp-for="NewPassword" class="form-control" />
|
||||
|
@ -15,7 +15,7 @@
|
||||
<p>Set a schedule for automated On-Chain Bitcoin Payouts. </p>
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
<form method="post">
|
||||
<div class="form-check">
|
||||
|
@ -41,7 +41,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xl-8 col-xxl-constrain">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div asp-validation-summary="ModelOnly"></div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Title" class="form-label" data-required></label>
|
||||
<input asp-for="Title" class="form-control" required />
|
||||
|
@ -35,7 +35,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xl-8 col-xxl-constrain">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div asp-validation-summary="ModelOnly"></div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Name" class="form-label" data-required></label>
|
||||
<input asp-for="Name" class="form-control" required />
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
<div class="form-group">
|
||||
<p>
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
<div class="col-xl-8 col-xxl-constrain">
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
<form method="post">
|
||||
<div class="form-group form-check">
|
||||
|
@ -8,7 +8,7 @@
|
||||
<div class="row">
|
||||
<div class="col-xl-6 col-xxl-constrain">
|
||||
<form method="post" asp-action="CreateUser">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div asp-validation-summary="ModelOnly"></div>
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="Email" class="form-label"></label>
|
||||
|
@ -17,7 +17,7 @@
|
||||
<div class="col-md-8">
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
|
@ -9,7 +9,7 @@
|
||||
<div class="col-md-8">
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
<div class="form-group">
|
||||
<p>Lightning charge is a simple API for invoicing on lightning network, you can use it with several plugins:</p>
|
||||
|
@ -22,7 +22,7 @@
|
||||
<div class="col-md-8">
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
<div class="form-group">
|
||||
<h5>Browser connection</h5>
|
||||
|
@ -416,6 +416,12 @@
|
||||
</form>
|
||||
}
|
||||
}
|
||||
@if (disabled)
|
||||
{
|
||||
<form asp-action="UnInstallPlugin" asp-route-plugin="@plugin.Identifier">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">Uninstall</button>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
|
||||
<h4 class="mb-3">Full node connection</h4>
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
|
||||
<h4 class="mb-3">Full node connection</h4>
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
|
||||
<div class="form-group">
|
||||
|
@ -16,7 +16,7 @@
|
||||
<form method="post">
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
<div class="form-group">
|
||||
<label asp-for="Provider" class="form-label"></label>
|
||||
|
@ -25,7 +25,7 @@
|
||||
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
|
||||
<form method="post" id="shopifyForm">
|
||||
|
@ -63,7 +63,7 @@
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
<h3 class="mb-3">Invoice Settings</h3>
|
||||
@if (Model.PaymentMethods.Any())
|
||||
|
@ -15,7 +15,7 @@
|
||||
<div class="col-xxl-constrain col-xl-8">
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<h3 class="mb-3">General</h3>
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
|
||||
<partial name="LocalhostBrowserSupport" />
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
|
||||
<partial name="LocalhostBrowserSupport" />
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
|
||||
<div class="my-5">
|
||||
|
@ -20,7 +20,7 @@
|
||||
@try
|
||||
{
|
||||
var client = LightningClientFactoryService.Create(Model.ConnectionString, NetworkProvider.GetNetwork<BTCPayNetwork>(Model.CryptoCode));
|
||||
client.GetDisplayName();
|
||||
<span>@client.GetDisplayName()</span>
|
||||
var uri = client.GetServerUri();
|
||||
if (uri is not null)
|
||||
{
|
||||
|
@ -20,7 +20,7 @@
|
||||
@try
|
||||
{
|
||||
var client = LightningClientFactoryService.Create(Model.ConnectionString, NetworkProvider.GetNetwork<BTCPayNetwork>(Model.CryptoCode));
|
||||
client.GetDisplayName();
|
||||
<span>@client.GetDisplayName()</span>
|
||||
var uri = client.GetServerUri();
|
||||
if (uri is not null)
|
||||
{
|
||||
|
@ -10,7 +10,7 @@
|
||||
<h3 class="mb-3">@ViewData["Title"]</h3>
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
<form method="post">
|
||||
<input type="hidden" asp-for="ShowScripting" />
|
||||
|
@ -156,11 +156,13 @@
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
For the macaroon options you need to provide the <code>admin.macaroon</code>.<br/>
|
||||
For the macaroon options you need to provide a macaroon with the <code>invoices:write</code> permission (e.g. <code>invoice.macaroon</code>, see
|
||||
<a href="https://docs.lightning.engineering/lightning-network-tools/lnd/macaroons" target="_blank" rel="noreferrer noopener">details</a>). If you
|
||||
want to display the node connection details, it also needs the <code>info:read</code> permission.<br/>
|
||||
The path to the LND data directory may vary, the following examples assume <code>/root/.lnd</code>.
|
||||
</p>
|
||||
<p class="mb-2">The <code>macaroon</code> parameter expects the HEX value, it can be obtained using this command:</p>
|
||||
<pre class="mb-4">xxd -p -c 256 /root/.lnd/data/chain/bitcoin/mainnet/admin.macaroon | tr -d '\n'</pre>
|
||||
<pre class="mb-4">xxd -p -c 256 /root/.lnd/data/chain/bitcoin/mainnet/invoice.macaroon | tr -d '\n'</pre>
|
||||
<p class="mb-2">
|
||||
You can omit <code>certthumbprint</code> if the certificate is trusted by your machine.<br/>
|
||||
The <code>certthumbprint</code> can be obtained using this command:
|
||||
|
@ -29,7 +29,7 @@
|
||||
</div>
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
|
||||
<form method="post">
|
||||
|
@ -1,7 +1,7 @@
|
||||
@model BTCPayServer.Models.StoreViewModels.CreateStoreViewModel
|
||||
|
||||
<form asp-action="CreateStore">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div asp-validation-summary="ModelOnly"></div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Name" class="form-label" data-required></label>
|
||||
<input asp-for="Name" class="form-control w-300px" required />
|
||||
|
@ -37,7 +37,7 @@
|
||||
<p class="mb-0">Otherwise you are exposing yourself to malicious site owners, or to malicious plugins installed in your browser.</p>
|
||||
</div>
|
||||
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
|
||||
<form method="post" asp-action="SignWithSeed" asp-route-walletId="@walletId">
|
||||
<partial name="SigningContext" for="SigningContext"/>
|
||||
|
@ -27,6 +27,21 @@
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* Form validation messages should match alert styles */
|
||||
.validation-summary-errors {
|
||||
padding: .75rem 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--btcpay-danger-text);
|
||||
background-color: var(--btcpay-danger);
|
||||
border: var(--btcpay-border-width) solid var(--btcpay-danger-border);
|
||||
border-radius: var(--btcpay-border-radius);
|
||||
}
|
||||
|
||||
.alert > :last-child,
|
||||
.validation-summary-errors > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* General and site-wide Bootstrap modifications */
|
||||
p {
|
||||
margin-bottom: 1.5rem;
|
||||
|
@ -108,7 +108,8 @@
|
||||
},
|
||||
"tags": [
|
||||
"Pull payments (Public)"
|
||||
]
|
||||
],
|
||||
"security": []
|
||||
}
|
||||
},
|
||||
"/api/v1/stores/{storeId}/pull-payments": {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>1.12.0</Version>
|
||||
<Version>1.12.1</Version>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
26
Changelog.md
26
Changelog.md
@ -1,5 +1,31 @@
|
||||
# Changelog
|
||||
|
||||
## 1.12.1
|
||||
|
||||
Recommended update for users using Boltcard with pull payments or Top-Up invoices.
|
||||
|
||||
Breaking change: Boltcards linked to pull payments in version 1.12.0 are not compatible with version 1.12.1.
|
||||
|
||||
# New Features
|
||||
|
||||
* A disabled plugin can now be uninstalled in the UI (#5570) @Kukks
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fix: Payments to Top-Up could go undetected due to a race condition (#5568) @NicolasDorier
|
||||
* Lightning: Fixed the connection display name in LN settings (#5569) @dennisreimann
|
||||
* Prevent redirection to archived store after login (#5566) @dennisreimann
|
||||
* Use PullPaymentId to derive the cardkey of a Boltcard (#5575) @NicolasDorier
|
||||
* Greenfield: The Link a boltcard to a pull payment route would not generate new keys for the boltcard when onExisting was set to UpdateVersion. @NicolasDorier
|
||||
|
||||
### Improvements
|
||||
|
||||
* Lightning Address: Use lowercase usernames when resolving (#5579) @dennisreimann
|
||||
* UI: Form validation summary now matches alert style (#5576, #5564) @dennisreimann
|
||||
* Improved error message in Vault if a hardware device isn't supported @NicolasDorier
|
||||
* Lightning: Allow LND to be used with non-admin macaroons (#5567) @dennisreimann
|
||||
* Fix in API Documentation: The Link a boltcard to a pull payment had incorrectly documented permissions. @NicolasDorier
|
||||
|
||||
## 1.12.0
|
||||
|
||||
### Noteworthy
|
||||
|
Reference in New Issue
Block a user