Compare commits

..

52 Commits

Author SHA1 Message Date
Kukks
8290a1606f Fix posdata with primitive array
fixes #4952
2023-05-05 11:29:53 +02:00
Kukks
eddd458744 Fix missing shopify link
fixes #4945
2023-05-05 09:51:45 +02:00
rockstardev
439ea20a89 Resolving weird Firefox form autocomplete behavior on our POS (#4950) 2023-05-04 18:49:35 +02:00
d11n
cec223c8e7 Fix missing closing div (#4944) 2023-05-03 10:55:20 +02:00
rockstardev
25cb188d00 Bumping LND to 0.16.2-beta 2023-04-29 09:10:07 -05:00
Pavlenex
31007a8d96 Merge pull request #4929 from dennisreimann/esc
Supporters: Add ESC
2023-04-27 18:30:09 +02:00
Dennis Reimann
a4fa8db69b Supporters: Add ESC 2023-04-27 18:28:29 +02:00
nicolas.dorier
6193835ea1 Make LightningListener nullable, fix some NRE 2023-04-27 13:37:32 +09:00
rockstardev
0c78e9e4ac Bumping LND to 0.16.1-beta (#4921) 2023-04-27 13:17:57 +09:00
nicolas.dorier
58c409e7fa Fix tests 2023-04-27 13:00:25 +09:00
Nicolas Dorier
9577eed524 Validate input in greenfield for payout processors (#4922) 2023-04-27 10:59:19 +09:00
Kukks
76f32cd064 Fix payment request overriding user provided email 2023-04-26 14:06:42 +02:00
Kukks
92d9c17095 Fix payment request merge conflict marker in UI 2023-04-26 14:06:23 +02:00
nicolas.dorier
b0396df33f Update changelog 2023-04-26 18:27:06 +09:00
nicolas.dorier
c17572c76f Clip configuration values for payout processors 2023-04-26 18:24:46 +09:00
nicolas.dorier
5c91e072a6 Prevent payout processors from stalling restart 2023-04-26 18:09:56 +09:00
nicolas.dorier
4991d0f965 Update Changelog and bump version 2023-04-26 17:57:25 +09:00
Andrew Camilleri
45b74e1ce5 Fix cart receipt + fix pos email form forwarding (#4917)
* Show correct array regardless of size

fixes #4890

* Email provided to pos form was not forwarded to form

fixes #4810

* Make invoice receipt url redirect to the invoice redirect url if receipt is not enabled

When setting up a default email rule upon invoice settlement, you would link to the receipt page naturally. However, if using the payment requests, receipts are disabled as the payment request itself is the receipt.  This commit makes the receipt url redirect to the invoice redirect url if available, and in the case of payment requests, it would mean the receipt url is the payment request url. fixes #4895

* Set the email address in the form when configured in the payment request

* fix pay request email copy

* fix payouts nav link

fixes #4788

* Update BTCPayServer/Views/UIPaymentRequest/EditPaymentRequest.cshtml

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>

---------

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
2023-04-26 16:45:35 +09:00
Nicolas Dorier
ccb4b9a9ba Merge pull request #4916 from dennisreimann/receipt-print
Receipt: Optimize print view
2023-04-26 15:41:41 +09:00
Nicolas Dorier
dd635071d6 Merge pull request #4909 from dennisreimann/fix-4889
POS: Fix choiceKey case
2023-04-26 15:16:54 +09:00
Nicolas Dorier
fe1448dfae Merge pull request #4911 from NicolasDorier/qiponbtq
Remove LNURLStandardInvoiceEnabled
2023-04-26 15:15:20 +09:00
Ikko Eltociear Ashimine
56855bc54d Fix typo in PayJoinEndpointController.cs (#4918) 2023-04-25 22:29:06 +02:00
nicolas.dorier
b51fa8df5a Fix indent 2023-04-25 21:03:12 +09:00
Dennis Reimann
3aa979cb11 Test updates 2023-04-25 13:07:01 +02:00
Dennis Reimann
c95f75bc6c Remove disable BOLT11 option 2023-04-25 12:21:27 +02:00
Dennis Reimann
b13a636f89 Fix syntax error 2023-04-25 12:11:08 +02:00
Dennis Reimann
8de55cef31 Unify checkout v1 behaviour with v2 2023-04-25 11:36:45 +02:00
Dennis Reimann
cb781f42e3 POS: Fix choiceKey case
I came across this while debugging #4889. This does not actually fix it, but it fixes an inconsistence in the casing of the parameter name.

However, I think the original issue is a caching problem in the browser. I was able to reproduce it on first load, after reloading the page once more it works as intended. The weird thing is: even though the values are correct on first load (verified via debugger), the `choiceKey` for the first item is set incorrectly to an integer value.
2023-04-25 09:16:09 +02:00
Dennis Reimann
b59292dc24 Receipt: Optimize print view
Fixes #4902
2023-04-25 07:30:15 +02:00
nicolas.dorier
03b793d7e2 Fix tests 2023-04-25 10:28:36 +09:00
nicolas.dorier
bee18d1cfb Can set LazyPaymentMethod at the invoice creation level 2023-04-25 08:38:42 +09:00
nicolas.dorier
d8698181f4 Remove LNURLStandardInvoiceEnabled 2023-04-24 23:52:40 +09:00
d11n
47f5d97eaf Prevent an NRE in LNURL (#4908)
* Prevent an NRE in LNURL

In addition to f05a7f9f14. Fixes #4904.

* Revert "Prevent an NRE in LNURL"

This reverts commit 0b241d61ab45b79297211e04da0e05c2cb10dada.

* Fix NRE

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-04-24 21:50:31 +09:00
Nicolas Dorier
cb3c5e56fd Fix: Poll of Lightning Invoice status might fail on LND if LNURL is used (#4910) 2023-04-24 18:15:39 +09:00
Nicolas Dorier
39b76c08de Fix: Form in Payment Requests was not setting its values to the invoices metadata (#4907) 2023-04-24 18:04:46 +09:00
Zaxounette
c3c8cc21ff Security Page Refactor (#4815)
* Update SECURITY.md

* typo

* project vs product

* Suggestion Update - Docker Deployment

Co-authored-by: d11n <mail@dennisreimann.de>

* Suggestion - Email highlight

Co-authored-by: d11n <mail@dennisreimann.de>

---------

Co-authored-by: d11n <mail@dennisreimann.de>
2023-04-24 15:04:56 +09:00
nicolas.dorier
6dba1b6d8b Nullable on LanguageService 2023-04-20 09:44:24 +09:00
nicolas.dorier
381fe70a79 Add UserAgent to IPN and webhook HTTP requests (Fix #4883) 2023-04-19 21:13:31 +09:00
ndeet
feb927c2e4 Add missing param, fix response code. (#4891) 2023-04-19 13:07:36 +09:00
d11n
e77bd4c188 Swagger: FIx typos in pull payments docs (#4893)
Fixes #4892.
2023-04-19 13:06:51 +09:00
d11n
43436fc49e Remove BTCPayServer.Plugins.Test (#4875)
Remove the plugin, which is superseded with the plugin template and move the PluginPacker to the root of th solution (which matches its location in the file system).
2023-04-17 11:38:03 +09:00
nicolas.dorier
d738f797ec bump 2023-04-17 11:11:00 +09:00
nicolas.dorier
b5de97f785 Update changelog 2023-04-17 10:54:00 +09:00
Nicolas Dorier
b0c1b0895d Fix crash if auto detect language on checkout page, and the language couldn't be detected (Fix #4881) (#4888) 2023-04-17 10:53:45 +09:00
nicolas.dorier
8e60932f81 Migrate reference to AppType in server settings (Fix #4882) 2023-04-17 10:34:41 +09:00
d11n
7d14cd74f2 LightningLikePaymentData: Null-check preimage (#4886)
Fixes #4884.
2023-04-17 00:08:40 +02:00
nicolas.dorier
717f1610f5 Update changelog 2023-04-13 16:28:30 +09:00
ndeet
f1abe6497f Fix wrong data mapping to store data instead of store user data. (#4874) 2023-04-13 16:27:40 +09:00
rockstardev
046129a57d Bumping LND to 0.16.0-beta (#4873) 2023-04-13 14:48:47 +09:00
nicolas.dorier
90d300a490 Remove superflous category in API doc 2023-04-13 08:41:46 +09:00
d11n
a2d506c0db Checkout v2: Confetti for processing payments (#4872)
Let's not spoil the fun for those paying on-chain.
2023-04-13 08:40:21 +09:00
d11n
58748a24da What's New entry for v1.9 (#4871)
Copy is just a proposal from my side. Link check expected to fail, because the blog post isn't up yet.
2023-04-12 20:40:20 +09:00
113 changed files with 866 additions and 1021 deletions

View File

@@ -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 &quot;../../../../Packed Plugins&quot;" />
<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>

View File

@@ -86,6 +86,7 @@ namespace BTCPayServer.Client.Models
public bool? RequiresRefundEmail { get; set; } = null; public bool? RequiresRefundEmail { get; set; } = null;
public string DefaultLanguage { get; set; } public string DefaultLanguage { get; set; }
public CheckoutType? CheckoutType { get; set; } public CheckoutType? CheckoutType { get; set; }
public bool? LazyPaymentMethods { get; set; }
} }
} }
public class InvoiceData : InvoiceDataBase public class InvoiceData : InvoiceDataBase

View File

@@ -3,7 +3,6 @@ namespace BTCPayServer.Client.Models
public class LNURLPayPaymentMethodBaseData public class LNURLPayPaymentMethodBaseData
{ {
public bool UseBech32Scheme { get; set; } public bool UseBech32Scheme { get; set; }
public bool EnableForStandardInvoices { get; set; }
public bool LUD12Enabled { get; set; } public bool LUD12Enabled { get; set; }
public LNURLPayPaymentMethodBaseData() public LNURLPayPaymentMethodBaseData()

View File

@@ -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; Enabled = enabled;
CryptoCode = cryptoCode; CryptoCode = cryptoCode;
UseBech32Scheme = useBech32Scheme; UseBech32Scheme = useBech32Scheme;
EnableForStandardInvoices = enableForStandardInvoices;
} }
} }
} }

View File

@@ -4,7 +4,6 @@ namespace BTCPayServer.Client.Models
{ {
public string ConnectionString { get; set; } public string ConnectionString { get; set; }
public bool DisableBOLT11PaymentOption { get; set; }
public LightningNetworkPaymentMethodBaseData() public LightningNetworkPaymentMethodBaseData()
{ {

View File

@@ -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; Enabled = enabled;
CryptoCode = cryptoCode; CryptoCode = cryptoCode;
ConnectionString = connectionString; ConnectionString = connectionString;
PaymentMethod = paymentMethod; PaymentMethod = paymentMethod;
DisableBOLT11PaymentOption = disableBOLT11PaymentOption;
} }
public string PaymentMethod { get; set; } public string PaymentMethod { get; set; }

View File

@@ -51,6 +51,7 @@ namespace BTCPayServer.Tests
user.RegisterDerivationScheme(cryptoCode); user.RegisterDerivationScheme(cryptoCode);
user.RegisterDerivationScheme("LTC"); user.RegisterDerivationScheme("LTC");
user.RegisterLightningNode(cryptoCode, LightningConnectionType.CLightning); user.RegisterLightningNode(cryptoCode, LightningConnectionType.CLightning);
user.SetLNUrl("BTC", false);
var btcNetwork = tester.PayTester.Networks.GetNetwork<BTCPayNetwork>(cryptoCode); var btcNetwork = tester.PayTester.Networks.GetNetwork<BTCPayNetwork>(cryptoCode);
var invoice = await user.BitPay.CreateInvoiceAsync( var invoice = await user.BitPay.CreateInvoiceAsync(
new Invoice new Invoice

View File

@@ -23,7 +23,7 @@
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" /> <PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
<PackageReference Include="Selenium.Support" Version="4.1.1" /> <PackageReference Include="Selenium.Support" Version="4.1.1" />
<PackageReference Include="Selenium.WebDriver" 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" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>

View File

@@ -164,7 +164,7 @@ namespace BTCPayServer.Tests
var invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC_LightningLike"); var invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC_LightningLike");
s.GoToInvoiceCheckout(invoiceId); 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(); s.Driver.Quit();
} }
@@ -187,7 +187,7 @@ namespace BTCPayServer.Tests
var invoiceId = s.CreateInvoice(10, "USD", "a@g.com"); var invoiceId = s.CreateInvoice(10, "USD", "a@g.com");
s.GoToInvoiceCheckout(invoiceId); 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)] [Fact(Timeout = TestTimeout)]

View File

@@ -45,14 +45,6 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("Save")).Click(); s.Driver.FindElement(By.Id("Save")).Click();
Assert.Contains("Store successfully updated", s.FindAlertMessage().Text); 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.GoToStore(StoreNavPages.CheckoutAppearance);
s.Driver.WaitForAndClick(By.Id("Presets")); s.Driver.WaitForAndClick(By.Id("Presets"));
s.Driver.WaitForAndClick(By.Id("Presets_InStore")); s.Driver.WaitForAndClick(By.Id("Presets_InStore"));
@@ -188,8 +180,10 @@ namespace BTCPayServer.Tests
}); });
s.Driver.Navigate().Refresh(); s.Driver.Navigate().Refresh();
// Pay full amount // Pay full amount
s.PayInvoice(); s.PayInvoice();
// Processing // Processing
TestUtils.Eventually(() => TestUtils.Eventually(() =>
{ {
@@ -197,8 +191,9 @@ namespace BTCPayServer.Tests
Assert.True(processingSection.Displayed); Assert.True(processingSection.Displayed);
Assert.Contains("Payment Received", processingSection.Text); Assert.Contains("Payment Received", processingSection.Text);
Assert.Contains("Your payment has been received and is now processing", processingSection.Text); Assert.Contains("Your payment has been received and is now processing", processingSection.Text);
Assert.True(s.Driver.ElementDoesNotExist(By.Id("confetti")));
}); });
s.Driver.FindElement(By.Id("confetti"));
// Mine // Mine
s.MineBlockOnInvoiceCheckout(); s.MineBlockOnInvoiceCheckout();
TestUtils.Eventually(() => TestUtils.Eventually(() =>
@@ -293,7 +288,6 @@ namespace BTCPayServer.Tests
s.GoToHome(); s.GoToHome();
s.GoToLightningSettings(); s.GoToLightningSettings();
Assert.True(s.Driver.FindElement(By.Id("LNURLEnabled")).Selected); Assert.True(s.Driver.FindElement(By.Id("LNURLEnabled")).Selected);
Assert.True(s.Driver.FindElement(By.Id("LNURLStandardInvoiceEnabled")).Selected);
// BIP21 with top-up invoice // BIP21 with top-up invoice
invoiceId = s.CreateInvoice(amount: null); invoiceId = s.CreateInvoice(amount: null);
@@ -389,7 +383,7 @@ namespace BTCPayServer.Tests
Assert.Contains("lang=de", s.Driver.Url); Assert.Contains("lang=de", s.Driver.Url);
s.Driver.Navigate().Refresh(); 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("Deutsch", languageSelect.SelectedOption.Text);
Assert.Equal("Details anzeigen", s.Driver.FindElement(By.Id("DetailsToggle")).Text); Assert.Equal("Details anzeigen", s.Driver.FindElement(By.Id("DetailsToggle")).Text);
languageSelect.SelectByText("English"); languageSelect.SelectByText("English");

View File

@@ -15,12 +15,15 @@ using BTCPayServer.Lightning;
using BTCPayServer.Models.InvoicingModels; using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning; using BTCPayServer.Payments.Lightning;
using BTCPayServer.PayoutProcessors;
using BTCPayServer.PayoutProcessors.OnChain;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Custodian.Client.MockCustodian; using BTCPayServer.Services.Custodian.Client.MockCustodian;
using BTCPayServer.Services.Notifications; using BTCPayServer.Services.Notifications;
using BTCPayServer.Services.Notifications.Blobs; using BTCPayServer.Services.Notifications.Blobs;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using NBitcoin; using NBitcoin;
using NBitpayClient; using NBitpayClient;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -1474,6 +1477,7 @@ namespace BTCPayServer.Tests
var newDeliveryId = await clientProfile.RedeliverWebhook(user.StoreId, hook.Id, delivery.Id); var newDeliveryId = await clientProfile.RedeliverWebhook(user.StoreId, hook.Id, delivery.Id);
req = await fakeServer.GetNextRequest(); req = await fakeServer.GetNextRequest();
req.Response.StatusCode = 404; req.Response.StatusCode = 404;
Assert.StartsWith("BTCPayServer", Assert.Single(req.Request.Headers.UserAgent));
await TestUtils.EventuallyAsync(async () => await TestUtils.EventuallyAsync(async () =>
{ {
// Releasing semaphore several times may help making this test less flaky // 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")); Assert.Empty(await adminClient.GetStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork"));
await adminClient.UpdateStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork", await adminClient.UpdateStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork",
new LightningAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(2) }); new LightningAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(600) });
Assert.Equal(2, Assert.Single(await adminClient.GetStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork")).IntervalSeconds.TotalSeconds); Assert.Equal(600, Assert.Single(await adminClient.GetStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork")).IntervalSeconds.TotalSeconds);
await TestUtils.EventuallyAsync(async () => await TestUtils.EventuallyAsync(async () =>
{ {
var payoutC = var payoutC =
@@ -3494,8 +3498,8 @@ namespace BTCPayServer.Tests
Assert.Empty(await adminClient.GetPayoutProcessors(admin.StoreId)); Assert.Empty(await adminClient.GetPayoutProcessors(admin.StoreId));
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC", await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC",
new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(100000) }); new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(3600) });
Assert.Equal(100000, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds); Assert.Equal(3600, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
var tpGen = Assert.Single(await adminClient.GetPayoutProcessors(admin.StoreId)); var tpGen = Assert.Single(await adminClient.GetPayoutProcessors(admin.StoreId));
Assert.Equal("BTC", Assert.Single(tpGen.PaymentMethods)); 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.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC"));
Assert.Empty(await adminClient.GetPayoutProcessors(admin.StoreId)); 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, 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 tester.ExplorerNode.GenerateAsync(1);
await TestUtils.EventuallyAsync(async () => await TestUtils.EventuallyAsync(async () =>
{ {
@@ -3519,8 +3525,9 @@ namespace BTCPayServer.Tests
Assert.Equal(3, payouts.Length); Assert.Equal(3, payouts.Length);
}); });
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC", await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC",
new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(5) }); new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(600), FeeBlockTarget = 1000 });
Assert.Equal(5, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds); Assert.Equal(600, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
await TestUtils.EventuallyAsync(async () => await TestUtils.EventuallyAsync(async () =>
{ {
Assert.Equal(2, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count()); 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)); Assert.Single(payouts.Where(data => data.State == PayoutState.InProgress));
}); });
await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address, var txid = await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.01m)); 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 () => await TestUtils.EventuallyAsync(async () =>
{ {
Assert.Equal(4, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count()); Assert.Equal(4, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count());

View File

@@ -11,8 +11,10 @@ using System.Threading.Tasks;
using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Controllers;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Lightning; using BTCPayServer.Lightning;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Invoices; 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.Name("buyerEmail")).SendKeys("aa@aa.com");
s.Driver.FindElement(By.CssSelector("input[type='submit']")).Click(); s.Driver.FindElement(By.CssSelector("input[type='submit']")).Click();
invoiceId = s.Driver.Url.Split('/').Last();
s.Driver.Navigate().GoToUrl(editUrl); s.Driver.Navigate().GoToUrl(editUrl);
Assert.Contains("aa@aa.com", s.Driver.PageSource); 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 //Custom Forms
s.GoToStore(StoreNavPages.Forms); s.GoToStore(StoreNavPages.Forms);
@@ -2085,22 +2090,23 @@ namespace BTCPayServer.Tests
}); });
// Standard invoice test // Standard invoice test
s.GoToStore(storeId); s.GoToStore(storeId);
s.GoToLightningSettings();
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), true);
SudoForceSaveLightningSettingsRightNowAndFast(s, cryptoCode);
i = s.CreateInvoice(storeId, 0.0000001m, cryptoCode); i = s.CreateInvoice(storeId, 0.0000001m, cryptoCode);
s.GoToInvoiceCheckout(i); s.GoToInvoiceCheckout(i);
s.Driver.FindElement(By.ClassName("payment__currencies")).Click(); s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Click();
// BOLT11 is also available for standard invoices // BOLT11 is also displayed for standard invoice (not LNURL, even if it is available)
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.Id("copy-tab")).Click(); s.Driver.FindElement(By.Id("copy-tab")).Click();
lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value"); var bolt11 = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
parsed = LNURL.LNURL.Parse(lnurl, out tag); var bolt11Parsed = Lightning.BOLT11PaymentRequest.Parse(bolt11, s.Server.ExplorerNode.Network);
fetchedReuqest = Assert.IsType<LNURLPayRequest>(await LNURL.LNURL.FetchInformation(parsed, new HttpClient())); 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.MaxSendable.ToDecimal(LightMoneyUnit.BTC));
Assert.Equal(0.0000001m, fetchedReuqest.MinSendable.ToDecimal(LightMoneyUnit.BTC)); Assert.Equal(0.0000001m, fetchedReuqest.MinSendable.ToDecimal(LightMoneyUnit.BTC));
await Assert.ThrowsAsync<LNUrlException>(async () => await Assert.ThrowsAsync<LNUrlException>(async () =>
{ {
await fetchedReuqest.SendRequest(new LightMoney(0.0000002m, LightMoneyUnit.BTC), await fetchedReuqest.SendRequest(new LightMoney(0.0000002m, LightMoneyUnit.BTC),
@@ -2122,13 +2128,6 @@ namespace BTCPayServer.Tests
Assert.Equal(new LightMoney(0.0000001m, LightMoneyUnit.BTC), Assert.Equal(new LightMoney(0.0000001m, LightMoneyUnit.BTC),
lnurlResponse2.GetPaymentRequest(network).MinimumAmount); lnurlResponse2.GetPaymentRequest(network).MinimumAmount);
s.GoToHome(); 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); i = s.CreateInvoice(storeId, 0.000001m, cryptoCode);
s.GoToInvoiceCheckout(i); s.GoToInvoiceCheckout(i);
@@ -2142,23 +2141,12 @@ namespace BTCPayServer.Tests
s.GoToHome(); s.GoToHome();
s.GoToLightningSettings(); s.GoToLightningSettings();
s.Driver.SetCheckbox(By.Id("LNURLBech32Mode"), false); 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(); s.Driver.FindElement(By.Id("save")).Click();
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text); Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
// Ensure the toggles are set correctly // Ensure the toggles are set correctly
s.GoToLightningSettings(); 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("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); i = s.CreateInvoice(storeId, null, cryptoCode);
s.GoToInvoiceCheckout(i); s.GoToInvoiceCheckout(i);
@@ -2174,15 +2162,13 @@ namespace BTCPayServer.Tests
s.AddLightningNode(LightningConnectionType.LndREST, false); s.AddLightningNode(LightningConnectionType.LndREST, false);
s.GoToLightningSettings(); s.GoToLightningSettings();
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true); 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(); s.Driver.FindElement(By.Id("save")).Click();
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text); 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.GoToInvoiceCheckout(invForPP);
s.Driver.FindElement(By.Id("copy-tab")).Click(); s.Driver.FindElement(By.Id("copy-tab")).Click();
lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value"); 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 // Check that pull payment has lightning option
s.GoToStore(s.StoreId, StoreNavPages.PullPayments); s.GoToStore(s.StoreId, StoreNavPages.PullPayments);

View File

@@ -177,7 +177,7 @@ namespace BTCPayServer.Tests
public async Task<PayResponse> SendLightningPaymentAsync(Invoice invoice) 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); bolt11 = bolt11.Replace("lightning:", "", StringComparison.OrdinalIgnoreCase);
return await CustomerLightningD.Pay(bolt11); return await CustomerLightningD.Pay(bolt11);
} }
@@ -194,7 +194,8 @@ namespace BTCPayServer.Tests
tcs.TrySetResult(evt); tcs.TrySetResult(evt);
} }
}); });
await action.Invoke(); if (action != null)
await action.Invoke();
var result = await tcs.Task; var result = await tcs.Task;
sub.Dispose(); sub.Dispose();
return result; return result;
@@ -247,6 +248,8 @@ namespace BTCPayServer.Tests
public List<string> Stores { get; internal set; } = new List<string>(); public List<string> Stores { get; internal set; } = new List<string>();
public bool DeleteStore { get; set; } = true; public bool DeleteStore { get; set; } = true;
public BTCPayNetworkBase DefaultNetwork => NetworkProvider.DefaultNetwork;
public void Dispose() public void Dispose()
{ {
foreach (var r in this.Resources) foreach (var r in this.Resources)

View File

@@ -214,6 +214,13 @@ namespace BTCPayServer.Tests
get => GenerateWalletResponseV.DerivationScheme; 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) private async Task RegisterAsync(bool isAdmin = false)
{ {
var account = parent.PayTester.GetController<UIAccountController>(); var account = parent.PayTester.GetController<UIAccountController>();

View File

@@ -112,7 +112,14 @@ namespace BTCPayServer.Tests
} }
catch (XunitException) when (!cts.Token.IsCancellationRequested) 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;
} }
} }
} }

