Compare commits
41 Commits
v1.9.1
...
fix-array-
Author | SHA1 | Date | |
---|---|---|---|
|
8290a1606f | ||
|
eddd458744 | ||
|
439ea20a89 | ||
|
cec223c8e7 | ||
|
25cb188d00 | ||
|
31007a8d96 | ||
|
a4fa8db69b | ||
|
6193835ea1 | ||
|
0c78e9e4ac | ||
|
58c409e7fa | ||
|
9577eed524 | ||
|
76f32cd064 | ||
|
92d9c17095 | ||
|
b0396df33f | ||
|
c17572c76f | ||
|
5c91e072a6 | ||
|
4991d0f965 | ||
|
45b74e1ce5 | ||
|
ccb4b9a9ba | ||
|
dd635071d6 | ||
|
fe1448dfae | ||
|
56855bc54d | ||
|
b51fa8df5a | ||
|
3aa979cb11 | ||
|
c95f75bc6c | ||
|
b13a636f89 | ||
|
8de55cef31 | ||
|
cb781f42e3 | ||
|
b59292dc24 | ||
|
03b793d7e2 | ||
|
bee18d1cfb | ||
|
d8698181f4 | ||
|
47f5d97eaf | ||
|
cb3c5e56fd | ||
|
39b76c08de | ||
|
c3c8cc21ff | ||
|
6dba1b6d8b | ||
|
381fe70a79 | ||
|
feb927c2e4 | ||
|
e77bd4c188 | ||
|
43436fc49e |
@@ -1,21 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Pack Test Plugin" type="DotNetProject" factoryName=".NET Project" singleton="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/BTCPayServer.PluginPacker/bin/Debug/netcoreapp3.1/BTCPayServer.PluginPacker.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="../../../../BTCPayServer.Plugins.Test\bin\Debug\netcoreapp3.1 BTCPayServer.Plugins.Test "../../../../Packed Plugins"" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/BTCPayServer.PluginPacker/bin/Debug/netcoreapp3.1" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/BTCPayServer.PluginPacker/BTCPayServer.PluginPacker.csproj" />
|
||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
|
||||
<method v="2">
|
||||
<option name="Build" default="false" projectName="BTCPayServer.Plugins.Test" projectPath="C:\Git\btcpayserver\Plugins\BTCPayServer.Plugins.Test\BTCPayServer.Plugins.Test.csproj" />
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
@@ -86,6 +86,7 @@ namespace BTCPayServer.Client.Models
|
||||
public bool? RequiresRefundEmail { get; set; } = null;
|
||||
public string DefaultLanguage { get; set; }
|
||||
public CheckoutType? CheckoutType { get; set; }
|
||||
public bool? LazyPaymentMethods { get; set; }
|
||||
}
|
||||
}
|
||||
public class InvoiceData : InvoiceDataBase
|
||||
|
@@ -3,7 +3,6 @@ namespace BTCPayServer.Client.Models
|
||||
public class LNURLPayPaymentMethodBaseData
|
||||
{
|
||||
public bool UseBech32Scheme { get; set; }
|
||||
public bool EnableForStandardInvoices { get; set; }
|
||||
public bool LUD12Enabled { get; set; }
|
||||
|
||||
public LNURLPayPaymentMethodBaseData()
|
||||
|
@@ -16,12 +16,11 @@ namespace BTCPayServer.Client.Models
|
||||
{
|
||||
}
|
||||
|
||||
public LNURLPayPaymentMethodData(string cryptoCode, bool enabled, bool useBech32Scheme, bool enableForStandardInvoices)
|
||||
public LNURLPayPaymentMethodData(string cryptoCode, bool enabled, bool useBech32Scheme)
|
||||
{
|
||||
Enabled = enabled;
|
||||
CryptoCode = cryptoCode;
|
||||
UseBech32Scheme = useBech32Scheme;
|
||||
EnableForStandardInvoices = enableForStandardInvoices;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@ namespace BTCPayServer.Client.Models
|
||||
{
|
||||
|
||||
public string ConnectionString { get; set; }
|
||||
public bool DisableBOLT11PaymentOption { get; set; }
|
||||
public LightningNetworkPaymentMethodBaseData()
|
||||
{
|
||||
|
||||
|
@@ -16,13 +16,12 @@ namespace BTCPayServer.Client.Models
|
||||
{
|
||||
}
|
||||
|
||||
public LightningNetworkPaymentMethodData(string cryptoCode, string connectionString, bool enabled, string paymentMethod, bool disableBOLT11PaymentOption)
|
||||
public LightningNetworkPaymentMethodData(string cryptoCode, string connectionString, bool enabled, string paymentMethod)
|
||||
{
|
||||
Enabled = enabled;
|
||||
CryptoCode = cryptoCode;
|
||||
ConnectionString = connectionString;
|
||||
PaymentMethod = paymentMethod;
|
||||
DisableBOLT11PaymentOption = disableBOLT11PaymentOption;
|
||||
}
|
||||
|
||||
public string PaymentMethod { get; set; }
|
||||
|
@@ -51,6 +51,7 @@ namespace BTCPayServer.Tests
|
||||
user.RegisterDerivationScheme(cryptoCode);
|
||||
user.RegisterDerivationScheme("LTC");
|
||||
user.RegisterLightningNode(cryptoCode, LightningConnectionType.CLightning);
|
||||
user.SetLNUrl("BTC", false);
|
||||
var btcNetwork = tester.PayTester.Networks.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
|
@@ -23,7 +23,7 @@
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
|
||||
<PackageReference Include="Selenium.Support" Version="4.1.1" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="4.1.1" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="110.0.5481.7700" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="112.0.5615.4900" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
@@ -164,7 +164,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC_LightningLike");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
Assert.Equal("Bitcoin (Lightning) (BTC)", s.Driver.FindElement(By.ClassName("payment__currencies")).Text);
|
||||
Assert.Equal("Bitcoin (Lightning)", s.Driver.FindElement(By.ClassName("payment__currencies")).Text);
|
||||
s.Driver.Quit();
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var invoiceId = s.CreateInvoice(10, "USD", "a@g.com");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
Assert.Contains("sats", s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Text);
|
||||
Assert.Contains("sats", s.Driver.FindElement(By.ClassName("buyerTotalLine")).Text);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
|
@@ -45,14 +45,6 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Save")).Click();
|
||||
Assert.Contains("Store successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
// Enable LNURL, which we will need for (non-)presence checks throughout this test
|
||||
s.GoToHome();
|
||||
s.GoToLightningSettings();
|
||||
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
|
||||
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), true);
|
||||
s.Driver.FindElement(By.Id("save")).Click();
|
||||
Assert.Contains("BTC Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
s.GoToStore(StoreNavPages.CheckoutAppearance);
|
||||
s.Driver.WaitForAndClick(By.Id("Presets"));
|
||||
s.Driver.WaitForAndClick(By.Id("Presets_InStore"));
|
||||
@@ -296,7 +288,6 @@ namespace BTCPayServer.Tests
|
||||
s.GoToHome();
|
||||
s.GoToLightningSettings();
|
||||
Assert.True(s.Driver.FindElement(By.Id("LNURLEnabled")).Selected);
|
||||
Assert.True(s.Driver.FindElement(By.Id("LNURLStandardInvoiceEnabled")).Selected);
|
||||
|
||||
// BIP21 with top-up invoice
|
||||
invoiceId = s.CreateInvoice(amount: null);
|
||||
@@ -392,7 +383,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("lang=de", s.Driver.Url);
|
||||
|
||||
s.Driver.Navigate().Refresh();
|
||||
languageSelect = new SelectElement(s.Driver.FindElement(By.Id("DefaultLang")));
|
||||
languageSelect = new SelectElement(s.Driver.WaitForElement(By.Id("DefaultLang")));
|
||||
Assert.Equal("Deutsch", languageSelect.SelectedOption.Text);
|
||||
Assert.Equal("Details anzeigen", s.Driver.FindElement(By.Id("DetailsToggle")).Text);
|
||||
languageSelect.SelectByText("English");
|
||||
|
@@ -15,12 +15,15 @@ using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.PayoutProcessors;
|
||||
using BTCPayServer.PayoutProcessors.OnChain;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Custodian.Client.MockCustodian;
|
||||
using BTCPayServer.Services.Notifications;
|
||||
using BTCPayServer.Services.Notifications.Blobs;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json;
|
||||
@@ -1474,6 +1477,7 @@ namespace BTCPayServer.Tests
|
||||
var newDeliveryId = await clientProfile.RedeliverWebhook(user.StoreId, hook.Id, delivery.Id);
|
||||
req = await fakeServer.GetNextRequest();
|
||||
req.Response.StatusCode = 404;
|
||||
Assert.StartsWith("BTCPayServer", Assert.Single(req.Request.Headers.UserAgent));
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
// Releasing semaphore several times may help making this test less flaky
|
||||
@@ -3408,8 +3412,8 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
Assert.Empty(await adminClient.GetStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork"));
|
||||
await adminClient.UpdateStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork",
|
||||
new LightningAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(2) });
|
||||
Assert.Equal(2, Assert.Single(await adminClient.GetStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork")).IntervalSeconds.TotalSeconds);
|
||||
new LightningAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(600) });
|
||||
Assert.Equal(600, Assert.Single(await adminClient.GetStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork")).IntervalSeconds.TotalSeconds);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var payoutC =
|
||||
@@ -3494,8 +3498,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.Empty(await adminClient.GetPayoutProcessors(admin.StoreId));
|
||||
|
||||
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC",
|
||||
new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(100000) });
|
||||
Assert.Equal(100000, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
|
||||
new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(3600) });
|
||||
Assert.Equal(3600, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
|
||||
|
||||
var tpGen = Assert.Single(await adminClient.GetPayoutProcessors(admin.StoreId));
|
||||
Assert.Equal("BTC", Assert.Single(tpGen.PaymentMethods));
|
||||
@@ -3508,8 +3512,10 @@ namespace BTCPayServer.Tests
|
||||
Assert.Empty(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC"));
|
||||
Assert.Empty(await adminClient.GetPayoutProcessors(admin.StoreId));
|
||||
|
||||
// Send just enough money to cover the smallest of the payouts.
|
||||
var fee = (await tester.PayTester.GetService<IFeeProviderFactory>().CreateFeeProvider(tester.DefaultNetwork).GetFeeRateAsync(100)).GetFee(150);
|
||||
await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.000012m));
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.00001m) + fee);
|
||||
await tester.ExplorerNode.GenerateAsync(1);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
@@ -3519,8 +3525,9 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(3, payouts.Length);
|
||||
});
|
||||
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC",
|
||||
new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(5) });
|
||||
Assert.Equal(5, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
|
||||
new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(600), FeeBlockTarget = 1000 });
|
||||
Assert.Equal(600, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
|
||||
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
Assert.Equal(2, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count());
|
||||
@@ -3528,8 +3535,10 @@ namespace BTCPayServer.Tests
|
||||
Assert.Single(payouts.Where(data => data.State == PayoutState.InProgress));
|
||||
});
|
||||
|
||||
await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.01m));
|
||||
var txid = await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.01m) + fee);
|
||||
await tester.WaitForEvent<NewOnChainTransactionEvent>(null, correctEvent: ev => ev.NewTransactionEvent.TransactionData.TransactionHash == txid);
|
||||
await tester.PayTester.GetService<PayoutProcessorService>().Restart(new PayoutProcessorService.PayoutProcessorQuery(admin.StoreId, "BTC"));
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
Assert.Equal(4, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count());
|
||||
|
@@ -11,8 +11,10 @@ using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
@@ -116,8 +118,11 @@ namespace BTCPayServer.Tests
|
||||
|
||||
s.Driver.FindElement(By.Name("buyerEmail")).SendKeys("aa@aa.com");
|
||||
s.Driver.FindElement(By.CssSelector("input[type='submit']")).Click();
|
||||
invoiceId = s.Driver.Url.Split('/').Last();
|
||||
s.Driver.Navigate().GoToUrl(editUrl);
|
||||
Assert.Contains("aa@aa.com", s.Driver.PageSource);
|
||||
var invoice = await s.Server.PayTester.GetService<InvoiceRepository>().GetInvoice(invoiceId);
|
||||
Assert.Equal("aa@aa.com", invoice.Metadata.BuyerEmail);
|
||||
|
||||
//Custom Forms
|
||||
s.GoToStore(StoreNavPages.Forms);
|
||||
@@ -2085,22 +2090,23 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
// Standard invoice test
|
||||
s.GoToStore(storeId);
|
||||
s.GoToLightningSettings();
|
||||
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), true);
|
||||
SudoForceSaveLightningSettingsRightNowAndFast(s, cryptoCode);
|
||||
i = s.CreateInvoice(storeId, 0.0000001m, cryptoCode);
|
||||
s.GoToInvoiceCheckout(i);
|
||||
s.Driver.FindElement(By.ClassName("payment__currencies")).Click();
|
||||
// BOLT11 is also available for standard invoices
|
||||
Assert.Equal(2, s.Driver.FindElements(By.CssSelector(".vex.vex-theme-btcpay .vex-content .vexmenu li.vexmenuitem")).Count);
|
||||
s.Driver.FindElement(By.CssSelector(".vex.vex-theme-btcpay .vex-content .vexmenu li.vexmenuitem")).Click();
|
||||
s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Click();
|
||||
// BOLT11 is also displayed for standard invoice (not LNURL, even if it is available)
|
||||
s.Driver.FindElement(By.Id("copy-tab")).Click();
|
||||
lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
|
||||
parsed = LNURL.LNURL.Parse(lnurl, out tag);
|
||||
fetchedReuqest = Assert.IsType<LNURLPayRequest>(await LNURL.LNURL.FetchInformation(parsed, new HttpClient()));
|
||||
var bolt11 = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
|
||||
var bolt11Parsed = Lightning.BOLT11PaymentRequest.Parse(bolt11, s.Server.ExplorerNode.Network);
|
||||
var invoiceId = s.Driver.Url.Split('/').Last();
|
||||
using (var resp = await s.Server.PayTester.HttpClient.GetAsync("BTC/lnurl/pay/i/" + invoiceId))
|
||||
{
|
||||
resp.EnsureSuccessStatusCode();
|
||||
fetchedReuqest = JsonConvert.DeserializeObject<LNURLPayRequest>(await resp.Content.ReadAsStringAsync());
|
||||
}
|
||||
Assert.Equal(0.0000001m, fetchedReuqest.MaxSendable.ToDecimal(LightMoneyUnit.BTC));
|
||||
Assert.Equal(0.0000001m, fetchedReuqest.MinSendable.ToDecimal(LightMoneyUnit.BTC));
|
||||
|
||||
|
||||
await Assert.ThrowsAsync<LNUrlException>(async () =>
|
||||
{
|
||||
await fetchedReuqest.SendRequest(new LightMoney(0.0000002m, LightMoneyUnit.BTC),
|
||||
@@ -2122,13 +2128,6 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(new LightMoney(0.0000001m, LightMoneyUnit.BTC),
|
||||
lnurlResponse2.GetPaymentRequest(network).MinimumAmount);
|
||||
s.GoToHome();
|
||||
s.GoToLightningSettings();
|
||||
// LNURL is enabled and settings are expanded
|
||||
Assert.True(s.Driver.FindElement(By.Id("LNURLEnabled")).Selected);
|
||||
Assert.Contains("show", s.Driver.FindElement(By.Id("LNURLSettings")).GetAttribute("class"));
|
||||
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), false);
|
||||
s.Driver.FindElement(By.Id("save")).Click();
|
||||
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
i = s.CreateInvoice(storeId, 0.000001m, cryptoCode);
|
||||
s.GoToInvoiceCheckout(i);
|
||||
@@ -2142,23 +2141,12 @@ namespace BTCPayServer.Tests
|
||||
s.GoToHome();
|
||||
s.GoToLightningSettings();
|
||||
s.Driver.SetCheckbox(By.Id("LNURLBech32Mode"), false);
|
||||
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), false);
|
||||
s.Driver.SetCheckbox(By.Id("DisableBolt11PaymentMethod"), true);
|
||||
s.Driver.FindElement(By.Id("save")).Click();
|
||||
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
// Ensure the toggles are set correctly
|
||||
s.GoToLightningSettings();
|
||||
|
||||
//TODO: DisableBolt11PaymentMethod is actually disabled because LNURLStandardInvoiceEnabled is disabled
|
||||
// checkboxes is not good choice here, in next release we should have multi choice instead
|
||||
Assert.False(s.Driver.FindElement(By.Id("LNURLBech32Mode")).Selected);
|
||||
Assert.False(s.Driver.FindElement(By.Id("LNURLStandardInvoiceEnabled")).Selected);
|
||||
|
||||
//even though we set DisableBolt11PaymentMethod to true, logic when saving it turns it back off as otherwise no lightning option is available at all!
|
||||
Assert.False(s.Driver.FindElement(By.Id("DisableBolt11PaymentMethod")).Selected);
|
||||
// Invoice creation should fail, because it is a standard invoice with amount, but DisableBolt11PaymentMethod = true and LNURLStandardInvoiceEnabled = false
|
||||
s.CreateInvoice(storeId, 0.0000001m, cryptoCode, "", null, expectedSeverity: StatusMessageModel.StatusSeverity.Success);
|
||||
|
||||
i = s.CreateInvoice(storeId, null, cryptoCode);
|
||||
s.GoToInvoiceCheckout(i);
|
||||
@@ -2174,15 +2162,13 @@ namespace BTCPayServer.Tests
|
||||
s.AddLightningNode(LightningConnectionType.LndREST, false);
|
||||
s.GoToLightningSettings();
|
||||
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
|
||||
s.Driver.SetCheckbox(By.Id("DisableBolt11PaymentMethod"), true);
|
||||
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), true);
|
||||
s.Driver.FindElement(By.Id("save")).Click();
|
||||
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||
var invForPP = s.CreateInvoice(0.0000001m, cryptoCode);
|
||||
var invForPP = s.CreateInvoice(null, cryptoCode);
|
||||
s.GoToInvoiceCheckout(invForPP);
|
||||
s.Driver.FindElement(By.Id("copy-tab")).Click();
|
||||
lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
|
||||
parsed = LNURL.LNURL.Parse(lnurl, out tag);
|
||||
LNURL.LNURL.Parse(lnurl, out tag);
|
||||
|
||||
// Check that pull payment has lightning option
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
|
@@ -177,7 +177,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public async Task<PayResponse> SendLightningPaymentAsync(Invoice invoice)
|
||||
{
|
||||
var bolt11 = invoice.CryptoInfo.Where(o => o.PaymentUrls.BOLT11 != null).First().PaymentUrls.BOLT11;
|
||||
var bolt11 = invoice.CryptoInfo.Where(o => o.PaymentUrls?.BOLT11 != null).First().PaymentUrls.BOLT11;
|
||||
bolt11 = bolt11.Replace("lightning:", "", StringComparison.OrdinalIgnoreCase);
|
||||
return await CustomerLightningD.Pay(bolt11);
|
||||
}
|
||||
@@ -194,7 +194,8 @@ namespace BTCPayServer.Tests
|
||||
tcs.TrySetResult(evt);
|
||||
}
|
||||
});
|
||||
await action.Invoke();
|
||||
if (action != null)
|
||||
await action.Invoke();
|
||||
var result = await tcs.Task;
|
||||
sub.Dispose();
|
||||
return result;
|
||||
@@ -247,6 +248,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public List<string> Stores { get; internal set; } = new List<string>();
|
||||
public bool DeleteStore { get; set; } = true;
|
||||
public BTCPayNetworkBase DefaultNetwork => NetworkProvider.DefaultNetwork;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var r in this.Resources)
|
||||
|
@@ -214,6 +214,13 @@ namespace BTCPayServer.Tests
|
||||
get => GenerateWalletResponseV.DerivationScheme;
|
||||
}
|
||||
|
||||
public void SetLNUrl(string cryptoCode, bool activated)
|
||||
{
|
||||
var lnSettingsVm = GetController<UIStoresController>().LightningSettings(StoreId, cryptoCode).AssertViewModel<LightningSettingsViewModel>();
|
||||
lnSettingsVm.LNURLEnabled = activated;
|
||||
Assert.IsType<RedirectToActionResult>(GetController<UIStoresController>().LightningSettings(lnSettingsVm).Result);
|
||||
}
|
||||
|
||||
private async Task RegisterAsync(bool isAdmin = false)
|
||||
{
|
||||
var account = parent.PayTester.GetController<UIAccountController>();
|
||||
|
@@ -112,7 +112,14 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
catch (XunitException) when (!cts.Token.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(500, cts.Token);
|
||||
bool timeout =false;
|
||||
try
|
||||
{
|
||||
await Task.Delay(500, cts.Token);
|
||||
}
|
||||
catch { timeout = true; }
|
||||
if (timeout)
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1635,6 +1635,7 @@ namespace BTCPayServer.Tests
|
||||
var cryptoCode = "BTC";
|
||||
user.GrantAccess(true);
|
||||
user.RegisterLightningNode(cryptoCode, LightningConnectionType.Charge);
|
||||
user.SetLNUrl(cryptoCode, false);
|
||||
var vm = user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModel<CheckoutAppearanceViewModel>();
|
||||
var criteria = Assert.Single(vm.PaymentMethodCriteria);
|
||||
Assert.Equal(new PaymentMethodId(cryptoCode, LightningPaymentType.Instance).ToString(), criteria.PaymentMethod);
|
||||
@@ -1649,16 +1650,12 @@ namespace BTCPayServer.Tests
|
||||
Price = 1.5m,
|
||||
Currency = "USD"
|
||||
}, Facade.Merchant);
|
||||
|
||||
Assert.Single(invoice.CryptoInfo);
|
||||
Assert.Equal(PaymentTypes.LightningLike.ToString(), invoice.CryptoInfo[0].PaymentType);
|
||||
|
||||
// Activating LNUrl, we should still have only 1 payment criteria that can be set.
|
||||
user.RegisterLightningNode(cryptoCode, LightningConnectionType.Charge);
|
||||
var lnSettingsVm = user.GetController<UIStoresController>().LightningSettings(user.StoreId, cryptoCode).AssertViewModel<LightningSettingsViewModel>();
|
||||
lnSettingsVm.LNURLEnabled = true;
|
||||
lnSettingsVm.LNURLStandardInvoiceEnabled = true;
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>().LightningSettings(lnSettingsVm).Result);
|
||||
user.SetLNUrl(cryptoCode, true);
|
||||
vm = user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModel<CheckoutAppearanceViewModel>();
|
||||
criteria = Assert.Single(vm.PaymentMethodCriteria);
|
||||
Assert.Equal(new PaymentMethodId(cryptoCode, LightningPaymentType.Instance).ToString(), criteria.PaymentMethod);
|
||||
|
@@ -243,7 +243,7 @@ services:
|
||||
- "5432"
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.16.0-beta
|
||||
image: btcpayserver/lnd:v0.16.2-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@@ -278,7 +278,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.16.0-beta
|
||||
image: btcpayserver/lnd:v0.16.2-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
|
@@ -230,7 +230,7 @@ services:
|
||||
- "5432"
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.16.0-beta
|
||||
image: btcpayserver/lnd:v0.16.2-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@@ -267,7 +267,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.16.0-beta
|
||||
image: btcpayserver/lnd:v0.16.2-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
|
@@ -47,7 +47,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BIP78.Sender" Version="0.2.2" />
|
||||
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.2" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.4.22" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.4.23" />
|
||||
<PackageReference Include="CsvHelper" Version="15.0.5" />
|
||||
<PackageReference Include="Dapper" Version="2.0.123" />
|
||||
<PackageReference Include="Fido2" Version="2.0.2" />
|
||||
|
@@ -145,7 +145,10 @@
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
|
||||
<a asp-area="" asp-controller="UIStorePullPayments" asp-action="Payouts" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.Payouts)" id="StoreNav-Payouts">
|
||||
<a asp-area=""
|
||||
asp-controller="UIStorePullPayments" asp-action="Payouts"
|
||||
asp-route-pullPaymentId=""
|
||||
asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.Payouts)" id="StoreNav-Payouts">
|
||||
<vc:icon symbol="payouts"/>
|
||||
<span>Payouts</span>
|
||||
</a>
|
||||
|
@@ -124,7 +124,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
|
||||
try
|
||||
{
|
||||
var invoice = await _invoiceController.CreatePaymentRequestInvoice(pr, amount, this.StoreData, Request, cancellationToken);
|
||||
var prData = await _paymentRequestRepository.FindPaymentRequest(pr.Id, null);
|
||||
var invoice = await _invoiceController.CreatePaymentRequestInvoice(prData, amount, pr.AmountDue, this.StoreData, Request, cancellationToken);
|
||||
return Ok(GreenfieldInvoiceController.ToModel(invoice, _linkGenerator, Request));
|
||||
}
|
||||
catch (BitpayHttpException e)
|
||||
|
@@ -2,6 +2,7 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
@@ -69,6 +70,9 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
public async Task<IActionResult> UpdateStoreLightningAutomatedPayoutProcessor(
|
||||
string storeId, string paymentMethod, LightningAutomatedPayoutSettings request)
|
||||
{
|
||||
AutomatedPayoutConstants.ValidateInterval(ModelState, request.IntervalSeconds, nameof(request.IntervalSeconds));
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
paymentMethod = PaymentMethodId.Parse(paymentMethod).ToString();
|
||||
var activeProcessor =
|
||||
(await _payoutProcessorService.GetProcessors(
|
||||
|
@@ -1,7 +1,9 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
@@ -75,6 +77,11 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
public async Task<IActionResult> UpdateStoreOnchainAutomatedPayoutProcessor(
|
||||
string storeId, string paymentMethod, OnChainAutomatedPayoutSettings request)
|
||||
{
|
||||
AutomatedPayoutConstants.ValidateInterval(ModelState, request.IntervalSeconds, nameof(request.IntervalSeconds));
|
||||
if (request.FeeBlockTarget is int t && (t < 1 || t > 1000))
|
||||
ModelState.AddModelError(nameof(request.FeeBlockTarget), "The feeBlockTarget should be between 1 and 1000");
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
paymentMethod = PaymentMethodId.Parse(paymentMethod).ToString();
|
||||
var activeProcessor =
|
||||
(await _payoutProcessorService.GetProcessors(
|
||||
|
@@ -54,7 +54,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
new LNURLPayPaymentMethodData(
|
||||
paymentMethod.PaymentId.CryptoCode,
|
||||
!excludedPaymentMethods.Match(paymentMethod.PaymentId),
|
||||
paymentMethod.UseBech32Scheme, paymentMethod.EnableForStandardInvoices
|
||||
paymentMethod.UseBech32Scheme
|
||||
)
|
||||
)
|
||||
.Where((result) => enabled is null || enabled == result.Enabled)
|
||||
@@ -124,8 +124,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
LNURLPaySupportedPaymentMethod? paymentMethod = new LNURLPaySupportedPaymentMethod()
|
||||
{
|
||||
CryptoCode = cryptoCode,
|
||||
UseBech32Scheme = paymentMethodData.UseBech32Scheme,
|
||||
EnableForStandardInvoices = paymentMethodData.EnableForStandardInvoices
|
||||
UseBech32Scheme = paymentMethodData.UseBech32Scheme
|
||||
};
|
||||
|
||||
var store = Store;
|
||||
@@ -154,7 +153,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
: new LNURLPayPaymentMethodData(
|
||||
paymentMethod.PaymentId.CryptoCode,
|
||||
!excluded,
|
||||
paymentMethod.UseBech32Scheme, paymentMethod.EnableForStandardInvoices
|
||||
paymentMethod.UseBech32Scheme
|
||||
);
|
||||
}
|
||||
private void AssertCryptoCodeWallet(string cryptoCode, out BTCPayNetwork network)
|
||||
|
@@ -66,8 +66,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
paymentMethod.GetExternalLightningUrl()?.ToString() ??
|
||||
paymentMethod.GetDisplayableConnectionString(),
|
||||
!excludedPaymentMethods.Match(paymentMethod.PaymentId),
|
||||
paymentMethod.PaymentId.ToStringNormalized(),
|
||||
paymentMethod.DisableBOLT11PaymentOption
|
||||
paymentMethod.PaymentId.ToStringNormalized()
|
||||
)
|
||||
)
|
||||
.Where((result) => enabled is null || enabled == result.Enabled)
|
||||
@@ -207,7 +206,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
? null
|
||||
: new LightningNetworkPaymentMethodData(paymentMethod.PaymentId.CryptoCode,
|
||||
paymentMethod.GetDisplayableConnectionString(), !excluded,
|
||||
paymentMethod.PaymentId.ToStringNormalized(), paymentMethod.DisableBOLT11PaymentOption);
|
||||
paymentMethod.PaymentId.ToStringNormalized());
|
||||
}
|
||||
|
||||
private BTCPayNetwork AssertSupportLightning(string cryptoCode)
|
||||
|
@@ -184,8 +184,14 @@ namespace BTCPayServer.Controllers
|
||||
var receipt = InvoiceDataBase.ReceiptOptions.Merge(store.GetStoreBlob().ReceiptOptions, i.ReceiptOptions);
|
||||
|
||||
if (receipt.Enabled is not true)
|
||||
{
|
||||
if (i.RedirectURL is not null)
|
||||
{
|
||||
return Redirect(i.RedirectURL.ToString());
|
||||
}
|
||||
return NotFound();
|
||||
|
||||
}
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var vm = new InvoiceReceiptViewModel
|
||||
{
|
||||
@@ -666,48 +672,55 @@ namespace BTCPayServer.Controllers
|
||||
var btcId = PaymentMethodId.Parse("BTC");
|
||||
var lnId = PaymentMethodId.Parse("BTC_LightningLike");
|
||||
var lnurlId = PaymentMethodId.Parse("BTC_LNURLPAY");
|
||||
|
||||
|
||||
var displayedPaymentMethods = invoice.GetPaymentMethods().Select(p => p.GetId()).ToList();
|
||||
|
||||
// Exclude Lightning if OnChainWithLnInvoiceFallback is active and we have both payment methods
|
||||
if (storeBlob is { OnChainWithLnInvoiceFallback: true } &&
|
||||
displayedPaymentMethods.Contains(btcId))
|
||||
{
|
||||
displayedPaymentMethods.Remove(lnId);
|
||||
displayedPaymentMethods.Remove(lnurlId);
|
||||
}
|
||||
|
||||
// BOLT11 doesn't really support payment without amount
|
||||
if (invoice.IsUnsetTopUp())
|
||||
displayedPaymentMethods.Remove(lnId);
|
||||
|
||||
// Exclude lnurl if bolt11 is available
|
||||
if (displayedPaymentMethods.Contains(lnId) && displayedPaymentMethods.Contains(lnurlId))
|
||||
displayedPaymentMethods.Remove(lnurlId);
|
||||
|
||||
if (paymentMethodId is not null && !displayedPaymentMethods.Contains(paymentMethodId))
|
||||
paymentMethodId = null;
|
||||
if (paymentMethodId is null)
|
||||
{
|
||||
var enabledPaymentIds = store.GetEnabledPaymentIds(_NetworkProvider).ToArray();
|
||||
|
||||
// Exclude Lightning if OnChainWithLnInvoiceFallback is active and we have both payment methods
|
||||
if (storeBlob is { CheckoutType: CheckoutType.V2, OnChainWithLnInvoiceFallback: true })
|
||||
{
|
||||
if (enabledPaymentIds.Contains(btcId) && enabledPaymentIds.Contains(lnId))
|
||||
{
|
||||
enabledPaymentIds = enabledPaymentIds.Where(pmId => pmId != lnId).ToArray();
|
||||
}
|
||||
if (enabledPaymentIds.Contains(btcId) && enabledPaymentIds.Contains(lnurlId))
|
||||
{
|
||||
enabledPaymentIds = enabledPaymentIds.Where(pmId => pmId != lnurlId).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
PaymentMethodId? invoicePaymentId = invoice.GetDefaultPaymentMethod();
|
||||
PaymentMethodId? storePaymentId = store.GetDefaultPaymentId();
|
||||
if (invoicePaymentId is not null)
|
||||
{
|
||||
if (enabledPaymentIds.Contains(invoicePaymentId))
|
||||
if (displayedPaymentMethods.Contains(invoicePaymentId))
|
||||
paymentMethodId = invoicePaymentId;
|
||||
}
|
||||
if (paymentMethodId is null && storePaymentId is not null)
|
||||
{
|
||||
if (enabledPaymentIds.Contains(storePaymentId))
|
||||
if (displayedPaymentMethods.Contains(storePaymentId))
|
||||
paymentMethodId = storePaymentId;
|
||||
}
|
||||
if (paymentMethodId is null && invoicePaymentId is not null)
|
||||
{
|
||||
paymentMethodId = invoicePaymentId.FindNearest(enabledPaymentIds);
|
||||
paymentMethodId = invoicePaymentId.FindNearest(displayedPaymentMethods);
|
||||
}
|
||||
if (paymentMethodId is null && storePaymentId is not null)
|
||||
{
|
||||
paymentMethodId = storePaymentId.FindNearest(enabledPaymentIds);
|
||||
paymentMethodId = storePaymentId.FindNearest(displayedPaymentMethods);
|
||||
}
|
||||
if (paymentMethodId is null)
|
||||
{
|
||||
paymentMethodId = enabledPaymentIds.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == PaymentTypes.BTCLike) ??
|
||||
enabledPaymentIds.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType != PaymentTypes.LNURLPay) ??
|
||||
enabledPaymentIds.FirstOrDefault();
|
||||
paymentMethodId = displayedPaymentMethods.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == PaymentTypes.BTCLike) ??
|
||||
displayedPaymentMethods.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType != PaymentTypes.LNURLPay) ??
|
||||
displayedPaymentMethods.FirstOrDefault();
|
||||
}
|
||||
isDefaultPaymentId = true;
|
||||
}
|
||||
@@ -721,30 +734,37 @@ namespace BTCPayServer.Controllers
|
||||
return null;
|
||||
var paymentMethodTemp = invoice
|
||||
.GetPaymentMethods()
|
||||
.FirstOrDefault(pm =>
|
||||
{
|
||||
var pmId = pm.GetId();
|
||||
return paymentMethodId.CryptoCode == pmId.CryptoCode &&
|
||||
((invoice.IsUnsetTopUp() && !storeBlob.OnChainWithLnInvoiceFallback) || pmId != lnurlId);
|
||||
});
|
||||
if (paymentMethodTemp == null)
|
||||
paymentMethodTemp = invoice.GetPaymentMethods().FirstOrDefault();
|
||||
.Where(p => displayedPaymentMethods.Contains(p.GetId()))
|
||||
.FirstOrDefault();
|
||||
if (paymentMethodTemp is null)
|
||||
return null;
|
||||
network = paymentMethodTemp.Network;
|
||||
paymentMethodId = paymentMethodTemp.GetId();
|
||||
}
|
||||
|
||||
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
|
||||
var paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
|
||||
if (!paymentMethodDetails.Activated)
|
||||
|
||||
// We activate the default payment method, and also those which aren't displayed (as they can't be set as default)
|
||||
bool activated = false;
|
||||
foreach (var pm in invoice.GetPaymentMethods())
|
||||
{
|
||||
if (await _invoiceActivator.ActivateInvoicePaymentMethod(paymentMethod.GetId(), invoice, store))
|
||||
var pmi = pm.GetId();
|
||||
if (pmi != paymentMethodId || !displayedPaymentMethods.Contains(pmi))
|
||||
continue;
|
||||
var pmd = pm.GetPaymentMethodDetails();
|
||||
if (!pmd.Activated)
|
||||
{
|
||||
return await GetInvoiceModel(invoiceId, paymentMethodId, lang);
|
||||
if (await _invoiceActivator.ActivateInvoicePaymentMethod(pmi, invoice, store))
|
||||
{
|
||||
activated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (activated)
|
||||
return await GetInvoiceModel(invoiceId, paymentMethodId, lang);
|
||||
|
||||
|
||||
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
|
||||
var paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
|
||||
var dto = invoice.EntityToDTO();
|
||||
var accounting = paymentMethod.Calculate();
|
||||
var paymentMethodHandler = _paymentMethodHandlerDictionary[paymentMethodId];
|
||||
@@ -773,11 +793,13 @@ namespace BTCPayServer.Controllers
|
||||
Request.Host,
|
||||
Request.PathBase) : null;
|
||||
|
||||
var isAltcoinsBuild = false;
|
||||
#if ALTCOINS
|
||||
isAltcoinsBuild = true;
|
||||
#endif
|
||||
|
||||
var model = new PaymentModel
|
||||
{
|
||||
#if ALTCOINS
|
||||
AltcoinsBuild = true,
|
||||
#endif
|
||||
Activated = paymentMethodDetails.Activated,
|
||||
CryptoCode = network.CryptoCode,
|
||||
RootPath = Request.PathBase.Value.WithTrailingSlash(),
|
||||
@@ -842,11 +864,15 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var availableCryptoPaymentMethodId = kv.GetId();
|
||||
var availableCryptoHandler = _paymentMethodHandlerDictionary[availableCryptoPaymentMethodId];
|
||||
var pmName = availableCryptoHandler.GetPaymentMethodName(availableCryptoPaymentMethodId);
|
||||
return new PaymentModel.AvailableCrypto
|
||||
{
|
||||
Displayed = displayedPaymentMethods.Contains(kv.GetId()),
|
||||
PaymentMethodId = kv.GetId().ToString(),
|
||||
CryptoCode = kv.Network?.CryptoCode ?? kv.GetId().CryptoCode,
|
||||
PaymentMethodName = availableCryptoHandler.GetPaymentMethodName(availableCryptoPaymentMethodId),
|
||||
PaymentMethodName = isAltcoinsBuild
|
||||
? pmName
|
||||
: pmName.Replace("Bitcoin (", "").Replace(")", "").Replace("Lightning ", ""),
|
||||
IsLightning =
|
||||
kv.GetId().PaymentType == PaymentTypes.LightningLike,
|
||||
CryptoImage = Request.GetRelativePathOrAbsolute(availableCryptoHandler.GetCryptoImage(availableCryptoPaymentMethodId)),
|
||||
@@ -862,17 +888,6 @@ namespace BTCPayServer.Controllers
|
||||
.ToList()
|
||||
};
|
||||
|
||||
// Exclude Lightning if OnChainWithLnInvoiceFallback is active and we have both payment methods
|
||||
if (storeBlob is { CheckoutType: CheckoutType.V2, OnChainWithLnInvoiceFallback: true })
|
||||
{
|
||||
var onchainPM = model.AvailableCryptos.Find(c => c.PaymentMethodId == btcId.ToString());
|
||||
var lightningPM = model.AvailableCryptos.Find(c => c.PaymentMethodId == lnId.ToString());
|
||||
if (onchainPM != null && lightningPM != null)
|
||||
{
|
||||
model.AvailableCryptos.Remove(lightningPM);
|
||||
}
|
||||
}
|
||||
|
||||
paymentMethodHandler.PreparePaymentModel(model, dto, storeBlob, paymentMethod);
|
||||
model.UISettings = paymentMethodHandler.GetCheckoutUISettings();
|
||||
model.PaymentMethodId = paymentMethodId.ToString();
|
||||
@@ -1286,10 +1301,20 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
case JTokenType.Array:
|
||||
var items = item.Value.AsEnumerable().ToList();
|
||||
var arrayResult = new List<object>();
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
result.TryAdd($"{item.Key}[{i}]", ParsePosData(items[i]));
|
||||
if (items[i] is JObject)
|
||||
{
|
||||
arrayResult.Add( ParsePosData(items[i]));
|
||||
}
|
||||
else
|
||||
{
|
||||
arrayResult.Add( items[i].ToString());
|
||||
}
|
||||
}
|
||||
|
||||
result.TryAdd(item.Key, arrayResult);
|
||||
|
||||
break;
|
||||
case JTokenType.Object:
|
||||
|
@@ -187,33 +187,36 @@ namespace BTCPayServer.Controllers
|
||||
return await CreateInvoiceCoreRaw(entity, store, excludeFilter, null, cancellationToken, entityManipulator);
|
||||
}
|
||||
|
||||
internal async Task<InvoiceEntity> CreatePaymentRequestInvoice(ViewPaymentRequestViewModel pr, decimal? amount, StoreData storeData, HttpRequest request, CancellationToken cancellationToken)
|
||||
internal async Task<InvoiceEntity> CreatePaymentRequestInvoice(Data.PaymentRequestData prData, decimal? amount, decimal amountDue, StoreData storeData, HttpRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (pr.AllowCustomPaymentAmounts && amount != null)
|
||||
amount = Math.Min(pr.AmountDue, amount.Value);
|
||||
var id = prData.Id;
|
||||
var prBlob = prData.GetBlob();
|
||||
if (prBlob.AllowCustomPaymentAmounts && amount != null)
|
||||
amount = Math.Min(amountDue, amount.Value);
|
||||
else
|
||||
amount = pr.AmountDue;
|
||||
var redirectUrl = _linkGenerator.PaymentRequestLink(pr.Id, request.Scheme, request.Host, request.PathBase);
|
||||
amount = amountDue;
|
||||
var redirectUrl = _linkGenerator.PaymentRequestLink(id, request.Scheme, request.Host, request.PathBase);
|
||||
|
||||
var invoiceMetadata =
|
||||
new InvoiceMetadata
|
||||
{
|
||||
OrderId = PaymentRequestRepository.GetOrderIdForPaymentRequest(pr.Id),
|
||||
PaymentRequestId = pr.Id,
|
||||
BuyerEmail = pr.Email
|
||||
};
|
||||
JObject invoiceMetadata = prData.GetBlob()?.FormResponse ?? new JObject();
|
||||
invoiceMetadata.Merge(new InvoiceMetadata
|
||||
{
|
||||
OrderId = PaymentRequestRepository.GetOrderIdForPaymentRequest(id),
|
||||
PaymentRequestId = id,
|
||||
BuyerEmail = invoiceMetadata.TryGetValue("buyerEmail", out var formEmail) && formEmail.Type == JTokenType.String ? formEmail.Value<string>():
|
||||
string.IsNullOrEmpty(prBlob.Email) ? null : prBlob.Email
|
||||
}.ToJObject(), new JsonMergeSettings() { MergeNullValueHandling = MergeNullValueHandling.Ignore });
|
||||
|
||||
var invoiceRequest =
|
||||
new CreateInvoiceRequest
|
||||
{
|
||||
Metadata = invoiceMetadata.ToJObject(),
|
||||
Currency = pr.Currency,
|
||||
Metadata = invoiceMetadata,
|
||||
Currency = prBlob.Currency,
|
||||
Amount = amount,
|
||||
Checkout = { RedirectURL = redirectUrl },
|
||||
Receipt = new InvoiceDataBase.ReceiptOptions { Enabled = false }
|
||||
};
|
||||
|
||||
var additionalTags = new List<string> { PaymentRequestRepository.GetInternalTag(pr.Id) };
|
||||
var additionalTags = new List<string> { PaymentRequestRepository.GetInternalTag(id) };
|
||||
return await CreateInvoiceCoreRaw(invoiceRequest, storeData, request.GetAbsoluteRoot(), additionalTags, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -246,6 +249,7 @@ namespace BTCPayServer.Controllers
|
||||
entity.RedirectAutomatically = invoice.Checkout.RedirectAutomatically ?? storeBlob.RedirectAutomatically;
|
||||
entity.CheckoutType = invoice.Checkout.CheckoutType;
|
||||
entity.RequiresRefundEmail = invoice.Checkout.RequiresRefundEmail;
|
||||
entity.LazyPaymentMethods = invoice.Checkout.LazyPaymentMethods ?? storeBlob.LazyPaymentMethods;
|
||||
IPaymentFilter? excludeFilter = null;
|
||||
if (invoice.Checkout.PaymentMethods != null)
|
||||
{
|
||||
@@ -458,7 +462,7 @@ namespace BTCPayServer.Controllers
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
|
||||
// Checkout v2 does not show a payment method switch for Bitcoin-only + BIP21, so exclude that case
|
||||
var preparePayment = storeBlob.LazyPaymentMethods && !storeBlob.OnChainWithLnInvoiceFallback
|
||||
var preparePayment = entity.LazyPaymentMethods && !storeBlob.OnChainWithLnInvoiceFallback
|
||||
? null
|
||||
: handler.PreparePayment(supportedPaymentMethod, store, network);
|
||||
var rate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, entity.Currency)];
|
||||
|
@@ -59,6 +59,7 @@ namespace BTCPayServer
|
||||
private readonly PullPaymentHostedService _pullPaymentHostedService;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
|
||||
private readonly IPluginHookService _pluginHookService;
|
||||
private readonly InvoiceActivator _invoiceActivator;
|
||||
|
||||
public UILNURLController(InvoiceRepository invoiceRepository,
|
||||
EventAggregator eventAggregator,
|
||||
@@ -72,7 +73,8 @@ namespace BTCPayServer
|
||||
LightningLikePayoutHandler lightningLikePayoutHandler,
|
||||
PullPaymentHostedService pullPaymentHostedService,
|
||||
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
|
||||
IPluginHookService pluginHookService)
|
||||
IPluginHookService pluginHookService,
|
||||
InvoiceActivator invoiceActivator)
|
||||
{
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_eventAggregator = eventAggregator;
|
||||
@@ -87,6 +89,7 @@ namespace BTCPayServer
|
||||
_pullPaymentHostedService = pullPaymentHostedService;
|
||||
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
|
||||
_pluginHookService = pluginHookService;
|
||||
_invoiceActivator = invoiceActivator;
|
||||
}
|
||||
|
||||
[HttpGet("withdraw/pp/{pullPaymentId}")]
|
||||
@@ -436,12 +439,16 @@ namespace BTCPayServer
|
||||
List<string> additionalTags = null,
|
||||
bool allowOverpay = true)
|
||||
{
|
||||
if (GetLNUrlPaymentMethodId(cryptoCode, store, out _) is null)
|
||||
var pmi = GetLNUrlPaymentMethodId(cryptoCode, store, out _);
|
||||
if (pmi is null)
|
||||
return NotFound("LNUrl or LN is disabled");
|
||||
|
||||
InvoiceEntity i;
|
||||
try
|
||||
{
|
||||
createInvoice.Checkout ??= new InvoiceDataBase.CheckoutOptions();
|
||||
createInvoice.Checkout.LazyPaymentMethods = false;
|
||||
createInvoice.Checkout.PaymentMethods = new[] { pmi.ToStringNormalized() };
|
||||
i = await _invoiceController.CreateInvoiceCoreRaw(createInvoice, store, Request.GetAbsoluteRoot(), additionalTags);
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -468,6 +475,8 @@ namespace BTCPayServer
|
||||
lnUrlMetadata ??= new Dictionary<string, string>();
|
||||
|
||||
var pm = i.GetPaymentMethod(pmi);
|
||||
if (pm is null)
|
||||
return null;
|
||||
var paymentMethodDetails = (LNURLPayPaymentMethodDetails)pm.GetPaymentMethodDetails();
|
||||
bool updatePaymentMethodDetails = false;
|
||||
if (lnUrlMetadata?.TryGetValue("text/identifier", out var lnAddress) is true && lnAddress is not null)
|
||||
@@ -494,7 +503,7 @@ namespace BTCPayServer
|
||||
lnurlRequest.Metadata = JsonConvert.SerializeObject(lnUrlMetadata.Select(kv => new[] { kv.Key, kv.Value }));
|
||||
if (i.Type != InvoiceType.TopUp)
|
||||
{
|
||||
lnurlRequest.MinSendable = new LightMoney(i.GetPaymentMethod(pmi).Calculate().Due.ToDecimal(MoneyUnit.Satoshi), LightMoneyUnit.Satoshi);
|
||||
lnurlRequest.MinSendable = new LightMoney(pm.Calculate().Due.ToDecimal(MoneyUnit.Satoshi), LightMoneyUnit.Satoshi);
|
||||
if (!allowOverpay)
|
||||
lnurlRequest.MaxSendable = lnurlRequest.MinSendable;
|
||||
}
|
||||
@@ -570,6 +579,15 @@ namespace BTCPayServer
|
||||
var lightningPaymentMethod = i.GetPaymentMethod(pmi);
|
||||
var paymentMethodDetails =
|
||||
lightningPaymentMethod?.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails;
|
||||
if (paymentMethodDetails is not null && !paymentMethodDetails.Activated)
|
||||
{
|
||||
if (!await _invoiceActivator.ActivateInvoicePaymentMethod(pmi, i, store))
|
||||
return NotFound();
|
||||
i = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||
lightningPaymentMethod = i.GetPaymentMethod(pmi);
|
||||
paymentMethodDetails = lightningPaymentMethod.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails;
|
||||
}
|
||||
|
||||
if (paymentMethodDetails?.LightningSupportedPaymentMethod is null)
|
||||
return NotFound();
|
||||
|
||||
|
@@ -235,6 +235,14 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
var form = Form.Parse(formData.Config);
|
||||
if (!string.IsNullOrEmpty(prBlob.Email))
|
||||
{
|
||||
var emailField = form.GetFieldByFullName("buyerEmail");
|
||||
if (emailField is not null)
|
||||
{
|
||||
emailField.Value = prBlob.Email;
|
||||
}
|
||||
}
|
||||
if (Request.Method == "POST" && Request.HasFormContentType)
|
||||
{
|
||||
form.ApplyValuesFromForm(Request.Form);
|
||||
@@ -277,7 +285,6 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
return BadRequest("Payment Request cannot be paid as it has been archived");
|
||||
}
|
||||
|
||||
if (!result.FormSubmitted && !string.IsNullOrEmpty(result.FormId))
|
||||
{
|
||||
var formData = await FormDataService.GetForm(result.FormId);
|
||||
@@ -322,7 +329,8 @@ namespace BTCPayServer.Controllers
|
||||
try
|
||||
{
|
||||
var store = await _storeRepository.FindStore(result.StoreId);
|
||||
var newInvoice = await _InvoiceController.CreatePaymentRequestInvoice(result, amount, store, Request, cancellationToken);
|
||||
var prData = await _PaymentRequestRepository.FindPaymentRequest(result.Id, null);
|
||||
var newInvoice = await _InvoiceController.CreatePaymentRequestInvoice(prData, amount, result.AmountDue, store, Request, cancellationToken);
|
||||
if (redirectToInvoice)
|
||||
{
|
||||
return RedirectToAction("Checkout", "UIInvoice", new { invoiceId = newInvoice.Id });
|
||||
|
@@ -181,7 +181,6 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
CryptoCode = vm.CryptoCode,
|
||||
UseBech32Scheme = true,
|
||||
EnableForStandardInvoices = false,
|
||||
LUD12Enabled = false
|
||||
});
|
||||
|
||||
@@ -245,26 +244,12 @@ namespace BTCPayServer.Controllers
|
||||
};
|
||||
SetExistingValues(store, vm);
|
||||
|
||||
if (lightning != null)
|
||||
{
|
||||
vm.DisableBolt11PaymentMethod = lightning.DisableBOLT11PaymentOption;
|
||||
}
|
||||
|
||||
var lnurl = GetExistingLNURLSupportedPaymentMethod(vm.CryptoCode, store);
|
||||
if (lnurl != null)
|
||||
{
|
||||
vm.LNURLEnabled = !store.GetStoreBlob().GetExcludedPaymentMethods().Match(lnurl.PaymentId);
|
||||
vm.LNURLBech32Mode = lnurl.UseBech32Scheme;
|
||||
vm.LNURLStandardInvoiceEnabled = lnurl.EnableForStandardInvoices;
|
||||
vm.LUD12Enabled = lnurl.LUD12Enabled;
|
||||
vm.DisableBolt11PaymentMethod =
|
||||
vm.LNURLEnabled && vm.LNURLStandardInvoiceEnabled && vm.DisableBolt11PaymentMethod;
|
||||
}
|
||||
else
|
||||
{
|
||||
//disable by default for now
|
||||
//vm.LNURLEnabled = !lnSet;
|
||||
vm.DisableBolt11PaymentMethod = false;
|
||||
}
|
||||
|
||||
return View(vm);
|
||||
@@ -290,22 +275,11 @@ namespace BTCPayServer.Controllers
|
||||
blob.LightningAmountInSatoshi = vm.LightningAmountInSatoshi;
|
||||
blob.LightningPrivateRouteHints = vm.LightningPrivateRouteHints;
|
||||
blob.OnChainWithLnInvoiceFallback = vm.OnChainWithLnInvoiceFallback;
|
||||
var disableBolt11PaymentMethod =
|
||||
vm.LNURLEnabled && vm.LNURLStandardInvoiceEnabled && vm.DisableBolt11PaymentMethod;
|
||||
var lnurlId = new PaymentMethodId(vm.CryptoCode, PaymentTypes.LNURLPay);
|
||||
blob.SetExcluded(lnurlId, !vm.LNURLEnabled);
|
||||
var lightning = GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store);
|
||||
// Going to mark "lightning" as non-null here assuming that if we are POSTing here it's because we have a Lightning Node set-up
|
||||
if (lightning!.DisableBOLT11PaymentOption != disableBolt11PaymentMethod)
|
||||
{
|
||||
needUpdate = true;
|
||||
lightning.DisableBOLT11PaymentOption = disableBolt11PaymentMethod;
|
||||
store.SetSupportedPaymentMethod(lightning);
|
||||
}
|
||||
|
||||
var lnurl = GetExistingLNURLSupportedPaymentMethod(vm.CryptoCode, store);
|
||||
if (lnurl is null || (
|
||||
lnurl.EnableForStandardInvoices != vm.LNURLStandardInvoiceEnabled ||
|
||||
lnurl.UseBech32Scheme != vm.LNURLBech32Mode ||
|
||||
lnurl.LUD12Enabled != vm.LUD12Enabled))
|
||||
{
|
||||
@@ -315,7 +289,6 @@ namespace BTCPayServer.Controllers
|
||||
store.SetSupportedPaymentMethod(new LNURLPaySupportedPaymentMethod
|
||||
{
|
||||
CryptoCode = vm.CryptoCode,
|
||||
EnableForStandardInvoices = vm.LNURLStandardInvoiceEnabled,
|
||||
UseBech32Scheme = vm.LNURLBech32Mode,
|
||||
LUD12Enabled = vm.LUD12Enabled
|
||||
});
|
||||
|
@@ -32,7 +32,7 @@ namespace BTCPayServer.HostedServices
|
||||
foreach (var t in _Tasks)
|
||||
t.ContinueWith(t =>
|
||||
{
|
||||
if (t.IsFaulted)
|
||||
if (t.IsFaulted && !CancellationToken.IsCancellationRequested)
|
||||
Logs.PayServer.LogWarning(t.Exception, $"Unhanded exception in {this.GetType().Name}");
|
||||
}, TaskScheduler.Default);
|
||||
return Task.CompletedTask;
|
||||
@@ -40,7 +40,7 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
internal abstract Task[] InitializeTasks();
|
||||
|
||||
protected CancellationToken Cancellation
|
||||
protected CancellationToken CancellationToken
|
||||
{
|
||||
get { return _Cts.Token; }
|
||||
}
|
||||
@@ -69,8 +69,6 @@ namespace BTCPayServer.HostedServices
|
||||
}
|
||||
}
|
||||
|
||||
public CancellationToken CancellationToken => _Cts.Token;
|
||||
|
||||
public virtual async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_Cts != null)
|
||||
|
@@ -43,7 +43,7 @@ namespace BTCPayServer.HostedServices
|
||||
readonly InvoiceRepository _InvoiceRepository;
|
||||
private readonly EmailSenderFactory _EmailSenderFactory;
|
||||
private readonly StoreRepository _StoreRepository;
|
||||
|
||||
public const string NamedClient = "bitpay-ipn";
|
||||
public BitpayIPNSender(
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IBackgroundJobClient jobClient,
|
||||
@@ -52,7 +52,7 @@ namespace BTCPayServer.HostedServices
|
||||
StoreRepository storeRepository,
|
||||
EmailSenderFactory emailSenderFactory)
|
||||
{
|
||||
_Client = httpClientFactory.CreateClient();
|
||||
_Client = httpClientFactory.CreateClient(NamedClient);
|
||||
_JobClient = jobClient;
|
||||
_EventAggregator = eventAggregator;
|
||||
_InvoiceRepository = invoiceRepository;
|
||||
@@ -232,7 +232,6 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
request.RequestUri = new Uri(notification.NotificationURL, UriKind.Absolute);
|
||||
request.Content = new StringContent(notificationString, UTF8, "application/json");
|
||||
|
||||
using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
cts.CancelAfter(TimeSpan.FromMinutes(1.0));
|
||||
var response = await _Client.SendAsync(request, cts.Token);
|
||||
|
@@ -28,8 +28,8 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await _transactionBroadcaster.ProcessAll(Cancellation);
|
||||
await Task.Delay(PollInternal, Cancellation);
|
||||
await _transactionBroadcaster.ProcessAll(CancellationToken);
|
||||
await Task.Delay(PollInternal, CancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@ namespace BTCPayServer.HostedServices
|
||||
readonly TimeSpan Period = TimeSpan.FromMinutes(60);
|
||||
async Task UpdateRecord()
|
||||
{
|
||||
using (var timeout = CancellationTokenSource.CreateLinkedTokenSource(Cancellation))
|
||||
using (var timeout = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken))
|
||||
{
|
||||
var settings = await SettingsRepository.GetSettingAsync<DynamicDnsSettings>() ?? new DynamicDnsSettings();
|
||||
foreach (var service in settings.Services)
|
||||
@@ -59,9 +59,9 @@ namespace BTCPayServer.HostedServices
|
||||
}
|
||||
}
|
||||
}
|
||||
using var delayCancel = CancellationTokenSource.CreateLinkedTokenSource(Cancellation);
|
||||
using var delayCancel = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken);
|
||||
var delay = Task.Delay(Period, delayCancel.Token);
|
||||
var changed = SettingsRepository.WaitSettingsChanged<DynamicDnsSettings>(Cancellation);
|
||||
var changed = SettingsRepository.WaitSettingsChanged<DynamicDnsSettings>(CancellationToken);
|
||||
await Task.WhenAny(delay, changed);
|
||||
delayCancel.Cancel();
|
||||
}
|
||||
|
@@ -44,7 +44,7 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
Logs.Events.LogError(ex, "Error while performing new version check");
|
||||
}
|
||||
await Task.Delay(TimeSpan.FromDays(1), Cancellation);
|
||||
await Task.Delay(TimeSpan.FromDays(1), CancellationToken);
|
||||
}
|
||||
|
||||
public async Task ProcessVersionCheck()
|
||||
@@ -52,7 +52,7 @@ namespace BTCPayServer.HostedServices
|
||||
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
|
||||
if (policies.CheckForNewVersions)
|
||||
{
|
||||
var tag = await _versionFetcher.Fetch(Cancellation);
|
||||
var tag = await _versionFetcher.Fetch(CancellationToken);
|
||||
if (tag != null && tag != _env.Version)
|
||||
{
|
||||
var dh = await _settingsRepository.GetSettingAsync<NewVersionCheckerDataHolder>() ?? new NewVersionCheckerDataHolder();
|
||||
|
@@ -64,10 +64,10 @@ namespace BTCPayServer.HostedServices
|
||||
var usedProviders = GetStillUsedProviders().ToArray();
|
||||
if (usedProviders.Length == 0)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(30), Cancellation);
|
||||
await Task.Delay(TimeSpan.FromSeconds(30), CancellationToken);
|
||||
return;
|
||||
}
|
||||
using (var timeout = CancellationTokenSource.CreateLinkedTokenSource(Cancellation))
|
||||
using (var timeout = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken))
|
||||
{
|
||||
timeout.CancelAfter(TimeSpan.FromSeconds(20.0));
|
||||
try
|
||||
@@ -97,7 +97,7 @@ namespace BTCPayServer.HostedServices
|
||||
await SaveRateCache();
|
||||
}
|
||||
}
|
||||
await Task.Delay(TimeSpan.FromSeconds(30), Cancellation);
|
||||
await Task.Delay(TimeSpan.FromSeconds(30), CancellationToken);
|
||||
}
|
||||
|
||||
public override async Task StartAsync(CancellationToken cancellationToken)
|
||||
|
@@ -13,6 +13,7 @@ using BTCPayServer.Controllers.Greenfield;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -31,6 +32,7 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
readonly Encoding UTF8 = new UTF8Encoding(false);
|
||||
public readonly static JsonSerializerSettings DefaultSerializerSettings;
|
||||
|
||||
static WebhookSender()
|
||||
{
|
||||
DefaultSerializerSettings = WebhookEvent.DefaultSerializerSettings;
|
||||
@@ -38,6 +40,7 @@ namespace BTCPayServer.HostedServices
|
||||
public const string OnionNamedClient = "greenfield-webhook.onion";
|
||||
public const string ClearnetNamedClient = "greenfield-webhook.clearnet";
|
||||
public const string LoopbackNamedClient = "greenfield-webhook.loopback";
|
||||
public static string[] AllClients = new[] { OnionNamedClient, ClearnetNamedClient, LoopbackNamedClient };
|
||||
private HttpClient GetClient(Uri uri)
|
||||
{
|
||||
return HttpClientFactory.CreateClient(uri.IsOnion() ? OnionNamedClient : uri.IsLoopback ? LoopbackNamedClient : ClearnetNamedClient);
|
||||
|
@@ -477,6 +477,17 @@ namespace BTCPayServer.Hosting
|
||||
services.AddSingleton<Cheater>();
|
||||
services.AddSingleton<IHostedService, Cheater>(o => o.GetRequiredService<Cheater>());
|
||||
}
|
||||
|
||||
var userAgent = new System.Net.Http.Headers.ProductInfoHeaderValue("BTCPayServer", BTCPayServerEnvironment.GetInformationalVersion());
|
||||
foreach (var clientName in WebhookSender.AllClients.Concat(new[] { BitpayIPNSender.NamedClient }))
|
||||
{
|
||||
services.AddHttpClient(clientName)
|
||||
.ConfigureHttpClient(client =>
|
||||
{
|
||||
client.DefaultRequestHeaders.UserAgent.Add(userAgent);
|
||||
});
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
|
@@ -21,6 +21,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
public string PaymentMethodName { get; set; }
|
||||
public bool IsLightning { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public bool Displayed { get; set; }
|
||||
}
|
||||
public string CustomCSSLink { get; set; }
|
||||
public string CustomLogoLink { get; set; }
|
||||
@@ -77,7 +78,6 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
public bool Activated { get; set; }
|
||||
public string InvoiceCurrency { get; set; }
|
||||
public string ReceiptLink { get; set; }
|
||||
public bool AltcoinsBuild { get; set; }
|
||||
public CheckoutType CheckoutType { get; set; }
|
||||
public int? RequiredConfirmations { get; set; }
|
||||
public long? ReceivedConfirmations { get; set; }
|
||||
|
@@ -24,13 +24,7 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
[Display(Name = "LNURL Classic Mode")]
|
||||
public bool LNURLBech32Mode { get; set; } = true;
|
||||
|
||||
[Display(Name = "LNURL enabled for standard invoices")]
|
||||
public bool LNURLStandardInvoiceEnabled { get; set; } = true;
|
||||
|
||||
[Display(Name = "Allow payee to pass a comment")]
|
||||
public bool LUD12Enabled { get; set; }
|
||||
|
||||
[Display(Name = "Do not offer BOLT11 for standard invoices")]
|
||||
public bool DisableBolt11PaymentMethod { get; set; }
|
||||
}
|
||||
}
|
||||
|
@@ -90,11 +90,6 @@ namespace BTCPayServer.Payments
|
||||
};
|
||||
}
|
||||
|
||||
public PaymentMethod GetPaymentMethodInInvoice(InvoiceEntity invoice, PaymentMethodId paymentMethodId)
|
||||
{
|
||||
return invoice.GetPaymentMethod(paymentMethodId);
|
||||
}
|
||||
|
||||
public virtual object PreparePayment(TSupportedPaymentMethod supportedPaymentMethod, StoreData store,
|
||||
BTCPayNetworkBase network)
|
||||
{
|
||||
|
@@ -50,55 +50,21 @@ namespace BTCPayServer.Payments.Lightning
|
||||
BTCPayNetwork network, object preparePaymentObject, IEnumerable<PaymentMethodId> invoicePaymentMethods)
|
||||
{
|
||||
var lnPmi = new PaymentMethodId(supportedPaymentMethod.CryptoCode, PaymentTypes.LightningLike);
|
||||
if (!supportedPaymentMethod.EnableForStandardInvoices &&
|
||||
paymentMethod.ParentEntity.Type == InvoiceType.Standard &&
|
||||
invoicePaymentMethods.Contains(lnPmi))
|
||||
var lnSupported = store.GetSupportedPaymentMethods(_networkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.SingleOrDefault(method => method.PaymentId == lnPmi);
|
||||
if (lnSupported is null)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException("LNURL is not enabled for standard invoices");
|
||||
}
|
||||
if (string.IsNullOrEmpty(paymentMethod.ParentEntity.Id))
|
||||
{
|
||||
var lnSupported = store.GetSupportedPaymentMethods(_networkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>().SingleOrDefault(method =>
|
||||
method.PaymentId.CryptoCode == supportedPaymentMethod.CryptoCode &&
|
||||
method.PaymentId.PaymentType == LightningPaymentType.Instance);
|
||||
|
||||
if (lnSupported is null)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException("LNURL requires a lightning node to be configured for the store.");
|
||||
}
|
||||
using var cts = new CancellationTokenSource(LightningLikePaymentHandler.LightningTimeout);
|
||||
try
|
||||
{
|
||||
var client = lnSupported.CreateLightningClient(network, Options.Value, _lightningClientFactoryService);
|
||||
await client.GetInfo(cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException) when (cts.IsCancellationRequested)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException("The lightning node did not reply in a timely manner");
|
||||
}
|
||||
|
||||
return new LNURLPayPaymentMethodDetails()
|
||||
{
|
||||
Activated = false,
|
||||
LightningSupportedPaymentMethod = lnSupported
|
||||
};
|
||||
throw new PaymentMethodUnavailableException("LNURL requires a lightning node to be configured for the store.");
|
||||
}
|
||||
|
||||
var client = lnSupported.CreateLightningClient(network, Options.Value, _lightningClientFactoryService);
|
||||
var nodeInfo = (await _lightningLikePaymentHandler.GetNodeInfo(lnSupported, _networkProvider.GetNetwork<BTCPayNetwork>(supportedPaymentMethod.CryptoCode), logs, paymentMethod.PreferOnion)).FirstOrDefault();
|
||||
|
||||
var lnLightningSupportedPaymentMethod =
|
||||
((LNURLPayPaymentMethodDetails)paymentMethod.GetPaymentMethodDetails()).LightningSupportedPaymentMethod;
|
||||
|
||||
NodeInfo? nodeInfo = null;
|
||||
if (lnLightningSupportedPaymentMethod != null)
|
||||
{
|
||||
nodeInfo = (await _lightningLikePaymentHandler.GetNodeInfo(lnLightningSupportedPaymentMethod, _networkProvider.GetNetwork<BTCPayNetwork>(supportedPaymentMethod.CryptoCode), logs, paymentMethod.PreferOnion)).FirstOrDefault();
|
||||
}
|
||||
|
||||
return new LNURLPayPaymentMethodDetails
|
||||
return new LNURLPayPaymentMethodDetails()
|
||||
{
|
||||
Activated = true,
|
||||
LightningSupportedPaymentMethod = lnLightningSupportedPaymentMethod,
|
||||
LightningSupportedPaymentMethod = lnSupported,
|
||||
Bech32Mode = supportedPaymentMethod.UseBech32Scheme,
|
||||
NodeInfo = nodeInfo?.ToString()
|
||||
};
|
||||
|
@@ -14,8 +14,6 @@ namespace BTCPayServer.Payments.Lightning
|
||||
|
||||
public bool UseBech32Scheme { get; set; }
|
||||
|
||||
public bool EnableForStandardInvoices { get; set; } = false;
|
||||
|
||||
public bool LUD12Enabled { get; set; } = true;
|
||||
|
||||
}
|
||||
|
@@ -47,7 +47,6 @@ namespace BTCPayServer.Payments
|
||||
return new LNURLPayPaymentMethodBaseData()
|
||||
{
|
||||
UseBech32Scheme = lightningSupportedPaymentMethod.UseBech32Scheme,
|
||||
EnableForStandardInvoices = lightningSupportedPaymentMethod.EnableForStandardInvoices,
|
||||
LUD12Enabled = lightningSupportedPaymentMethod.LUD12Enabled
|
||||
};
|
||||
return null;
|
||||
|
@@ -53,10 +53,6 @@ namespace BTCPayServer.Payments.Lightning
|
||||
LightningSupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, Data.StoreData store,
|
||||
BTCPayNetwork network, object preparePaymentObject, IEnumerable<PaymentMethodId> invoicePaymentMethods)
|
||||
{
|
||||
if (supportedPaymentMethod.DisableBOLT11PaymentOption)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException("BOLT11 payment method is disabled");
|
||||
}
|
||||
if (paymentMethod.ParentEntity.Type == InvoiceType.TopUp)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException("Lightning Network payment method is not available for top-up invoices");
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
@@ -38,7 +39,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly PaymentService _paymentService;
|
||||
readonly Channel<string> _CheckInvoices = Channel.CreateUnbounded<string>();
|
||||
Task _CheckingInvoice;
|
||||
Task? _CheckingInvoice;
|
||||
readonly Dictionary<(string, string), LightningInstanceListener> _InstanceListeners = new Dictionary<(string, string), LightningInstanceListener>();
|
||||
|
||||
public LightningListener(EventAggregator aggregator,
|
||||
@@ -87,12 +88,15 @@ namespace BTCPayServer.Payments.Lightning
|
||||
var invoice = await GetInvoice(invoiceId);
|
||||
foreach (var listenedInvoice in GetListenedInvoices(invoice))
|
||||
{
|
||||
var instanceListenerKey = (listenedInvoice.Network.CryptoCode, GetLightningUrl(listenedInvoice.SupportedPaymentMethod).ToString());
|
||||
var connStr = GetLightningUrl(listenedInvoice.SupportedPaymentMethod);
|
||||
if (connStr is null)
|
||||
continue;
|
||||
var instanceListenerKey = (listenedInvoice.Network.CryptoCode, connStr.ToString());
|
||||
lock (_InstanceListeners)
|
||||
{
|
||||
if (!_InstanceListeners.TryGetValue(instanceListenerKey, out var instanceListener))
|
||||
{
|
||||
instanceListener ??= new LightningInstanceListener(_InvoiceRepository, _Aggregator, lightningClientFactory, listenedInvoice.Network, GetLightningUrl(listenedInvoice.SupportedPaymentMethod), _paymentService, Logs);
|
||||
instanceListener ??= new LightningInstanceListener(_InvoiceRepository, _Aggregator, lightningClientFactory, listenedInvoice.Network, connStr, _paymentService, Logs);
|
||||
_InstanceListeners.TryAdd(instanceListenerKey, instanceListener);
|
||||
}
|
||||
instanceListener.AddListenedInvoice(listenedInvoice);
|
||||
@@ -143,7 +147,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
.Where(c => new[] { PaymentTypes.LightningLike, LNURLPayPaymentType.Instance }.Contains(c.GetId().PaymentType)))
|
||||
{
|
||||
LightningLikePaymentMethodDetails lightningMethod;
|
||||
LightningSupportedPaymentMethod lightningSupportedMethod;
|
||||
LightningSupportedPaymentMethod? lightningSupportedMethod;
|
||||
switch (paymentMethod.GetPaymentMethodDetails())
|
||||
{
|
||||
case LNURLPayPaymentMethodDetails lnurlPayPaymentMethodDetails:
|
||||
@@ -167,16 +171,17 @@ namespace BTCPayServer.Payments.Lightning
|
||||
continue;
|
||||
var network = _NetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethod.GetId().CryptoCode);
|
||||
|
||||
listenedInvoices.Add(new ListenedInvoice()
|
||||
{
|
||||
Expiration = invoice.ExpirationTime,
|
||||
Uri = GetLightningUrl(lightningSupportedMethod).BaseUri.AbsoluteUri,
|
||||
PaymentMethodDetails = lightningMethod,
|
||||
SupportedPaymentMethod = lightningSupportedMethod,
|
||||
PaymentMethod = paymentMethod,
|
||||
Network = network,
|
||||
InvoiceId = invoice.Id
|
||||
});
|
||||
var lnUri = GetLightningUrl(lightningSupportedMethod);
|
||||
if (lnUri == null)
|
||||
continue;
|
||||
listenedInvoices.Add(new ListenedInvoice(
|
||||
lnUri.BaseUri,
|
||||
invoice.ExpirationTime,
|
||||
lightningMethod,
|
||||
lightningSupportedMethod,
|
||||
paymentMethod,
|
||||
network,
|
||||
invoice.Id));
|
||||
}
|
||||
return listenedInvoices;
|
||||
}
|
||||
@@ -221,7 +226,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
}));
|
||||
leases.Add(_Aggregator.Subscribe<Events.InvoiceNewPaymentDetailsEvent>(inv =>
|
||||
{
|
||||
if (inv.PaymentMethodId.PaymentType == LNURLPayPaymentType.Instance)
|
||||
if (inv.PaymentMethodId.PaymentType == LNURLPayPaymentType.Instance && !string.IsNullOrEmpty(inv.InvoiceId))
|
||||
{
|
||||
_memoryCache.Remove(GetCacheKey(inv.InvoiceId));
|
||||
_CheckInvoices.Writer.TryWrite(inv.InvoiceId);
|
||||
@@ -262,6 +267,8 @@ namespace BTCPayServer.Payments.Lightning
|
||||
.Where(method => new[] { PaymentTypes.LightningLike, LNURLPayPaymentType.Instance }.Contains(method.GetId().PaymentType))
|
||||
.ToArray();
|
||||
var store = await _storeRepository.FindStore(invoice.StoreId);
|
||||
if (store is null)
|
||||
return;
|
||||
if (paymentMethods.Any())
|
||||
{
|
||||
var logs = new InvoiceLogs();
|
||||
@@ -334,9 +341,11 @@ namespace BTCPayServer.Payments.Lightning
|
||||
.CreatePaymentMethodDetails(logs, supportedMethod, paymentMethod, store,
|
||||
paymentMethod.Network, prepObj, pmis));
|
||||
|
||||
var instanceListenerKey = (paymentMethod.Network.CryptoCode,
|
||||
GetLightningUrl(supportedMethod).ToString());
|
||||
LightningInstanceListener instanceListener;
|
||||
var connStr = GetLightningUrl(supportedMethod);
|
||||
if (connStr is null)
|
||||
continue;
|
||||
var instanceListenerKey = (paymentMethod.Network.CryptoCode, connStr.ToString());
|
||||
LightningInstanceListener? instanceListener;
|
||||
lock (_InstanceListeners)
|
||||
{
|
||||
_InstanceListeners.TryGetValue(instanceListenerKey, out instanceListener);
|
||||
@@ -346,16 +355,17 @@ namespace BTCPayServer.Payments.Lightning
|
||||
await _InvoiceRepository.NewPaymentDetails(invoice.Id, newPaymentMethodDetails,
|
||||
paymentMethod.Network);
|
||||
|
||||
instanceListener.AddListenedInvoice(new ListenedInvoice()
|
||||
{
|
||||
Expiration = invoice.ExpirationTime,
|
||||
Uri = GetLightningUrl(supportedMethod).BaseUri.AbsoluteUri,
|
||||
PaymentMethodDetails = newPaymentMethodDetails,
|
||||
SupportedPaymentMethod = supportedMethod,
|
||||
PaymentMethod = paymentMethod,
|
||||
Network = (BTCPayNetwork)paymentMethod.Network,
|
||||
InvoiceId = invoice.Id
|
||||
});
|
||||
var url = GetLightningUrl(supportedMethod);
|
||||
if (url is null)
|
||||
continue;
|
||||
instanceListener.AddListenedInvoice(new ListenedInvoice(
|
||||
url.BaseUri,
|
||||
invoice.ExpirationTime,
|
||||
newPaymentMethodDetails,
|
||||
supportedMethod,
|
||||
paymentMethod,
|
||||
(BTCPayNetwork)paymentMethod.Network,
|
||||
invoice.Id));
|
||||
|
||||
_Aggregator.Publish(new Events.InvoiceNewPaymentDetailsEvent(invoice.Id,
|
||||
newPaymentMethodDetails, paymentMethod.GetId()));
|
||||
@@ -374,14 +384,14 @@ namespace BTCPayServer.Payments.Lightning
|
||||
|
||||
}
|
||||
|
||||
private LightningConnectionString GetLightningUrl(LightningSupportedPaymentMethod supportedMethod)
|
||||
private LightningConnectionString? GetLightningUrl(LightningSupportedPaymentMethod supportedMethod)
|
||||
{
|
||||
var url = supportedMethod.GetExternalLightningUrl();
|
||||
if (url != null)
|
||||
return url;
|
||||
if (Options.Value.InternalLightningByCryptoCode.TryGetValue(supportedMethod.CryptoCode, out var conn))
|
||||
return conn;
|
||||
throw new InvalidOperationException($"{supportedMethod.CryptoCode}: The internal lightning node is not set up");
|
||||
return null;
|
||||
}
|
||||
|
||||
TimeSpan _PollInterval = TimeSpan.FromMinutes(1.0);
|
||||
@@ -400,7 +410,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
}
|
||||
}
|
||||
}
|
||||
private Timer _ListenPoller;
|
||||
private Timer? _ListenPoller;
|
||||
|
||||
public IOptions<LightningNetworkOptions> Options { get; }
|
||||
|
||||
@@ -412,7 +422,8 @@ namespace BTCPayServer.Payments.Lightning
|
||||
_Cts.Cancel();
|
||||
try
|
||||
{
|
||||
await _CheckingInvoice;
|
||||
if (_CheckingInvoice != null)
|
||||
await _CheckingInvoice;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -420,7 +431,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
}
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(_ListeningInstances.Select(c => c.Value.Listening).ToArray());
|
||||
await Task.WhenAll(_ListeningInstances.Select(c => c.Value.Listening).Where(c => c != null).ToArray()!);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -479,7 +490,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
|
||||
public bool Empty => _ListenedInvoices.IsEmpty;
|
||||
public bool IsListening => Listening?.Status is TaskStatus.Running || Listening?.Status is TaskStatus.WaitingForActivation;
|
||||
public Task Listening { get; set; }
|
||||
public Task? Listening { get; set; }
|
||||
public void EnsureListening(CancellationToken cancellation)
|
||||
{
|
||||
if (!IsListening)
|
||||
@@ -490,7 +501,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
Listening = Listen(StopListeningCancellationTokenSource.Token);
|
||||
}
|
||||
}
|
||||
public CancellationTokenSource StopListeningCancellationTokenSource;
|
||||
public CancellationTokenSource? StopListeningCancellationTokenSource;
|
||||
async Task Listen(CancellationToken cancellation)
|
||||
{
|
||||
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Start listening {ConnectionString.BaseUri}");
|
||||
@@ -566,6 +577,8 @@ namespace BTCPayServer.Payments.Lightning
|
||||
|
||||
public async Task<bool> AddPayment(LightningInvoice notification, string invoiceId, PaymentType paymentType)
|
||||
{
|
||||
if (notification?.PaidAt is null)
|
||||
return false;
|
||||
var payment = await _paymentService.AddPayment(invoiceId, notification.PaidAt.Value, new LightningLikePaymentData
|
||||
{
|
||||
BOLT11 = notification.BOLT11,
|
||||
@@ -595,15 +608,15 @@ namespace BTCPayServer.Payments.Lightning
|
||||
}
|
||||
}
|
||||
|
||||
class ListenedInvoice
|
||||
public record ListenedInvoice(
|
||||
Uri Uri,
|
||||
DateTimeOffset Expiration,
|
||||
LightningLikePaymentMethodDetails PaymentMethodDetails,
|
||||
LightningSupportedPaymentMethod SupportedPaymentMethod,
|
||||
PaymentMethod PaymentMethod,
|
||||
BTCPayNetwork Network,
|
||||
string InvoiceId)
|
||||
{
|
||||
public bool IsExpired() { return DateTimeOffset.UtcNow > Expiration; }
|
||||
public DateTimeOffset Expiration { get; set; }
|
||||
public LightningLikePaymentMethodDetails PaymentMethodDetails { get; set; }
|
||||
public LightningSupportedPaymentMethod SupportedPaymentMethod { get; set; }
|
||||
public PaymentMethod PaymentMethod { get; set; }
|
||||
public string Uri { get; internal set; }
|
||||
public BTCPayNetwork Network { get; internal set; }
|
||||
public string InvoiceId { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
@@ -101,7 +101,7 @@ public class LightningPendingPayoutListener : BaseAsyncService
|
||||
break;
|
||||
case PayoutLightningBlob payoutLightningBlob:
|
||||
{
|
||||
var payment = await client.GetPayment(payoutLightningBlob.Id, Cancellation);
|
||||
var payment = await client.GetPayment(payoutLightningBlob.Id, CancellationToken);
|
||||
if (payment is null)
|
||||
{
|
||||
continue;
|
||||
@@ -126,8 +126,8 @@ public class LightningPendingPayoutListener : BaseAsyncService
|
||||
}
|
||||
}
|
||||
|
||||
await context.SaveChangesAsync(Cancellation);
|
||||
await Task.Delay(TimeSpan.FromSeconds(SecondsDelay), Cancellation);
|
||||
await context.SaveChangesAsync(CancellationToken);
|
||||
await Task.Delay(TimeSpan.FromSeconds(SecondsDelay), CancellationToken);
|
||||
}
|
||||
|
||||
internal override Task[] InitializeTasks()
|
||||
|
@@ -17,8 +17,6 @@ namespace BTCPayServer.Payments.Lightning
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public string? LightningConnectionString { get; set; }
|
||||
|
||||
public bool DisableBOLT11PaymentOption { get; set; } = false;
|
||||
|
||||
public LightningConnectionString? GetExternalLightningUrl()
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
@@ -299,9 +299,8 @@ namespace BTCPayServer.Payments.PayJoin
|
||||
if (walletReceiveMatch is null && invoice is not null)
|
||||
{
|
||||
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
|
||||
var paymentDetails =
|
||||
paymentMethod.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod;
|
||||
if (paymentDetails is null || !paymentDetails.PayjoinEnabled)
|
||||
var paymentDetails = paymentMethod?.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod;
|
||||
if (paymentMethod is null || paymentDetails is null || !paymentDetails.PayjoinEnabled)
|
||||
continue;
|
||||
due = paymentMethod.Calculate().TotalDue - output.Value;
|
||||
if (due > Money.Zero)
|
||||
@@ -384,7 +383,7 @@ namespace BTCPayServer.Payments.PayJoin
|
||||
WellknownMetadataKeys.AccountHDKey);
|
||||
if (extKeyStr == null)
|
||||
{
|
||||
// This should not happen, as we check the existance of private key before creating invoice with payjoin
|
||||
// This should not happen, as we check the existence of private key before creating invoice with payjoin
|
||||
return CreatePayjoinErrorAndLog(503, PayjoinReceiverWellknownErrors.Unavailable, "The HD Key of the store changed");
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
@@ -11,7 +12,7 @@ namespace BTCPayServer.Payments
|
||||
/// </summary>
|
||||
public class PaymentMethodId
|
||||
{
|
||||
public PaymentMethodId? FindNearest(PaymentMethodId[] others)
|
||||
public PaymentMethodId? FindNearest(IEnumerable<PaymentMethodId> others)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(others);
|
||||
return others.FirstOrDefault(f => f == this) ??
|
||||
|
@@ -9,13 +9,31 @@ using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin.Protocol;
|
||||
using PayoutData = BTCPayServer.Data.PayoutData;
|
||||
using PayoutProcessorData = BTCPayServer.Data.PayoutProcessorData;
|
||||
|
||||
namespace BTCPayServer.PayoutProcessors;
|
||||
|
||||
public class AutomatedPayoutConstants
|
||||
{
|
||||
public const double MinIntervalMinutes = 10.0;
|
||||
public const double MaxIntervalMinutes = 60.0;
|
||||
public static void ValidateInterval(ModelStateDictionary modelState, TimeSpan timeSpan, string parameterName)
|
||||
{
|
||||
if (timeSpan < TimeSpan.FromMinutes(AutomatedPayoutConstants.MinIntervalMinutes))
|
||||
{
|
||||
modelState.AddModelError(parameterName, $"The minimum interval is {AutomatedPayoutConstants.MinIntervalMinutes * 60} seconds");
|
||||
}
|
||||
if (timeSpan > TimeSpan.FromMinutes(AutomatedPayoutConstants.MaxIntervalMinutes))
|
||||
{
|
||||
modelState.AddModelError(parameterName, $"The maximum interval is {AutomatedPayoutConstants.MaxIntervalMinutes * 60} seconds");
|
||||
}
|
||||
}
|
||||
}
|
||||
public abstract class BaseAutomatedPayoutProcessor<T> : BaseAsyncService where T : AutomatedPayoutBlob
|
||||
{
|
||||
protected readonly StoreRepository _storeRepository;
|
||||
@@ -74,7 +92,7 @@ public abstract class BaseAutomatedPayoutProcessor<T> : BaseAsyncService where T
|
||||
States = new[] { PayoutState.AwaitingPayment },
|
||||
PaymentMethods = new[] { _PayoutProcesserSettings.PaymentMethod },
|
||||
Stores = new[] { _PayoutProcesserSettings.StoreId }
|
||||
}, context);
|
||||
}, context, CancellationToken);
|
||||
if (payouts.Any())
|
||||
{
|
||||
Logs.PayServer.LogInformation($"{payouts.Count} found to process. Starting (and after will sleep for {blob.Interval})");
|
||||
@@ -86,6 +104,12 @@ public abstract class BaseAutomatedPayoutProcessor<T> : BaseAsyncService where T
|
||||
new AfterPayoutFilterData(store, paymentMethod, payouts));
|
||||
}
|
||||
}
|
||||
|
||||
// Clip interval
|
||||
if (blob.Interval < TimeSpan.FromMinutes(AutomatedPayoutConstants.MinIntervalMinutes))
|
||||
blob.Interval = TimeSpan.FromMinutes(AutomatedPayoutConstants.MinIntervalMinutes);
|
||||
if (blob.Interval > TimeSpan.FromMinutes(AutomatedPayoutConstants.MaxIntervalMinutes))
|
||||
blob.Interval = TimeSpan.FromMinutes(AutomatedPayoutConstants.MaxIntervalMinutes);
|
||||
await Task.Delay(blob.Interval, CancellationToken);
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
@@ -66,6 +67,8 @@ public class UILightningAutomatedPayoutProcessorsController : Controller
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> Configure(string storeId, string cryptoCode, LightningTransferViewModel automatedTransferBlob)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return View(automatedTransferBlob);
|
||||
if (!_lightningAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(id =>
|
||||
id.CryptoCode.Equals(cryptoCode, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
@@ -120,7 +123,7 @@ public class UILightningAutomatedPayoutProcessorsController : Controller
|
||||
{
|
||||
IntervalMinutes = blob.Interval.TotalMinutes;
|
||||
}
|
||||
|
||||
[Range(AutomatedPayoutConstants.MinIntervalMinutes, AutomatedPayoutConstants.MaxIntervalMinutes)]
|
||||
public double IntervalMinutes { get; set; }
|
||||
|
||||
public AutomatedPayoutBlob ToBlob()
|
||||
|
@@ -43,7 +43,8 @@ namespace BTCPayServer.PayoutProcessors.OnChain
|
||||
PayoutProcessorData payoutProcesserSettings,
|
||||
PullPaymentHostedService pullPaymentHostedService,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
IPluginHookService pluginHookService) :
|
||||
IPluginHookService pluginHookService,
|
||||
IFeeProviderFactory feeProviderFactory) :
|
||||
base(logger, storeRepository, payoutProcesserSettings, applicationDbContextFactory, pullPaymentHostedService,
|
||||
btcPayNetworkProvider, pluginHookService)
|
||||
{
|
||||
@@ -53,9 +54,11 @@ namespace BTCPayServer.PayoutProcessors.OnChain
|
||||
_bitcoinLikePayoutHandler = bitcoinLikePayoutHandler;
|
||||
_eventAggregator = eventAggregator;
|
||||
WalletRepository = walletRepository;
|
||||
FeeProvider = feeProviderFactory.CreateFeeProvider(_btcPayNetworkProvider.GetNetwork(PaymentMethodId.CryptoCode));
|
||||
}
|
||||
|
||||
public WalletRepository WalletRepository { get; }
|
||||
public IFeeProvider FeeProvider { get; }
|
||||
|
||||
protected override async Task Process(ISupportedPaymentMethod paymentMethod, List<PayoutData> payouts)
|
||||
{
|
||||
@@ -95,7 +98,7 @@ namespace BTCPayServer.PayoutProcessors.OnChain
|
||||
storePaymentMethod.AccountDerivation, DerivationFeature.Change, 0, true);
|
||||
|
||||
var processorBlob = GetBlob(_PayoutProcesserSettings);
|
||||
var feeRate = await explorerClient.GetFeeRateAsync(processorBlob.FeeTargetBlock, new FeeRate(1m));
|
||||
var feeRate = await FeeProvider.GetFeeRateAsync(Math.Max(processorBlob.FeeTargetBlock, 1));
|
||||
|
||||
var transfersProcessing = new List<PayoutData>();
|
||||
foreach (var transferRequest in payouts)
|
||||
@@ -133,7 +136,7 @@ namespace BTCPayServer.PayoutProcessors.OnChain
|
||||
try
|
||||
{
|
||||
txBuilder.SetChange(changeAddress.Address);
|
||||
txBuilder.SendEstimatedFees(feeRate.FeeRate);
|
||||
txBuilder.SendEstimatedFees(feeRate);
|
||||
workingTx = txBuilder.BuildTransaction(true);
|
||||
transfersProcessing.Add(transferRequest);
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
@@ -79,6 +80,8 @@ public class UIOnChainAutomatedPayoutProcessorsController : Controller
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> Configure(string storeId, string cryptoCode, OnChainTransferViewModel automatedTransferBlob)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return View(automatedTransferBlob);
|
||||
if (!_onChainAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(id =>
|
||||
id.CryptoCode.Equals(cryptoCode, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
@@ -135,8 +138,10 @@ public class UIOnChainAutomatedPayoutProcessorsController : Controller
|
||||
FeeTargetBlock = blob.FeeTargetBlock;
|
||||
}
|
||||
|
||||
[Range(1, 1000)]
|
||||
public int FeeTargetBlock { get; set; }
|
||||
|
||||
[Range(AutomatedPayoutConstants.MinIntervalMinutes, AutomatedPayoutConstants.MaxIntervalMinutes)]
|
||||
public double IntervalMinutes { get; set; }
|
||||
|
||||
public OnChainAutomatedPayoutBlob ToBlob()
|
||||
|
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -43,6 +44,15 @@ public class PayoutProcessorService : EventHostedServiceBase
|
||||
|
||||
public class PayoutProcessorQuery
|
||||
{
|
||||
public PayoutProcessorQuery()
|
||||
{
|
||||
|
||||
}
|
||||
public PayoutProcessorQuery(string storeId, string paymentMethod)
|
||||
{
|
||||
Stores = new[] { storeId };
|
||||
PaymentMethods = new[] { paymentMethod };
|
||||
}
|
||||
public string[] Stores { get; set; }
|
||||
public string[] Processors { get; set; }
|
||||
public string[] PaymentMethods { get; set; }
|
||||
@@ -166,4 +176,12 @@ public class PayoutProcessorService : EventHostedServiceBase
|
||||
processorUpdated.Processed?.SetResult();
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task Restart(PayoutProcessorQuery payoutProcessorQuery)
|
||||
{
|
||||
foreach (var data in await GetProcessors(payoutProcessorQuery))
|
||||
{
|
||||
await StartOrUpdateProcessor(data, default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -253,15 +253,15 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||
{
|
||||
var vm = new PostRedirectViewModel
|
||||
{
|
||||
AspAction = nameof(POSForm),
|
||||
AspController = "UIPointOfSale",
|
||||
RouteParameters = new Dictionary<string, string> { { "appId", appId } },
|
||||
FormParameters = new MultiValueDictionary<string, string>(Request.Form.Select(pair => new KeyValuePair<string, IReadOnlyCollection<string>>(pair.Key, pair.Value)))
|
||||
FormUrl = Url.Action(nameof(POSForm), "UIPointOfSale", new {appId, buyerEmail = email}),
|
||||
FormParameters = new MultiValueDictionary<string, string>(Request.Form.Select(pair =>
|
||||
new KeyValuePair<string, IReadOnlyCollection<string>>(pair.Key, pair.Value)))
|
||||
};
|
||||
if (viewType.HasValue)
|
||||
{
|
||||
vm.RouteParameters.Add("viewType", viewType.Value.ToString());
|
||||
}
|
||||
|
||||
return View("PostRedirect", vm);
|
||||
}
|
||||
|
||||
@@ -405,6 +405,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||
var store = await _appService.GetStore(app);
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var form = Form.Parse(formData.Config);
|
||||
form.ApplyValuesFromForm(Request.Query);
|
||||
var vm = new FormViewModel
|
||||
{
|
||||
StoreName = store.StoreName,
|
||||
|
@@ -15,6 +15,7 @@ namespace BTCPayServer.Plugins.Shopify
|
||||
public override void Execute(IServiceCollection applicationBuilder)
|
||||
{
|
||||
applicationBuilder.AddSingleton<IHostedService, ShopifyOrderMarkerHostedService>();
|
||||
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("Shopify/NavExtension", "header-nav"));
|
||||
base.Execute(applicationBuilder);
|
||||
}
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@
|
||||
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
|
||||
"BTCPAY_UPDATEURL": "",
|
||||
"BTCPAY_DOCKERDEPLOYMENT": "true",
|
||||
"BTCPAY_RECOMMENDED-PLUGINS": "BTCPayServer.Plugins.Test",
|
||||
"BTCPAY_RECOMMENDED-PLUGINS": "",
|
||||
"BTCPAY_CHEATMODE": "true",
|
||||
"BTCPAY_EXPLORERPOSTGRES": "User ID=postgres;Include Error Detail=true;Host=127.0.0.1;Port=39372;Database=nbxplorer"
|
||||
},
|
||||
@@ -64,7 +64,7 @@
|
||||
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
|
||||
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
|
||||
"BTCPAY_DOCKERDEPLOYMENT": "true",
|
||||
"BTCPAY_RECOMMENDED-PLUGINS": "BTCPayServer.Plugins.Test",
|
||||
"BTCPAY_RECOMMENDED-PLUGINS": "",
|
||||
"BTCPAY_CHEATMODE": "true",
|
||||
"BTCPAY_EXPLORERPOSTGRES": "User ID=postgres;Include Error Detail=true;Host=127.0.0.1;Port=39372;Database=nbxplorer"
|
||||
},
|
||||
@@ -103,7 +103,7 @@
|
||||
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
|
||||
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
|
||||
"BTCPAY_DOCKERDEPLOYMENT": "true",
|
||||
"BTCPAY_RECOMMENDED-PLUGINS": "BTCPayServer.Plugins.Test",
|
||||
"BTCPAY_RECOMMENDED-PLUGINS": "",
|
||||
"BTCPAY_CHEATMODE": "true",
|
||||
"BTCPAY_EXPLORERPOSTGRES": "User ID=postgres;Include Error Detail=true;Host=127.0.0.1;Port=39372;Database=nbxplorer"
|
||||
},
|
||||
|
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using BTCPayServer.Configuration;
|
||||
@@ -17,7 +18,7 @@ namespace BTCPayServer.Services
|
||||
readonly TorServices torServices;
|
||||
public BTCPayServerEnvironment(IWebHostEnvironment env, BTCPayNetworkProvider provider, TorServices torServices, BTCPayServerOptions opts)
|
||||
{
|
||||
Version = typeof(BTCPayServerEnvironment).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||
Version = GetInformationalVersion();
|
||||
Commit = typeof(BTCPayServerEnvironment).GetTypeInfo().Assembly.GetCustomAttribute<GitCommitAttribute>()?.ShortSHA;
|
||||
#if DEBUG
|
||||
Build = "Debug";
|
||||
@@ -35,6 +36,12 @@ namespace BTCPayServer.Services
|
||||
this.torServices = torServices;
|
||||
CheatMode = opts.CheatMode;
|
||||
}
|
||||
|
||||
internal static string GetInformationalVersion()
|
||||
{
|
||||
return typeof(BTCPayServerEnvironment).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||
}
|
||||
|
||||
public IWebHostEnvironment Environment
|
||||
{
|
||||
get; set;
|
||||
|
@@ -461,6 +461,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public CheckoutType? CheckoutType { get; set; }
|
||||
public bool LazyPaymentMethods { get; set; }
|
||||
|
||||
public bool IsExpired()
|
||||
{
|
||||
|
@@ -213,12 +213,18 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
var paymentDestination = details.GetPaymentDestination();
|
||||
string address = GetDestination(paymentMethod);
|
||||
await context.AddressInvoices.AddAsync(new AddressInvoiceData()
|
||||
if (address != null)
|
||||
{
|
||||
InvoiceDataId = invoice.Id,
|
||||
CreatedTime = DateTimeOffset.UtcNow,
|
||||
}.Set(address, paymentMethod.GetId()));
|
||||
textSearch.Add(paymentDestination);
|
||||
await context.AddressInvoices.AddAsync(new AddressInvoiceData()
|
||||
{
|
||||
InvoiceDataId = invoice.Id,
|
||||
CreatedTime = DateTimeOffset.UtcNow,
|
||||
}.Set(address, paymentMethod.GetId()));
|
||||
}
|
||||
if (paymentDestination != null)
|
||||
{
|
||||
textSearch.Add(paymentDestination);
|
||||
}
|
||||
textSearch.Add(paymentMethod.Calculate().TotalDue.ToString());
|
||||
}
|
||||
await context.PendingInvoices.AddAsync(new PendingInvoiceData() { Id = invoice.Id });
|
||||
|
@@ -44,6 +44,8 @@ namespace BTCPayServer.Services.Invoices
|
||||
return null;
|
||||
InvoiceEntity invoiceEntity = invoice.GetBlob(_btcPayNetworkProvider);
|
||||
PaymentMethod paymentMethod = invoiceEntity.GetPaymentMethod(new PaymentMethodId(network.CryptoCode, paymentData.GetPaymentType()));
|
||||
if (paymentMethod is null)
|
||||
return null;
|
||||
IPaymentMethodDetails paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
|
||||
PaymentEntity entity = new PaymentEntity
|
||||
{
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@@ -45,7 +46,7 @@ namespace BTCPayServer.Services
|
||||
|
||||
using var stream = new StreamReader(file);
|
||||
var json = stream.ReadToEnd();
|
||||
result.Add(JObject.Parse(json).ToObject<Language>());
|
||||
result.Add(JObject.Parse(json).ToObject<Language>()!);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -73,7 +74,7 @@ namespace BTCPayServer.Services
|
||||
return items;
|
||||
}
|
||||
|
||||
public Language FindLanguageInAcceptLanguageHeader(string acceptLanguageHeader)
|
||||
public Language? FindLanguageInAcceptLanguageHeader(string? acceptLanguageHeader)
|
||||
{
|
||||
if (acceptLanguageHeader is null)
|
||||
return null;
|
||||
@@ -130,7 +131,7 @@ namespace BTCPayServer.Services
|
||||
* Look for a supported language that matches the given locale (can be in different notations like "nl" or "nl-NL").
|
||||
* Example: "nl" is not supported, but we do have "nl-NL"
|
||||
*/
|
||||
public Language FindLanguage(string locale)
|
||||
public Language? FindLanguage(string locale)
|
||||
{
|
||||
var supportedLangs = GetLanguages();
|
||||
var split = locale.Split('-', StringSplitOptions.RemoveEmptyEntries);
|
||||
@@ -150,7 +151,7 @@ namespace BTCPayServer.Services
|
||||
return countryMatches.FirstOrDefault() ?? langMatches.FirstOrDefault();
|
||||
}
|
||||
|
||||
public Language AutoDetectLanguageUsingHeader(IHeaderDictionary headerDictionary, string defaultLang)
|
||||
public Language? AutoDetectLanguageUsingHeader(IHeaderDictionary headerDictionary, string? defaultLang)
|
||||
{
|
||||
if (headerDictionary?.TryGetValue("Accept-Language",
|
||||
out var acceptLanguage) is true && !string.IsNullOrEmpty(acceptLanguage))
|
||||
|
@@ -35,7 +35,7 @@ namespace BTCPayServer.Services
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(120), Cancellation);
|
||||
await Task.Delay(TimeSpan.FromSeconds(120), CancellationToken);
|
||||
}
|
||||
List<TorService> result = new List<TorService>();
|
||||
try
|
||||
|
@@ -31,8 +31,8 @@
|
||||
{
|
||||
@if (item.Price.Type != ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup)
|
||||
{
|
||||
<form method="post" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false" data-buy>
|
||||
<input type="hidden" name="choicekey" value="@item.Id" />
|
||||
<form method="post" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false" data-buy autocomplete="off">
|
||||
<input type="hidden" name="choiceKey" value="@item.Id" />
|
||||
@{PayFormInputContent(item.BuyButtonText ?? Model.CustomButtonText, item.Price.Type, item.Price.Value, item.Price.Value);}
|
||||
</form>
|
||||
}
|
||||
@@ -95,8 +95,8 @@
|
||||
{
|
||||
if (itemPriceType == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Fixed && priceValue == 0)
|
||||
{
|
||||
<div class="input-group ">
|
||||
<input class="form-control" type="text" readonly value="Free"/>
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="text" readonly value="Free"/>
|
||||
<button class="btn btn-primary text-nowrap" type="submit">@buttonText</button>
|
||||
</div>
|
||||
}
|
||||
|
@@ -1,65 +1,77 @@
|
||||
@model (Dictionary<string, object> Items, int Level)
|
||||
|
||||
@functions {
|
||||
|
||||
private bool IsValidURL(string source)
|
||||
{
|
||||
return Uri.TryCreate(source, UriKind.Absolute, out var uriResult) &&
|
||||
(uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);
|
||||
return Uri.TryCreate(source, UriKind.Absolute, out var uriResult) &&
|
||||
(uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@if (Model.Items.Count > 0)
|
||||
{
|
||||
<table class="table my-0" v-pre>
|
||||
@foreach (var (key, value) in Model.Items)
|
||||
{
|
||||
<tr>
|
||||
@if (value is string str)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
<table class="table my-0" v-pre>
|
||||
@foreach (var (key, value) in Model.Items)
|
||||
{
|
||||
<tr>
|
||||
@if (value is string str)
|
||||
{
|
||||
<th class="w-150px">@key</th>
|
||||
}
|
||||
<td>
|
||||
@if (IsValidURL(str))
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
<a href="@str" target="_blank" rel="noreferrer noopener">@str</a>
|
||||
<th class="w-150px">@key</th>
|
||||
}
|
||||
else
|
||||
{
|
||||
@value?.ToString()
|
||||
}
|
||||
</td>
|
||||
}
|
||||
else if (value is Dictionary<string, object>subItems)
|
||||
{
|
||||
@* This is the array case *@
|
||||
if (subItems.Count == 1 && subItems.First().Value is string str2)
|
||||
{
|
||||
<th class="w-150px">@key</th>
|
||||
<td>
|
||||
@if (IsValidURL(str2))
|
||||
@if (IsValidURL(str))
|
||||
{
|
||||
<a href="@str2" target="_blank" rel="noreferrer noopener">@str2</a>
|
||||
<a href="@str" target="_blank" rel="noreferrer noopener">@str</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
@subItems.First().Value?.ToString()
|
||||
@value?.ToString()
|
||||
}
|
||||
</td>
|
||||
}
|
||||
else if (subItems.Count > 0)
|
||||
{
|
||||
<td colspan="2" >
|
||||
@{
|
||||
Write(Html.Raw($"<h{Model.Level + 3} class=\"mt-4 mb-3\">"));
|
||||
Write(key);
|
||||
Write(Html.Raw($"</h{Model.Level + 3}>"));
|
||||
}
|
||||
<partial name="PosData" model="(subItems, Model.Level + 1)"/>
|
||||
</td>
|
||||
}
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
else if (value is Dictionary<string, object> {Count: > 0 } subItems)
|
||||
{
|
||||
<td colspan="2">
|
||||
@{
|
||||
@if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
Write(Html.Raw($"<h{Model.Level + 3} class=\"mt-4 mb-3\">"));
|
||||
Write(key);
|
||||
Write(Html.Raw($"</h{Model.Level + 3}>"));
|
||||
}
|
||||
}
|
||||
<partial name="PosData" model="@((subItems, Model.Level + 1))" />
|
||||
</td>
|
||||
}
|
||||
else if (value is IEnumerable<object> valueArray)
|
||||
{
|
||||
<td colspan="2">
|
||||
@{
|
||||
@if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
Write(Html.Raw($"<h{Model.Level + 3} class=\"mt-4 mb-3\">"));
|
||||
Write(key);
|
||||
Write(Html.Raw($"</h{Model.Level + 3}>"));
|
||||
}
|
||||
}
|
||||
@foreach (var item in valueArray)
|
||||
{
|
||||
@if (item is Dictionary<string, object> {Count: > 0 } subItems2)
|
||||
{
|
||||
<partial name="PosData" model="@((subItems2, Model.Level + 1))" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<partial name="PosData" model="@((new Dictionary<string, object>() {{"", item}}, Model.Level + 1))" />
|
||||
}
|
||||
}
|
||||
</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
}
|
||||
|
20
BTCPayServer/Views/Shared/Shopify/NavExtension.cshtml
Normal file
20
BTCPayServer/Views/Shared/Shopify/NavExtension.cshtml
Normal file
@@ -0,0 +1,20 @@
|
||||
@using BTCPayServer.Client
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@using BTCPayServer.Abstractions.TagHelpers
|
||||
@using BTCPayServer.Views.Stores
|
||||
@model BTCPayServer.Components.MainNav.MainNavViewModel
|
||||
@{
|
||||
var store = Context.GetStoreData();
|
||||
}
|
||||
|
||||
@if (store != null)
|
||||
{
|
||||
|
||||
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
|
||||
<a asp-area="" asp-controller="UIShopify" asp-action="EditShopify" asp-route-storeId="@store.Id" class="nav-link @ViewData.IsActivePage("shopify", nameof(StoreNavPages))" id="StoreNav-Shopify">
|
||||
<vc:icon symbol="shopify" />
|
||||
<span>Shopify</span>
|
||||
</a>
|
||||
</li>
|
||||
}
|
@@ -1,12 +0,0 @@
|
||||
@using BTCPayServer.Views.Stores
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@{
|
||||
var store = Context.GetStoreData();
|
||||
}
|
||||
|
||||
<li class="nav-item">
|
||||
<a asp-area="" asp-controller="UIShopify" asp-action="EditShopify" asp-route-storeId="@store.Id" class="nav-link @ViewData.IsActivePage("shopify", nameof(StoreNavPages))" id="StoreNav-Shopify">
|
||||
<vc:icon symbol="shopify"/>
|
||||
<span>Shopify</span>
|
||||
</a>
|
||||
</li>
|
@@ -1 +1,2 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 84" role="img" alt="BTCPay Server"><path d="M5.206 83.433a4.86 4.86 0 01-4.859-4.861V5.431a4.86 4.86 0 119.719 0v73.141a4.861 4.861 0 01-4.86 4.861" fill="currentColor" class="logo-brand-light"/><path d="M5.209 83.433a4.862 4.862 0 01-2.086-9.253L32.43 60.274 2.323 38.093a4.861 4.861 0 015.766-7.826l36.647 26.999a4.864 4.864 0 01-.799 8.306L7.289 82.964a4.866 4.866 0 01-2.08.469" fill="currentColor" class="logo-brand-medium"/><path d="M5.211 54.684a4.86 4.86 0 01-2.887-8.774L32.43 23.73 3.123 9.821a4.861 4.861 0 014.166-8.784l36.648 17.394a4.86 4.86 0 01.799 8.305l-36.647 27a4.844 4.844 0 01-2.878.948" fill="currentColor" class="logo-brand-light"/><path d="M10.066 31.725v20.553L24.01 42.006z" fill="currentColor" class="logo-brand-dark"/><path d="M10.066 5.431A4.861 4.861 0 005.206.57 4.86 4.86 0 00.347 5.431v61.165h9.72V5.431h-.001z" fill="currentColor" class="logo-brand-light"/><path d="M74.355 41.412c3.114.884 4.84 3.704 4.84 7.238 0 5.513-3.368 8.082-7.955 8.082H60.761V27.271h9.259c4.504 0 7.997 2.146 7.997 7.743 0 2.821-1.179 5.43-3.662 6.398m-4.293-.716c3.324 0 6.018-1.179 6.018-5.724 0-4.586-2.776-5.808-6.145-5.808h-7.197v11.531h7.324v.001zm1.052 14.099c3.366 0 6.06-1.768 6.06-6.145 0-4.713-3.072-6.144-6.901-6.144h-7.534v12.288h8.375v.001zM98.893 27.271v1.81h-8.122v27.651h-1.979V29.081h-8.123v-1.81zM112.738 26.85c5.01 0 9.554 2.524 10.987 8.543h-1.895c-1.348-4.923-5.303-6.732-9.134-6.732-6.944 0-10.605 5.681-10.605 13.341 0 8.08 3.661 13.256 10.646 13.256 4.125 0 7.828-1.85 9.26-7.279h1.895c-1.264 6.271-6.229 9.174-11.154 9.174-7.87 0-12.583-5.808-12.583-15.15 0-8.966 4.969-15.153 12.583-15.153M138.709 27.271c5.091 0 8.795 3.326 8.795 9.764 0 6.06-3.704 9.722-8.795 9.722h-7.746v9.976h-1.935V27.271h9.681zm0 17.549c3.745 0 6.816-2.397 6.816-7.827 0-5.429-2.947-7.869-6.816-7.869h-7.746V44.82h7.746zM147.841 56.732v-.255l11.741-29.29h.885l11.615 29.29v.255h-2.062l-3.322-8.501H153.27l-3.324 8.501h-2.105zm12.164-26.052l-6.059 15.697h12.078l-6.019-15.697zM189.551 27.271h2.104v.293l-9.176 16.92v12.248h-2.02V44.484l-9.216-16.961v-.252h2.147l3.997 7.492 4.043 7.786h.04l4.081-7.786z" fill="currentColor" class="logo-brand-text"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 84" role="img" alt="BTCPay Server" class="d-print-none"><path d="M5.206 83.433a4.86 4.86 0 01-4.859-4.861V5.431a4.86 4.86 0 119.719 0v73.141a4.861 4.861 0 01-4.86 4.861" fill="currentColor" class="logo-brand-light"/><path d="M5.209 83.433a4.862 4.862 0 01-2.086-9.253L32.43 60.274 2.323 38.093a4.861 4.861 0 015.766-7.826l36.647 26.999a4.864 4.864 0 01-.799 8.306L7.289 82.964a4.866 4.866 0 01-2.08.469" fill="currentColor" class="logo-brand-medium"/><path d="M5.211 54.684a4.86 4.86 0 01-2.887-8.774L32.43 23.73 3.123 9.821a4.861 4.861 0 014.166-8.784l36.648 17.394a4.86 4.86 0 01.799 8.305l-36.647 27a4.844 4.844 0 01-2.878.948" fill="currentColor" class="logo-brand-light"/><path d="M10.066 31.725v20.553L24.01 42.006z" fill="currentColor" class="logo-brand-dark"/><path d="M10.066 5.431A4.861 4.861 0 005.206.57 4.86 4.86 0 00.347 5.431v61.165h9.72V5.431h-.001z" fill="currentColor" class="logo-brand-light"/><path d="M74.355 41.412c3.114.884 4.84 3.704 4.84 7.238 0 5.513-3.368 8.082-7.955 8.082H60.761V27.271h9.259c4.504 0 7.997 2.146 7.997 7.743 0 2.821-1.179 5.43-3.662 6.398m-4.293-.716c3.324 0 6.018-1.179 6.018-5.724 0-4.586-2.776-5.808-6.145-5.808h-7.197v11.531h7.324v.001zm1.052 14.099c3.366 0 6.06-1.768 6.06-6.145 0-4.713-3.072-6.144-6.901-6.144h-7.534v12.288h8.375v.001zM98.893 27.271v1.81h-8.122v27.651h-1.979V29.081h-8.123v-1.81zM112.738 26.85c5.01 0 9.554 2.524 10.987 8.543h-1.895c-1.348-4.923-5.303-6.732-9.134-6.732-6.944 0-10.605 5.681-10.605 13.341 0 8.08 3.661 13.256 10.646 13.256 4.125 0 7.828-1.85 9.26-7.279h1.895c-1.264 6.271-6.229 9.174-11.154 9.174-7.87 0-12.583-5.808-12.583-15.15 0-8.966 4.969-15.153 12.583-15.153M138.709 27.271c5.091 0 8.795 3.326 8.795 9.764 0 6.06-3.704 9.722-8.795 9.722h-7.746v9.976h-1.935V27.271h9.681zm0 17.549c3.745 0 6.816-2.397 6.816-7.827 0-5.429-2.947-7.869-6.816-7.869h-7.746V44.82h7.746zM147.841 56.732v-.255l11.741-29.29h.885l11.615 29.29v.255h-2.062l-3.322-8.501H153.27l-3.324 8.501h-2.105zm12.164-26.052l-6.059 15.697h12.078l-6.019-15.697zM189.551 27.271h2.104v.293l-9.176 16.92v12.248h-2.02V44.484l-9.216-16.961v-.252h2.147l3.997 7.492 4.043 7.786h.04l4.081-7.786z" fill="currentColor" class="logo-brand-text"/></svg>
|
||||
<span class="d-none d-print-inline">BTCPay Server</span>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -31,9 +31,10 @@
|
||||
<vc:icon symbol="warning" />
|
||||
</div>
|
||||
<div class="lead text-center">
|
||||
<h1 class="text-center text-warning mb-3">
|
||||
Secure your recovery phrase
|
||||
</h1>
|
||||
<h1 class="text-center text-warning mb-3">
|
||||
Secure your recovery phrase
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lead text-center">
|
||||
<p class="mb-0">
|
||||
|
@@ -1,5 +1,7 @@
|
||||
@model PaymentModel
|
||||
|
||||
@{
|
||||
var displayedPaymentMethods = Model.AvailableCryptos.Where(a => a.Displayed).ToList();
|
||||
}
|
||||
<div class="top-header">
|
||||
<div class="header">
|
||||
@if (!string.IsNullOrEmpty(Model.CustomLogoLink))
|
||||
@@ -45,26 +47,23 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="single-item-order__right">
|
||||
@if (Model.AvailableCryptos.Count > 1)
|
||||
@if (displayedPaymentMethods.Count > 1)
|
||||
{
|
||||
<div class="paywithRowRight cursorPointer" v-on:click="openPaymentMethodDialog">
|
||||
<span class="payment__currencies " v-show="!changingCurrencies">
|
||||
<img v-bind:src="srvModel.cryptoImage" />
|
||||
<span>{{srvModel.paymentMethodName}} ({{srvModel.cryptoCode}})</span>
|
||||
<span v-show="srvModel.isLightning">⚡</span>
|
||||
<span>{{srvModel.paymentMethodName}}</span>
|
||||
<span class="clickable_indicator fa fa-angle-right"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div id="vexPopupDialog">
|
||||
<ul class="vexmenu">
|
||||
@foreach (var crypto in Model.AvailableCryptos)
|
||||
@foreach (var crypto in displayedPaymentMethods)
|
||||
{
|
||||
<li class="vexmenuitem">
|
||||
<a href="@crypto.Link" class="payment-method" data-payment-method="@crypto.PaymentMethodId" rel="noreferrer noopener">
|
||||
<img alt="@crypto.PaymentMethodName" src="@crypto.CryptoImage" asp-append-version="true" />
|
||||
@crypto.PaymentMethodName
|
||||
@(crypto.IsLightning ? Html.Raw("⚡") : null)
|
||||
<span>@crypto.CryptoCode</span>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
@@ -75,7 +74,7 @@
|
||||
{
|
||||
<div class="payment__currencies_noborder">
|
||||
<img v-bind:src="srvModel.cryptoImage" />
|
||||
<span>{{srvModel.paymentMethodName}} ({{srvModel.cryptoCode}})</span>
|
||||
<span>{{srvModel.paymentMethodName}}</span>
|
||||
<span v-show="srvModel.isLightning">⚡</span>
|
||||
</div>
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
@model PaymentModel
|
||||
@model PaymentModel
|
||||
@{
|
||||
Layout = null;
|
||||
var displayedPaymentMethods = Model.AvailableCryptos.Where(a => a.Displayed).ToList();
|
||||
}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
@@ -24,13 +25,13 @@
|
||||
{
|
||||
<h1 class="text-danger">This payment method requires javascript.</h1>
|
||||
}
|
||||
@if (Model.AvailableCryptos.Count > 1)
|
||||
@if (displayedPaymentMethods.Count > 1)
|
||||
{
|
||||
<div>
|
||||
<hr />
|
||||
<h2>Pay with:</h2>
|
||||
<ul style="list-style-type: none;padding-left: 5px;">
|
||||
@foreach (var crypto in Model.AvailableCryptos)
|
||||
@foreach (var crypto in displayedPaymentMethods)
|
||||
{
|
||||
<li style="height: 32px; line-height: 32px;">
|
||||
<a asp-action="CheckoutNoScript" asp-route-invoiceId="@Model.InvoiceId" asp-route-paymentMethodId="@crypto.PaymentMethodId">
|
||||
|
@@ -12,19 +12,9 @@
|
||||
ViewData["Title"] = Model.HtmlTitle;
|
||||
Csp.UnsafeEval();
|
||||
var hasPaymentPlugins = UiExtensions.Any(extension => extension.Location == "checkout-payment-method");
|
||||
// Show LNURL as selectable payment method only for top up invoices + non-BIP21 case
|
||||
var displayedPaymentMethods = Model.IsUnsetTopUp && !Model.OnChainWithLnInvoiceFallback
|
||||
? Model.AvailableCryptos
|
||||
: Model.AvailableCryptos.Where(c => c.PaymentMethodId != "BTC_LNURLPAY").ToList();
|
||||
var displayedPaymentMethods = Model.AvailableCryptos.Where(c => c.Displayed).ToList();
|
||||
}
|
||||
@functions {
|
||||
private string PaymentMethodName(PaymentModel.AvailableCrypto pm)
|
||||
{
|
||||
return Model.AltcoinsBuild
|
||||
? pm.PaymentMethodName
|
||||
: pm.PaymentMethodName.Replace("Bitcoin (", "").Replace(")", "").Replace("Lightning ", "");
|
||||
}
|
||||
|
||||
private string ToJsValue(object value)
|
||||
{
|
||||
return Safe.Json(value).ToString()?.Replace("\"", "'");
|
||||
@@ -99,7 +89,7 @@
|
||||
class="btcpay-pill m-0 payment-method"
|
||||
:class="{ active: pmId === @ToJsValue(crypto.PaymentMethodId) }"
|
||||
v-on:click.prevent="changePaymentMethod(@ToJsValue(crypto.PaymentMethodId))">
|
||||
@PaymentMethodName(crypto)
|
||||
@crypto.PaymentMethodName
|
||||
</a>
|
||||
}
|
||||
@await Component.InvokeAsync("UiExtensionPoint", new { location = "checkout-payment-method", model = Model })
|
||||
|
@@ -42,7 +42,7 @@
|
||||
|
||||
<div class="d-flex flex-column justify-content-center gap-4">
|
||||
<partial name="_StoreHeader" model="(Model.StoreName, Model.LogoFileId)" />
|
||||
<div id="InvoiceSummary" class="bg-tile p-3 p-sm-4 rounded d-flex flex-wrap align-items-center">
|
||||
<div id="InvoiceSummary" class="bg-tile p-3 p-sm-4 rounded d-flex flex-wrap align-items-center justify-content-center">
|
||||
@if (isProcessing)
|
||||
{
|
||||
<div class="lead text-center p-4 fw-semibold" id="invoice-processing">
|
||||
@@ -59,9 +59,7 @@
|
||||
{
|
||||
if (Model.ReceiptOptions.ShowQR is true)
|
||||
{
|
||||
<div class="mx-auto">
|
||||
<vc:qr-code data="@Context.Request.GetCurrentUrl()"></vc:qr-code>
|
||||
</div>
|
||||
<vc:qr-code data="@Context.Request.GetCurrentUrl()"></vc:qr-code>
|
||||
}
|
||||
<dl class="d-flex flex-column gap-4 mb-0 flex-fill">
|
||||
<div class="d-flex flex-column">
|
||||
@@ -115,58 +113,70 @@
|
||||
if (Model.Payments?.Any() is true)
|
||||
{
|
||||
<div id="PaymentDetails" class="bg-tile p-3 p-sm-4 rounded">
|
||||
<h2 class="h4 mb-3 d-print-none">Payment Details</h2>
|
||||
<div class="table-responsive my-0">
|
||||
<h2 class="h4 mb-3">Payment Details</h2>
|
||||
<div class="table-responsive my-0 d-print-none">
|
||||
<table class="table table-borderless my-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="fw-normal text-secondary">Date</th>
|
||||
<th class="fw-normal text-secondary">Payment</th>
|
||||
<th class="fw-normal text-secondary text-end">Paid</th>
|
||||
<th class="fw-normal text-secondary text-end">Rate</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="fw-normal text-secondary">Date</th>
|
||||
<th class="fw-normal text-secondary">Payment</th>
|
||||
<th class="fw-normal text-secondary text-end">Paid</th>
|
||||
<th class="fw-normal text-secondary text-end">Rate</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@foreach (var payment in Model.Payments)
|
||||
{
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="text-nowrap">@payment.ReceivedDate.ToBrowserDate()</td>
|
||||
<td class="text-nowrap">@payment.Amount @payment.PaymentMethod</td>
|
||||
<td class="text-end text-nowrap">@payment.PaidFormatted</td>
|
||||
<td class="text-end text-nowrap">@payment.RateFormatted</td>
|
||||
</tr>
|
||||
@if (!string.IsNullOrEmpty(payment.Destination))
|
||||
{
|
||||
<tr>
|
||||
<td class="text-nowrap">@payment.ReceivedDate.ToBrowserDate()</td>
|
||||
<td class="text-nowrap">@payment.Amount @payment.PaymentMethod</td>
|
||||
<td class="text-end text-nowrap">@payment.PaidFormatted</td>
|
||||
<td class="text-end text-nowrap">@payment.RateFormatted</td>
|
||||
<th class="fw-normal text-nowrap text-secondary">
|
||||
Destination
|
||||
</th>
|
||||
<td class="fw-normal" colspan="3">
|
||||
<vc:truncate-center text="@payment.Destination" classes="truncate-center-id" />
|
||||
</td>
|
||||
</tr>
|
||||
@if (!string.IsNullOrEmpty(payment.Destination))
|
||||
{
|
||||
<tr>
|
||||
<th class="fw-normal text-nowrap text-secondary">
|
||||
Destination
|
||||
</th>
|
||||
<td class="fw-normal" colspan="3">
|
||||
<vc:truncate-center text="@payment.Destination" classes="truncate-center-id" />
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(payment.PaymentProof))
|
||||
{
|
||||
<tr>
|
||||
<th class="fw-normal text-nowrap text-secondary">
|
||||
Payment Proof
|
||||
</th>
|
||||
<td class="fw-normal" colspan="3">
|
||||
<vc:truncate-center text="@payment.PaymentProof" link="@payment.Link" classes="truncate-center-id" />
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(payment.PaymentProof))
|
||||
{
|
||||
<tr>
|
||||
<th class="fw-normal text-nowrap text-secondary">
|
||||
Payment Proof
|
||||
</th>
|
||||
<td class="fw-normal" colspan="3">
|
||||
<vc:truncate-center text="@payment.PaymentProof" link="@payment.Link" classes="truncate-center-id" />
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
}
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-none d-print-block">
|
||||
@foreach (var payment in Model.Payments)
|
||||
{
|
||||
<div class="mb-4">
|
||||
<strong>@payment.PaidFormatted</strong> = @payment.Amount @payment.PaymentMethod, Rate: @payment.RateFormatted
|
||||
@if (!string.IsNullOrEmpty(payment.PaymentProof))
|
||||
{
|
||||
<div>Proof: @payment.PaymentProof</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
if (Model.AdditionalData?.Any() is true)
|
||||
{
|
||||
<div id="AdditionalData" class="bg-tile p-3 p-sm-4 rounded">
|
||||
<h2 class="h4 mb-3 d-print-none">Additional Data</h2>
|
||||
<h2 class="h4 mb-3">Additional Data</h2>
|
||||
<div class="table-responsive my-0">
|
||||
<partial name="PosData" model="(Model.AdditionalData, 1)"/>
|
||||
</div>
|
||||
@@ -177,7 +187,7 @@
|
||||
</div>
|
||||
</main>
|
||||
<footer class="store-footer">
|
||||
<p permission="@Policies.CanViewInvoices">
|
||||
<p permission="@Policies.CanViewInvoices" class="d-print-none">
|
||||
<a asp-action="Invoice" asp-route-invoiceId="@Model.InvoiceId">
|
||||
Admin details
|
||||
</a>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@using BTCPayServer.Views.Stores
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@model BTCPayServer.PayoutProcessors.Lightning.UILightningAutomatedPayoutProcessorsController.LightningTransferViewModel
|
||||
@@ -23,6 +23,7 @@
|
||||
<div class="input-group">
|
||||
<input asp-for="IntervalMinutes" class="form-control" inputmode="numeric" style="max-width:10ch;">
|
||||
<span class="input-group-text">minutes</span>
|
||||
<span asp-validation-for="IntervalMinutes" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<button name="command" type="submit" class="btn btn-primary mt-2" value="Save" id="Save">Save</button>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@using BTCPayServer.Views.Stores
|
||||
@model BTCPayServer.PayoutProcessors.OnChain.UIOnChainAutomatedPayoutProcessorsController.OnChainTransferViewModel
|
||||
@{
|
||||
@@ -22,6 +22,7 @@
|
||||
<div class="input-group">
|
||||
<input asp-for="IntervalMinutes" class="form-control" inputmode="numeric" style="max-width:10ch;">
|
||||
<span class="input-group-text">minutes</span>
|
||||
<span asp-validation-for="IntervalMinutes" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -29,6 +30,7 @@
|
||||
<div class="input-group">
|
||||
<input asp-for="FeeTargetBlock" class="form-control" min="1" inputmode="numeric" style="max-width:10ch;">
|
||||
<span class="input-group-text">blocks</span>
|
||||
<span asp-validation-for="FeeTargetBlock" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<button name="command" type="submit" class="btn btn-primary mt-2" value="Save" id="Save">Save</button>
|
||||
|
@@ -86,7 +86,7 @@
|
||||
<label asp-for="Email" class="form-label"></label>
|
||||
<input type="email" asp-for="Email" placeholder="Firstname Lastname <email@example.com>" class="form-control" />
|
||||
<span asp-validation-for="Email" class="text-danger"></span>
|
||||
<div id="PaymentRequestEmailHelpBlock" class="form-text">Receive updates for this payment request.</div>
|
||||
<div id="PaymentRequestEmailHelpBlock" class="form-text">The recipient's email. This will send notification mails to the recipient, as configured by the <a asp-action="StoreEmails" asp-controller="UIStores" asp-route-storeId="@Model.StoreId">email rules</a>, and include the email in the invoice export data.</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="FormId" class="form-label"></label>
|
||||
|
@@ -98,27 +98,6 @@
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-group d-flex align-items-center">
|
||||
<input type="checkbox" asp-for="LNURLStandardInvoiceEnabled" class="btcpay-toggle me-3" />
|
||||
<div class="">
|
||||
<label asp-for="LNURLStandardInvoiceEnabled" class="form-label mb-0 me-1"></label>
|
||||
<div class="form-text">Required for Lightning Address, the pay button and apps.</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-group d-flex align-items-center">
|
||||
<input type="checkbox" asp-for="DisableBolt11PaymentMethod" class="btcpay-toggle me-3" />
|
||||
<div class="">
|
||||
<label asp-for="DisableBolt11PaymentMethod" class="form-label mb-0 me-1"></label>
|
||||
<div class="form-text">Performance: Turn it off if users should pay only via LNURL.</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<input type="checkbox" asp-for="LUD12Enabled" class="btcpay-toggle me-3" />
|
||||
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 69 KiB |
1
BTCPayServer/wwwroot/img/readme/supporter_esc.svg
Normal file
1
BTCPayServer/wwwroot/img/readme/supporter_esc.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="150" height="100" style="clip-rule:evenodd;fill-rule:evenodd;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision" version="1.1" id="svg587" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><style id="style324">.st2{fill:#ffc214}.st3{fill:#f9f185}.st0{fill:#222221}.st1{fill:#272425}</style><g id="g821-3" transform="translate(1.936 86.514) scale(.13236)" style="clip-rule:evenodd;fill-rule:evenodd;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision"><path style="clip-rule:evenodd;fill:#fff;fill-rule:evenodd;stroke-width:8.91671;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision" id="rect875-3-6" d="M-14.629-653.622H1118.63v755.506H-14.629z"/><g transform="matrix(.14533 0 0 -.14533 261.343 12.553)" fill="#000" stroke="none" id="g919" style="clip-rule:evenodd;fill-rule:evenodd;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision"><path d="M360 3891c-150-48-258-176-281-334-11-82-11-3062 0-3144 24-161 136-291 290-337 49-14 211-16 1631-16s1582 2 1631 16c154 46 266 176 290 337 11 82 11 3062 0 3144-24 161-136 291-290 337-49 14-211 16-1635 15-1483 0-1584-1-1636-18zm3239-156c63-22 98-51 131-108l30-52V395l-30-52c-33-57-68-86-131-108-63-23-3135-23-3198 0-63 22-98 51-131 108l-30 52v3180l30 52c32 55 68 86 127 108 59 21 3141 22 3202 0z" id="path911" style="clip-rule:evenodd;fill-rule:evenodd;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision"/><path d="M1043 2620c-142-29-257-130-304-267-17-51-19-85-19-368s2-317 19-368c39-113 126-204 238-248 54-22 73-23 276-24h219l24 28c31 36 31 71-1 102-24 25-26 25-208 25-226 0-268 10-337 79-59 59-70 94-70 222v97l31 6c17 3 152 6 300 6h268l20 26c27 34 27 64 0 98l-20 26h-268c-148 0-283 3-301 6l-32 7 3 117c4 108 6 120 32 158 36 56 97 98 158 111 27 6 129 11 225 11 172 0 175 0 199 25 32 31 32 66 1 102l-24 28-194 2c-106 1-212-3-235-7z" id="path913" style="clip-rule:evenodd;fill-rule:evenodd;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision"/><path d="M1952 2614c-109-29-192-99-241-203-22-48-26-70-26-146 0-82 3-97 33-157 37-75 96-133 170-169 35-17 76-24 163-30 131-9 151-15 198-55 119-100 77-295-73-339-37-11-103-15-251-15-198 0-202 0-223-23-30-32-29-77 4-108 26-24 26-24 257-24 215 1 236 2 287 23 80 32 153 100 192 180 30 60 33 75 33 157s-3 97-33 157c-36 75-96 133-170 169-36 17-74 24-163 30-86 6-127 13-157 28-124 63-148 217-49 319 51 53 102 62 333 62h203l20 26c31 39 27 77-10 108l-31 26-206-1c-146 0-223-5-260-15z" id="path915" style="clip-rule:evenodd;fill-rule:evenodd;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision"/><path d="M2971 2619c-156-30-285-155-320-310-14-57-14-591 0-648 36-158 164-281 324-310 91-17 389-14 424 4 44 22 56 80 25 120-14 19-30 20-227 25-205 5-214 7-263 32-59 31-101 82-120 146-20 65-20 550 0 615 19 63 64 118 123 147 46 24 58 25 253 28 192 3 206 5 228 25 12 11 22 32 22 48 0 80-26 89-247 88-98-1-198-5-222-10z" id="path917" style="clip-rule:evenodd;fill-rule:evenodd;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision"/></g></g></svg>
|
After Width: | Height: | Size: 3.2 KiB |
@@ -1173,6 +1173,11 @@
|
||||
"nullable": true,
|
||||
"description": "Default payment type for the invoice (e.g., BTC, BTC-LightningNetwork). Default payment method set for the store is used if this parameter is not specified."
|
||||
},
|
||||
"lazyPaymentMethods": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
"description": "If true, payment methods are enabled individually upon user interaction in the invoice. Default to store's settings'"
|
||||
},
|
||||
"expirationMinutes": {
|
||||
"nullable": true,
|
||||
"description": "The number of minutes after which an invoice becomes expired. Defaults to the store's settings. (The default store settings is 15)",
|
||||
|
@@ -403,44 +403,44 @@
|
||||
}
|
||||
},
|
||||
"/api/v1/pull-payments/{pullPaymentId}/lnurl": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "pullPaymentId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The ID of the pull payment",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"get": {
|
||||
"summary": "Get Pull Payment LNURL details",
|
||||
"operationId": "PullPayments_GetPullPaymentLNURL",
|
||||
"description": "Get Pull Payment LNURL details",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Pull payment LNURL details",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LNURLData"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Pull payment not found"
|
||||
},
|
||||
"400": {
|
||||
"description": "Pull payment found but does not support LNURL"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Pull payments (Public)"
|
||||
],
|
||||
"security": []
|
||||
}
|
||||
"parameters": [
|
||||
{
|
||||
"name": "pullPaymentId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The ID of the pull payment",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"get": {
|
||||
"summary": "Get Pull Payment LNURL details",
|
||||
"operationId": "PullPayments_GetPullPaymentLNURL",
|
||||
"description": "Get Pull Payment LNURL details",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Pull payment LNURL details",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LNURLData"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Pull payment not found"
|
||||
},
|
||||
"400": {
|
||||
"description": "Pull payment found but does not support LNURL"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Pull payments (Public)"
|
||||
],
|
||||
"security": []
|
||||
}
|
||||
},
|
||||
"/api/v1/stores/{storeId}/payouts": {
|
||||
"parameters": [
|
||||
@@ -1070,19 +1070,19 @@
|
||||
}
|
||||
},
|
||||
"LNURLData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lnurlBech32": {
|
||||
"type": "string",
|
||||
"description": "Bech32 representation of LNRURL",
|
||||
"example": "lightning:lnurl1dp68gup69uhnzv3h9cczuvpwxyarzdp3xsez7sj5gvh42j2vfe24ynp0wa5hg6rywfshwtmswqhngvntdd6x6uzvx4jrvu2kvvur23n8v46rwjpexcc45563fn53w7"
|
||||
},
|
||||
"lnurlUri": {
|
||||
"type": "string",
|
||||
"description": "Bech32 representation of LNURL",
|
||||
"example": "lnurlw://example.com/BTC/UILNURL/withdraw/pp/42kktmpL5d6qVc85Fget7H961ZSQ"
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lnurlBech32": {
|
||||
"type": "string",
|
||||
"description": "Bech32 representation of LNURL",
|
||||
"example": "lightning:lnurl1dp68gup69uhnzv3h9cczuvpwxyarzdp3xsez7sj5gvh42j2vfe24ynp0wa5hg6rywfshwtmswqhngvntdd6x6uzvx4jrvu2kvvur23n8v46rwjpexcc45563fn53w7"
|
||||
},
|
||||
"lnurlUri": {
|
||||
"type": "string",
|
||||
"description": "URI representation of LNURL",
|
||||
"example": "lnurlw://example.com/BTC/UILNURL/withdraw/pp/42kktmpL5d6qVc85Fget7H961ZSQ"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -143,7 +143,7 @@
|
||||
"x-position": 1
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"200": {
|
||||
"description": "The email was sent (scheduled) successfully"
|
||||
},
|
||||
"400": {
|
||||
@@ -215,6 +215,11 @@
|
||||
"fromDisplay": {
|
||||
"type": "string",
|
||||
"description": "The name of the sender"
|
||||
},
|
||||
"disableCertificateCheck": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Disable TLS certificate security checks"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -279,11 +279,6 @@
|
||||
"type": "string",
|
||||
"description": "The lightning connection string. Set to 'Internal Node' to use the internal node. (See [this doc](https://github.com/btcpayserver/BTCPayServer.Lightning/blob/master/README.md#examples) for some example)",
|
||||
"example": "type=clightning;server=..."
|
||||
},
|
||||
"disableBOLT11PaymentOption": {
|
||||
|
||||
"type": "boolean",
|
||||
"description": "Whether to disable generation of bolt11 invoices. Useful when wanting to only use LNURL Pay exclusively."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -303,10 +303,6 @@
|
||||
"type": "boolean",
|
||||
"description": "Whether to use [LUD-01](https://github.com/fiatjaf/lnurl-rfc/blob/luds/01.md)'s bech32 format or to use [LUD-17](https://github.com/fiatjaf/lnurl-rfc/blob/luds/17.md) url formatting. "
|
||||
},
|
||||
"enableForStandardInvoices": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to allow this payment method to also be used for standard invoices and not just top-up invoices."
|
||||
},
|
||||
"lud12Enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Allow comments to be passed on via lnurl."
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>1.9.1</Version>
|
||||
<Version>1.9.2</Version>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
13
Changelog.md
13
Changelog.md
@@ -1,5 +1,18 @@
|
||||
# Changelog
|
||||
|
||||
## 1.9.2
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fix: Shop's new receipt and cart not displaying items correctly and missing additional information (#4890. @Kukks
|
||||
* Fix: Email sent to PoS URL via POST not being inserted into email/custom form (#4810). @Kukks
|
||||
* Fix: Regression causing payment request form data to not be saved in invoices (#4895) @NicolasDorier @Kukks
|
||||
* Fix: After opening payouts page of a pull payment, then clicking on the store's `Payouts` menu would still show only the same pull payment's payout (#4788) @Kukks
|
||||
* Fix: Optimized print view in receipt (#4916 #4902) @dennisreimann
|
||||
* Fix: NFC and PoS print view not working without checking "Allow LNUrl for standard invoice". This superfluous option has been removed. (#4911) @NicolasDorier
|
||||
* Fix: Automated payouts could hang the restart of the server. @NicolasDorier
|
||||
* Fix: Missing validation on payout processor configuration @NicolasDorier
|
||||
|
||||
## 1.9.1
|
||||
|
||||
### Bug fixes
|
||||
|
@@ -1,21 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
|
||||
<PreserveCompilationContext>false</PreserveCompilationContext>
|
||||
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
|
||||
<AssemblyVersion>1.0.0</AssemblyVersion>
|
||||
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<ProjectReference Include="..\..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
|
||||
<EmbeddedResource Include="Resources\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.9">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
@@ -1,35 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Plugins.Test.Data;
|
||||
using BTCPayServer.Plugins.Test.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test
|
||||
{
|
||||
[Route("extensions/test")]
|
||||
public class UITestExtensionController : Controller
|
||||
{
|
||||
private readonly TestPluginService _testPluginService;
|
||||
|
||||
public UITestExtensionController(TestPluginService testPluginService)
|
||||
{
|
||||
_testPluginService = testPluginService;
|
||||
}
|
||||
|
||||
// GET
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
return View(new TestPluginPageViewModel()
|
||||
{
|
||||
Data = await _testPluginService.Get()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class TestPluginPageViewModel
|
||||
{
|
||||
public List<TestPluginData> Data { get; set; }
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test.Data
|
||||
{
|
||||
public class TestPluginData
|
||||
{
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public string Id { get; set; }
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
@@ -1,46 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Plugins.Test.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test
|
||||
{
|
||||
public class TestPluginDbContext : DbContext
|
||||
{
|
||||
private readonly bool _designTime;
|
||||
|
||||
public DbSet<TestPluginData> TestPluginRecords { get; set; }
|
||||
|
||||
public TestPluginDbContext(DbContextOptions<TestPluginDbContext> options, bool designTime = false)
|
||||
: base(options)
|
||||
{
|
||||
_designTime = designTime;
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
modelBuilder.HasDefaultSchema("BTCPayServer.Plugins.Test");
|
||||
if (Database.IsSqlite() && !_designTime)
|
||||
{
|
||||
// SQLite does not have proper support for DateTimeOffset via Entity Framework Core, see the limitations
|
||||
// here: https://docs.microsoft.com/en-us/ef/core/providers/sqlite/limitations#query-limitations
|
||||
// To work around this, when the Sqlite database provider is used, all model properties of type DateTimeOffset
|
||||
// use the DateTimeOffsetToBinaryConverter
|
||||
// Based on: https://github.com/aspnet/EntityFrameworkCore/issues/10784#issuecomment-415769754
|
||||
// This only supports millisecond precision, but should be sufficient for most use cases.
|
||||
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
|
||||
{
|
||||
var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == typeof(DateTimeOffset));
|
||||
foreach (var property in properties)
|
||||
{
|
||||
modelBuilder
|
||||
.Entity(entityType.Name)
|
||||
.Property(property.Name)
|
||||
.HasConversion(new Microsoft.EntityFrameworkCore.Storage.ValueConversion.DateTimeOffsetToBinaryConverter());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,37 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test.Migrations
|
||||
{
|
||||
[DbContext(typeof(TestPluginDbContext))]
|
||||
[Migration("20201117154419_Init")]
|
||||
public partial class Init : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.EnsureSchema(
|
||||
name: "BTCPayServer.Plugins.Test");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TestPluginRecords",
|
||||
schema: "BTCPayServer.Plugins.Test",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(nullable: false),
|
||||
Timestamp = table.Column<DateTimeOffset>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TestPluginRecords", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "TestPluginRecords",
|
||||
schema: "BTCPayServer.Plugins.Test");
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,36 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using BTCPayServer.Plugins.Test;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test.Migrations
|
||||
{
|
||||
[DbContext(typeof(TestPluginDbContext))]
|
||||
partial class TestPluginDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("BTCPayServer.Plugins.Test")
|
||||
.HasAnnotation("ProductVersion", "3.1.10");
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Plugins.Test.Data.TestPluginData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("Timestamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("TestPluginRecords");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 34 KiB |
@@ -1,44 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test
|
||||
{
|
||||
public class ApplicationPartsLogger : IHostedService
|
||||
{
|
||||
private readonly ILogger<ApplicationPartsLogger> _logger;
|
||||
private readonly ApplicationPartManager _partManager;
|
||||
|
||||
public ApplicationPartsLogger(ILogger<ApplicationPartsLogger> logger, ApplicationPartManager partManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_partManager = partManager;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// Get the names of all the application parts. This is the short assembly name for AssemblyParts
|
||||
var applicationParts = _partManager.ApplicationParts.Select(x => x.Name);
|
||||
|
||||
// Create a controller feature, and populate it from the application parts
|
||||
var controllerFeature = new ControllerFeature();
|
||||
_partManager.PopulateFeature(controllerFeature);
|
||||
|
||||
// Get the names of all of the controllers
|
||||
var controllers = controllerFeature.Controllers.Select(x => x.Name);
|
||||
|
||||
// Log the application parts and controllers
|
||||
_logger.LogInformation("Found the following application parts: '{ApplicationParts}' with the following controllers: '{Controllers}'",
|
||||
string.Join(", ", applicationParts), string.Join(", ", controllers));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Required by the interface
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
}
|
@@ -1,37 +0,0 @@
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test
|
||||
{
|
||||
|
||||
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<TestPluginDbContext>
|
||||
{
|
||||
public TestPluginDbContext CreateDbContext(string[] args)
|
||||
{
|
||||
|
||||
var builder = new DbContextOptionsBuilder<TestPluginDbContext>();
|
||||
|
||||
builder.UseSqlite("Data Source=temp.db");
|
||||
|
||||
return new TestPluginDbContext(builder.Options, true);
|
||||
}
|
||||
}
|
||||
|
||||
public class TestPluginDbContextFactory : BaseDbContextFactory<TestPluginDbContext>
|
||||
{
|
||||
public TestPluginDbContextFactory(IOptions<DatabaseOptions> options) : base(options, "BTCPayServer.Plugins.Test")
|
||||
{
|
||||
}
|
||||
|
||||
public override TestPluginDbContext CreateContext()
|
||||
{
|
||||
var builder = new DbContextOptionsBuilder<TestPluginDbContext>();
|
||||
ConfigureBuilder(builder);
|
||||
return new TestPluginDbContext(builder.Options);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,34 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Plugins.Test.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test.Services
|
||||
{
|
||||
public class TestPluginService
|
||||
{
|
||||
private readonly TestPluginDbContextFactory _testPluginDbContextFactory;
|
||||
|
||||
public TestPluginService(TestPluginDbContextFactory testPluginDbContextFactory)
|
||||
{
|
||||
_testPluginDbContextFactory = testPluginDbContextFactory;
|
||||
}
|
||||
|
||||
public async Task AddTestDataRecord()
|
||||
{
|
||||
await using var context = _testPluginDbContextFactory.CreateContext();
|
||||
|
||||
await context.TestPluginRecords.AddAsync(new TestPluginData() { Timestamp = DateTimeOffset.UtcNow });
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
|
||||
public async Task<List<TestPluginData>> Get()
|
||||
{
|
||||
await using var context = _testPluginDbContextFactory.CreateContext();
|
||||
|
||||
return await context.TestPluginRecords.ToListAsync();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Abstractions.Services;
|
||||
using BTCPayServer.Plugins.Test.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test
|
||||
{
|
||||
public class TestPlugin : BaseBTCPayServerPlugin
|
||||
{
|
||||
public override string Identifier { get; } = "BTCPayServer.Plugins.Test";
|
||||
public override string Name { get; } = "Test Plugin!";
|
||||
public override string Description { get; } = "This is a description of the loaded test extension!";
|
||||
|
||||
public override void Execute(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<IUIExtension>(new UIExtension("TestExtensionNavExtension", "header-nav"));
|
||||
services.AddHostedService<ApplicationPartsLogger>();
|
||||
services.AddHostedService<TestPluginMigrationRunner>();
|
||||
services.AddSingleton<TestPluginService>();
|
||||
services.AddSingleton<TestPluginDbContextFactory>();
|
||||
services.AddDbContext<TestPluginDbContext>((provider, o) =>
|
||||
{
|
||||
var factory = provider.GetRequiredService<TestPluginDbContextFactory>();
|
||||
factory.ConfigureBuilder(o);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,44 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Plugins.Test.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace BTCPayServer.Plugins.Test;
|
||||
|
||||
public class TestPluginMigrationRunner : IHostedService
|
||||
{
|
||||
public class TestPluginDataMigrationHistory
|
||||
{
|
||||
public bool UpdatedSomething { get; set; }
|
||||
}
|
||||
private readonly TestPluginDbContextFactory _testPluginDbContextFactory;
|
||||
private readonly ISettingsRepository _settingsRepository;
|
||||
private readonly TestPluginService _testPluginService;
|
||||
|
||||
public TestPluginMigrationRunner(TestPluginDbContextFactory testPluginDbContextFactory, ISettingsRepository settingsRepository, TestPluginService testPluginService)
|
||||
{
|
||||
_testPluginDbContextFactory = testPluginDbContextFactory;
|
||||
_settingsRepository = settingsRepository;
|
||||
_testPluginService = testPluginService;
|
||||
}
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var settings = await _settingsRepository.GetSettingAsync<TestPluginDataMigrationHistory>() ??
|
||||
new TestPluginDataMigrationHistory();
|
||||
await using var ctx = _testPluginDbContextFactory.CreateContext();
|
||||
await ctx.Database.MigrateAsync(cancellationToken: cancellationToken);
|
||||
if (!settings.UpdatedSomething)
|
||||
{
|
||||
await _testPluginService.AddTestDataRecord();
|
||||
settings.UpdatedSomething = true;
|
||||
await _settingsRepository.UpdateSetting(settings);
|
||||
}
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
@@ -1,2 +0,0 @@
|
||||
|
||||
<li class="nav-item"><a asp-controller="UITestExtension" asp-action="Index" class="nav-link" >Dear Nicolas Dorier</a></li>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user