View File

@@ -1635,6 +1635,7 @@ namespace BTCPayServer.Tests
var cryptoCode = "BTC"; var cryptoCode = "BTC";
user.GrantAccess(true); user.GrantAccess(true);
user.RegisterLightningNode(cryptoCode, LightningConnectionType.Charge); user.RegisterLightningNode(cryptoCode, LightningConnectionType.Charge);
user.SetLNUrl(cryptoCode, false);
var vm = user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModel<CheckoutAppearanceViewModel>(); var vm = user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModel<CheckoutAppearanceViewModel>();
var criteria = Assert.Single(vm.PaymentMethodCriteria); var criteria = Assert.Single(vm.PaymentMethodCriteria);
Assert.Equal(new PaymentMethodId(cryptoCode, LightningPaymentType.Instance).ToString(), criteria.PaymentMethod); Assert.Equal(new PaymentMethodId(cryptoCode, LightningPaymentType.Instance).ToString(), criteria.PaymentMethod);
@@ -1649,16 +1650,12 @@ namespace BTCPayServer.Tests
Price = 1.5m, Price = 1.5m,
Currency = "USD" Currency = "USD"
}, Facade.Merchant); }, Facade.Merchant);
Assert.Single(invoice.CryptoInfo); Assert.Single(invoice.CryptoInfo);
Assert.Equal(PaymentTypes.LightningLike.ToString(), invoice.CryptoInfo[0].PaymentType); Assert.Equal(PaymentTypes.LightningLike.ToString(), invoice.CryptoInfo[0].PaymentType);
// Activating LNUrl, we should still have only 1 payment criteria that can be set. // Activating LNUrl, we should still have only 1 payment criteria that can be set.
user.RegisterLightningNode(cryptoCode, LightningConnectionType.Charge); user.RegisterLightningNode(cryptoCode, LightningConnectionType.Charge);
var lnSettingsVm = user.GetController<UIStoresController>().LightningSettings(user.StoreId, cryptoCode).AssertViewModel<LightningSettingsViewModel>(); user.SetLNUrl(cryptoCode, true);
lnSettingsVm.LNURLEnabled = true;
lnSettingsVm.LNURLStandardInvoiceEnabled = true;
Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>().LightningSettings(lnSettingsVm).Result);
vm = user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModel<CheckoutAppearanceViewModel>(); vm = user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModel<CheckoutAppearanceViewModel>();
criteria = Assert.Single(vm.PaymentMethodCriteria); criteria = Assert.Single(vm.PaymentMethodCriteria);
Assert.Equal(new PaymentMethodId(cryptoCode, LightningPaymentType.Instance).ToString(), criteria.PaymentMethod); Assert.Equal(new PaymentMethodId(cryptoCode, LightningPaymentType.Instance).ToString(), criteria.PaymentMethod);
@@ -2445,6 +2442,31 @@ namespace BTCPayServer.Tests
Assert.False(fn.Seen); Assert.False(fn.Seen);
} }
[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanFixMappedDomainAppType()
{
using var tester = CreateServerTester(newDb: true);
await tester.StartAsync();
var f = tester.PayTester.GetService<ApplicationDbContextFactory>();
using (var ctx = f.CreateContext())
{
var setting = new SettingData() { Id = "BTCPayServer.Services.PoliciesSettings" };
setting.Value = JObject.Parse("{\"RootAppId\": null, \"RootAppType\": 1, \"Experimental\": false, \"PluginSource\": null, \"LockSubscription\": false, \"DisableSSHService\": false, \"PluginPreReleases\": false, \"BlockExplorerLinks\": [],\"DomainToAppMapping\": [{\"AppId\": \"87kj5yKay8mB4UUZcJhZH5TqDKMD3CznjwLjiu1oYZXe\", \"Domain\": \"donate.nicolas-dorier.com\", \"AppType\": 0}], \"CheckForNewVersions\": false, \"AllowHotWalletForAll\": false, \"RequiresConfirmedEmail\": false, \"DiscourageSearchEngines\": false, \"DisableInstantNotifications\": false, \"DisableNonAdminCreateUserApi\": false, \"AllowHotWalletRPCImportForAll\": false, \"AllowLightningInternalNodeForAll\": false, \"DisableStoresToUseServerEmailSettings\": false}").ToString();
ctx.Settings.Add(setting);
await ctx.SaveChangesAsync();
}
await RestartMigration(tester);
using (var ctx = f.CreateContext())
{
var setting = await ctx.Settings.FirstOrDefaultAsync(c => c.Id == "BTCPayServer.Services.PoliciesSettings");
var o = JObject.Parse(setting.Value);
Assert.Equal("Crowdfund", o["RootAppType"].Value<string>());
o = (JObject)((JArray)o["DomainToAppMapping"])[0];
Assert.Equal("PointOfSale", o["AppType"].Value<string>());
}
}
[Fact(Timeout = LongRunningTestTimeout)] [Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")] [Trait("Integration", "Integration")]
public async Task CanDoLightningInternalNodeMigration() public async Task CanDoLightningInternalNodeMigration()

View File

@@ -243,7 +243,7 @@ services:
- "5432" - "5432"
merchant_lnd: merchant_lnd:
image: btcpayserver/lnd:v0.15.4-beta-1 image: btcpayserver/lnd:v0.16.2-beta
restart: unless-stopped restart: unless-stopped
environment: environment:
LND_CHAIN: "btc" LND_CHAIN: "btc"
@@ -278,7 +278,7 @@ services:
- bitcoind - bitcoind
customer_lnd: customer_lnd:
image: btcpayserver/lnd:v0.15.4-beta-1 image: btcpayserver/lnd:v0.16.2-beta
restart: unless-stopped restart: unless-stopped
environment: environment:
LND_CHAIN: "btc" LND_CHAIN: "btc"

View File

@@ -230,7 +230,7 @@ services:
- "5432" - "5432"
merchant_lnd: merchant_lnd:
image: btcpayserver/lnd:v0.15.4-beta-1 image: btcpayserver/lnd:v0.16.2-beta
restart: unless-stopped restart: unless-stopped
environment: environment:
LND_CHAIN: "btc" LND_CHAIN: "btc"
@@ -267,7 +267,7 @@ services:
- bitcoind - bitcoind
customer_lnd: customer_lnd:
image: btcpayserver/lnd:v0.15.4-beta-1 image: btcpayserver/lnd:v0.16.2-beta
restart: unless-stopped restart: unless-stopped
environment: environment:
LND_CHAIN: "btc" LND_CHAIN: "btc"

View File

@@ -47,7 +47,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="BIP78.Sender" Version="0.2.2" /> <PackageReference Include="BIP78.Sender" Version="0.2.2" />
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.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="CsvHelper" Version="15.0.5" />
<PackageReference Include="Dapper" Version="2.0.123" /> <PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="Fido2" Version="2.0.2" /> <PackageReference Include="Fido2" Version="2.0.2" />

View File

@@ -145,7 +145,10 @@
</a> </a>
</li> </li>
<li class="nav-item" permission="@Policies.CanModifyStoreSettings"> <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"/> <vc:icon symbol="payouts"/>
<span>Payouts</span> <span>Payouts</span>
</a> </a>

View File

@@ -124,7 +124,8 @@ namespace BTCPayServer.Controllers.Greenfield
try 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)); return Ok(GreenfieldInvoiceController.ToModel(invoice, _linkGenerator, Request));
} }
catch (BitpayHttpException e) catch (BitpayHttpException e)

View File

@@ -2,6 +2,7 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Data; using BTCPayServer.Data;
@@ -69,6 +70,9 @@ namespace BTCPayServer.Controllers.Greenfield
public async Task<IActionResult> UpdateStoreLightningAutomatedPayoutProcessor( public async Task<IActionResult> UpdateStoreLightningAutomatedPayoutProcessor(
string storeId, string paymentMethod, LightningAutomatedPayoutSettings request) 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(); paymentMethod = PaymentMethodId.Parse(paymentMethod).ToString();
var activeProcessor = var activeProcessor =
(await _payoutProcessorService.GetProcessors( (await _payoutProcessorService.GetProcessors(

View File

@@ -1,7 +1,9 @@
#nullable enable #nullable enable
using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Data; using BTCPayServer.Data;
@@ -75,6 +77,11 @@ namespace BTCPayServer.Controllers.Greenfield
public async Task<IActionResult> UpdateStoreOnchainAutomatedPayoutProcessor( public async Task<IActionResult> UpdateStoreOnchainAutomatedPayoutProcessor(
string storeId, string paymentMethod, OnChainAutomatedPayoutSettings request) 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(); paymentMethod = PaymentMethodId.Parse(paymentMethod).ToString();
var activeProcessor = var activeProcessor =
(await _payoutProcessorService.GetProcessors( (await _payoutProcessorService.GetProcessors(

View File

@@ -54,7 +54,7 @@ namespace BTCPayServer.Controllers.Greenfield
new LNURLPayPaymentMethodData( new LNURLPayPaymentMethodData(
paymentMethod.PaymentId.CryptoCode, paymentMethod.PaymentId.CryptoCode,
!excludedPaymentMethods.Match(paymentMethod.PaymentId), !excludedPaymentMethods.Match(paymentMethod.PaymentId),
paymentMethod.UseBech32Scheme, paymentMethod.EnableForStandardInvoices paymentMethod.UseBech32Scheme
) )
) )
.Where((result) => enabled is null || enabled == result.Enabled) .Where((result) => enabled is null || enabled == result.Enabled)
@@ -124,8 +124,7 @@ namespace BTCPayServer.Controllers.Greenfield
LNURLPaySupportedPaymentMethod? paymentMethod = new LNURLPaySupportedPaymentMethod() LNURLPaySupportedPaymentMethod? paymentMethod = new LNURLPaySupportedPaymentMethod()
{ {
CryptoCode = cryptoCode, CryptoCode = cryptoCode,
UseBech32Scheme = paymentMethodData.UseBech32Scheme, UseBech32Scheme = paymentMethodData.UseBech32Scheme
EnableForStandardInvoices = paymentMethodData.EnableForStandardInvoices
}; };
var store = Store; var store = Store;
@@ -154,7 +153,7 @@ namespace BTCPayServer.Controllers.Greenfield
: new LNURLPayPaymentMethodData( : new LNURLPayPaymentMethodData(
paymentMethod.PaymentId.CryptoCode, paymentMethod.PaymentId.CryptoCode,
!excluded, !excluded,
paymentMethod.UseBech32Scheme, paymentMethod.EnableForStandardInvoices paymentMethod.UseBech32Scheme
); );
} }
private void AssertCryptoCodeWallet(string cryptoCode, out BTCPayNetwork network) private void AssertCryptoCodeWallet(string cryptoCode, out BTCPayNetwork network)

View File

@@ -66,8 +66,7 @@ namespace BTCPayServer.Controllers.Greenfield
paymentMethod.GetExternalLightningUrl()?.ToString() ?? paymentMethod.GetExternalLightningUrl()?.ToString() ??
paymentMethod.GetDisplayableConnectionString(), paymentMethod.GetDisplayableConnectionString(),
!excludedPaymentMethods.Match(paymentMethod.PaymentId), !excludedPaymentMethods.Match(paymentMethod.PaymentId),
paymentMethod.PaymentId.ToStringNormalized(), paymentMethod.PaymentId.ToStringNormalized()
paymentMethod.DisableBOLT11PaymentOption
) )
) )
.Where((result) => enabled is null || enabled == result.Enabled) .Where((result) => enabled is null || enabled == result.Enabled)
@@ -207,7 +206,7 @@ namespace BTCPayServer.Controllers.Greenfield
? null ? null
: new LightningNetworkPaymentMethodData(paymentMethod.PaymentId.CryptoCode, : new LightningNetworkPaymentMethodData(paymentMethod.PaymentId.CryptoCode,
paymentMethod.GetDisplayableConnectionString(), !excluded, paymentMethod.GetDisplayableConnectionString(), !excluded,
paymentMethod.PaymentId.ToStringNormalized(), paymentMethod.DisableBOLT11PaymentOption); paymentMethod.PaymentId.ToStringNormalized());
} }
private BTCPayNetwork AssertSupportLightning(string cryptoCode) private BTCPayNetwork AssertSupportLightning(string cryptoCode)

View File

@@ -184,8 +184,14 @@ namespace BTCPayServer.Controllers
var receipt = InvoiceDataBase.ReceiptOptions.Merge(store.GetStoreBlob().ReceiptOptions, i.ReceiptOptions); var receipt = InvoiceDataBase.ReceiptOptions.Merge(store.GetStoreBlob().ReceiptOptions, i.ReceiptOptions);
if (receipt.Enabled is not true) if (receipt.Enabled is not true)
{
if (i.RedirectURL is not null)
{
return Redirect(i.RedirectURL.ToString());
}
return NotFound(); return NotFound();
}
var storeBlob = store.GetStoreBlob(); var storeBlob = store.GetStoreBlob();
var vm = new InvoiceReceiptViewModel var vm = new InvoiceReceiptViewModel
{ {
@@ -666,48 +672,55 @@ namespace BTCPayServer.Controllers
var btcId = PaymentMethodId.Parse("BTC"); var btcId = PaymentMethodId.Parse("BTC");
var lnId = PaymentMethodId.Parse("BTC_LightningLike"); var lnId = PaymentMethodId.Parse("BTC_LightningLike");
var lnurlId = PaymentMethodId.Parse("BTC_LNURLPAY"); 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) 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? invoicePaymentId = invoice.GetDefaultPaymentMethod();
PaymentMethodId? storePaymentId = store.GetDefaultPaymentId(); PaymentMethodId? storePaymentId = store.GetDefaultPaymentId();
if (invoicePaymentId is not null) if (invoicePaymentId is not null)
{ {
if (enabledPaymentIds.Contains(invoicePaymentId)) if (displayedPaymentMethods.Contains(invoicePaymentId))
paymentMethodId = invoicePaymentId; paymentMethodId = invoicePaymentId;
} }
if (paymentMethodId is null && storePaymentId is not null) if (paymentMethodId is null && storePaymentId is not null)
{ {
if (enabledPaymentIds.Contains(storePaymentId)) if (displayedPaymentMethods.Contains(storePaymentId))
paymentMethodId = storePaymentId; paymentMethodId = storePaymentId;
} }
if (paymentMethodId is null && invoicePaymentId is not null) if (paymentMethodId is null && invoicePaymentId is not null)
{ {
paymentMethodId = invoicePaymentId.FindNearest(enabledPaymentIds); paymentMethodId = invoicePaymentId.FindNearest(displayedPaymentMethods);
} }
if (paymentMethodId is null && storePaymentId is not null) if (paymentMethodId is null && storePaymentId is not null)
{ {
paymentMethodId = storePaymentId.FindNearest(enabledPaymentIds); paymentMethodId = storePaymentId.FindNearest(displayedPaymentMethods);
} }
if (paymentMethodId is null) if (paymentMethodId is null)
{ {
paymentMethodId = enabledPaymentIds.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == PaymentTypes.BTCLike) ?? paymentMethodId = displayedPaymentMethods.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == PaymentTypes.BTCLike) ??
enabledPaymentIds.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType != PaymentTypes.LNURLPay) ?? displayedPaymentMethods.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType != PaymentTypes.LNURLPay) ??
enabledPaymentIds.FirstOrDefault(); displayedPaymentMethods.FirstOrDefault();
} }
isDefaultPaymentId = true; isDefaultPaymentId = true;
} }
@@ -721,30 +734,37 @@ namespace BTCPayServer.Controllers
return null; return null;
var paymentMethodTemp = invoice var paymentMethodTemp = invoice
.GetPaymentMethods() .GetPaymentMethods()
.FirstOrDefault(pm => .Where(p => displayedPaymentMethods.Contains(p.GetId()))
{ .FirstOrDefault();
var pmId = pm.GetId();
return paymentMethodId.CryptoCode == pmId.CryptoCode &&
((invoice.IsUnsetTopUp() && !storeBlob.OnChainWithLnInvoiceFallback) || pmId != lnurlId);
});
if (paymentMethodTemp == null)
paymentMethodTemp = invoice.GetPaymentMethods().FirstOrDefault();
if (paymentMethodTemp is null) if (paymentMethodTemp is null)
return null; return null;
network = paymentMethodTemp.Network; network = paymentMethodTemp.Network;
paymentMethodId = paymentMethodTemp.GetId(); paymentMethodId = paymentMethodTemp.GetId();
} }
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
var paymentMethodDetails = paymentMethod.GetPaymentMethodDetails(); // We activate the default payment method, and also those which aren't displayed (as they can't be set as default)
if (!paymentMethodDetails.Activated) 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 dto = invoice.EntityToDTO();
var accounting = paymentMethod.Calculate(); var accounting = paymentMethod.Calculate();
var paymentMethodHandler = _paymentMethodHandlerDictionary[paymentMethodId]; var paymentMethodHandler = _paymentMethodHandlerDictionary[paymentMethodId];
@@ -754,7 +774,7 @@ namespace BTCPayServer.Controllers
{ {
case "auto": case "auto":
case null when storeBlob.AutoDetectLanguage: case null when storeBlob.AutoDetectLanguage:
lang = _languageService.AutoDetectLanguageUsingHeader(HttpContext.Request.Headers, null).Code; lang = _languageService.AutoDetectLanguageUsingHeader(HttpContext.Request.Headers, null)?.Code;
break; break;
case { } langs when !string.IsNullOrEmpty(langs): case { } langs when !string.IsNullOrEmpty(langs):
{ {
@@ -773,11 +793,13 @@ namespace BTCPayServer.Controllers
Request.Host, Request.Host,
Request.PathBase) : null; Request.PathBase) : null;
var isAltcoinsBuild = false;
#if ALTCOINS
isAltcoinsBuild = true;
#endif
var model = new PaymentModel var model = new PaymentModel
{ {
#if ALTCOINS
AltcoinsBuild = true,
#endif
Activated = paymentMethodDetails.Activated, Activated = paymentMethodDetails.Activated,
CryptoCode = network.CryptoCode, CryptoCode = network.CryptoCode,
RootPath = Request.PathBase.Value.WithTrailingSlash(), RootPath = Request.PathBase.Value.WithTrailingSlash(),
@@ -842,11 +864,15 @@ namespace BTCPayServer.Controllers
{ {
var availableCryptoPaymentMethodId = kv.GetId(); var availableCryptoPaymentMethodId = kv.GetId();
var availableCryptoHandler = _paymentMethodHandlerDictionary[availableCryptoPaymentMethodId]; var availableCryptoHandler = _paymentMethodHandlerDictionary[availableCryptoPaymentMethodId];
var pmName = availableCryptoHandler.GetPaymentMethodName(availableCryptoPaymentMethodId);
return new PaymentModel.AvailableCrypto return new PaymentModel.AvailableCrypto
{ {
Displayed = displayedPaymentMethods.Contains(kv.GetId()),
PaymentMethodId = kv.GetId().ToString(), PaymentMethodId = kv.GetId().ToString(),
CryptoCode = kv.Network?.CryptoCode ?? kv.GetId().CryptoCode, CryptoCode = kv.Network?.CryptoCode ?? kv.GetId().CryptoCode,
PaymentMethodName = availableCryptoHandler.GetPaymentMethodName(availableCryptoPaymentMethodId), PaymentMethodName = isAltcoinsBuild
? pmName
: pmName.Replace("Bitcoin (", "").Replace(")", "").Replace("Lightning ", ""),
IsLightning = IsLightning =
kv.GetId().PaymentType == PaymentTypes.LightningLike, kv.GetId().PaymentType == PaymentTypes.LightningLike,
CryptoImage = Request.GetRelativePathOrAbsolute(availableCryptoHandler.GetCryptoImage(availableCryptoPaymentMethodId)), CryptoImage = Request.GetRelativePathOrAbsolute(availableCryptoHandler.GetCryptoImage(availableCryptoPaymentMethodId)),
@@ -862,17 +888,6 @@ namespace BTCPayServer.Controllers
.ToList() .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); paymentMethodHandler.PreparePaymentModel(model, dto, storeBlob, paymentMethod);
model.UISettings = paymentMethodHandler.GetCheckoutUISettings(); model.UISettings = paymentMethodHandler.GetCheckoutUISettings();
model.PaymentMethodId = paymentMethodId.ToString(); model.PaymentMethodId = paymentMethodId.ToString();
@@ -1286,10 +1301,20 @@ namespace BTCPayServer.Controllers
{ {
case JTokenType.Array: case JTokenType.Array:
var items = item.Value.AsEnumerable().ToList(); var items = item.Value.AsEnumerable().ToList();
var arrayResult = new List<object>();
for (var i = 0; i < items.Count; i++) 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; break;
case JTokenType.Object: case JTokenType.Object:

View File

@@ -187,33 +187,36 @@ namespace BTCPayServer.Controllers
return await CreateInvoiceCoreRaw(entity, store, excludeFilter, null, cancellationToken, entityManipulator); 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) var id = prData.Id;
amount = Math.Min(pr.AmountDue, amount.Value); var prBlob = prData.GetBlob();
if (prBlob.AllowCustomPaymentAmounts && amount != null)
amount = Math.Min(amountDue, amount.Value);
else else
amount = pr.AmountDue; amount = amountDue;
var redirectUrl = _linkGenerator.PaymentRequestLink(pr.Id, request.Scheme, request.Host, request.PathBase); var redirectUrl = _linkGenerator.PaymentRequestLink(id, request.Scheme, request.Host, request.PathBase);
var invoiceMetadata = JObject invoiceMetadata = prData.GetBlob()?.FormResponse ?? new JObject();
new InvoiceMetadata invoiceMetadata.Merge(new InvoiceMetadata
{ {
OrderId = PaymentRequestRepository.GetOrderIdForPaymentRequest(pr.Id), OrderId = PaymentRequestRepository.GetOrderIdForPaymentRequest(id),
PaymentRequestId = pr.Id, PaymentRequestId = id,
BuyerEmail = pr.Email 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 = var invoiceRequest =
new CreateInvoiceRequest new CreateInvoiceRequest
{ {
Metadata = invoiceMetadata.ToJObject(), Metadata = invoiceMetadata,
Currency = pr.Currency, Currency = prBlob.Currency,
Amount = amount, Amount = amount,
Checkout = { RedirectURL = redirectUrl }, Checkout = { RedirectURL = redirectUrl },
Receipt = new InvoiceDataBase.ReceiptOptions { Enabled = false } 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); return await CreateInvoiceCoreRaw(invoiceRequest, storeData, request.GetAbsoluteRoot(), additionalTags, cancellationToken);
} }
@@ -246,6 +249,7 @@ namespace BTCPayServer.Controllers
entity.RedirectAutomatically = invoice.Checkout.RedirectAutomatically ?? storeBlob.RedirectAutomatically; entity.RedirectAutomatically = invoice.Checkout.RedirectAutomatically ?? storeBlob.RedirectAutomatically;
entity.CheckoutType = invoice.Checkout.CheckoutType; entity.CheckoutType = invoice.Checkout.CheckoutType;
entity.RequiresRefundEmail = invoice.Checkout.RequiresRefundEmail; entity.RequiresRefundEmail = invoice.Checkout.RequiresRefundEmail;
entity.LazyPaymentMethods = invoice.Checkout.LazyPaymentMethods ?? storeBlob.LazyPaymentMethods;
IPaymentFilter? excludeFilter = null; IPaymentFilter? excludeFilter = null;
if (invoice.Checkout.PaymentMethods != null) if (invoice.Checkout.PaymentMethods != null)
{ {
@@ -458,7 +462,7 @@ namespace BTCPayServer.Controllers
var storeBlob = store.GetStoreBlob(); var storeBlob = store.GetStoreBlob();
// Checkout v2 does not show a payment method switch for Bitcoin-only + BIP21, so exclude that case // 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 ? null
: handler.PreparePayment(supportedPaymentMethod, store, network); : handler.PreparePayment(supportedPaymentMethod, store, network);
var rate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, entity.Currency)]; var rate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, entity.Currency)];

View File

@@ -59,6 +59,7 @@ namespace BTCPayServer
private readonly PullPaymentHostedService _pullPaymentHostedService; private readonly PullPaymentHostedService _pullPaymentHostedService;
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings; private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
private readonly IPluginHookService _pluginHookService; private readonly IPluginHookService _pluginHookService;
private readonly InvoiceActivator _invoiceActivator;
public UILNURLController(InvoiceRepository invoiceRepository, public UILNURLController(InvoiceRepository invoiceRepository,
EventAggregator eventAggregator, EventAggregator eventAggregator,
@@ -72,7 +73,8 @@ namespace BTCPayServer
LightningLikePayoutHandler lightningLikePayoutHandler, LightningLikePayoutHandler lightningLikePayoutHandler,
PullPaymentHostedService pullPaymentHostedService, PullPaymentHostedService pullPaymentHostedService,
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings, BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
IPluginHookService pluginHookService) IPluginHookService pluginHookService,
InvoiceActivator invoiceActivator)
{ {
_invoiceRepository = invoiceRepository; _invoiceRepository = invoiceRepository;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
@@ -87,6 +89,7 @@ namespace BTCPayServer
_pullPaymentHostedService = pullPaymentHostedService; _pullPaymentHostedService = pullPaymentHostedService;
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings; _btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
_pluginHookService = pluginHookService; _pluginHookService = pluginHookService;
_invoiceActivator = invoiceActivator;
} }
[HttpGet("withdraw/pp/{pullPaymentId}")] [HttpGet("withdraw/pp/{pullPaymentId}")]
@@ -436,12 +439,16 @@ namespace BTCPayServer
List<string> additionalTags = null, List<string> additionalTags = null,
bool allowOverpay = true) 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"); return NotFound("LNUrl or LN is disabled");
InvoiceEntity i; InvoiceEntity i;
try 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); i = await _invoiceController.CreateInvoiceCoreRaw(createInvoice, store, Request.GetAbsoluteRoot(), additionalTags);
} }
catch (Exception e) catch (Exception e)
@@ -468,6 +475,8 @@ namespace BTCPayServer
lnUrlMetadata ??= new Dictionary<string, string>(); lnUrlMetadata ??= new Dictionary<string, string>();
var pm = i.GetPaymentMethod(pmi); var pm = i.GetPaymentMethod(pmi);
if (pm is null)
return null;
var paymentMethodDetails = (LNURLPayPaymentMethodDetails)pm.GetPaymentMethodDetails(); var paymentMethodDetails = (LNURLPayPaymentMethodDetails)pm.GetPaymentMethodDetails();
bool updatePaymentMethodDetails = false; bool updatePaymentMethodDetails = false;
if (lnUrlMetadata?.TryGetValue("text/identifier", out var lnAddress) is true && lnAddress is not null) 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 })); lnurlRequest.Metadata = JsonConvert.SerializeObject(lnUrlMetadata.Select(kv => new[] { kv.Key, kv.Value }));
if (i.Type != InvoiceType.TopUp) 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) if (!allowOverpay)
lnurlRequest.MaxSendable = lnurlRequest.MinSendable; lnurlRequest.MaxSendable = lnurlRequest.MinSendable;
} }
@@ -570,6 +579,15 @@ namespace BTCPayServer
var lightningPaymentMethod = i.GetPaymentMethod(pmi); var lightningPaymentMethod = i.GetPaymentMethod(pmi);
var paymentMethodDetails = var paymentMethodDetails =
lightningPaymentMethod?.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails; 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) if (paymentMethodDetails?.LightningSupportedPaymentMethod is null)
return NotFound(); return NotFound();

View File

@@ -235,6 +235,14 @@ namespace BTCPayServer.Controllers
} }
var form = Form.Parse(formData.Config); 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) if (Request.Method == "POST" && Request.HasFormContentType)
{ {
form.ApplyValuesFromForm(Request.Form); form.ApplyValuesFromForm(Request.Form);
@@ -277,7 +285,6 @@ namespace BTCPayServer.Controllers
return BadRequest("Payment Request cannot be paid as it has been archived"); return BadRequest("Payment Request cannot be paid as it has been archived");
} }
if (!result.FormSubmitted && !string.IsNullOrEmpty(result.FormId)) if (!result.FormSubmitted && !string.IsNullOrEmpty(result.FormId))
{ {
var formData = await FormDataService.GetForm(result.FormId); var formData = await FormDataService.GetForm(result.FormId);
@@ -322,7 +329,8 @@ namespace BTCPayServer.Controllers
try try
{ {
var store = await _storeRepository.FindStore(result.StoreId); 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) if (redirectToInvoice)
{ {
return RedirectToAction("Checkout", "UIInvoice", new { invoiceId = newInvoice.Id }); return RedirectToAction("Checkout", "UIInvoice", new { invoiceId = newInvoice.Id });

View File

@@ -181,7 +181,6 @@ namespace BTCPayServer.Controllers
{ {
CryptoCode = vm.CryptoCode, CryptoCode = vm.CryptoCode,
UseBech32Scheme = true, UseBech32Scheme = true,
EnableForStandardInvoices = false,
LUD12Enabled = false LUD12Enabled = false
}); });
@@ -245,26 +244,12 @@ namespace BTCPayServer.Controllers
}; };
SetExistingValues(store, vm); SetExistingValues(store, vm);
if (lightning != null)
{
vm.DisableBolt11PaymentMethod = lightning.DisableBOLT11PaymentOption;
}
var lnurl = GetExistingLNURLSupportedPaymentMethod(vm.CryptoCode, store); var lnurl = GetExistingLNURLSupportedPaymentMethod(vm.CryptoCode, store);
if (lnurl != null) if (lnurl != null)
{ {
vm.LNURLEnabled = !store.GetStoreBlob().GetExcludedPaymentMethods().Match(lnurl.PaymentId); vm.LNURLEnabled = !store.GetStoreBlob().GetExcludedPaymentMethods().Match(lnurl.PaymentId);
vm.LNURLBech32Mode = lnurl.UseBech32Scheme; vm.LNURLBech32Mode = lnurl.UseBech32Scheme;
vm.LNURLStandardInvoiceEnabled = lnurl.EnableForStandardInvoices;
vm.LUD12Enabled = lnurl.LUD12Enabled; 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); return View(vm);
@@ -290,22 +275,11 @@ namespace BTCPayServer.Controllers
blob.LightningAmountInSatoshi = vm.LightningAmountInSatoshi; blob.LightningAmountInSatoshi = vm.LightningAmountInSatoshi;
blob.LightningPrivateRouteHints = vm.LightningPrivateRouteHints; blob.LightningPrivateRouteHints = vm.LightningPrivateRouteHints;
blob.OnChainWithLnInvoiceFallback = vm.OnChainWithLnInvoiceFallback; blob.OnChainWithLnInvoiceFallback = vm.OnChainWithLnInvoiceFallback;
var disableBolt11PaymentMethod =
vm.LNURLEnabled && vm.LNURLStandardInvoiceEnabled && vm.DisableBolt11PaymentMethod;
var lnurlId = new PaymentMethodId(vm.CryptoCode, PaymentTypes.LNURLPay); var lnurlId = new PaymentMethodId(vm.CryptoCode, PaymentTypes.LNURLPay);
blob.SetExcluded(lnurlId, !vm.LNURLEnabled); 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); var lnurl = GetExistingLNURLSupportedPaymentMethod(vm.CryptoCode, store);
if (lnurl is null || ( if (lnurl is null || (
lnurl.EnableForStandardInvoices != vm.LNURLStandardInvoiceEnabled ||
lnurl.UseBech32Scheme != vm.LNURLBech32Mode || lnurl.UseBech32Scheme != vm.LNURLBech32Mode ||
lnurl.LUD12Enabled != vm.LUD12Enabled)) lnurl.LUD12Enabled != vm.LUD12Enabled))
{ {
@@ -315,7 +289,6 @@ namespace BTCPayServer.Controllers
store.SetSupportedPaymentMethod(new LNURLPaySupportedPaymentMethod store.SetSupportedPaymentMethod(new LNURLPaySupportedPaymentMethod
{ {
CryptoCode = vm.CryptoCode, CryptoCode = vm.CryptoCode,
EnableForStandardInvoices = vm.LNURLStandardInvoiceEnabled,
UseBech32Scheme = vm.LNURLBech32Mode, UseBech32Scheme = vm.LNURLBech32Mode,
LUD12Enabled = vm.LUD12Enabled LUD12Enabled = vm.LUD12Enabled
}); });

View File

@@ -32,7 +32,7 @@ namespace BTCPayServer.HostedServices
foreach (var t in _Tasks) foreach (var t in _Tasks)
t.ContinueWith(t => t.ContinueWith(t =>
{ {
if (t.IsFaulted) if (t.IsFaulted && !CancellationToken.IsCancellationRequested)
Logs.PayServer.LogWarning(t.Exception, $"Unhanded exception in {this.GetType().Name}"); Logs.PayServer.LogWarning(t.Exception, $"Unhanded exception in {this.GetType().Name}");
}, TaskScheduler.Default); }, TaskScheduler.Default);
return Task.CompletedTask; return Task.CompletedTask;
@@ -40,7 +40,7 @@ namespace BTCPayServer.HostedServices
internal abstract Task[] InitializeTasks(); internal abstract Task[] InitializeTasks();
protected CancellationToken Cancellation protected CancellationToken CancellationToken
{ {
get { return _Cts.Token; } get { return _Cts.Token; }
} }
@@ -69,8 +69,6 @@ namespace BTCPayServer.HostedServices
} }
} }
public CancellationToken CancellationToken => _Cts.Token;
public virtual async Task StopAsync(CancellationToken cancellationToken) public virtual async Task StopAsync(CancellationToken cancellationToken)
{ {
if (_Cts != null) if (_Cts != null)

View File

@@ -43,7 +43,7 @@ namespace BTCPayServer.HostedServices
readonly InvoiceRepository _InvoiceRepository; readonly InvoiceRepository _InvoiceRepository;
private readonly EmailSenderFactory _EmailSenderFactory; private readonly EmailSenderFactory _EmailSenderFactory;
private readonly StoreRepository _StoreRepository; private readonly StoreRepository _StoreRepository;
public const string NamedClient = "bitpay-ipn";
public BitpayIPNSender( public BitpayIPNSender(
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
IBackgroundJobClient jobClient, IBackgroundJobClient jobClient,
@@ -52,7 +52,7 @@ namespace BTCPayServer.HostedServices
StoreRepository storeRepository, StoreRepository storeRepository,
EmailSenderFactory emailSenderFactory) EmailSenderFactory emailSenderFactory)
{ {
_Client = httpClientFactory.CreateClient(); _Client = httpClientFactory.CreateClient(NamedClient);
_JobClient = jobClient; _JobClient = jobClient;
_EventAggregator = eventAggregator; _EventAggregator = eventAggregator;
_InvoiceRepository = invoiceRepository; _InvoiceRepository = invoiceRepository;
@@ -232,7 +232,6 @@ namespace BTCPayServer.HostedServices
request.RequestUri = new Uri(notification.NotificationURL, UriKind.Absolute); request.RequestUri = new Uri(notification.NotificationURL, UriKind.Absolute);
request.Content = new StringContent(notificationString, UTF8, "application/json"); request.Content = new StringContent(notificationString, UTF8, "application/json");
using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(TimeSpan.FromMinutes(1.0)); cts.CancelAfter(TimeSpan.FromMinutes(1.0));
var response = await _Client.SendAsync(request, cts.Token); var response = await _Client.SendAsync(request, cts.Token);

View File

@@ -28,8 +28,8 @@ namespace BTCPayServer.HostedServices
{ {
while (true) while (true)
{ {
await _transactionBroadcaster.ProcessAll(Cancellation); await _transactionBroadcaster.ProcessAll(CancellationToken);
await Task.Delay(PollInternal, Cancellation); await Task.Delay(PollInternal, CancellationToken);
} }
} }
} }

View File

@@ -30,7 +30,7 @@ namespace BTCPayServer.HostedServices
readonly TimeSpan Period = TimeSpan.FromMinutes(60); readonly TimeSpan Period = TimeSpan.FromMinutes(60);
async Task UpdateRecord() async Task UpdateRecord()
{ {
using (var timeout = CancellationTokenSource.CreateLinkedTokenSource(Cancellation)) using (var timeout = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken))
{ {
var settings = await SettingsRepository.GetSettingAsync<DynamicDnsSettings>() ?? new DynamicDnsSettings(); var settings = await SettingsRepository.GetSettingAsync<DynamicDnsSettings>() ?? new DynamicDnsSettings();
foreach (var service in settings.Services) 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 delay = Task.Delay(Period, delayCancel.Token);
var changed = SettingsRepository.WaitSettingsChanged<DynamicDnsSettings>(Cancellation); var changed = SettingsRepository.WaitSettingsChanged<DynamicDnsSettings>(CancellationToken);
await Task.WhenAny(delay, changed); await Task.WhenAny(delay, changed);
delayCancel.Cancel(); delayCancel.Cancel();
} }

View File

@@ -44,7 +44,7 @@ namespace BTCPayServer.HostedServices
{ {
Logs.Events.LogError(ex, "Error while performing new version check"); 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() public async Task ProcessVersionCheck()
@@ -52,7 +52,7 @@ namespace BTCPayServer.HostedServices
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings(); var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
if (policies.CheckForNewVersions) if (policies.CheckForNewVersions)
{ {
var tag = await _versionFetcher.Fetch(Cancellation); var tag = await _versionFetcher.Fetch(CancellationToken);
if (tag != null && tag != _env.Version) if (tag != null && tag != _env.Version)
{ {
var dh = await _settingsRepository.GetSettingAsync<NewVersionCheckerDataHolder>() ?? new NewVersionCheckerDataHolder(); var dh = await _settingsRepository.GetSettingAsync<NewVersionCheckerDataHolder>() ?? new NewVersionCheckerDataHolder();

View File

@@ -64,10 +64,10 @@ namespace BTCPayServer.HostedServices
var usedProviders = GetStillUsedProviders().ToArray(); var usedProviders = GetStillUsedProviders().ToArray();
if (usedProviders.Length == 0) if (usedProviders.Length == 0)
{ {
await Task.Delay(TimeSpan.FromSeconds(30), Cancellation); await Task.Delay(TimeSpan.FromSeconds(30), CancellationToken);
return; return;
} }
using (var timeout = CancellationTokenSource.CreateLinkedTokenSource(Cancellation)) using (var timeout = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken))
{ {
timeout.CancelAfter(TimeSpan.FromSeconds(20.0)); timeout.CancelAfter(TimeSpan.FromSeconds(20.0));
try try
@@ -97,7 +97,7 @@ namespace BTCPayServer.HostedServices
await SaveRateCache(); await SaveRateCache();
} }
} }
await Task.Delay(TimeSpan.FromSeconds(30), Cancellation); await Task.Delay(TimeSpan.FromSeconds(30), CancellationToken);
} }
public override async Task StartAsync(CancellationToken cancellationToken) public override async Task StartAsync(CancellationToken cancellationToken)

View File

@@ -13,6 +13,7 @@ using BTCPayServer.Controllers.Greenfield;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Events; using BTCPayServer.Events;
using BTCPayServer.Logging; using BTCPayServer.Logging;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -31,6 +32,7 @@ namespace BTCPayServer.HostedServices
{ {
readonly Encoding UTF8 = new UTF8Encoding(false); readonly Encoding UTF8 = new UTF8Encoding(false);
public readonly static JsonSerializerSettings DefaultSerializerSettings; public readonly static JsonSerializerSettings DefaultSerializerSettings;
static WebhookSender() static WebhookSender()
{ {
DefaultSerializerSettings = WebhookEvent.DefaultSerializerSettings; DefaultSerializerSettings = WebhookEvent.DefaultSerializerSettings;
@@ -38,6 +40,7 @@ namespace BTCPayServer.HostedServices
public const string OnionNamedClient = "greenfield-webhook.onion"; public const string OnionNamedClient = "greenfield-webhook.onion";
public const string ClearnetNamedClient = "greenfield-webhook.clearnet"; public const string ClearnetNamedClient = "greenfield-webhook.clearnet";
public const string LoopbackNamedClient = "greenfield-webhook.loopback"; public const string LoopbackNamedClient = "greenfield-webhook.loopback";
public static string[] AllClients = new[] { OnionNamedClient, ClearnetNamedClient, LoopbackNamedClient };
private HttpClient GetClient(Uri uri) private HttpClient GetClient(Uri uri)
{ {
return HttpClientFactory.CreateClient(uri.IsOnion() ? OnionNamedClient : uri.IsLoopback ? LoopbackNamedClient : ClearnetNamedClient); return HttpClientFactory.CreateClient(uri.IsOnion() ? OnionNamedClient : uri.IsLoopback ? LoopbackNamedClient : ClearnetNamedClient);

View File

@@ -477,6 +477,17 @@ namespace BTCPayServer.Hosting
services.AddSingleton<Cheater>(); services.AddSingleton<Cheater>();
services.AddSingleton<IHostedService, Cheater>(o => o.GetRequiredService<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; return services;
} }

View File

@@ -242,6 +242,12 @@ namespace BTCPayServer.Hosting
settings.FixSeqAfterSqliteMigration = true; settings.FixSeqAfterSqliteMigration = true;
await _Settings.UpdateSetting(settings); await _Settings.UpdateSetting(settings);
} }
if (!settings.FixMappedDomainAppType)
{
await FixMappedDomainAppType();
settings.FixMappedDomainAppType = true;
await _Settings.UpdateSetting(settings);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -250,6 +256,44 @@ namespace BTCPayServer.Hosting
} }
} }
private async Task FixMappedDomainAppType()
{
await using var ctx = _DBContextFactory.CreateContext();
var setting = await ctx.Settings.FirstOrDefaultAsync(s => s.Id == "BTCPayServer.Services.PoliciesSettings");
if (setting?.Value is null)
return;
string MapToString(int v)
{
return v switch
{
0 => "PointOfSale",
1 => "Crowdfund",
_ => throw new NotSupportedException()
};
}
var data = JObject.Parse(setting.Value);
if (data["RootAppType"]?.Type is JTokenType.Integer)
{
var v = data["RootAppType"].Value<int>();
data["RootAppType"] = new JValue(MapToString(v));
}
var arr = data["DomainToAppMapping"] as JArray;
if (arr != null)
{
foreach (var map in arr)
{
if (map["AppType"]?.Type is JTokenType.Integer)
{
var v = map["AppType"].Value<int>();
map["AppType"] = new JValue(MapToString(v));
}
}
}
setting.Value = data.ToString();
await ctx.SaveChangesAsync();
}
private async Task FixSeqAfterSqliteMigration() private async Task FixSeqAfterSqliteMigration()
{ {
await using var ctx = _DBContextFactory.CreateContext(); await using var ctx = _DBContextFactory.CreateContext();

View File

@@ -21,6 +21,7 @@ namespace BTCPayServer.Models.InvoicingModels
public string PaymentMethodName { get; set; } public string PaymentMethodName { get; set; }
public bool IsLightning { get; set; } public bool IsLightning { get; set; }
public string CryptoCode { get; set; } public string CryptoCode { get; set; }
public bool Displayed { get; set; }
} }
public string CustomCSSLink { get; set; } public string CustomCSSLink { get; set; }
public string CustomLogoLink { get; set; } public string CustomLogoLink { get; set; }
@@ -77,7 +78,6 @@ namespace BTCPayServer.Models.InvoicingModels
public bool Activated { get; set; } public bool Activated { get; set; }
public string InvoiceCurrency { get; set; } public string InvoiceCurrency { get; set; }
public string ReceiptLink { get; set; } public string ReceiptLink { get; set; }
public bool AltcoinsBuild { get; set; }
public CheckoutType CheckoutType { get; set; } public CheckoutType CheckoutType { get; set; }
public int? RequiredConfirmations { get; set; } public int? RequiredConfirmations { get; set; }
public long? ReceivedConfirmations { get; set; } public long? ReceivedConfirmations { get; set; }

View File

@@ -24,13 +24,7 @@ namespace BTCPayServer.Models.StoreViewModels
[Display(Name = "LNURL Classic Mode")] [Display(Name = "LNURL Classic Mode")]
public bool LNURLBech32Mode { get; set; } = true; 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")] [Display(Name = "Allow payee to pass a comment")]
public bool LUD12Enabled { get; set; } public bool LUD12Enabled { get; set; }
[Display(Name = "Do not offer BOLT11 for standard invoices")]
public bool DisableBolt11PaymentMethod { get; set; }
} }
} }

View File

@@ -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, public virtual object PreparePayment(TSupportedPaymentMethod supportedPaymentMethod, StoreData store,
BTCPayNetworkBase network) BTCPayNetworkBase network)
{ {

View File

@@ -50,55 +50,21 @@ namespace BTCPayServer.Payments.Lightning
BTCPayNetwork network, object preparePaymentObject, IEnumerable<PaymentMethodId> invoicePaymentMethods) BTCPayNetwork network, object preparePaymentObject, IEnumerable<PaymentMethodId> invoicePaymentMethods)
{ {
var lnPmi = new PaymentMethodId(supportedPaymentMethod.CryptoCode, PaymentTypes.LightningLike); var lnPmi = new PaymentMethodId(supportedPaymentMethod.CryptoCode, PaymentTypes.LightningLike);
if (!supportedPaymentMethod.EnableForStandardInvoices && var lnSupported = store.GetSupportedPaymentMethods(_networkProvider)
paymentMethod.ParentEntity.Type == InvoiceType.Standard && .OfType<LightningSupportedPaymentMethod>()
invoicePaymentMethods.Contains(lnPmi)) .SingleOrDefault(method => method.PaymentId == lnPmi);
if (lnSupported is null)
{ {
throw new PaymentMethodUnavailableException("LNURL is not enabled for standard invoices"); throw new PaymentMethodUnavailableException("LNURL requires a lightning node to be configured for the store.");
}
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
};
} }
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 = return new LNURLPayPaymentMethodDetails()
((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
{ {
Activated = true, Activated = true,
LightningSupportedPaymentMethod = lnLightningSupportedPaymentMethod, LightningSupportedPaymentMethod = lnSupported,
Bech32Mode = supportedPaymentMethod.UseBech32Scheme, Bech32Mode = supportedPaymentMethod.UseBech32Scheme,
NodeInfo = nodeInfo?.ToString() NodeInfo = nodeInfo?.ToString()
}; };

View File

@@ -14,8 +14,6 @@ namespace BTCPayServer.Payments.Lightning
public bool UseBech32Scheme { get; set; } public bool UseBech32Scheme { get; set; }
public bool EnableForStandardInvoices { get; set; } = false;
public bool LUD12Enabled { get; set; } = true; public bool LUD12Enabled { get; set; } = true;
} }

View File

@@ -47,7 +47,6 @@ namespace BTCPayServer.Payments
return new LNURLPayPaymentMethodBaseData() return new LNURLPayPaymentMethodBaseData()
{ {
UseBech32Scheme = lightningSupportedPaymentMethod.UseBech32Scheme, UseBech32Scheme = lightningSupportedPaymentMethod.UseBech32Scheme,
EnableForStandardInvoices = lightningSupportedPaymentMethod.EnableForStandardInvoices,
LUD12Enabled = lightningSupportedPaymentMethod.LUD12Enabled LUD12Enabled = lightningSupportedPaymentMethod.LUD12Enabled
}; };
return null; return null;

View File

@@ -66,7 +66,7 @@ namespace BTCPayServer.Payments.Lightning
public string GetPaymentProof() public string GetPaymentProof()
{ {
return Preimage.ToString(); return Preimage?.ToString();
} }
} }
} }

View File

@@ -53,10 +53,6 @@ namespace BTCPayServer.Payments.Lightning
LightningSupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, Data.StoreData store, LightningSupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, Data.StoreData store,
BTCPayNetwork network, object preparePaymentObject, IEnumerable<PaymentMethodId> invoicePaymentMethods) BTCPayNetwork network, object preparePaymentObject, IEnumerable<PaymentMethodId> invoicePaymentMethods)
{ {
if (supportedPaymentMethod.DisableBOLT11PaymentOption)
{
throw new PaymentMethodUnavailableException("BOLT11 payment method is disabled");
}
if (paymentMethod.ParentEntity.Type == InvoiceType.TopUp) if (paymentMethod.ParentEntity.Type == InvoiceType.TopUp)
{ {
throw new PaymentMethodUnavailableException("Lightning Network payment method is not available for top-up invoices"); throw new PaymentMethodUnavailableException("Lightning Network payment method is not available for top-up invoices");

View File

@@ -1,3 +1,4 @@
#nullable enable
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@@ -38,7 +39,7 @@ namespace BTCPayServer.Payments.Lightning
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly PaymentService _paymentService; private readonly PaymentService _paymentService;
readonly Channel<string> _CheckInvoices = Channel.CreateUnbounded<string>(); readonly Channel<string> _CheckInvoices = Channel.CreateUnbounded<string>();
Task _CheckingInvoice; Task? _CheckingInvoice;
readonly Dictionary<(string, string), LightningInstanceListener> _InstanceListeners = new Dictionary<(string, string), LightningInstanceListener>(); readonly Dictionary<(string, string), LightningInstanceListener> _InstanceListeners = new Dictionary<(string, string), LightningInstanceListener>();
public LightningListener(EventAggregator aggregator, public LightningListener(EventAggregator aggregator,
@@ -87,12 +88,15 @@ namespace BTCPayServer.Payments.Lightning
var invoice = await GetInvoice(invoiceId); var invoice = await GetInvoice(invoiceId);
foreach (var listenedInvoice in GetListenedInvoices(invoice)) 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) lock (_InstanceListeners)
{ {
if (!_InstanceListeners.TryGetValue(instanceListenerKey, out var instanceListener)) 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); _InstanceListeners.TryAdd(instanceListenerKey, instanceListener);
} }
instanceListener.AddListenedInvoice(listenedInvoice); instanceListener.AddListenedInvoice(listenedInvoice);
@@ -143,7 +147,7 @@ namespace BTCPayServer.Payments.Lightning
.Where(c => new[] { PaymentTypes.LightningLike, LNURLPayPaymentType.Instance }.Contains(c.GetId().PaymentType))) .Where(c => new[] { PaymentTypes.LightningLike, LNURLPayPaymentType.Instance }.Contains(c.GetId().PaymentType)))
{ {
LightningLikePaymentMethodDetails lightningMethod; LightningLikePaymentMethodDetails lightningMethod;
LightningSupportedPaymentMethod lightningSupportedMethod; LightningSupportedPaymentMethod? lightningSupportedMethod;
switch (paymentMethod.GetPaymentMethodDetails()) switch (paymentMethod.GetPaymentMethodDetails())
{ {
case LNURLPayPaymentMethodDetails lnurlPayPaymentMethodDetails: case LNURLPayPaymentMethodDetails lnurlPayPaymentMethodDetails:
@@ -167,16 +171,17 @@ namespace BTCPayServer.Payments.Lightning
continue; continue;
var network = _NetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethod.GetId().CryptoCode); var network = _NetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethod.GetId().CryptoCode);
listenedInvoices.Add(new ListenedInvoice() var lnUri = GetLightningUrl(lightningSupportedMethod);
{ if (lnUri == null)
Expiration = invoice.ExpirationTime, continue;
Uri = GetLightningUrl(lightningSupportedMethod).BaseUri.AbsoluteUri, listenedInvoices.Add(new ListenedInvoice(
PaymentMethodDetails = lightningMethod, lnUri.BaseUri,
SupportedPaymentMethod = lightningSupportedMethod, invoice.ExpirationTime,
PaymentMethod = paymentMethod, lightningMethod,
Network = network, lightningSupportedMethod,
InvoiceId = invoice.Id paymentMethod,
}); network,
invoice.Id));
} }
return listenedInvoices; return listenedInvoices;
} }
@@ -221,7 +226,7 @@ namespace BTCPayServer.Payments.Lightning
})); }));
leases.Add(_Aggregator.Subscribe<Events.InvoiceNewPaymentDetailsEvent>(inv => 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)); _memoryCache.Remove(GetCacheKey(inv.InvoiceId));
_CheckInvoices.Writer.TryWrite(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)) .Where(method => new[] { PaymentTypes.LightningLike, LNURLPayPaymentType.Instance }.Contains(method.GetId().PaymentType))
.ToArray(); .ToArray();
var store = await _storeRepository.FindStore(invoice.StoreId); var store = await _storeRepository.FindStore(invoice.StoreId);
if (store is null)
return;
if (paymentMethods.Any()) if (paymentMethods.Any())
{ {
var logs = new InvoiceLogs(); var logs = new InvoiceLogs();
@@ -334,9 +341,11 @@ namespace BTCPayServer.Payments.Lightning
.CreatePaymentMethodDetails(logs, supportedMethod, paymentMethod, store, .CreatePaymentMethodDetails(logs, supportedMethod, paymentMethod, store,
paymentMethod.Network, prepObj, pmis)); paymentMethod.Network, prepObj, pmis));
var instanceListenerKey = (paymentMethod.Network.CryptoCode, var connStr = GetLightningUrl(supportedMethod);
GetLightningUrl(supportedMethod).ToString()); if (connStr is null)
LightningInstanceListener instanceListener; continue;
var instanceListenerKey = (paymentMethod.Network.CryptoCode, connStr.ToString());
LightningInstanceListener? instanceListener;
lock (_InstanceListeners) lock (_InstanceListeners)
{ {
_InstanceListeners.TryGetValue(instanceListenerKey, out instanceListener); _InstanceListeners.TryGetValue(instanceListenerKey, out instanceListener);
@@ -346,16 +355,17 @@ namespace BTCPayServer.Payments.Lightning
await _InvoiceRepository.NewPaymentDetails(invoice.Id, newPaymentMethodDetails, await _InvoiceRepository.NewPaymentDetails(invoice.Id, newPaymentMethodDetails,
paymentMethod.Network); paymentMethod.Network);
instanceListener.AddListenedInvoice(new ListenedInvoice() var url = GetLightningUrl(supportedMethod);
{ if (url is null)
Expiration = invoice.ExpirationTime, continue;
Uri = GetLightningUrl(supportedMethod).BaseUri.AbsoluteUri, instanceListener.AddListenedInvoice(new ListenedInvoice(
PaymentMethodDetails = newPaymentMethodDetails, url.BaseUri,
SupportedPaymentMethod = supportedMethod, invoice.ExpirationTime,
PaymentMethod = paymentMethod, newPaymentMethodDetails,
Network = (BTCPayNetwork)paymentMethod.Network, supportedMethod,
InvoiceId = invoice.Id paymentMethod,
}); (BTCPayNetwork)paymentMethod.Network,
invoice.Id));
_Aggregator.Publish(new Events.InvoiceNewPaymentDetailsEvent(invoice.Id, _Aggregator.Publish(new Events.InvoiceNewPaymentDetailsEvent(invoice.Id,
newPaymentMethodDetails, paymentMethod.GetId())); 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(); var url = supportedMethod.GetExternalLightningUrl();
if (url != null) if (url != null)
return url; return url;
if (Options.Value.InternalLightningByCryptoCode.TryGetValue(supportedMethod.CryptoCode, out var conn)) if (Options.Value.InternalLightningByCryptoCode.TryGetValue(supportedMethod.CryptoCode, out var conn))
return conn; return conn;
throw new InvalidOperationException($"{supportedMethod.CryptoCode}: The internal lightning node is not set up"); return null;
} }
TimeSpan _PollInterval = TimeSpan.FromMinutes(1.0); 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; } public IOptions<LightningNetworkOptions> Options { get; }
@@ -412,7 +422,8 @@ namespace BTCPayServer.Payments.Lightning
_Cts.Cancel(); _Cts.Cancel();
try try
{ {
await _CheckingInvoice; if (_CheckingInvoice != null)
await _CheckingInvoice;
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@@ -420,7 +431,7 @@ namespace BTCPayServer.Payments.Lightning
} }
try 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) catch (OperationCanceledException)
{ {
@@ -479,7 +490,7 @@ namespace BTCPayServer.Payments.Lightning
public bool Empty => _ListenedInvoices.IsEmpty; public bool Empty => _ListenedInvoices.IsEmpty;
public bool IsListening => Listening?.Status is TaskStatus.Running || Listening?.Status is TaskStatus.WaitingForActivation; 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) public void EnsureListening(CancellationToken cancellation)
{ {
if (!IsListening) if (!IsListening)
@@ -490,7 +501,7 @@ namespace BTCPayServer.Payments.Lightning
Listening = Listen(StopListeningCancellationTokenSource.Token); Listening = Listen(StopListeningCancellationTokenSource.Token);
} }
} }
public CancellationTokenSource StopListeningCancellationTokenSource; public CancellationTokenSource? StopListeningCancellationTokenSource;
async Task Listen(CancellationToken cancellation) async Task Listen(CancellationToken cancellation)
{ {
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Start listening {ConnectionString.BaseUri}"); 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) 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 var payment = await _paymentService.AddPayment(invoiceId, notification.PaidAt.Value, new LightningLikePaymentData
{ {
BOLT11 = notification.BOLT11, 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 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; }
} }
} }

View File

@@ -101,7 +101,7 @@ public class LightningPendingPayoutListener : BaseAsyncService
break; break;
case PayoutLightningBlob payoutLightningBlob: case PayoutLightningBlob payoutLightningBlob:
{ {
var payment = await client.GetPayment(payoutLightningBlob.Id, Cancellation); var payment = await client.GetPayment(payoutLightningBlob.Id, CancellationToken);
if (payment is null) if (payment is null)
{ {
continue; continue;
@@ -126,8 +126,8 @@ public class LightningPendingPayoutListener : BaseAsyncService
} }
} }
await context.SaveChangesAsync(Cancellation); await context.SaveChangesAsync(CancellationToken);
await Task.Delay(TimeSpan.FromSeconds(SecondsDelay), Cancellation); await Task.Delay(TimeSpan.FromSeconds(SecondsDelay), CancellationToken);
} }
internal override Task[] InitializeTasks() internal override Task[] InitializeTasks()

View File

@@ -17,8 +17,6 @@ namespace BTCPayServer.Payments.Lightning
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string? LightningConnectionString { get; set; } public string? LightningConnectionString { get; set; }
public bool DisableBOLT11PaymentOption { get; set; } = false;
public LightningConnectionString? GetExternalLightningUrl() public LightningConnectionString? GetExternalLightningUrl()
{ {
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete

View File

@@ -299,9 +299,8 @@ namespace BTCPayServer.Payments.PayJoin
if (walletReceiveMatch is null && invoice is not null) if (walletReceiveMatch is null && invoice is not null)
{ {
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId); var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
var paymentDetails = var paymentDetails = paymentMethod?.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod;
paymentMethod.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod; if (paymentMethod is null || paymentDetails is null || !paymentDetails.PayjoinEnabled)
if (paymentDetails is null || !paymentDetails.PayjoinEnabled)
continue; continue;
due = paymentMethod.Calculate().TotalDue - output.Value; due = paymentMethod.Calculate().TotalDue - output.Value;
if (due > Money.Zero) if (due > Money.Zero)
@@ -384,7 +383,7 @@ namespace BTCPayServer.Payments.PayJoin
WellknownMetadataKeys.AccountHDKey); WellknownMetadataKeys.AccountHDKey);
if (extKeyStr == null) 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"); return CreatePayjoinErrorAndLog(503, PayjoinReceiverWellknownErrors.Unavailable, "The HD Key of the store changed");
} }

View File

@@ -1,5 +1,6 @@
#nullable enable #nullable enable
using System; using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
@@ -11,7 +12,7 @@ namespace BTCPayServer.Payments
/// </summary> /// </summary>
public class PaymentMethodId public class PaymentMethodId
{ {
public PaymentMethodId? FindNearest(PaymentMethodId[] others) public PaymentMethodId? FindNearest(IEnumerable<PaymentMethodId> others)
{ {
ArgumentNullException.ThrowIfNull(others); ArgumentNullException.ThrowIfNull(others);
return others.FirstOrDefault(f => f == this) ?? return others.FirstOrDefault(f => f == this) ??

View File

@@ -9,13 +9,31 @@ using BTCPayServer.HostedServices;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NBitcoin.Protocol;
using PayoutData = BTCPayServer.Data.PayoutData; using PayoutData = BTCPayServer.Data.PayoutData;
using PayoutProcessorData = BTCPayServer.Data.PayoutProcessorData; using PayoutProcessorData = BTCPayServer.Data.PayoutProcessorData;
namespace BTCPayServer.PayoutProcessors; 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 public abstract class BaseAutomatedPayoutProcessor<T> : BaseAsyncService where T : AutomatedPayoutBlob
{ {
protected readonly StoreRepository _storeRepository; protected readonly StoreRepository _storeRepository;
@@ -74,7 +92,7 @@ public abstract class BaseAutomatedPayoutProcessor<T> : BaseAsyncService where T
States = new[] { PayoutState.AwaitingPayment }, States = new[] { PayoutState.AwaitingPayment },
PaymentMethods = new[] { _PayoutProcesserSettings.PaymentMethod }, PaymentMethods = new[] { _PayoutProcesserSettings.PaymentMethod },
Stores = new[] { _PayoutProcesserSettings.StoreId } Stores = new[] { _PayoutProcesserSettings.StoreId }
}, context); }, context, CancellationToken);
if (payouts.Any()) if (payouts.Any())
{ {
Logs.PayServer.LogInformation($"{payouts.Count} found to process. Starting (and after will sleep for {blob.Interval})"); 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)); 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); await Task.Delay(blob.Interval, CancellationToken);
} }

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
@@ -66,6 +67,8 @@ public class UILightningAutomatedPayoutProcessorsController : Controller
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Configure(string storeId, string cryptoCode, LightningTransferViewModel automatedTransferBlob) public async Task<IActionResult> Configure(string storeId, string cryptoCode, LightningTransferViewModel automatedTransferBlob)
{ {
if (!ModelState.IsValid)
return View(automatedTransferBlob);
if (!_lightningAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(id => if (!_lightningAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(id =>
id.CryptoCode.Equals(cryptoCode, StringComparison.InvariantCultureIgnoreCase))) id.CryptoCode.Equals(cryptoCode, StringComparison.InvariantCultureIgnoreCase)))
{ {
@@ -120,7 +123,7 @@ public class UILightningAutomatedPayoutProcessorsController : Controller
{ {
IntervalMinutes = blob.Interval.TotalMinutes; IntervalMinutes = blob.Interval.TotalMinutes;
} }
[Range(AutomatedPayoutConstants.MinIntervalMinutes, AutomatedPayoutConstants.MaxIntervalMinutes)]
public double IntervalMinutes { get; set; } public double IntervalMinutes { get; set; }
public AutomatedPayoutBlob ToBlob() public AutomatedPayoutBlob ToBlob()

View File

@@ -43,7 +43,8 @@ namespace BTCPayServer.PayoutProcessors.OnChain
PayoutProcessorData payoutProcesserSettings, PayoutProcessorData payoutProcesserSettings,
PullPaymentHostedService pullPaymentHostedService, PullPaymentHostedService pullPaymentHostedService,
BTCPayNetworkProvider btcPayNetworkProvider, BTCPayNetworkProvider btcPayNetworkProvider,
IPluginHookService pluginHookService) : IPluginHookService pluginHookService,
IFeeProviderFactory feeProviderFactory) :
base(logger, storeRepository, payoutProcesserSettings, applicationDbContextFactory, pullPaymentHostedService, base(logger, storeRepository, payoutProcesserSettings, applicationDbContextFactory, pullPaymentHostedService,
btcPayNetworkProvider, pluginHookService) btcPayNetworkProvider, pluginHookService)
{ {
@@ -53,9 +54,11 @@ namespace BTCPayServer.PayoutProcessors.OnChain
_bitcoinLikePayoutHandler = bitcoinLikePayoutHandler; _bitcoinLikePayoutHandler = bitcoinLikePayoutHandler;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
WalletRepository = walletRepository; WalletRepository = walletRepository;
FeeProvider = feeProviderFactory.CreateFeeProvider(_btcPayNetworkProvider.GetNetwork(PaymentMethodId.CryptoCode));
} }
public WalletRepository WalletRepository { get; } public WalletRepository WalletRepository { get; }
public IFeeProvider FeeProvider { get; }
protected override async Task Process(ISupportedPaymentMethod paymentMethod, List<PayoutData> payouts) protected override async Task Process(ISupportedPaymentMethod paymentMethod, List<PayoutData> payouts)
{ {
@@ -95,7 +98,7 @@ namespace BTCPayServer.PayoutProcessors.OnChain
storePaymentMethod.AccountDerivation, DerivationFeature.Change, 0, true); storePaymentMethod.AccountDerivation, DerivationFeature.Change, 0, true);
var processorBlob = GetBlob(_PayoutProcesserSettings); 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>(); var transfersProcessing = new List<PayoutData>();
foreach (var transferRequest in payouts) foreach (var transferRequest in payouts)
@@ -133,7 +136,7 @@ namespace BTCPayServer.PayoutProcessors.OnChain
try try
{ {
txBuilder.SetChange(changeAddress.Address); txBuilder.SetChange(changeAddress.Address);
txBuilder.SendEstimatedFees(feeRate.FeeRate); txBuilder.SendEstimatedFees(feeRate);
workingTx = txBuilder.BuildTransaction(true); workingTx = txBuilder.BuildTransaction(true);
transfersProcessing.Add(transferRequest); transfersProcessing.Add(transferRequest);
} }

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
@@ -79,6 +80,8 @@ public class UIOnChainAutomatedPayoutProcessorsController : Controller
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Configure(string storeId, string cryptoCode, OnChainTransferViewModel automatedTransferBlob) public async Task<IActionResult> Configure(string storeId, string cryptoCode, OnChainTransferViewModel automatedTransferBlob)
{ {
if (!ModelState.IsValid)
return View(automatedTransferBlob);
if (!_onChainAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(id => if (!_onChainAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(id =>
id.CryptoCode.Equals(cryptoCode, StringComparison.InvariantCultureIgnoreCase))) id.CryptoCode.Equals(cryptoCode, StringComparison.InvariantCultureIgnoreCase)))
{ {
@@ -135,8 +138,10 @@ public class UIOnChainAutomatedPayoutProcessorsController : Controller
FeeTargetBlock = blob.FeeTargetBlock; FeeTargetBlock = blob.FeeTargetBlock;
} }
[Range(1, 1000)]
public int FeeTargetBlock { get; set; } public int FeeTargetBlock { get; set; }
[Range(AutomatedPayoutConstants.MinIntervalMinutes, AutomatedPayoutConstants.MaxIntervalMinutes)]
public double IntervalMinutes { get; set; } public double IntervalMinutes { get; set; }
public OnChainAutomatedPayoutBlob ToBlob() public OnChainAutomatedPayoutBlob ToBlob()

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -43,6 +44,15 @@ public class PayoutProcessorService : EventHostedServiceBase
public class PayoutProcessorQuery public class PayoutProcessorQuery
{ {
public PayoutProcessorQuery()
{
}
public PayoutProcessorQuery(string storeId, string paymentMethod)
{
Stores = new[] { storeId };
PaymentMethods = new[] { paymentMethod };
}
public string[] Stores { get; set; } public string[] Stores { get; set; }
public string[] Processors { get; set; } public string[] Processors { get; set; }
public string[] PaymentMethods { get; set; } public string[] PaymentMethods { get; set; }
@@ -166,4 +176,12 @@ public class PayoutProcessorService : EventHostedServiceBase
processorUpdated.Processed?.SetResult(); processorUpdated.Processed?.SetResult();
} }
} }
internal async Task Restart(PayoutProcessorQuery payoutProcessorQuery)
{
foreach (var data in await GetProcessors(payoutProcessorQuery))
{
await StartOrUpdateProcessor(data, default);
}
}
} }

View File

@@ -253,15 +253,15 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
{ {
var vm = new PostRedirectViewModel var vm = new PostRedirectViewModel
{ {
AspAction = nameof(POSForm), FormUrl = Url.Action(nameof(POSForm), "UIPointOfSale", new {appId, buyerEmail = email}),
AspController = "UIPointOfSale", FormParameters = new MultiValueDictionary<string, string>(Request.Form.Select(pair =>
RouteParameters = new Dictionary<string, string> { { "appId", appId } }, new KeyValuePair<string, IReadOnlyCollection<string>>(pair.Key, pair.Value)))
FormParameters = new MultiValueDictionary<string, string>(Request.Form.Select(pair => new KeyValuePair<string, IReadOnlyCollection<string>>(pair.Key, pair.Value)))
}; };
if (viewType.HasValue) if (viewType.HasValue)
{ {
vm.RouteParameters.Add("viewType", viewType.Value.ToString()); vm.RouteParameters.Add("viewType", viewType.Value.ToString());
} }
return View("PostRedirect", vm); return View("PostRedirect", vm);
} }
@@ -405,6 +405,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
var store = await _appService.GetStore(app); var store = await _appService.GetStore(app);
var storeBlob = store.GetStoreBlob(); var storeBlob = store.GetStoreBlob();
var form = Form.Parse(formData.Config); var form = Form.Parse(formData.Config);
form.ApplyValuesFromForm(Request.Query);
var vm = new FormViewModel var vm = new FormViewModel
{ {
StoreName = store.StoreName, StoreName = store.StoreName,

View File

@@ -15,6 +15,7 @@ namespace BTCPayServer.Plugins.Shopify
public override void Execute(IServiceCollection applicationBuilder) public override void Execute(IServiceCollection applicationBuilder)
{ {
applicationBuilder.AddSingleton<IHostedService, ShopifyOrderMarkerHostedService>(); applicationBuilder.AddSingleton<IHostedService, ShopifyOrderMarkerHostedService>();
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("Shopify/NavExtension", "header-nav"));
base.Execute(applicationBuilder); base.Execute(applicationBuilder);
} }
} }

View File

@@ -27,7 +27,7 @@
"BTCPAY_SOCKSENDPOINT": "localhost:9050", "BTCPAY_SOCKSENDPOINT": "localhost:9050",
"BTCPAY_UPDATEURL": "", "BTCPAY_UPDATEURL": "",
"BTCPAY_DOCKERDEPLOYMENT": "true", "BTCPAY_DOCKERDEPLOYMENT": "true",
"BTCPAY_RECOMMENDED-PLUGINS": "BTCPayServer.Plugins.Test", "BTCPAY_RECOMMENDED-PLUGINS": "",
"BTCPAY_CHEATMODE": "true", "BTCPAY_CHEATMODE": "true",
"BTCPAY_EXPLORERPOSTGRES": "User ID=postgres;Include Error Detail=true;Host=127.0.0.1;Port=39372;Database=nbxplorer" "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_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
"BTCPAY_SOCKSENDPOINT": "localhost:9050", "BTCPAY_SOCKSENDPOINT": "localhost:9050",
"BTCPAY_DOCKERDEPLOYMENT": "true", "BTCPAY_DOCKERDEPLOYMENT": "true",
"BTCPAY_RECOMMENDED-PLUGINS": "BTCPayServer.Plugins.Test", "BTCPAY_RECOMMENDED-PLUGINS": "",
"BTCPAY_CHEATMODE": "true", "BTCPAY_CHEATMODE": "true",
"BTCPAY_EXPLORERPOSTGRES": "User ID=postgres;Include Error Detail=true;Host=127.0.0.1;Port=39372;Database=nbxplorer" "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_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
"BTCPAY_SOCKSENDPOINT": "localhost:9050", "BTCPAY_SOCKSENDPOINT": "localhost:9050",
"BTCPAY_DOCKERDEPLOYMENT": "true", "BTCPAY_DOCKERDEPLOYMENT": "true",
"BTCPAY_RECOMMENDED-PLUGINS": "BTCPayServer.Plugins.Test", "BTCPAY_RECOMMENDED-PLUGINS": "",
"BTCPAY_CHEATMODE": "true", "BTCPAY_CHEATMODE": "true",
"BTCPAY_EXPLORERPOSTGRES": "User ID=postgres;Include Error Detail=true;Host=127.0.0.1;Port=39372;Database=nbxplorer" "BTCPAY_EXPLORERPOSTGRES": "User ID=postgres;Include Error Detail=true;Host=127.0.0.1;Port=39372;Database=nbxplorer"
}, },

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using BTCPayServer.Configuration; using BTCPayServer.Configuration;
@@ -17,7 +18,7 @@ namespace BTCPayServer.Services
readonly TorServices torServices; readonly TorServices torServices;
public BTCPayServerEnvironment(IWebHostEnvironment env, BTCPayNetworkProvider provider, TorServices torServices, BTCPayServerOptions opts) 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; Commit = typeof(BTCPayServerEnvironment).GetTypeInfo().Assembly.GetCustomAttribute<GitCommitAttribute>()?.ShortSHA;
#if DEBUG #if DEBUG
Build = "Debug"; Build = "Debug";
@@ -35,6 +36,12 @@ namespace BTCPayServer.Services
this.torServices = torServices; this.torServices = torServices;
CheatMode = opts.CheatMode; CheatMode = opts.CheatMode;
} }
internal static string GetInformationalVersion()
{
return typeof(BTCPayServerEnvironment).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
}
public IWebHostEnvironment Environment public IWebHostEnvironment Environment
{ {
get; set; get; set;

View File

@@ -461,6 +461,7 @@ namespace BTCPayServer.Services.Invoices
[JsonConverter(typeof(StringEnumConverter))] [JsonConverter(typeof(StringEnumConverter))]
public CheckoutType? CheckoutType { get; set; } public CheckoutType? CheckoutType { get; set; }
public bool LazyPaymentMethods { get; set; }
public bool IsExpired() public bool IsExpired()
{ {

View File

@@ -213,12 +213,18 @@ namespace BTCPayServer.Services.Invoices
} }
var paymentDestination = details.GetPaymentDestination(); var paymentDestination = details.GetPaymentDestination();
string address = GetDestination(paymentMethod); string address = GetDestination(paymentMethod);
await context.AddressInvoices.AddAsync(new AddressInvoiceData() if (address != null)
{ {
InvoiceDataId = invoice.Id, await context.AddressInvoices.AddAsync(new AddressInvoiceData()
CreatedTime = DateTimeOffset.UtcNow, {
}.Set(address, paymentMethod.GetId())); InvoiceDataId = invoice.Id,
textSearch.Add(paymentDestination); CreatedTime = DateTimeOffset.UtcNow,
}.Set(address, paymentMethod.GetId()));
}
if (paymentDestination != null)
{
textSearch.Add(paymentDestination);
}
textSearch.Add(paymentMethod.Calculate().TotalDue.ToString()); textSearch.Add(paymentMethod.Calculate().TotalDue.ToString());
} }
await context.PendingInvoices.AddAsync(new PendingInvoiceData() { Id = invoice.Id }); await context.PendingInvoices.AddAsync(new PendingInvoiceData() { Id = invoice.Id });

View File

@@ -44,6 +44,8 @@ namespace BTCPayServer.Services.Invoices
return null; return null;
InvoiceEntity invoiceEntity = invoice.GetBlob(_btcPayNetworkProvider); InvoiceEntity invoiceEntity = invoice.GetBlob(_btcPayNetworkProvider);
PaymentMethod paymentMethod = invoiceEntity.GetPaymentMethod(new PaymentMethodId(network.CryptoCode, paymentData.GetPaymentType())); PaymentMethod paymentMethod = invoiceEntity.GetPaymentMethod(new PaymentMethodId(network.CryptoCode, paymentData.GetPaymentType()));
if (paymentMethod is null)
return null;
IPaymentMethodDetails paymentMethodDetails = paymentMethod.GetPaymentMethodDetails(); IPaymentMethodDetails paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
PaymentEntity entity = new PaymentEntity PaymentEntity entity = new PaymentEntity
{ {

View File

@@ -1,3 +1,4 @@
#nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@@ -45,7 +46,7 @@ namespace BTCPayServer.Services
using var stream = new StreamReader(file); using var stream = new StreamReader(file);
var json = stream.ReadToEnd(); var json = stream.ReadToEnd();
result.Add(JObject.Parse(json).ToObject<Language>()); result.Add(JObject.Parse(json).ToObject<Language>()!);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -73,7 +74,7 @@ namespace BTCPayServer.Services
return items; return items;
} }
public Language FindLanguageInAcceptLanguageHeader(string acceptLanguageHeader) public Language? FindLanguageInAcceptLanguageHeader(string? acceptLanguageHeader)
{ {
if (acceptLanguageHeader is null) if (acceptLanguageHeader is null)
return 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"). * 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" * 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 supportedLangs = GetLanguages();
var split = locale.Split('-', StringSplitOptions.RemoveEmptyEntries); var split = locale.Split('-', StringSplitOptions.RemoveEmptyEntries);
@@ -150,7 +151,7 @@ namespace BTCPayServer.Services
return countryMatches.FirstOrDefault() ?? langMatches.FirstOrDefault(); return countryMatches.FirstOrDefault() ?? langMatches.FirstOrDefault();
} }
public Language AutoDetectLanguageUsingHeader(IHeaderDictionary headerDictionary, string defaultLang) public Language? AutoDetectLanguageUsingHeader(IHeaderDictionary headerDictionary, string? defaultLang)
{ {
if (headerDictionary?.TryGetValue("Accept-Language", if (headerDictionary?.TryGetValue("Accept-Language",
out var acceptLanguage) is true && !string.IsNullOrEmpty(acceptLanguage)) out var acceptLanguage) is true && !string.IsNullOrEmpty(acceptLanguage))

View File

@@ -36,5 +36,6 @@ namespace BTCPayServer.Services
public bool MigrateWalletColors { get; set; } public bool MigrateWalletColors { get; set; }
public bool FileSystemStorageAsDefault { get; set; } public bool FileSystemStorageAsDefault { get; set; }
public bool FixSeqAfterSqliteMigration { get; set; } public bool FixSeqAfterSqliteMigration { get; set; }
public bool FixMappedDomainAppType { get; set; }
} }
} }

View File

@@ -35,7 +35,7 @@ namespace BTCPayServer.Services
} }
else else
{ {
await Task.Delay(TimeSpan.FromSeconds(120), Cancellation); await Task.Delay(TimeSpan.FromSeconds(120), CancellationToken);
} }
List<TorService> result = new List<TorService>(); List<TorService> result = new List<TorService>();
try try

View File

@@ -31,8 +31,8 @@
{ {
@if (item.Price.Type != ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup) @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> <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" /> <input type="hidden" name="choiceKey" value="@item.Id" />
@{PayFormInputContent(item.BuyButtonText ?? Model.CustomButtonText, item.Price.Type, item.Price.Value, item.Price.Value);} @{PayFormInputContent(item.BuyButtonText ?? Model.CustomButtonText, item.Price.Type, item.Price.Value, item.Price.Value);}
</form> </form>
} }
@@ -95,8 +95,8 @@
{ {
if (itemPriceType == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Fixed && priceValue == 0) if (itemPriceType == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Fixed && priceValue == 0)
{ {
<div class="input-group "> <div class="input-group">
<input class="form-control" type="text" readonly value="Free"/> <input class="form-control" type="text" readonly value="Free"/>
<button class="btn btn-primary text-nowrap" type="submit">@buttonText</button> <button class="btn btn-primary text-nowrap" type="submit">@buttonText</button>
</div> </div>
} }

View File

@@ -1,65 +1,77 @@
@model (Dictionary<string, object> Items, int Level) @model (Dictionary<string, object> Items, int Level)
@functions { @functions {
private bool IsValidURL(string source) private bool IsValidURL(string source)
{ {
return Uri.TryCreate(source, UriKind.Absolute, out var uriResult) && return Uri.TryCreate(source, UriKind.Absolute, out var uriResult) &&
(uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps); (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);
} }
} }
@if (Model.Items.Count > 0) @if (Model.Items.Count > 0)
{ {
<table class="table my-0" v-pre> <table class="table my-0" v-pre>
@foreach (var (key, value) in Model.Items) @foreach (var (key, value) in Model.Items)
{ {
<tr> <tr>
@if (value is string str) @if (value is string str)
{
if (!string.IsNullOrEmpty(key))
{ {
<th class="w-150px">@key</th> if (!string.IsNullOrEmpty(key))
}
<td>
@if (IsValidURL(str))
{ {
<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> <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 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> </td>
} }
} else if (value is Dictionary<string, object> {Count: > 0 } subItems)
</tr> {
} <td colspan="2">
</table> @{
@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>
} }

View 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>
}

View File

@@ -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>

View File

@@ -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

View File

@@ -31,9 +31,10 @@
<vc:icon symbol="warning" /> <vc:icon symbol="warning" />
</div> </div>
<div class="lead text-center"> <div class="lead text-center">
<h1 class="text-center text-warning mb-3"> <h1 class="text-center text-warning mb-3">
Secure your recovery&nbsp;phrase Secure your recovery&nbsp;phrase
</h1> </h1>
</div>
</div> </div>
<div class="lead text-center"> <div class="lead text-center">
<p class="mb-0"> <p class="mb-0">

View File

@@ -1,5 +1,7 @@
@model PaymentModel @model PaymentModel
@{
var displayedPaymentMethods = Model.AvailableCryptos.Where(a => a.Displayed).ToList();
}
<div class="top-header"> <div class="top-header">
<div class="header"> <div class="header">
@if (!string.IsNullOrEmpty(Model.CustomLogoLink)) @if (!string.IsNullOrEmpty(Model.CustomLogoLink))
@@ -45,26 +47,23 @@
</div> </div>
</div> </div>
<div class="single-item-order__right"> <div class="single-item-order__right">
@if (Model.AvailableCryptos.Count > 1) @if (displayedPaymentMethods.Count > 1)
{ {
<div class="paywithRowRight cursorPointer" v-on:click="openPaymentMethodDialog"> <div class="paywithRowRight cursorPointer" v-on:click="openPaymentMethodDialog">
<span class="payment__currencies " v-show="!changingCurrencies"> <span class="payment__currencies " v-show="!changingCurrencies">
<img v-bind:src="srvModel.cryptoImage" /> <img v-bind:src="srvModel.cryptoImage" />
<span>{{srvModel.paymentMethodName}} ({{srvModel.cryptoCode}})</span> <span>{{srvModel.paymentMethodName}}</span>
<span v-show="srvModel.isLightning">&#9889;</span>
<span class="clickable_indicator fa fa-angle-right"></span> <span class="clickable_indicator fa fa-angle-right"></span>
</span> </span>
</div> </div>
<div id="vexPopupDialog"> <div id="vexPopupDialog">
<ul class="vexmenu"> <ul class="vexmenu">
@foreach (var crypto in Model.AvailableCryptos) @foreach (var crypto in displayedPaymentMethods)
{ {
<li class="vexmenuitem"> <li class="vexmenuitem">
<a href="@crypto.Link" class="payment-method" data-payment-method="@crypto.PaymentMethodId" rel="noreferrer noopener"> <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" /> <img alt="@crypto.PaymentMethodName" src="@crypto.CryptoImage" asp-append-version="true" />
@crypto.PaymentMethodName @crypto.PaymentMethodName
@(crypto.IsLightning ? Html.Raw("&#9889;") : null)
<span>@crypto.CryptoCode</span>
</a> </a>
</li> </li>
} }
@@ -75,7 +74,7 @@
{ {
<div class="payment__currencies_noborder"> <div class="payment__currencies_noborder">
<img v-bind:src="srvModel.cryptoImage" /> <img v-bind:src="srvModel.cryptoImage" />
<span>{{srvModel.paymentMethodName}} ({{srvModel.cryptoCode}})</span> <span>{{srvModel.paymentMethodName}}</span>
<span v-show="srvModel.isLightning">&#9889;</span> <span v-show="srvModel.isLightning">&#9889;</span>
</div> </div>
} }

View File

@@ -1,6 +1,7 @@
@model PaymentModel @model PaymentModel
@{ @{
Layout = null; Layout = null;
var displayedPaymentMethods = Model.AvailableCryptos.Where(a => a.Displayed).ToList();
} }
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
@@ -24,13 +25,13 @@
{ {
<h1 class="text-danger">This payment method requires javascript.</h1> <h1 class="text-danger">This payment method requires javascript.</h1>
} }
@if (Model.AvailableCryptos.Count > 1) @if (displayedPaymentMethods.Count > 1)
{ {
<div> <div>
<hr /> <hr />
<h2>Pay with:</h2> <h2>Pay with:</h2>
<ul style="list-style-type: none;padding-left: 5px;"> <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;"> <li style="height: 32px; line-height: 32px;">
<a asp-action="CheckoutNoScript" asp-route-invoiceId="@Model.InvoiceId" asp-route-paymentMethodId="@crypto.PaymentMethodId"> <a asp-action="CheckoutNoScript" asp-route-invoiceId="@Model.InvoiceId" asp-route-paymentMethodId="@crypto.PaymentMethodId">

View File

@@ -12,19 +12,9 @@
ViewData["Title"] = Model.HtmlTitle; ViewData["Title"] = Model.HtmlTitle;
Csp.UnsafeEval(); Csp.UnsafeEval();
var hasPaymentPlugins = UiExtensions.Any(extension => extension.Location == "checkout-payment-method"); 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.AvailableCryptos.Where(c => c.Displayed).ToList();
var displayedPaymentMethods = Model.IsUnsetTopUp && !Model.OnChainWithLnInvoiceFallback
? Model.AvailableCryptos
: Model.AvailableCryptos.Where(c => c.PaymentMethodId != "BTC_LNURLPAY").ToList();
} }
@functions { @functions {
private string PaymentMethodName(PaymentModel.AvailableCrypto pm)
{
return Model.AltcoinsBuild
? pm.PaymentMethodName
: pm.PaymentMethodName.Replace("Bitcoin (", "").Replace(")", "").Replace("Lightning ", "");
}
private string ToJsValue(object value) private string ToJsValue(object value)
{ {
return Safe.Json(value).ToString()?.Replace("\"", "'"); return Safe.Json(value).ToString()?.Replace("\"", "'");
@@ -99,7 +89,7 @@
class="btcpay-pill m-0 payment-method" class="btcpay-pill m-0 payment-method"
:class="{ active: pmId === @ToJsValue(crypto.PaymentMethodId) }" :class="{ active: pmId === @ToJsValue(crypto.PaymentMethodId) }"
v-on:click.prevent="changePaymentMethod(@ToJsValue(crypto.PaymentMethodId))"> v-on:click.prevent="changePaymentMethod(@ToJsValue(crypto.PaymentMethodId))">
@PaymentMethodName(crypto) @crypto.PaymentMethodName
</a> </a>
} }
@await Component.InvokeAsync("UiExtensionPoint", new { location = "checkout-payment-method", model = Model }) @await Component.InvokeAsync("UiExtensionPoint", new { location = "checkout-payment-method", model = Model })
@@ -112,6 +102,7 @@
<div v-if="isProcessing" id="processing" key="processing"> <div v-if="isProcessing" id="processing" key="processing">
<div class="top"> <div class="top">
<span class="icn"> <span class="icn">
<div id="confetti" v-if="srvModel.celebratePayment" v-on:click="celebratePayment(5000)"></div>
<vc:icon symbol="payment-sent" /> <vc:icon symbol="payment-sent" />
</span> </span>
<h4 v-t="'payment_received'"></h4> <h4 v-t="'payment_received'"></h4>

View File

@@ -42,7 +42,7 @@
<div class="d-flex flex-column justify-content-center gap-4"> <div class="d-flex flex-column justify-content-center gap-4">
<partial name="_StoreHeader" model="(Model.StoreName, Model.LogoFileId)" /> <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) @if (isProcessing)
{ {
<div class="lead text-center p-4 fw-semibold" id="invoice-processing"> <div class="lead text-center p-4 fw-semibold" id="invoice-processing">
@@ -59,9 +59,7 @@
{ {
if (Model.ReceiptOptions.ShowQR is true) if (Model.ReceiptOptions.ShowQR is true)
{ {
<div class="mx-auto"> <vc:qr-code data="@Context.Request.GetCurrentUrl()"></vc:qr-code>
<vc:qr-code data="@Context.Request.GetCurrentUrl()"></vc:qr-code>
</div>
} }
<dl class="d-flex flex-column gap-4 mb-0 flex-fill"> <dl class="d-flex flex-column gap-4 mb-0 flex-fill">
<div class="d-flex flex-column"> <div class="d-flex flex-column">
@@ -115,58 +113,70 @@
if (Model.Payments?.Any() is true) if (Model.Payments?.Any() is true)
{ {
<div id="PaymentDetails" class="bg-tile p-3 p-sm-4 rounded"> <div id="PaymentDetails" class="bg-tile p-3 p-sm-4 rounded">
<h2 class="h4 mb-3 d-print-none">Payment Details</h2> <h2 class="h4 mb-3">Payment Details</h2>
<div class="table-responsive my-0"> <div class="table-responsive my-0 d-print-none">
<table class="table table-borderless my-0"> <table class="table table-borderless my-0">
<thead> <thead>
<tr> <tr>
<th class="fw-normal text-secondary">Date</th> <th class="fw-normal text-secondary">Date</th>
<th class="fw-normal text-secondary">Payment</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">Paid</th>
<th class="fw-normal text-secondary text-end">Rate</th> <th class="fw-normal text-secondary text-end">Rate</th>
</tr> </tr>
</thead> </thead>
@foreach (var payment in Model.Payments) @foreach (var payment in Model.Payments)
{ {
<tbody> <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> <tr>
<td class="text-nowrap">@payment.ReceivedDate.ToBrowserDate()</td> <th class="fw-normal text-nowrap text-secondary">
<td class="text-nowrap">@payment.Amount @payment.PaymentMethod</td> Destination
<td class="text-end text-nowrap">@payment.PaidFormatted</td> </th>
<td class="text-end text-nowrap">@payment.RateFormatted</td> <td class="fw-normal" colspan="3">
<vc:truncate-center text="@payment.Destination" classes="truncate-center-id" />
</td>
</tr> </tr>
@if (!string.IsNullOrEmpty(payment.Destination)) }
{ @if (!string.IsNullOrEmpty(payment.PaymentProof))
<tr> {
<th class="fw-normal text-nowrap text-secondary"> <tr>
Destination <th class="fw-normal text-nowrap text-secondary">
</th> Payment Proof
<td class="fw-normal" colspan="3"> </th>
<vc:truncate-center text="@payment.Destination" classes="truncate-center-id" /> <td class="fw-normal" colspan="3">
</td> <vc:truncate-center text="@payment.PaymentProof" link="@payment.Link" classes="truncate-center-id" />
</tr> </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> </tbody>
} }
</table> </table>
</div> </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> </div>
} }
if (Model.AdditionalData?.Any() is true) if (Model.AdditionalData?.Any() is true)
{ {
<div id="AdditionalData" class="bg-tile p-3 p-sm-4 rounded"> <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"> <div class="table-responsive my-0">
<partial name="PosData" model="(Model.AdditionalData, 1)"/> <partial name="PosData" model="(Model.AdditionalData, 1)"/>
</div> </div>
@@ -177,7 +187,7 @@
</div> </div>
</main> </main>
<footer class="store-footer"> <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"> <a asp-action="Invoice" asp-route-invoiceId="@Model.InvoiceId">
Admin details Admin details
</a> </a>

View File

@@ -1,4 +1,4 @@
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Views.Stores @using BTCPayServer.Views.Stores
@using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.AspNetCore.Mvc.TagHelpers
@model BTCPayServer.PayoutProcessors.Lightning.UILightningAutomatedPayoutProcessorsController.LightningTransferViewModel @model BTCPayServer.PayoutProcessors.Lightning.UILightningAutomatedPayoutProcessorsController.LightningTransferViewModel
@@ -23,6 +23,7 @@
<div class="input-group"> <div class="input-group">
<input asp-for="IntervalMinutes" class="form-control" inputmode="numeric" style="max-width:10ch;"> <input asp-for="IntervalMinutes" class="form-control" inputmode="numeric" style="max-width:10ch;">
<span class="input-group-text">minutes</span> <span class="input-group-text">minutes</span>
<span asp-validation-for="IntervalMinutes" class="text-danger"></span>
</div> </div>
</div> </div>
<button name="command" type="submit" class="btn btn-primary mt-2" value="Save" id="Save">Save</button> <button name="command" type="submit" class="btn btn-primary mt-2" value="Save" id="Save">Save</button>

View File

@@ -1,4 +1,4 @@
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Views.Stores @using BTCPayServer.Views.Stores
@model BTCPayServer.PayoutProcessors.OnChain.UIOnChainAutomatedPayoutProcessorsController.OnChainTransferViewModel @model BTCPayServer.PayoutProcessors.OnChain.UIOnChainAutomatedPayoutProcessorsController.OnChainTransferViewModel
@{ @{
@@ -22,6 +22,7 @@
<div class="input-group"> <div class="input-group">
<input asp-for="IntervalMinutes" class="form-control" inputmode="numeric" style="max-width:10ch;"> <input asp-for="IntervalMinutes" class="form-control" inputmode="numeric" style="max-width:10ch;">
<span class="input-group-text">minutes</span> <span class="input-group-text">minutes</span>
<span asp-validation-for="IntervalMinutes" class="text-danger"></span>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@@ -29,6 +30,7 @@
<div class="input-group"> <div class="input-group">
<input asp-for="FeeTargetBlock" class="form-control" min="1" inputmode="numeric" style="max-width:10ch;"> <input asp-for="FeeTargetBlock" class="form-control" min="1" inputmode="numeric" style="max-width:10ch;">
<span class="input-group-text">blocks</span> <span class="input-group-text">blocks</span>
<span asp-validation-for="FeeTargetBlock" class="text-danger"></span>
</div> </div>
</div> </div>
<button name="command" type="submit" class="btn btn-primary mt-2" value="Save" id="Save">Save</button> <button name="command" type="submit" class="btn btn-primary mt-2" value="Save" id="Save">Save</button>

View File

@@ -86,7 +86,7 @@
<label asp-for="Email" class="form-label"></label> <label asp-for="Email" class="form-label"></label>
<input type="email" asp-for="Email" placeholder="Firstname Lastname <email@example.com>" class="form-control" /> <input type="email" asp-for="Email" placeholder="Firstname Lastname <email@example.com>" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span> <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>
<div class="form-group"> <div class="form-group">
<label asp-for="FormId" class="form-label"></label> <label asp-for="FormId" class="form-label"></label>

View File

@@ -167,7 +167,7 @@
<div class="form-check"> <div class="form-check">
<input asp-for="AutoDetectLanguage" type="checkbox" class="form-check-input" /> <input asp-for="AutoDetectLanguage" type="checkbox" class="form-check-input" />
<label asp-for="AutoDetectLanguage" class="form-check-label"></label> <label asp-for="AutoDetectLanguage" class="form-check-label"></label>
<div class="form-text">Detects the language of the customer's browser with 99.9% accuracy.</div> <div class="form-text">Detects the language of the customer's browser.</div>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@@ -34,6 +34,10 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<h5 class="alert-heading">Updated in <a href="https://blog.btcpayserver.org/btcpay-server-1-9-0/" target="_blank" rel="noreferrer noopener">v1.9.0</a></h5>
<p class="mb-2">Lots of improvements for our new checkout experience, which is now also the default for new stores — better NFC support, improved receipts and … confetti! 🎉</p>
<p class="mb-0">You'll also notice that wallet labels are featured more prominently. And the invoice details page now contains all the information you'd expect.</p>
<hr style="height:1px;background-color:var(--btcpay-body-text-muted);margin:var(--btcpay-space-m) 0;" />
<h5 class="alert-heading">Updated in <a href="https://blog.btcpayserver.org/btcpay-server-1-8-0/" target="_blank" rel="noreferrer noopener">v1.8.0</a></h5> <h5 class="alert-heading">Updated in <a href="https://blog.btcpayserver.org/btcpay-server-1-8-0/" target="_blank" rel="noreferrer noopener">v1.8.0</a></h5>
<p class="mb-2">Bear markets are for building: This version brings custom checkout forms, store branding options, a redesigned Point of Sale keypad view, new notification icons and address labeling.</p> <p class="mb-2">Bear markets are for building: This version brings custom checkout forms, store branding options, a redesigned Point of Sale keypad view, new notification icons and address labeling.</p>
<p class="mb-0">You like that? Consider <a href="https://opensats.org/projects/btcpayserver" target="_blank" rel="noreferrer noopener">supporting BTCPay Server via OpenSats</a>.</p> <p class="mb-0">You like that? Consider <a href="https://opensats.org/projects/btcpayserver" target="_blank" rel="noreferrer noopener">supporting BTCPay Server via OpenSats</a>.</p>
@@ -41,10 +45,6 @@
<h5 class="alert-heading">Updated in <a href="https://blog.btcpayserver.org/btcpay-server-1-7-0/" target="_blank" rel="noreferrer noopener">v1.7.0</a></h5> <h5 class="alert-heading">Updated in <a href="https://blog.btcpayserver.org/btcpay-server-1-7-0/" target="_blank" rel="noreferrer noopener">v1.7.0</a></h5>
<p class="mb-2">We've redesigned the checkout and the new version is available as an opt-in feature. We're looking forward to your <a href="https://github.com/btcpayserver/btcpayserver/discussions/4308" target="_blank" rel="noreferrer noopener">feedback</a>!</p> <p class="mb-2">We've redesigned the checkout and the new version is available as an opt-in feature. We're looking forward to your <a href="https://github.com/btcpayserver/btcpayserver/discussions/4308" target="_blank" rel="noreferrer noopener">feedback</a>!</p>
<p class="mb-0">You can now also request customer data (e.g. their shipping address) when they pay an invoice.</p> <p class="mb-0">You can now also request customer data (e.g. their shipping address) when they pay an invoice.</p>
<hr style="height:1px;background-color:var(--btcpay-body-text-muted);margin:var(--btcpay-space-m) 0;" />
<h5 class="alert-heading">Updated in <a href="https://blog.btcpayserver.org/btcpay-server-1-6-0/" target="_blank" rel="noreferrer noopener">v1.6.0</a></h5>
<p class="mb-2">The dashboard now contains your Lightning balances and services, as well as Point of Sale statistics.</p>
<p class="mb-0">We've also added invoice receipts and LNURL withdraw for payouts.</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -98,27 +98,6 @@
</div> </div>
</label> </label>
</div> </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="form-group mb-3">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<input type="checkbox" asp-for="LUD12Enabled" class="btcpay-toggle me-3" /> <input type="checkbox" asp-for="LUD12Enabled" class="btcpay-toggle me-3" />

View File

@@ -192,6 +192,11 @@ function initApp() {
if (newValue === true && oldValue === false) { if (newValue === true && oldValue === false) {
// poll from here on // poll from here on
this.listenForConfirmations(); this.listenForConfirmations();
// celebration!
const self = this;
Vue.nextTick(function () {
self.celebratePayment(5000);
});
} }
}, },
isSettled: function (newValue, oldValue) { isSettled: function (newValue, oldValue) {

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 69 KiB

View 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

View File

@@ -128,7 +128,6 @@
"get": { "get": {
"tags": [ "tags": [
"Apps", "Apps",
"POS",
"Point of Sale" "Point of Sale"
], ],
"operationId": "Apps_GetPointOfSaleApp", "operationId": "Apps_GetPointOfSaleApp",

View File

@@ -1173,6 +1173,11 @@
"nullable": true, "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." "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": { "expirationMinutes": {
"nullable": true, "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)", "description": "The number of minutes after which an invoice becomes expired. Defaults to the store's settings. (The default store settings is 15)",

View File

@@ -403,44 +403,44 @@
} }
}, },
"/api/v1/pull-payments/{pullPaymentId}/lnurl": { "/api/v1/pull-payments/{pullPaymentId}/lnurl": {
"parameters": [ "parameters": [
{ {
"name": "pullPaymentId", "name": "pullPaymentId",
"in": "path", "in": "path",
"required": true, "required": true,
"description": "The ID of the pull payment", "description": "The ID of the pull payment",
"schema": { "schema": {
"type": "string" "type": "string"
} }
} }
], ],
"get": { "get": {
"summary": "Get Pull Payment LNURL details", "summary": "Get Pull Payment LNURL details",
"operationId": "PullPayments_GetPullPaymentLNURL", "operationId": "PullPayments_GetPullPaymentLNURL",
"description": "Get Pull Payment LNURL details", "description": "Get Pull Payment LNURL details",
"responses": { "responses": {
"200": { "200": {
"description": "Pull payment LNURL details", "description": "Pull payment LNURL details",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/LNURLData" "$ref": "#/components/schemas/LNURLData"
} }
} }
} }
}, },
"404": { "404": {
"description": "Pull payment not found" "description": "Pull payment not found"
}, },
"400": { "400": {
"description": "Pull payment found but does not support LNURL" "description": "Pull payment found but does not support LNURL"
} }
}, },
"tags": [ "tags": [
"Pull payments (Public)" "Pull payments (Public)"
], ],
"security": [] "security": []
} }
}, },
"/api/v1/stores/{storeId}/payouts": { "/api/v1/stores/{storeId}/payouts": {
"parameters": [ "parameters": [
@@ -1070,19 +1070,19 @@
} }
}, },
"LNURLData": { "LNURLData": {
"type": "object", "type": "object",
"properties": { "properties": {
"lnurlBech32": { "lnurlBech32": {
"type": "string", "type": "string",
"description": "Bech32 representation of LNRURL", "description": "Bech32 representation of LNURL",
"example": "lightning:lnurl1dp68gup69uhnzv3h9cczuvpwxyarzdp3xsez7sj5gvh42j2vfe24ynp0wa5hg6rywfshwtmswqhngvntdd6x6uzvx4jrvu2kvvur23n8v46rwjpexcc45563fn53w7" "example": "lightning:lnurl1dp68gup69uhnzv3h9cczuvpwxyarzdp3xsez7sj5gvh42j2vfe24ynp0wa5hg6rywfshwtmswqhngvntdd6x6uzvx4jrvu2kvvur23n8v46rwjpexcc45563fn53w7"
}, },
"lnurlUri": { "lnurlUri": {
"type": "string", "type": "string",
"description": "Bech32 representation of LNURL", "description": "URI representation of LNURL",
"example": "lnurlw://example.com/BTC/UILNURL/withdraw/pp/42kktmpL5d6qVc85Fget7H961ZSQ" "example": "lnurlw://example.com/BTC/UILNURL/withdraw/pp/42kktmpL5d6qVc85Fget7H961ZSQ"
}
} }
}
} }
} }
}, },

View File

@@ -143,7 +143,7 @@
"x-position": 1 "x-position": 1
}, },
"responses": { "responses": {
"201": { "200": {
"description": "The email was sent (scheduled) successfully" "description": "The email was sent (scheduled) successfully"
}, },
"400": { "400": {
@@ -215,6 +215,11 @@
"fromDisplay": { "fromDisplay": {
"type": "string", "type": "string",
"description": "The name of the sender" "description": "The name of the sender"
},
"disableCertificateCheck": {
"type": "boolean",
"default": false,
"description": "Disable TLS certificate security checks"
} }
} }
} }

View File

@@ -279,11 +279,6 @@
"type": "string", "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)", "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=..." "example": "type=clightning;server=..."
},
"disableBOLT11PaymentOption": {
"type": "boolean",
"description": "Whether to disable generation of bolt11 invoices. Useful when wanting to only use LNURL Pay exclusively."
} }
} }
}, },

View File

@@ -303,10 +303,6 @@
"type": "boolean", "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. " "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": { "lud12Enabled": {
"type": "boolean", "type": "boolean",
"description": "Allow comments to be passed on via lnurl." "description": "Allow comments to be passed on via lnurl."

View File

@@ -189,7 +189,7 @@
"StoreUserDataList": { "StoreUserDataList": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/components/schemas/StoreData" "$ref": "#/components/schemas/StoreUserData"
} }
}, },
"StoreUserData": { "StoreUserData": {

View File

@@ -1,5 +1,5 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>1.9.0</Version> <Version>1.9.2</Version>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@@ -1,5 +1,26 @@
# Changelog # 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
* Fix crash if auto detect language on checkout page, and the language couldn't be detected (Fix #4881) @NicolasDorier
* Error 500 when viewing Receipt Page (Fix #4884) @dennisreimann
* When updating to version v1.9.0 the mapping to the APP stops working (Fix #4882) @NicolasDorier
## 1.9.0 ## 1.9.0
### Breaking change ### Breaking change
@@ -40,6 +61,7 @@ We introduce another flag, `--deprecated`, which allows you to start with SQLite
* Point of Sale: Fix escaped HTML entities in item title (#4798) @dennisreimann * Point of Sale: Fix escaped HTML entities in item title (#4798) @dennisreimann
* Fix: Labels added by payouts to transactions shouldn't show HTML markups (#4790) @dennisreimann * Fix: Labels added by payouts to transactions shouldn't show HTML markups (#4790) @dennisreimann
* If store user is Guest, "issue refund" shouldn't be an option (#4595 #3512) @Kukks * If store user is Guest, "issue refund" shouldn't be an option (#4595 #3512) @Kukks
* Fix wrong data mapping to store data instead of store user data (#4874) @ndeet
### Improvements ### Improvements

View File

@@ -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>

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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());
}
}
}
}
}
}

View File

@@ -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");
}
}
}

Some files were not shown because too many files have changed in this diff Show More