Compare commits

..

65 Commits

Author SHA1 Message Date
7c6a0f25c1 Merge branch 'master' into ext-point-pp 2023-07-13 10:21:24 +02:00
fd6d361e1a CheckoutV2: When WebSocket disconnects, we should continue polling via XHR ()
* When WebSocket disconnects, we should continue polling via XHR

* Update BTCPayServer/wwwroot/checkout-v2/checkout.js

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

---------

Co-authored-by: d11n <mail@dennisreimann.de>
2023-07-11 21:56:13 +02:00
b5f0924651 Serialize PosAppCartItem.value as decimal instead of string 2023-07-11 15:49:16 +09:00
1600dd4759 POS: Backwards-compatible price parsing ()
* POS: Backwards-compatible price parsing

Fixes  and a regression introduced in bbff9710bf2f4a66bd6f4cd9e8ee55618d0ca5e0: The price in posData needs to be parsed in a backwards-compatible manner, as the old format of price as an object exists in the invoice metadata.

* Test corner cases

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-07-11 15:32:01 +09:00
c777746b69 Custom Forms: Allow HTML in labels and help text ()
* Custom Forms: Allow HTML in labels and help text

Fixes .

* Vue: Sanitize labels and helper text input

* Form editor: Fix blur on input for select option values

---------

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
2023-07-11 13:02:02 +09:00
9f5466a41f Make sure CheckJsContent run as part of CI, and ignore end of line differences 2023-07-11 09:41:28 +09:00
4d1e4801bf Dark theme color fix 2023-07-10 11:33:39 +02:00
5e469ff9c0 Improve rates ()
* Removes Chaincoin shitcoin which is so dead even its website is gone
* Add ExchangeRateHost and FreeCurrencyRates as new rate providers
* Add recommended rate providers for UGX and RSD
* Fix BTX rate by switching to graviex
* Fix BTC rate by switching to exmo
* Fix LCAD rate script
2023-07-10 17:31:48 +09:00
2f3eedea5b Invoice lists: Show icons for payment methods () 2023-07-08 17:33:13 +02:00
5c5d6dc1e2 Bumping LND to 0.16.4-beta 2023-07-08 08:22:42 -05:00
fbe31ce64f Support LNURL in pay button ()
* Support LNURL in pay button

* UI updates

* Cleanups

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-07-06 10:12:31 +02:00
0b082138c8 Payment Requests: List view improvements ()
* List invoice checkbox variant

* Remove custom css

* Improve payment requests list view

* Improve Payment Requests List View

* List invoice checkbox variant

* Remove custom css

* Improve payment requests list view

* Improve Payment Requests List View

* Update payment request (name link leads to view not edit)

* Refactoring

---------

Co-authored-by: d11n <mail@dennisreimann.de>
2023-07-06 10:02:23 +02:00
966e598f10 Apps: Add direct file upload in item editor () 2023-07-06 11:01:36 +09:00
098a36e1c1 Update ViewPullPayment.cshtml 2023-07-05 14:28:53 +02:00
e998340387 POS: Account for custom amount in cart view ()
* Add failing test

* Account for custom amount

* Test fix
2023-07-05 17:23:15 +09:00
f6b27cc5f9 Compare domains in lowercase
Domains are case-insensitive, so this comparision should be too.

I encountered this issue with a Citadel user who accidentially named their domain an uppercase name (Pay.example.com), but browsers automatically converted it to pay.example.com
2023-07-03 08:49:16 +02:00
f3dbf1e139 Allow browser to access LND config () 2023-06-30 15:08:23 +09:00
627d84fc91 Update to Bootstrap v5.3 ()
Based on 
2023-06-30 09:21:27 +09:00
8cde8c01df Add category feature to the PoS with Cart ()
* Add grouping feature to the PoS with Cart

* Improve UI

* Rename groups to categories

* Make it easier to select categories of the items

* Refactor TemplateEditor, use TomSelect for categories

* Prevent Vue code insertion

* Prevent empty categories

* Add label ids

* Add test case

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-06-30 09:13:15 +09:00
3fb5e85369 add extension point for pull payment view 2023-06-29 15:44:06 +02:00
983b8c1f54 Fix changelog 2023-06-27 21:32:05 +09:00
d666d8ea1a Changelog 1.10.3 ()
* Changelog 1.10.3

* Apply suggestions from code review

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

* Apply suggestions from code review

* Update changelog

---------

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
Co-authored-by: d11n <mail@dennisreimann.de>
2023-06-27 21:23:56 +09:00
3ed81c3a78 Greenfield: Fix missing default currency in stores API
Docs mention it should be present, but it wasn't. Fixes .
2023-06-27 12:52:24 +02:00
4afec2e2b6 Fix: Using lnaddresses on Nostr should not result in lots of invoice being created 2023-06-27 12:50:24 +02:00
db83d238d5 Crowdfund: Fix JS errors in empty state ()
An empty crfowdfund with the default perk had JS errors.
2023-06-27 09:42:18 +09:00
fdcf7b3b7a Bumping LND to 0.16.3-beta () 2023-06-27 09:06:31 +09:00
53aafcf86b Fix: The current preimage of a invoice's lightning payment method should be available via API () 2023-06-23 19:12:11 +09:00
aec84f6d67 Dashboard: Limit "Top Items" to five ()
Feedback we got at BTCPrague: Do not show more than five items in the top list, because otherwise the list can get very long if there's a POS with many items.
2023-06-23 11:31:05 +02:00
01e9f82d24 Policies: Update wording to fit API keys and Roles ()
* Policies: Update wording to fit API keys and Roles

Closes .

* API keys: Improve spacing
2023-06-22 10:37:30 +02:00
2eff45e65c Ajaxify the wallet transaction list to avoid timeout (Fix ) ()
* Ajaxify the wallet transaction list to avoid timeout (Fix )

* Add cancellation to request to wallet transactions

* Fix tests

* Improve empty state

* Cleanups

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-06-22 16:09:53 +09:00
13203c3e2b Receipt improvements ()
* Remove Order ID link

* Add separate print version for receipt

* Fix POS number handling and add keypad test

Fixes .

* Add formatting function

* Remove OrderUrl for POS, bring back order link for receipt

* Update BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs
2023-06-22 15:57:29 +09:00
82c5e0e43d Dashboard: Make invoice badges consistent with those on invoices list ()
Closes .
2023-06-22 15:47:12 +09:00
a1575f404b Invoices: Fix search box shrinking too small ()
Fixes .
2023-06-21 09:52:42 +02:00
e1509506dc Upgrade Bootstrap-Vue and fix tooltip positioning
Fixes .
2023-06-21 08:31:13 +02:00
0c1d0d7b05 Fix: formResponse and formId missing from API's GetPaymentRequest route 2023-06-21 12:47:21 +09:00
ad70856af0 Fix: LN payments failed to be detected on litd () 2023-06-21 12:15:46 +09:00
8615f120ce Fix tests 2023-06-20 22:37:05 +09:00
0d0477d661 Lightning: Relax GetInfo constraint for LNDhub connections ()
* Lightning: Relax GetInfo constraint for LNDhub connections

The LNDhub-compatible implementation by LNbits does not support the `GetInfo` call for all their funding sources — see lnbits/lnbits#1182. By catching that exception in combination with the `LndHubLightningClient`, we give people the ability to still use their LNbits-based LNDhub as a Lightning node.

Fixes .

* Update approach to handling unsupported GetInfo calls
2023-06-20 17:28:16 +09:00
b31dc30878 Make file management UI more useful ()
* Make file management UI more useful

* Simplify markup

* Move file info to top

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-06-20 08:58:28 +02:00
6e392f4cfb After changing PoS items in UpdatePoS ident the JSON template 2023-06-19 14:44:12 +09:00
cc3bdc331e Fix build 2023-06-16 23:19:47 +09:00
76faf77a1c Fix keypad view broken by previous commit 2023-06-16 23:18:47 +09:00
d8c0e5bf3a Add extension point to template editor () 2023-06-16 23:05:49 +09:00
28c4c320cc Checkout v2: Add return link in processing state ()
* Checkout v2: Add return link in processing state

* Update copy text position
2023-06-16 23:05:08 +09:00
e81403ec3f Fix: Applying a discount in PoS with cart wasn't working () 2023-06-16 23:02:14 +09:00
f11424f73a Pull Payment: Support LNURL Withdraw with SATS denomination ()
* Pull Payment: Support LNURL Withdraw with SATS denomination

* Refactor and add tests
2023-06-16 10:56:17 +09:00
fa8b977016 Remove id from create webhook endpoint; fix consistency. () 2023-06-16 10:53:41 +09:00
d181846339 Refund: Fix overpaid option ()
Closes .
2023-06-16 10:52:52 +09:00
1956919886 Do not crash when an invoice have an amount that is too big () 2023-06-16 10:47:58 +09:00
0f66498965 NFC: Do not start scanning if unsupported
Fixes .
2023-06-14 09:14:09 +02:00
918cd152b1 Fix: Incorrect rounding in the receipt of PoS invoice (fix ) () 2023-06-13 20:34:21 +02:00
d3222df396 Fix build warnings ()
Fixes these two:

```
/source/BTCPayServer/Hosting/MigrationStartupTask.cs(643,49): warning CS0168: The variable 'items' is declared but never used [/source/BTCPayServer/BTCPayServer.csproj]
/source/BTCPayServer/Hosting/MigrationStartupTask.cs(644,24): warning CS0168: The variable 'newTemplate' is declared but never used [/source/BTCPayServer/BTCPayServer.csproj]
```
2023-06-13 20:46:44 +09:00
a84ffd8c7e Crowdfund: Fix null pointer exception for topup type (missing price) ()
Items with type topup have a price = null and hence not even the property set (ignored in JSON). This needs to be handled in the temlate, otherwise this exception occurs:

```
An unhandled exception was thrown by the application.
System.InvalidOperationException: Nullable object must have a value.
   at AspNetCoreGeneratedDocument.Views_Shared_Crowdfund_Public_ContributeForm.<>c__DisplayClass24_0.<<ExecuteAsync>b__0>d.MoveNext()
```
2023-06-13 20:46:27 +09:00
6d0f9120b8 prep for 1.10.2 2023-06-07 18:02:51 +02:00
aafb4a7f2a Fix stale invoice api for settle invoice
fixes 
2023-06-07 17:57:03 +02:00
ae432ff237 Fix: Crash on migation of old instances (Fix ) 2023-06-07 10:20:39 +02:00
cdc318c71a Pay Button: Fix circular reference when serializing JSON
When apps were set, the `GetAllApps` included the store data, which led to a circular reference when serializing the JSON. That data isn't necessary here, so we can just drop it before rendering.

Fixes .
2023-06-05 12:35:06 +02:00
94d1cec8a9 Hide Sensitive Info: Fix script location
The script snippet needs to be located outside of the theme if-conditions, otherwise it only works if no custom theme is applied.
2023-06-05 12:34:54 +02:00
c0bc19ea59 Update Changelog 2023-06-02 18:21:57 +09:00
6f07714cd9 Language update 2023-06-02 18:18:10 +09:00
a9d2cac23c bump 1.10.1 2023-06-02 18:15:56 +09:00
693b46126b Bump Bitcoin core to 25.0 () 2023-06-02 16:41:35 +09:00
bbff9710bf fix cart + form combination bug fixes 2023-06-02 09:34:55 +02:00
358e122775 Fix tests 2023-06-01 22:17:42 +09:00
3818468932 Pluginify on chain wallet setup ()
* Pluginify on chain wallet setup

This PR fixes a few logical points in the wallet setup flow to allow more extensive plugin flexibility; It also fixes an issue when building plugins that requires an Altcoin config profile. Here is an example showcasing the Liquid+ plugin using this to enforce that it is a hot wallet (a requirement it has) and that import to RPC is always set, and a new option that is used to configure the wallet further https://i.imgur.com/pDPQ73v.gif

* Update BTCPayServer/Controllers/UIStoresController.Onchain.cs

* update nbx
2023-06-01 21:18:28 +09:00
166 changed files with 3853 additions and 18673 deletions
BTCPayServer.Client
BTCPayServer.Common
BTCPayServer.Rating
BTCPayServer.Tests
BTCPayServer
BTCPayServer.csproj
Components
Controllers
Data
DerivationSchemeSettings.csExtensions.cs
Extensions
HostedServices
Hosting
JsonConverters
ModelBinders
Models
Payments
Plugins
Security/GreenField
Services
Views
WalletId.cs
wwwroot
Build
Changelog.md

@ -12,6 +12,8 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryUrl>https://github.com/btcpayserver/btcpayserver</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Configurations>Debug;Release;Altcoins-Debug;Altcoins-Release</Configurations>
<Platforms>AnyCPU</Platforms>
</PropertyGroup>
<PropertyGroup>
<Version Condition=" '$(Version)' == '' ">1.7.2</Version>

@ -16,7 +16,7 @@ namespace BTCPayServer
DefaultRateRules = new[]
{
"BTG_X = BTG_BTC * BTC_X",
"BTG_BTC = bitfinex(BTG_BTC)",
"BTG_BTC = exmo(BTG_BTC)",
},
CryptoImagePath = "imlegacy/btg.svg",
LightningImagePath = "imlegacy/btg-lightning.svg",

@ -17,7 +17,7 @@ namespace BTCPayServer
DefaultRateRules = new[]
{
"BTX_X = BTX_BTC * BTC_X",
"BTX_BTC = hitbtc(BTX_BTC)"
"BTX_BTC = graviex(BTX_BTC)"
},
CryptoImagePath = "imlegacy/bitcore.svg",
LightningImagePath = "imlegacy/bitcore-lightning.svg",

@ -1,32 +0,0 @@
using NBitcoin;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitChaincoin()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("CHC");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Chaincoin",
BlockExplorerLink = NetworkType == ChainName.Mainnet
? "https://explorer.chaincoin.org/Explorer/Transaction/{0}"
: "https://test.explorer.chaincoin.org/Explorer/Transaction/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
DefaultRateRules = new[]
{
"CHC_X = CHC_BTC * BTC_X",
"CHC_BTC = txbit(CHC_X)"
},
CryptoImagePath = "imlegacy/chaincoin.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
//https://github.com/satoshilabs/slips/blob/master/slip-0044.md
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("711'")
: new KeyPath("1'")
});
}
}
}

@ -63,6 +63,7 @@ namespace BTCPayServer
"LCAD_CAD = 1",
"LCAD_X = CAD_BTC * BTC_X",
"LCAD_BTC = bylls(CAD_BTC)",
"CAD_BTC = LCAD_BTC"
},
AssetId = new uint256("0e99c1a6da379d1f4151fb9df90449d40d0608f6cb33a5bcbfc8c265f42bab0a"),
DisplayName = "Liquid CAD",

@ -56,7 +56,6 @@ namespace BTCPayServer
InitViacoin();
InitMonero();
InitZcash();
InitChaincoin();
// InitArgoneum();//their rate source is down 9/15/20.
// InitMonetaryUnit(); Not supported from Bittrex from 11/23/2022, dead shitcoin

@ -4,7 +4,7 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="NBXplorer.Client" Version="4.2.3" />
<PackageReference Include="NBXplorer.Client" Version="4.2.5" />
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="2.0.1" />
</ItemGroup>
<ItemGroup Condition="'$(Altcoins)' != 'true'">

@ -15,7 +15,7 @@ namespace BTCPayServer.Rating
while (true)
{
var rounded = decimal.Round(value, divisibility, MidpointRounding.AwayFromZero);
if ((Math.Abs(rounded - value) / value) < 0.001m)
if ((Math.Abs(rounded - value) / value) < 0.01m)
{
value = rounded;
break;

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Rating;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Services.Rates;
public class ExchangeRateHostRateProvider : IRateProvider
{
public RateSourceInfo RateSourceInfo => new("exchangeratehost", "Yadio", "https://api.exchangerate.host/latest?base=BTC");
private readonly HttpClient _httpClient;
public ExchangeRateHostRateProvider(HttpClient httpClient)
{
_httpClient = httpClient ?? new HttpClient();
}
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync(RateSourceInfo.Url, cancellationToken);
response.EnsureSuccessStatusCode();
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
if(jobj["success"].Value<bool>() is not true || !jobj["base"].Value<string>().Equals("BTC", StringComparison.InvariantCulture))
throw new Exception("exchangerate.host returned a non success response or the base currency was not the requested one (BTC)");
var results = (JObject) jobj["rates"] ;
//key value is currency code to rate value
var list = new List<PairRate>();
foreach (var item in results)
{
string name = item.Key;
var value = item.Value.Value<decimal>();
list.Add(new PairRate(new CurrencyPair("BTC", name), new BidAsk(value)));
}
return list.ToArray();
}
}

@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Rating;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Services.Rates;
public class FreeCurrencyRatesRateProvider : IRateProvider
{
public RateSourceInfo RateSourceInfo => new("free-currency-rates", "Free Currency Rates", "https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@1/latest/currencies/btc.min.json");
private readonly HttpClient _httpClient;
public FreeCurrencyRatesRateProvider(HttpClient httpClient)
{
_httpClient = httpClient ?? new HttpClient();
}
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync(RateSourceInfo.Url, cancellationToken);
response.EnsureSuccessStatusCode();
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
var results = (JObject) jobj["btc"] ;
//key value is currency code to rate value
var list = new List<PairRate>();
foreach (var item in results)
{
string name = item.Key;
var value = item.Value.Value<decimal>();
list.Add(new PairRate(new CurrencyPair("BTC", name), new BidAsk(value)));
}
return list.ToArray();
}
}

@ -669,7 +669,7 @@ donation:
Assert.Equal("Wanna tip?", vmview.CustomTipText);
Assert.Equal("15,18,20", string.Join(',', vmview.CustomTipPercentages));
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, null, null, null, null, "orange").Result);
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, choiceKey: "orange").Result);
//
var invoices = await user.BitPay.GetInvoicesAsync();
@ -678,7 +678,7 @@ donation:
Assert.Equal("CAD", orangeInvoice.Currency);
Assert.Equal("orange", orangeInvoice.ItemDesc);
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, null, null, null, null, "apple").Result);
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, choiceKey: "apple").Result);
invoices = user.BitPay.GetInvoices();
var appleInvoice = invoices.SingleOrDefault(invoice => invoice.ItemCode.Equals("apple"));
@ -687,7 +687,7 @@ donation:
// testing custom amount
var action = Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 6.6m, null, null, null, null, "donation").Result);
.ViewPointOfSale(app.Id, PosViewType.Cart, 6.6m, choiceKey: "donation").Result);
Assert.Equal(nameof(UIInvoiceController.Checkout), action.ActionName);
invoices = user.BitPay.GetInvoices();
var donationInvoice = invoices.Single(i => i.Price == 6.6m);
@ -760,20 +760,20 @@ noninventoryitem:
await tester.WaitForEvent<AppInventoryUpdaterHostedService.UpdateAppInventory>(() =>
{
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, null, null, null, null, "inventoryitem").Result);
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "inventoryitem").Result);
return Task.CompletedTask;
});
//we already bought all available stock so this should fail
await Task.Delay(100);
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, null, null, null, null, "inventoryitem").Result);
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "inventoryitem").Result);
//inventoryitem has unlimited items available
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, null, null, null, null, "noninventoryitem").Result);
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "noninventoryitem").Result);
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, null, null, null, null, "noninventoryitem").Result);
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "noninventoryitem").Result);
//verify invoices where created
invoices = user.BitPay.GetInvoices();
@ -809,22 +809,22 @@ normal:
vmpos.Template = AppService.SerializeTemplate(MigrationStartupTask.ParsePOSYML(vmpos.Template));
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, null, null, null, null, "btconly").Result);
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "btconly").Result);
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, null, null, null, null, "normal").Result);
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "normal").Result);
invoices = user.BitPay.GetInvoices();
var normalInvoice = invoices.Single(invoice => invoice.ItemCode == "normal");
var btcOnlyInvoice = invoices.Single(invoice => invoice.ItemCode == "btconly");
Assert.Single(btcOnlyInvoice.CryptoInfo);
Assert.Equal("BTC",
btcOnlyInvoice.CryptoInfo.First().CryptoCode);
Assert.Equal(BitcoinPaymentType.Instance.ToString(),
Assert.Equal(PaymentTypes.BTCLike.ToString(),
btcOnlyInvoice.CryptoInfo.First().PaymentType);
Assert.Equal(2, normalInvoice.CryptoInfo.Length);
Assert.Contains(
normalInvoice.CryptoInfo,
s => BitcoinPaymentType.Instance.ToString() == s.PaymentType && new[] { "BTC", "LTC" }.Contains(
s => PaymentTypes.BTCLike.ToString() == s.PaymentType && new[] { "BTC", "LTC" }.Contains(
s.CryptoCode));
//test topup option
@ -865,7 +865,7 @@ g:
Assert.Contains(items, item => item.Id == "g" && item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Topup);
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Static, null, null, null, null, null, "g").Result);
.ViewPointOfSale(app.Id, PosViewType.Static, choiceKey: "g").Result);
invoices = user.BitPay.GetInvoices();
var topupInvoice = invoices.Single(invoice => invoice.ItemCode == "g");
Assert.Equal(0, topupInvoice.Price);

@ -210,7 +210,7 @@ namespace BTCPayServer.Tests
Assert.True(s.Driver.FindElement(By.Name("btcpay")).Displayed);
});
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(invoice
.GetPaymentMethod(new PaymentMethodId("BTC", BitcoinPaymentType.Instance))
.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike))
.GetPaymentMethodDetails().GetPaymentDestination(), Network.RegTest),
new Money(0.001m, MoneyUnit.BTC));

@ -452,7 +452,7 @@ namespace BTCPayServer.Tests
iframe.WaitUntilAvailable(By.Id("Checkout-v2"));
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(invoice
.GetPaymentMethod(new PaymentMethodId("BTC", BitcoinPaymentType.Instance))
.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike))
.GetPaymentMethodDetails().GetPaymentDestination(), Network.RegTest),
new Money(0.001m, MoneyUnit.BTC));

@ -122,6 +122,13 @@ retry:
driver.ExecuteJavaScript($"document.getElementById('{element}').{funcName}()");
}
public static void WaitWalletTransactionsLoaded(this IWebDriver driver)
{
var wait = new WebDriverWait(driver, SeleniumTester.ImplicitWait);
wait.UntilJsIsReady();
wait.Until(d => d.WaitForElement(By.CssSelector("#WalletTransactions[data-loaded='true']")));
}
public static IWebElement WaitForElement(this IWebDriver driver, By selector)
{
var wait = new WebDriverWait(driver, SeleniumTester.ImplicitWait);

@ -368,7 +368,7 @@ namespace BTCPayServer.Tests
});
entity.Price = 5000;
var paymentMethod = entity.GetPaymentMethods().TryGet("BTC", BitcoinPaymentType.Instance);
var paymentMethod = entity.GetPaymentMethods().TryGet("BTC", PaymentTypes.BTCLike);
var accounting = paymentMethod.Calculate();
Assert.Equal(Money.Coins(1.1m), accounting.Due);
Assert.Equal(Money.Coins(1.1m), accounting.TotalDue);
@ -424,11 +424,11 @@ namespace BTCPayServer.Tests
new PaymentMethod() { CryptoCode = "LTC", Rate = 500, NextNetworkFee = Money.Coins(0.01m) });
entity.SetPaymentMethods(paymentMethods);
entity.Payments = new List<PaymentEntity>();
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", BitcoinPaymentType.Instance));
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
accounting = paymentMethod.Calculate();
Assert.Equal(Money.Coins(5.1m), accounting.Due);
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", BitcoinPaymentType.Instance));
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
accounting = paymentMethod.Calculate();
Assert.Equal(Money.Coins(10.01m), accounting.TotalDue);
@ -441,7 +441,7 @@ namespace BTCPayServer.Tests
NetworkFee = 0.1m
});
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", BitcoinPaymentType.Instance));
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
accounting = paymentMethod.Calculate();
Assert.Equal(Money.Coins(4.2m), accounting.Due);
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
@ -449,7 +449,7 @@ namespace BTCPayServer.Tests
Assert.Equal(Money.Coins(5.2m), accounting.TotalDue);
Assert.Equal(2, accounting.TxRequired);
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", BitcoinPaymentType.Instance));
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
accounting = paymentMethod.Calculate();
Assert.Equal(Money.Coins(10.01m + 0.1m * 2 - 2.0m /* 8.21m */), accounting.Due);
Assert.Equal(Money.Coins(0.0m), accounting.CryptoPaid);
@ -464,7 +464,7 @@ namespace BTCPayServer.Tests
NetworkFee = 0.01m
});
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", BitcoinPaymentType.Instance));
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
accounting = paymentMethod.Calculate();
Assert.Equal(Money.Coins(4.2m - 0.5m + 0.01m / 2), accounting.Due);
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
@ -472,7 +472,7 @@ namespace BTCPayServer.Tests
Assert.Equal(Money.Coins(5.2m + 0.01m / 2), accounting.TotalDue); // The fee for LTC added
Assert.Equal(2, accounting.TxRequired);
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", BitcoinPaymentType.Instance));
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
accounting = paymentMethod.Calculate();
Assert.Equal(Money.Coins(8.21m - 1.0m + 0.01m), accounting.Due);
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
@ -489,7 +489,7 @@ namespace BTCPayServer.Tests
NetworkFee = 0.1m
});
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", BitcoinPaymentType.Instance));
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
accounting = paymentMethod.Calculate();
Assert.Equal(Money.Zero, accounting.Due);
Assert.Equal(Money.Coins(1.0m) + remaining, accounting.CryptoPaid);
@ -498,7 +498,7 @@ namespace BTCPayServer.Tests
Assert.Equal(accounting.Paid, accounting.TotalDue);
Assert.Equal(2, accounting.TxRequired);
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", BitcoinPaymentType.Instance));
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
accounting = paymentMethod.Calculate();
Assert.Equal(Money.Zero, accounting.Due);
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
@ -556,7 +556,7 @@ namespace BTCPayServer.Tests
entity.PaymentTolerance = 0;
var paymentMethod = entity.GetPaymentMethods().TryGet("BTC", BitcoinPaymentType.Instance);
var paymentMethod = entity.GetPaymentMethods().TryGet("BTC", PaymentTypes.BTCLike);
var accounting = paymentMethod.Calculate();
Assert.Equal(Money.Coins(1.1m), accounting.Due);
Assert.Equal(Money.Coins(1.1m), accounting.TotalDue);
@ -1145,6 +1145,45 @@ namespace BTCPayServer.Tests
Assert.Equal("000000161", m.OrderId);
}
[Fact]
public void CanParseOldPosAppData()
{
var data = new JObject()
{
["price"] = 1.64m
}.ToString();
Assert.Equal(1.64m, JsonConvert.DeserializeObject<PosAppCartItem>(data).Price);
data = new JObject()
{
["price"] = new JObject()
{
["value"] = 1.65m
}
}.ToString();
Assert.Equal(1.65m, JsonConvert.DeserializeObject<PosAppCartItem>(data).Price);
data = new JObject()
{
["price"] = new JObject()
{
["value"] = "1.6305"
}
}.ToString();
Assert.Equal(1.6305m, JsonConvert.DeserializeObject<PosAppCartItem>(data).Price);
data = new JObject()
{
["price"] = new JObject()
{
["value"] = null
}
}.ToString();
Assert.Equal(0.0m, JsonConvert.DeserializeObject<PosAppCartItem>(data).Price);
var o = JObject.Parse(JsonConvert.SerializeObject(new PosAppCartItem() { Price = 1.356m }));
Assert.Equal(1.356m, o["price"].Value<decimal>());
}
[Fact]
public void CanParseCurrencyValue()
{
@ -1873,7 +1912,7 @@ namespace BTCPayServer.Tests
}));
invoiceEntity.SetPaymentMethods(paymentMethods);
var btc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("BTC", BitcoinPaymentType.Instance));
var btc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
var accounting = btc.Calculate();
invoiceEntity.Payments.Add(
@ -1907,7 +1946,7 @@ namespace BTCPayServer.Tests
Assert.Equal(Money.Zero, accounting.Due);
Assert.Equal(Money.Zero, accounting.DueUncapped);
var ltc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("LTC", BitcoinPaymentType.Instance));
var ltc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
accounting = ltc.Calculate();
Assert.Equal(Money.Zero, accounting.Due);
@ -2015,7 +2054,7 @@ namespace BTCPayServer.Tests
{
Above = true,
Value = new CurrencyValue() {Currency = "BTC", Value = 0.1m},
PaymentMethod = new PaymentMethodId("BTC", BitcoinPaymentType.Instance)
PaymentMethod = new PaymentMethodId("BTC", PaymentTypes.BTCLike)
}
};
var newBlob = new Serializer(null).ToString(blob).Replace("paymentMethod\":\"BTC\"", "paymentMethod\":\"ETH_ZYC\"");

@ -1074,6 +1074,22 @@ namespace BTCPayServer.Tests
var lnrURLs = await unauthenticated.GetPullPaymentLNURL(test4.Id);
Assert.IsType<string>(lnrURLs.LNURLBech32);
Assert.IsType<string>(lnrURLs.LNURLUri);
Assert.Equal(12.303228134m, test4.Amount);
Assert.Equal("BTC", test4.Currency);
// Test with SATS denomination values
var testSats = await client.CreatePullPayment(storeId, new Client.Models.CreatePullPaymentRequest()
{
Name = "Test SATS",
Amount = 21000,
Currency = "SATS",
PaymentMethods = new[] { "BTC", "BTC-LightningNetwork", "BTC_LightningLike" }
});
lnrURLs = await unauthenticated.GetPullPaymentLNURL(testSats.Id);
Assert.IsType<string>(lnrURLs.LNURLBech32);
Assert.IsType<string>(lnrURLs.LNURLUri);
Assert.Equal(21000, testSats.Amount);
Assert.Equal("SATS", testSats.Currency);
//permission test around auto approved pps and payouts
var nonApproved = await acc.CreateClient(Policies.CanCreateNonApprovedPullPayments);
@ -2615,7 +2631,7 @@ namespace BTCPayServer.Tests
for (int i = 0; i < invoices.Length; i++)
{
pm[i] = Assert.Single(await client.GetInvoicePaymentMethods(user.StoreId, (await invoices[i]).Id));
Assert.False(pm[i].AdditionalData.HasValues);
Assert.True(pm[i].AdditionalData.HasValues);
}
// Pay them all at once
@ -3267,7 +3283,7 @@ namespace BTCPayServer.Tests
void VerifyLightning(Dictionary<string, GenericPaymentMethodData> dictionary)
{
Assert.True(dictionary.TryGetValue(new PaymentMethodId("BTC", LightningPaymentType.Instance).ToStringNormalized(), out var item));
Assert.True(dictionary.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToStringNormalized(), out var item));
var lightningNetworkPaymentMethodBaseData = Assert.IsType<JObject>(item.Data).ToObject<LightningNetworkPaymentMethodBaseData>();
Assert.Equal("Internal Node", lightningNetworkPaymentMethodBaseData.ConnectionString);
}
@ -3282,7 +3298,7 @@ namespace BTCPayServer.Tests
void VerifyOnChain(Dictionary<string, GenericPaymentMethodData> dictionary)
{
Assert.True(dictionary.TryGetValue(new PaymentMethodId("BTC", BitcoinPaymentType.Instance).ToStringNormalized(), out var item));
Assert.True(dictionary.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.BTCLike).ToStringNormalized(), out var item));
var paymentMethodBaseData = Assert.IsType<JObject>(item.Data).ToObject<OnChainPaymentMethodBaseData>();
Assert.Equal(randK, paymentMethodBaseData.DerivationScheme);
}
@ -3304,14 +3320,14 @@ namespace BTCPayServer.Tests
tester.GetLightningConnectionString(LightningConnectionType.CLightning, true), true));
methods = await viewerOnlyClient.GetStorePaymentMethods(store.Id);
Assert.True(methods.TryGetValue(new PaymentMethodId("BTC", LightningPaymentType.Instance).ToStringNormalized(), out var item));
Assert.True(methods.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToStringNormalized(), out var item));
var lightningNetworkPaymentMethodBaseData = Assert.IsType<JObject>(item.Data).ToObject<LightningNetworkPaymentMethodBaseData>();
Assert.Equal("*NEED CanModifyStoreSettings PERMISSION TO VIEW*", lightningNetworkPaymentMethodBaseData.ConnectionString);
methods = await adminClient.GetStorePaymentMethods(store.Id);
Assert.True(methods.TryGetValue(new PaymentMethodId("BTC", LightningPaymentType.Instance).ToStringNormalized(), out item));
Assert.True(methods.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToStringNormalized(), out item));
lightningNetworkPaymentMethodBaseData = Assert.IsType<JObject>(item.Data).ToObject<LightningNetworkPaymentMethodBaseData>();
Assert.NotEqual("*NEED CanModifyStoreSettings PERMISSION TO VIEW*", lightningNetworkPaymentMethodBaseData.ConnectionString);

@ -135,10 +135,10 @@ donation:
Assert.Equal("donation", vmview.Items[1].Title);
// orange is available
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, null, null, null, null, "orange").Result);
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, choiceKey: "orange").Result);
// apple is not found
Assert.IsType<NotFoundResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, null, null, null, null, "apple").Result);
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, choiceKey: "apple").Result);
}
}
}

@ -930,7 +930,7 @@ retry:
tester.ExplorerClient.Network.NBitcoinNetwork);
var senderStore = await tester.PayTester.StoreRepository.FindStore(senderUser.StoreId);
var paymentMethodId = new PaymentMethodId("BTC", BitcoinPaymentType.Instance);
var paymentMethodId = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
var derivationSchemeSettings = senderStore.GetSupportedPaymentMethods(tester.NetworkProvider)
.OfType<DerivationSchemeSettings>().SingleOrDefault(settings =>
settings.PaymentId == paymentMethodId);

@ -1,5 +1,6 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
@ -961,11 +962,13 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.CssSelector("label[for='DefaultView_Cart']")).Click();
s.Driver.FindElement(By.CssSelector(".template-item:nth-of-type(1) .btn-primary")).Click();
s.Driver.FindElement(By.Id("BuyButtonText")).SendKeys("Take my money");
s.Driver.FindElement(By.Id("EditorCategories-ts-control")).SendKeys("Drinks");
s.Driver.FindElement(By.Id("SaveItemChanges")).Click();
s.Driver.FindElement(By.Id("ToggleRawEditor")).Click();
var template = s.Driver.FindElement(By.Id("Template")).GetAttribute("value");
Assert.Contains("\"buyButtonText\":\"Take my money\"", template);
Assert.Contains("\"buyButtonText\": \"Take my money\"", template);
Assert.Matches("\"categories\": \\[\n\\s+\"Drinks\"\n\\s+\\]", template);
s.Driver.FindElement(By.Id("SaveSettings")).Click();
Assert.Contains("App updated", s.FindAlertMessage().Text);
@ -979,6 +982,14 @@ namespace BTCPayServer.Tests
Assert.True(s.Driver.PageSource.Contains("Tea shop"), "Unable to create PoS");
Assert.True(s.Driver.PageSource.Contains("Cart"), "PoS not showing correct default view");
Assert.True(s.Driver.PageSource.Contains("Take my money"), "PoS not showing correct default view");
Assert.Equal(5, s.Driver.FindElements(By.CssSelector(".card-deck .card:not(.d-none)")).Count);
var drinks = s.Driver.FindElement(By.CssSelector("label[for='Category-Drinks']"));
Assert.Equal("Drinks", drinks.Text);
drinks.Click();
Assert.Single(s.Driver.FindElements(By.CssSelector(".card-deck .card:not(.d-none)")));
s.Driver.FindElement(By.CssSelector("label[for='Category-*']")).Click();
Assert.Equal(5, s.Driver.FindElements(By.CssSelector(".card-deck .card:not(.d-none)")).Count);
s.Driver.Url = posBaseUrl + "/static";
Assert.False(s.Driver.PageSource.Contains("Cart"), "Static PoS not showing correct view");
@ -1145,12 +1156,13 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("ArchivePaymentRequest")).Click();
Assert.Contains("The payment request has been archived", s.FindAlertMessage().Text);
Assert.DoesNotContain("Pay123", s.Driver.PageSource);
s.Driver.FindElement(By.Id("SearchDropdownToggle")).Click();
s.Driver.FindElement(By.Id("SearchIncludeArchived")).Click();
s.Driver.FindElement(By.Id("StatusOptionsToggle")).Click();
s.Driver.WaitForElement(By.Id("StatusOptionsIncludeArchived")).Click();
Assert.Contains("Pay123", s.Driver.PageSource);
// unarchive (from list)
s.Driver.FindElement(By.Id($"ToggleArchival-{payReqId}")).Click();
s.Driver.FindElement(By.Id($"ToggleActions-{payReqId}")).Click();
s.Driver.WaitForElement(By.Id($"ToggleArchival-{payReqId}")).Click();
Assert.Contains("The payment request has been unarchived", s.FindAlertMessage().Text);
Assert.Contains("Pay123", s.Driver.PageSource);
}
@ -1441,7 +1453,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("CancelWizard")).Click();
// Check the label is applied to the tx
s.Driver.WaitWalletTransactionsLoaded();
Assert.Equal("label2", s.Driver.FindElement(By.XPath("//*[@id=\"WalletTransactionsList\"]//*[contains(@class, 'transaction-label')]")).Text);
//change the wallet and ensure old address is not there and generating a new one does not result in the prev one
@ -1492,7 +1504,9 @@ namespace BTCPayServer.Tests
// Check the tx sent earlier arrived
s.Driver.FindElement(By.Id($"StoreNav-Wallet{cryptoCode}")).Click();
Assert.Contains(tx.ToString(), s.Driver.PageSource);
s.Driver.WaitWalletTransactionsLoaded();
s.Driver.FindElement(By.PartialLinkText(tx.ToString()));
var walletTransactionUri = new Uri(s.Driver.Url);
// Send to bob
@ -1618,9 +1632,8 @@ namespace BTCPayServer.Tests
// Transactions list is empty
s.Driver.FindElement(By.Id($"StoreNav-Wallet{cryptoCode}")).Click();
Assert.Contains("There are no transactions yet.", s.Driver.PageSource);
s.Driver.AssertElementNotFound(By.Id("ExportDropdownToggle"));
s.Driver.AssertElementNotFound(By.Id("ActionsDropdownToggle"));
s.Driver.WaitWalletTransactionsLoaded();
Assert.Contains("There are no transactions yet", s.Driver.FindElement(By.Id("WalletTransactions")).Text);
}
[Fact]
@ -1748,7 +1761,6 @@ namespace BTCPayServer.Tests
Assert.Contains(labels, element => element.Text == "pull-payment");
});
s.GoToStore(s.StoreId, StoreNavPages.Payouts);
s.Driver.FindElement(By.Id($"{PayoutState.InProgress}-view")).Click();
ReadOnlyCollection<IWebElement> txs;
@ -1859,7 +1871,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("Destination")).SendKeys(bolt);
s.Driver.FindElement(By.Id("SelectedPaymentMethod")).Click();
s.Driver.FindElement(By.CssSelector(
$"#SelectedPaymentMethod option[value={new PaymentMethodId("BTC", LightningPaymentType.Instance)}]"))
$"#SelectedPaymentMethod option[value={new PaymentMethodId("BTC", PaymentTypes.LightningLike)}]"))
.Click();
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys(Keys.Enter);
@ -1874,7 +1886,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("Destination")).SendKeys(bolt);
s.Driver.FindElement(By.Id("SelectedPaymentMethod")).Click();
s.Driver.FindElement(By.CssSelector(
$"#SelectedPaymentMethod option[value={new PaymentMethodId("BTC", LightningPaymentType.Instance)}]"))
$"#SelectedPaymentMethod option[value={new PaymentMethodId("BTC", PaymentTypes.LightningLike)}]"))
.Click();
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys(Keys.Enter);
@ -1883,7 +1895,7 @@ namespace BTCPayServer.Tests
Assert.Contains(PayoutState.AwaitingApproval.GetStateString(), s.Driver.PageSource);
s.GoToStore(newStore.storeId, StoreNavPages.Payouts);
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", LightningPaymentType.Instance)}-view")).Click();
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike)}-view")).Click();
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-view")).Click();
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click();
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click();
@ -1894,7 +1906,7 @@ namespace BTCPayServer.Tests
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
s.GoToStore(newStore.storeId, StoreNavPages.Payouts);
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", LightningPaymentType.Instance)}-view")).Click();
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike)}-view")).Click();
s.Driver.FindElement(By.Id($"{PayoutState.Completed}-view")).Click();
if (!s.Driver.PageSource.Contains(bolt))
@ -1905,7 +1917,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-selectAllCheckbox")).Click();
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-actions")).Click();
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-mark-paid")).Click();
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", LightningPaymentType.Instance)}-view")).Click();
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike)}-view")).Click();
s.Driver.FindElement(By.Id($"{PayoutState.Completed}-view")).Click();
Assert.Contains(bolt, s.Driver.PageSource);
@ -1932,8 +1944,7 @@ namespace BTCPayServer.Tests
Assert.Contains(PayoutState.AwaitingPayment.GetStateString(), s.Driver.PageSource);
//lnurl-w support check
// LNURL Withdraw support check with BTC denomination
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
@ -2001,6 +2012,42 @@ namespace BTCPayServer.Tests
Assert.Contains(PayoutState.AwaitingApproval.GetStateString(), s.Driver.PageSource);
});
// LNURL Withdraw support check with SATS denomination
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
s.Driver.FindElement(By.Id("Name")).SendKeys("PP SATS");
s.Driver.SetCheckbox(By.Id("AutoApproveClaims"), true);
s.Driver.FindElement(By.Id("Amount")).Clear();
s.Driver.FindElement(By.Id("Amount")).SendKeys("21021");
s.Driver.FindElement(By.Id("Currency")).Clear();
s.Driver.FindElement(By.Id("Currency")).SendKeys("SATS" + Keys.Enter);
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
s.Driver.FindElement(By.LinkText("View")).Click();
s.Driver.FindElement(By.CssSelector("#lnurlwithdraw-button")).Click();
lnurl = new Uri(LNURL.LNURL.Parse(s.Driver.FindElement(By.Id("qr-code-data-input")).GetAttribute("value"), out _).ToString().Replace("https", "http"));
s.Driver.FindElement(By.CssSelector("button[data-bs-dismiss='modal']")).Click();
var amount = new LightMoney(21021, LightMoneyUnit.Satoshi);
info = Assert.IsType<LNURLWithdrawRequest>(await LNURL.LNURL.FetchInformation(lnurl, s.Server.PayTester.HttpClient));
Assert.Equal(amount, info.MaxWithdrawable);
Assert.Equal(amount, info.CurrentBalance);
info = Assert.IsType<LNURLWithdrawRequest>(await LNURL.LNURL.FetchInformation(info.BalanceCheck, s.Server.PayTester.HttpClient));
Assert.Equal(amount, info.MaxWithdrawable);
Assert.Equal(amount, info.CurrentBalance);
bolt2 = (await s.Server.CustomerLightningD.CreateInvoice(
amount,
$"LNurl w payout test {DateTime.UtcNow.Ticks}",
TimeSpan.FromHours(1), CancellationToken.None));
response = await info.SendRequest(bolt2.BOLT11, s.Server.PayTester.HttpClient);
await TestUtils.EventuallyAsync(async () =>
{
s.Driver.Navigate().Refresh();
Assert.Contains(bolt2.BOLT11, s.Driver.PageSource);
Assert.Contains(PayoutState.Completed.GetStateString(), s.Driver.PageSource);
Assert.Equal(LightningInvoiceStatus.Paid, (await s.Server.CustomerLightningD.GetInvoice(bolt2.Id)).Status);
});
}
[Fact]
@ -2040,6 +2087,145 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Trait("Selenium", "Selenium")]
[Trait("Lightning", "Lightning")]
public async Task CanUsePOSKeypad()
{
using var s = CreateSeleniumTester();
s.Server.ActivateLightning();
await s.StartAsync();
await s.Server.EnsureChannelsSetup();
s.RegisterNewUser(true);
s.CreateNewStore();
s.GoToStore();
s.AddLightningNode(LightningConnectionType.CLightning, false);
s.Driver.FindElement(By.Id("StoreNav-CreatePointOfSale")).Click();
s.Driver.FindElement(By.Id("AppName")).SendKeys(Guid.NewGuid().ToString());
s.Driver.FindElement(By.Id("Create")).Click();
TestUtils.Eventually(() => Assert.Contains("App successfully created", s.FindAlertMessage().Text));
s.Driver.FindElement(By.CssSelector("label[for='DefaultView_Light']")).Click();
s.Driver.FindElement(By.Id("Currency")).SendKeys("EUR");
s.Driver.FindElement(By.Id("CustomTipPercentages")).Clear();
s.Driver.FindElement(By.Id("CustomTipPercentages")).SendKeys("10,21");
s.Driver.FindElement(By.Id("SaveSettings")).Click();
Assert.Contains("App updated", s.FindAlertMessage().Text);
s.Driver.FindElement(By.Id("ViewApp")).Click();
var windows = s.Driver.WindowHandles;
Assert.Equal(2, windows.Count);
s.Driver.SwitchTo().Window(windows[1]);
s.Driver.WaitForElement(By.ClassName("keypad"));
// basic checks
Assert.Contains("EUR", s.Driver.FindElement(By.Id("Currency")).Text);
Assert.Contains("0,00", s.Driver.FindElement(By.Id("Amount")).Text);
Assert.Equal("", s.Driver.FindElement(By.Id("Calculation")).Text);
Assert.True(s.Driver.FindElement(By.Id("ModeTablist-amount")).Selected);
Assert.False(s.Driver.FindElement(By.Id("ModeTablist-discount")).Enabled);
Assert.False(s.Driver.FindElement(By.Id("ModeTablist-tip")).Enabled);
// Amount: 1234,56
s.Driver.FindElement(By.CssSelector(".keypad [data-key='1']")).Click();
s.Driver.FindElement(By.CssSelector(".keypad [data-key='2']")).Click();
s.Driver.FindElement(By.CssSelector(".keypad [data-key='3']")).Click();
s.Driver.FindElement(By.CssSelector(".keypad [data-key='4']")).Click();
s.Driver.FindElement(By.CssSelector(".keypad [data-key='.']")).Click();
s.Driver.FindElement(By.CssSelector(".keypad [data-key='5']")).Click();
s.Driver.FindElement(By.CssSelector(".keypad [data-key='6']")).Click();
Assert.Equal("1.234,56", s.Driver.FindElement(By.Id("Amount")).Text);
Assert.True(s.Driver.FindElement(By.Id("ModeTablist-discount")).Enabled);
Assert.True(s.Driver.FindElement(By.Id("ModeTablist-tip")).Enabled);
Assert.Equal("", s.Driver.FindElement(By.Id("Calculation")).Text);
// Discount: 10%
s.Driver.FindElement(By.CssSelector("label[for='ModeTablist-discount']")).Click();
s.Driver.FindElement(By.CssSelector(".keypad [data-key='1']")).Click();
s.Driver.FindElement(By.CssSelector(".keypad [data-key='0']")).Click();
Assert.Contains("1.111,10", s.Driver.FindElement(By.Id("Amount")).Text);
Assert.Contains("10% discount", s.Driver.FindElement(By.Id("Discount")).Text);
Assert.Contains("1.234,56 € - 123,46 € (10%)", s.Driver.FindElement(By.Id("Calculation")).Text);
// Tip: 10%
s.Driver.FindElement(By.CssSelector("label[for='ModeTablist-tip']")).Click();
s.Driver.WaitForElement(By.Id("Tip-Custom"));
s.Driver.FindElement(By.Id("Tip-10")).Click();
Assert.Contains("1.222,21", s.Driver.FindElement(By.Id("Amount")).Text);
Assert.Contains("1.234,56 € - 123,46 € (10%) + 111,11 € (10%)", s.Driver.FindElement(By.Id("Calculation")).Text);
// Pay
s.Driver.FindElement(By.Id("pay-button")).Click();
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
s.Driver.FindElement(By.Id("DetailsToggle")).Click();
s.Driver.WaitForElement(By.Id("PaymentDetails-TotalFiat"));
Assert.Contains("1 222,21 €", s.Driver.FindElement(By.Id("PaymentDetails-TotalFiat")).Text);
}
[Fact]
[Trait("Selenium", "Selenium")]
[Trait("Lightning", "Lightning")]
public async Task CanUsePOSCart()
{
using var s = CreateSeleniumTester();
s.Server.ActivateLightning();
await s.StartAsync();
await s.Server.EnsureChannelsSetup();
s.RegisterNewUser(true);
s.CreateNewStore();
s.GoToStore();
s.AddLightningNode(LightningConnectionType.CLightning, false);
s.Driver.FindElement(By.Id("StoreNav-CreatePointOfSale")).Click();
s.Driver.FindElement(By.Id("AppName")).SendKeys(Guid.NewGuid().ToString());
s.Driver.FindElement(By.Id("Create")).Click();
Assert.Contains("App successfully created", s.FindAlertMessage().Text);
s.Driver.FindElement(By.CssSelector("label[for='DefaultView_Cart']")).Click();
s.Driver.FindElement(By.Id("Currency")).SendKeys("EUR");
s.Driver.FindElement(By.Id("ShowCustomAmount")).Click();
s.Driver.FindElement(By.Id("SaveSettings")).Click();
Assert.Contains("App updated", s.FindAlertMessage().Text);
s.Driver.FindElement(By.Id("ViewApp")).Click();
var windows = s.Driver.WindowHandles;
Assert.Equal(2, windows.Count);
s.Driver.SwitchTo().Window(windows[1]);
s.Driver.WaitForElement(By.Id("js-cart-list"));
Assert.Empty(s.Driver.FindElements(By.CssSelector("#js-cart-list tbody tr")));
Assert.Equal("0,00 €", s.Driver.FindElement(By.Id("CartTotal")).Text);
Assert.False(s.Driver.FindElement(By.Id("CartClear")).Displayed);
// Select and clear
s.Driver.FindElement(By.CssSelector(".card.js-add-cart:nth-child(1)")).Click();
Assert.Single(s.Driver.FindElements(By.CssSelector("#js-cart-list tbody tr")));
s.Driver.FindElement(By.Id("CartClear")).Click();
Assert.Empty(s.Driver.FindElements(By.CssSelector("#js-cart-list tbody tr")));
Thread.Sleep(250);
// Select items
s.Driver.FindElement(By.CssSelector(".card.js-add-cart:nth-child(2)")).Click();
Thread.Sleep(250);
s.Driver.FindElement(By.CssSelector(".card.js-add-cart:nth-child(1)")).Click();
Thread.Sleep(250);
Assert.Equal(2, s.Driver.FindElements(By.CssSelector("#js-cart-list tbody tr")).Count);
Assert.Equal("2,00 €", s.Driver.FindElement(By.Id("CartTotal")).Text);
// Custom amount
s.Driver.FindElement(By.Id("CartCustomAmount")).SendKeys("1.5");
s.Driver.FindElement(By.Id("CartTotal")).Click();
Assert.Equal("3,50 €", s.Driver.FindElement(By.Id("CartTotal")).Text);
s.Driver.FindElement(By.Id("js-cart-confirm")).Click();
// Pay
Assert.Equal("3,50 €", s.Driver.FindElement(By.Id("CartSummaryTotal")).Text);
s.Driver.FindElement(By.Id("js-cart-pay")).Click();
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
s.Driver.FindElement(By.Id("DetailsToggle")).Click();
s.Driver.WaitForElement(By.Id("PaymentDetails-TotalFiat"));
Assert.Contains("3,50 €", s.Driver.FindElement(By.Id("PaymentDetails-TotalFiat")).Text);
}
[Fact]
[Trait("Selenium", "Selenium")]
[Trait("Lightning", "Lightning")]
@ -2185,7 +2371,7 @@ namespace BTCPayServer.Tests
// Check that pull payment has lightning option
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
Assert.Equal(new PaymentMethodId(cryptoCode, LightningPaymentType.Instance), PaymentMethodId.Parse(Assert.Single(s.Driver.FindElements(By.CssSelector("input[name='PaymentMethods']"))).GetAttribute("value")));
Assert.Equal(new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike), PaymentMethodId.Parse(Assert.Single(s.Driver.FindElements(By.CssSelector("input[name='PaymentMethods']"))).GetAttribute("value")));
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
s.Driver.FindElement(By.Id("Amount")).Clear();
s.Driver.FindElement(By.Id("Amount")).SendKeys("0.0000001");
@ -2269,7 +2455,7 @@ namespace BTCPayServer.Tests
var addresses = s.Driver.FindElements(By.ClassName("lightning-address-value"));
Assert.Equal(2, addresses.Count);
var callbacks = new List<Uri>();
foreach (IWebElement webElement in addresses)
{
var value = webElement.GetAttribute("value");
@ -2287,6 +2473,7 @@ namespace BTCPayServer.Tests
lnaddress2 = m["text/identifier"];
Assert.Equal(2, request.MinSendable.ToDecimal(LightMoneyUnit.Satoshi));
Assert.Equal(10, request.MaxSendable.ToDecimal(LightMoneyUnit.Satoshi));
callbacks.Add(request.Callback);
break;
case { } v when v.StartsWith(lnaddress1):
@ -2294,6 +2481,7 @@ namespace BTCPayServer.Tests
lnaddress1 = m["text/identifier"];
Assert.Equal(1, request.MinSendable.ToDecimal(LightMoneyUnit.Satoshi));
Assert.Equal(6.12m, request.MaxSendable.ToDecimal(LightMoneyUnit.BTC));
callbacks.Add(request.Callback);
break;
default:
Assert.False(true, "Should have matched");
@ -2301,12 +2489,24 @@ namespace BTCPayServer.Tests
}
}
var repo = s.Server.PayTester.GetService<InvoiceRepository>();
var invoices = await repo.GetInvoices(new InvoiceQuery() { StoreId = new[] { s.StoreId } });
// Resolving a ln address shouldn't create any btcpay invoice.
// This must be done because some NOST clients resolve ln addresses preemptively without user interaction
Assert.Empty(invoices);
// Calling the callbacks should create the invoices
foreach (var callback in callbacks)
{
using var r = await s.Server.PayTester.HttpClient.GetAsync(callback);
await r.Content.ReadAsStringAsync();
}
invoices = await repo.GetInvoices(new InvoiceQuery() { StoreId = new[] { s.StoreId } });
Assert.Equal(2, invoices.Length);
var emailSuffix = $"@{s.Server.PayTester.HostName}:{s.Server.PayTester.Port}";
foreach (var i in invoices)
{
var lightningPaymentMethod = i.GetPaymentMethod(new PaymentMethodId("BTC", LNURLPayPaymentType.Instance));
var lightningPaymentMethod = i.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.LNURLPay));
var paymentMethodDetails =
lightningPaymentMethod.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails;
Assert.Contains(

@ -290,9 +290,9 @@ retry:
}
[Fact]
public void CanGetRateCryptoCurrenciesByDefault()
public async Task CanGetRateCryptoCurrenciesByDefault()
{
string[] brokenShitcoins = { "BTX_USD", "CHC_USD" };
string[] brokenShitcoins = { };
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
var factory = FastTests.CreateBTCPayRateFactory();
var fetcher = new RateFetcher(factory);
@ -305,15 +305,37 @@ retry:
var result = fetcher.FetchRates(pairs, rules, default);
foreach ((CurrencyPair key, Task<RateResult> value) in result)
{
var rateResult = value.GetAwaiter().GetResult();
var rateResult = await value;
TestLogs.LogInformation($"Testing {key}");
if (brokenShitcoins.Contains(key.ToString()))
continue;
Assert.True(rateResult.BidAsk != null, $"Impossible to get the rate {rateResult.EvaluatedRule}");
}
var b = new StoreBlob();
foreach (var k in StoreBlob.RecommendedExchanges)
{
b.DefaultCurrency = k.Key;
rules = b.GetDefaultRateRules(provider);
pairs =
provider.GetAll()
.Select(c => new CurrencyPair(c.CryptoCode, k.Key))
.ToHashSet();
result = fetcher.FetchRates(pairs, rules, default);
foreach ((CurrencyPair key, Task<RateResult> value) in result)
{
var rateResult = await value;
TestLogs.LogInformation($"Testing {key} when default currency is {k.Key}");
if (brokenShitcoins.Contains(key.ToString()))
continue;
Assert.True(rateResult.BidAsk != null, $"Impossible to get the rate {rateResult.EvaluatedRule}");
}
}
}
[Fact]
[Trait("Fast", "Fast")]
public async Task CheckJsContent()
{
// This test verify that no malicious js is added in the minified files.
@ -322,47 +344,63 @@ retry:
var actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "bootstrap", "bootstrap.bundle.min.js").Trim();
var version = Regex.Match(actual, "Bootstrap v([0-9]+.[0-9]+.[0-9]+)").Groups[1].Value;
var expected = (await (await client.GetAsync($"https://cdn.jsdelivr.net/npm/bootstrap@{version}/dist/js/bootstrap.bundle.min.js")).Content.ReadAsStringAsync()).Trim();
Assert.Equal(expected, actual);
EqualJsContent(expected, actual);
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "clipboard.js", "clipboard.js");
expected = (await (await client.GetAsync("https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.8/clipboard.js")).Content.ReadAsStringAsync()).Trim();
Assert.Equal(expected, actual);
EqualJsContent(expected, actual);
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "vuejs", "vue.min.js").Trim();
version = Regex.Match(actual, "Vue\\.js v([0-9]+.[0-9]+.[0-9]+)").Groups[1].Value;
expected = (await (await client.GetAsync($"https://cdnjs.cloudflare.com/ajax/libs/vue/{version}/vue.min.js")).Content.ReadAsStringAsync()).Trim();
Assert.Equal(expected, actual);
EqualJsContent(expected, actual);
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "i18next", "i18next.min.js").Trim();
expected = (await (await client.GetAsync("https://cdnjs.cloudflare.com/ajax/libs/i18next/22.0.6/i18next.min.js")).Content.ReadAsStringAsync()).Trim();
Assert.Equal(expected, actual);
EqualJsContent(expected, actual);
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "i18next", "i18nextHttpBackend.min.js").Trim();
expected = (await (await client.GetAsync("https://cdnjs.cloudflare.com/ajax/libs/i18next-http-backend/2.0.1/i18nextHttpBackend.min.js")).Content.ReadAsStringAsync()).Trim();
Assert.Equal(expected, actual);
EqualJsContent(expected, actual);
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "i18next", "vue-i18next.js").Trim();
expected = (await (await client.GetAsync("https://unpkg.com/@panter/vue-i18next@0.15.2/dist/vue-i18next.js")).Content.ReadAsStringAsync()).Trim();
Assert.Equal(expected, actual);
EqualJsContent(expected, actual);
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "vue-qrcode", "vue-qrcode.min.js").Trim();
version = Regex.Match(actual, "vue-qrcode v([0-9]+.[0-9]+.[0-9]+)").Groups[1].Value;
expected = (await (await client.GetAsync($"https://unpkg.com/@chenfengyuan/vue-qrcode@{version}/dist/vue-qrcode.min.js")).Content.ReadAsStringAsync()).Trim();
Assert.Equal(expected, actual);
EqualJsContent(expected, actual);
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "tom-select", "tom-select.complete.min.js").Trim();
expected = (await (await client.GetAsync($"https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/js/tom-select.complete.min.js")).Content.ReadAsStringAsync()).Trim();
Assert.Equal(expected, actual);
EqualJsContent(expected, actual);
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "dom-confetti", "dom-confetti.min.js").Trim();
version = Regex.Match(actual, "Original file: /npm/dom-confetti@([0-9]+.[0-9]+.[0-9]+)/lib/main.js").Groups[1].Value;
expected = (await (await client.GetAsync($"https://cdn.jsdelivr.net/npm/dom-confetti@{version}/lib/main.min.js")).Content.ReadAsStringAsync()).Trim();
Assert.Equal(expected, actual);
EqualJsContent(expected, actual);
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "vue-sortable", "sortable.min.js").Trim();
version = Regex.Match(actual, "Sortable ([0-9]+.[0-9]+.[0-9]+) ").Groups[1].Value;
expected = (await (await client.GetAsync($"https://unpkg.com/sortablejs@{version}/Sortable.min.js")).Content.ReadAsStringAsync()).Trim();
Assert.Equal(expected, actual);
EqualJsContent(expected, actual);
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "bootstrap-vue", "bootstrap-vue.min.js").Trim();
version = Regex.Match(actual, "BootstrapVue ([0-9]+.[0-9]+.[0-9]+)").Groups[1].Value;
expected = (await (await client.GetAsync($"https://cdnjs.cloudflare.com/ajax/libs/bootstrap-vue/{version}/bootstrap-vue.min.js")).Content.ReadAsStringAsync()).Trim();
EqualJsContent(expected, actual);
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "vue-sanitize-directive", "vue-sanitize-directive.umd.min.js").Trim();
version = Regex.Match(actual, "Original file: /npm/vue-sanitize-directive@([0-9]+.[0-9]+.[0-9]+)").Groups[1].Value;
expected = (await (await client.GetAsync($"https://cdn.jsdelivr.net/npm/vue-sanitize-directive@{version}/dist/vue-sanitize-directive.umd.min.js")).Content.ReadAsStringAsync()).Trim();
EqualJsContent(expected, actual);
}
private void EqualJsContent(string expected, string actual)
{
if (expected != actual)
Assert.Equal(expected, actual.ReplaceLineEndings("\n"));
}
string GetFileContent(params string[] path)

@ -39,6 +39,7 @@ using BTCPayServer.Plugins.PayButton;
using BTCPayServer.Plugins.PointOfSale;
using BTCPayServer.Plugins.PointOfSale.Controllers;
using BTCPayServer.Security.Bitpay;
using BTCPayServer.Security.Greenfield;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
@ -718,7 +719,7 @@ namespace BTCPayServer.Tests
btcDerivationScheme.GetDerivation(new KeyPath("0/90")).ScriptPubKey, Money.Coins(1.0m));
tester.ExplorerNode.Generate(1);
var transactions = Assert.IsType<ListTransactionsViewModel>(Assert
.IsType<ViewResult>(walletController.WalletTransactions(walletId).Result).Model);
.IsType<ViewResult>(walletController.WalletTransactions(walletId, loadTransactions: true).Result).Model);
Assert.Empty(transactions.Transactions);
Assert.IsType<RedirectToActionResult>(walletController.WalletRescan(walletId, rescan).Result);
@ -747,7 +748,7 @@ namespace BTCPayServer.Tests
Assert.NotNull(rescan.TimeOfScan);
Assert.Equal(1, rescan.LastSuccess.Found);
transactions = Assert.IsType<ListTransactionsViewModel>(Assert
.IsType<ViewResult>(walletController.WalletTransactions(walletId).Result).Model);
.IsType<ViewResult>(walletController.WalletTransactions(walletId, loadTransactions: true).Result).Model);
var tx = Assert.Single(transactions.Transactions);
Assert.Equal(tx.Id, txId.ToString());
@ -762,7 +763,7 @@ namespace BTCPayServer.Tests
await walletController.ModifyTransaction(walletId, tx.Id, addcomment: "hello"));
transactions = Assert.IsType<ListTransactionsViewModel>(Assert
.IsType<ViewResult>(walletController.WalletTransactions(walletId).Result).Model);
.IsType<ViewResult>(walletController.WalletTransactions(walletId, loadTransactions: true).Result).Model);
tx = Assert.Single(transactions.Transactions);
Assert.Equal("hello", tx.Comment);
@ -774,7 +775,7 @@ namespace BTCPayServer.Tests
await walletController.ModifyTransaction(walletId, tx.Id, removelabel: "test2"));
transactions = Assert.IsType<ListTransactionsViewModel>(Assert
.IsType<ViewResult>(walletController.WalletTransactions(walletId).Result).Model);
.IsType<ViewResult>(walletController.WalletTransactions(walletId, loadTransactions: true).Result).Model);
tx = Assert.Single(transactions.Transactions);
Assert.Equal("hello", tx.Comment);
@ -1329,7 +1330,7 @@ namespace BTCPayServer.Tests
var invoiceAddress = BitcoinAddress.Create(btcmethod.Destination, cashCow.Network);
var btc = new PaymentMethodId("BTC", BitcoinPaymentType.Instance);
var btc = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
var networkFee = (await tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id))
.GetPaymentMethods()[btc]
.GetPaymentMethodDetails()
@ -1484,8 +1485,8 @@ namespace BTCPayServer.Tests
await user.RegisterLightningNodeAsync("BTC");
var lnMethod = new PaymentMethodId("BTC", LightningPaymentType.Instance).ToString();
var btcMethod = new PaymentMethodId("BTC", BitcoinPaymentType.Instance).ToString();
var lnMethod = new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToString();
var btcMethod = new PaymentMethodId("BTC", PaymentTypes.BTCLike).ToString();
// We allow BTC and LN, but not BTC under 5 USD, so only LN should be in the invoice
var vm = Assert.IsType<CheckoutAppearanceViewModel>(Assert
@ -1510,7 +1511,7 @@ namespace BTCPayServer.Tests
}, Facade.Merchant);
Assert.Single(invoice.CryptoInfo);
Assert.Equal(LightningPaymentType.Instance.ToString(), invoice.CryptoInfo[0].PaymentType);
Assert.Equal(PaymentTypes.LightningLike.ToString(), invoice.CryptoInfo[0].PaymentType);
// Let's replicate https://github.com/btcpayserver/btcpayserver/issues/2963
// We allow BTC for more than 5 USD, and LN for less than 150. The default is LN, so the default
@ -1643,7 +1644,7 @@ namespace BTCPayServer.Tests
Currency = "USD"
}, Facade.Merchant);
Assert.Single(invoice.CryptoInfo);
Assert.Equal(LightningPaymentType.Instance.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.
user.RegisterLightningNode(cryptoCode);
@ -1807,7 +1808,7 @@ namespace BTCPayServer.Tests
public async Task CanChangeNetworkFeeMode()
{
using var tester = CreateServerTester();
var btc = new PaymentMethodId("BTC", BitcoinPaymentType.Instance);
var btc = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
@ -1984,6 +1985,7 @@ namespace BTCPayServer.Tests
var user = tester.NewAccount();
user.GrantAccess(true);
user.RegisterDerivationScheme("BTC");
var btcpayClient = await user.CreateClient();
DateTimeOffset expiration = DateTimeOffset.UtcNow + TimeSpan.FromMinutes(21);
@ -2064,6 +2066,20 @@ namespace BTCPayServer.Tests
var zeroInvoicePM = await greenfield.GetInvoicePaymentMethods(user.StoreId, zeroInvoice.Id);
Assert.Empty(zeroInvoicePM);
var invoice6 = await btcpayClient.CreateInvoice(user.StoreId,
new CreateInvoiceRequest()
{
Amount = GreenfieldConstants.MaxAmount,
Currency = "USD"
});
var repo = tester.PayTester.GetService<InvoiceRepository>();
var entity = (await repo.GetInvoice(invoice6.Id));
Assert.Equal((decimal)ulong.MaxValue, entity.Price);
entity.GetPaymentMethods().First().Calculate();
// Shouldn't be possible as we clamp the value, but existing invoice may have that
entity.Price = decimal.MaxValue;
entity.GetPaymentMethods().First().Calculate();
}
[Fact(Timeout = LongRunningTestTimeout)]
@ -2242,7 +2258,7 @@ namespace BTCPayServer.Tests
c =>
{
Assert.False(c.AfterExpiration);
Assert.Equal(new PaymentMethodId("BTC", BitcoinPaymentType.Instance).ToStringNormalized(), c.PaymentMethod);
Assert.Equal(new PaymentMethodId("BTC", PaymentTypes.BTCLike).ToStringNormalized(), c.PaymentMethod);
Assert.NotNull(c.Payment);
Assert.Equal(invoice.BitcoinAddress, c.Payment.Destination);
Assert.StartsWith(txId.ToString(), c.Payment.Id);
@ -2252,7 +2268,7 @@ namespace BTCPayServer.Tests
c =>
{
Assert.False(c.AfterExpiration);
Assert.Equal(new PaymentMethodId("BTC", BitcoinPaymentType.Instance).ToStringNormalized(), c.PaymentMethod);
Assert.Equal(new PaymentMethodId("BTC", PaymentTypes.BTCLike).ToStringNormalized(), c.PaymentMethod);
Assert.NotNull(c.Payment);
Assert.Equal(invoice.BitcoinAddress, c.Payment.Destination);
Assert.StartsWith(txId.ToString(), c.Payment.Id);

@ -73,7 +73,7 @@ services:
- "sshd_datadir:/root/.ssh"
devlnd:
image: btcpayserver/bitcoin:24.1-1
image: btcpayserver/bitcoin:25.0
environment:
BITCOIN_NETWORK: regtest
BITCOIN_WALLETDIR: "/data/wallets"
@ -135,7 +135,7 @@ services:
bitcoind:
restart: unless-stopped
image: btcpayserver/bitcoin:24.1-1
image: btcpayserver/bitcoin:25.0
environment:
BITCOIN_NETWORK: regtest
BITCOIN_WALLETDIR: "/data/wallets"
@ -224,7 +224,7 @@ services:
- "5432"
merchant_lnd:
image: btcpayserver/lnd:v0.16.2-beta
image: btcpayserver/lnd:v0.16.4-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
@ -259,7 +259,7 @@ services:
- bitcoind
customer_lnd:
image: btcpayserver/lnd:v0.16.2-beta
image: btcpayserver/lnd:v0.16.4-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"

@ -70,7 +70,7 @@ services:
- "sshd_datadir:/root/.ssh"
devlnd:
image: btcpayserver/bitcoin:24.1-1
image: btcpayserver/bitcoin:25.0
environment:
BITCOIN_NETWORK: regtest
BITCOIN_WALLETDIR: "/data/wallets"
@ -121,7 +121,7 @@ services:
bitcoind:
restart: unless-stopped
image: btcpayserver/bitcoin:24.1-1
image: btcpayserver/bitcoin:25.0
environment:
BITCOIN_NETWORK: regtest
BITCOIN_WALLETDIR: "/data/wallets"
@ -211,7 +211,7 @@ services:
- "5432"
merchant_lnd:
image: btcpayserver/lnd:v0.16.2-beta
image: btcpayserver/lnd:v0.16.4-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
@ -248,7 +248,7 @@ services:
- bitcoind
customer_lnd:
image: btcpayserver/lnd:v0.16.2-beta
image: btcpayserver/lnd:v0.16.4-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"

@ -48,7 +48,7 @@
<PackageReference Include="YamlDotNet" Version="8.0.0" />
<PackageReference Include="BIP78.Sender" Version="0.2.2" />
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.2" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.4.26" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.4.28" />
<PackageReference Include="CsvHelper" Version="15.0.5" />
<PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="Fido2" Version="2.0.2" />

@ -40,7 +40,7 @@ public class AppTopItems : ViewComponent
var app = HttpContext.GetAppData();
var entries = await _appService.GetItemStats(app);
vm.SalesCount = entries.Select(e => e.SalesCount).ToList();
vm.Entries = entries.ToList();
vm.Entries = entries.Take(5).ToList();
vm.AppType = app.AppType;
vm.AppUrl = await appBaseType.ConfigureLink(app);
vm.Name = app.Name;

@ -88,7 +88,7 @@ public class StoreLightningBalance : ViewComponent
private ILightningClient GetLightningClient(StoreData store, string cryptoCode)
{
var network = _networkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
var id = new PaymentMethodId(cryptoCode, LightningPaymentType.Instance);
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
var existing = store.GetSupportedPaymentMethods(_networkProvider)
.OfType<LightningSupportedPaymentMethod>()
.FirstOrDefault(d => d.PaymentId == id);

@ -3,6 +3,7 @@
@using BTCPayServer.Services
@using BTCPayServer.Services.Invoices
@inject DisplayFormatter DisplayFormatter
@inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
@model BTCPayServer.Components.StoreRecentInvoices.StoreRecentInvoicesViewModel
<div class="widget store-recent-invoices" id="StoreRecentInvoices-@Model.Store.Id">
@ -51,19 +52,41 @@
<a asp-controller="UIInvoice" asp-action="Invoice" asp-route-invoiceId="@invoice.InvoiceId" class="text-break">@invoice.InvoiceId</a>
</td>
<td>
<span class="badge badge-@invoice.Status.Status.ToModernStatus().ToString().ToLower()">
@invoice.Status.Status.ToModernStatus().ToString()
@if (invoice.Status.ExceptionStatus != InvoiceExceptionStatus.None)
<div class="d-flex align-items-center gap-2">
@if (invoice.Details.Archived)
{
@($"({invoice.Status.ExceptionStatus.ToString()})")
<span class="badge bg-warning">archived</span>
}
</span>
@if (invoice.HasRefund)
{
<span class="badge bg-warning">
Refund
<span class="badge badge-@invoice.Status.Status.ToModernStatus().ToString().ToLower()">
@invoice.Status.Status.ToModernStatus().ToString()
@if (invoice.Status.ExceptionStatus != InvoiceExceptionStatus.None)
{
@($"({invoice.Status.ExceptionStatus.ToString()})")
}
</span>
}
@foreach (var paymentMethodId in invoice.Details.Payments.Select(payment => payment.GetPaymentMethodId()).Distinct())
{
var image = PaymentMethodHandlerDictionary[paymentMethodId]?.GetCryptoImage(paymentMethodId);
var badge = paymentMethodId.PaymentType.GetBadge();
if (!string.IsNullOrEmpty(image) || !string.IsNullOrEmpty(badge))
{
<span class="d-inline-flex align-items-center gap-1">
@if (!string.IsNullOrEmpty(image))
{
<img src="@Context.Request.GetRelativePathOrAbsolute(image)" alt="@paymentMethodId.PaymentType.ToString()" style="height:1.5em" />
}
@if (!string.IsNullOrEmpty(badge))
{
@badge
}
</span>
}
}
@if (invoice.HasRefund)
{
<span class="badge bg-warning">Refund</span>
}
</div>
</td>
<td class="text-end">
<span data-sensitive>@DisplayFormatter.Currency(invoice.Amount, invoice.Currency)</span>

@ -1,4 +1,5 @@
using System;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Services.Invoices;
namespace BTCPayServer.Components.StoreRecentInvoices;
@ -11,5 +12,7 @@ public class StoreRecentInvoiceViewModel
public string Currency { get; set; }
public InvoiceState Status { get; set; }
public DateTimeOffset Date { get; set; }
public InvoiceDetailsModel Details { get; set; }
public bool HasRefund { get; set; }
}

@ -3,11 +3,13 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Data;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
namespace BTCPayServer.Components.StoreRecentInvoices;
@ -53,17 +55,22 @@ public class StoreRecentInvoices : ViewComponent
});
vm.Invoices = (from invoice in invoiceEntities
let state = invoice.GetInvoiceState()
select new StoreRecentInvoiceViewModel
{
Date = invoice.InvoiceTime,
Status = state,
HasRefund = invoice.Refunds.Any(),
InvoiceId = invoice.Id,
OrderId = invoice.Metadata.OrderId ?? string.Empty,
Amount = invoice.Price,
Currency = invoice.Currency
}).ToList();
let state = invoice.GetInvoiceState()
select new StoreRecentInvoiceViewModel
{
Date = invoice.InvoiceTime,
Status = state,
HasRefund = invoice.Refunds.Any(),
InvoiceId = invoice.Id,
OrderId = invoice.Metadata.OrderId ?? string.Empty,
Amount = invoice.Price,
Currency = invoice.Currency,
Details = new InvoiceDetailsModel
{
Archived = invoice.Archived,
Payments = invoice.GetPayments(false)
}
}).ToList();
return View(vm);
}

@ -58,7 +58,7 @@ public class StoreRecentTransactions : ViewComponent
{
var network = derivationSettings.Network;
var wallet = _walletProvider.GetWallet(network);
var allTransactions = await wallet.FetchTransactionHistory(derivationSettings.AccountDerivation, 0, 5, TimeSpan.FromDays(31.0));
var allTransactions = await wallet.FetchTransactionHistory(derivationSettings.AccountDerivation, 0, 5, TimeSpan.FromDays(31.0), cancellationToken: this.HttpContext.RequestAborted);
var walletTransactionsInfo = await _walletRepository.GetWalletTransactionsInfo(vm.WalletId, allTransactions.Select(t => t.TransactionId.ToString()).ToArray());
transactions = allTransactions

@ -9,7 +9,6 @@ using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Data;
using BTCPayServer.Filters;
using BTCPayServer.Models;
using BTCPayServer.Payments;
using BTCPayServer.Rating;
using BTCPayServer.Security;
using BTCPayServer.Security.Bitpay;
@ -31,7 +30,6 @@ namespace BTCPayServer.Controllers
readonly BTCPayNetworkProvider _networkProvider;
readonly CurrencyNameTable _currencyNameTable;
readonly StoreRepository _storeRepo;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
private StoreData CurrentStore => HttpContext.GetStoreData();
@ -39,13 +37,12 @@ namespace BTCPayServer.Controllers
RateFetcher rateProviderFactory,
BTCPayNetworkProvider networkProvider,
StoreRepository storeRepo,
CurrencyNameTable currencyNameTable, PaymentTypeRegistry paymentTypeRegistry)
CurrencyNameTable currencyNameTable)
{
_rateProviderFactory = rateProviderFactory ?? throw new ArgumentNullException(nameof(rateProviderFactory));
_networkProvider = networkProvider;
_storeRepo = storeRepo;
_currencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
_paymentTypeRegistry = paymentTypeRegistry;
}
[Route("rates/{baseCurrency}")]
@ -53,7 +50,7 @@ namespace BTCPayServer.Controllers
[BitpayAPIConstraint]
public async Task<IActionResult> GetBaseCurrencyRates(string baseCurrency, CancellationToken cancellationToken)
{
var supportedMethods = CurrentStore.GetSupportedPaymentMethods(_networkProvider, _paymentTypeRegistry);
var supportedMethods = CurrentStore.GetSupportedPaymentMethods(_networkProvider);
var currencyCodes = supportedMethods.Where(method => !string.IsNullOrEmpty(method.PaymentId.CryptoCode))
.Select(method => method.PaymentId.CryptoCode).Distinct();

@ -50,17 +50,14 @@ namespace BTCPayServer.Controllers.Greenfield
private readonly CustodianAccountRepository _custodianAccountRepository;
private readonly IEnumerable<ICustodian> _custodianRegistry;
private readonly IAuthorizationService _authorizationService;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
public GreenfieldCustodianAccountController(CustodianAccountRepository custodianAccountRepository,
IEnumerable<ICustodian> custodianRegistry,
IAuthorizationService authorizationService,
PaymentTypeRegistry paymentTypeRegistry)
IAuthorizationService authorizationService)
{
_custodianAccountRepository = custodianAccountRepository;
_custodianRegistry = custodianRegistry;
_authorizationService = authorizationService;
_paymentTypeRegistry = paymentTypeRegistry;
}
[HttpGet("~/api/v1/stores/{storeId}/custodian-accounts")]
@ -226,8 +223,8 @@ namespace BTCPayServer.Controllers.Greenfield
if (custodian is ICanDeposit depositableCustodian)
{
var pm = _paymentTypeRegistry.TryParsePaymentMethod(paymentMethod, out _);
if (!pm)
var pm = PaymentMethodId.TryParse(paymentMethod);
if (pm == null)
{
return this.CreateAPIError(400, "unsupported-payment-method",
$"Unsupported payment method.");
@ -349,7 +346,7 @@ namespace BTCPayServer.Controllers.Greenfield
if (custodian is ICanWithdraw withdrawableCustodian)
{
var pm = _paymentTypeRegistry.TryParsePaymentMethod(request.PaymentMethod);
var pm = PaymentMethodId.TryParse(request.PaymentMethod);
if (pm == null)
{
return this.CreateAPIError(400, "unsupported-payment-method",
@ -388,7 +385,7 @@ namespace BTCPayServer.Controllers.Greenfield
if (custodian is ICanWithdraw withdrawableCustodian)
{
var pm = _paymentTypeRegistry.TryParsePaymentMethod(request.PaymentMethod);
var pm = PaymentMethodId.TryParse(request.PaymentMethod);
if (pm == null)
{
return this.CreateAPIError(400, "unsupported-payment-method",

@ -12,6 +12,7 @@ using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.Payments;
using BTCPayServer.Rating;
using BTCPayServer.Security.Greenfield;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
@ -40,7 +41,6 @@ namespace BTCPayServer.Controllers.Greenfield
private readonly RateFetcher _rateProvider;
private readonly InvoiceActivator _invoiceActivator;
private readonly ApplicationDbContextFactory _dbContextFactory;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
public LanguageService LanguageService { get; }
@ -48,8 +48,7 @@ namespace BTCPayServer.Controllers.Greenfield
LinkGenerator linkGenerator, LanguageService languageService, BTCPayNetworkProvider btcPayNetworkProvider,
CurrencyNameTable currencyNameTable, RateFetcher rateProvider,
InvoiceActivator invoiceActivator,
PullPaymentHostedService pullPaymentService, ApplicationDbContextFactory dbContextFactory,
PaymentTypeRegistry paymentTypeRegistry)
PullPaymentHostedService pullPaymentService, ApplicationDbContextFactory dbContextFactory)
{
_invoiceController = invoiceController;
_invoiceRepository = invoiceRepository;
@ -60,7 +59,6 @@ namespace BTCPayServer.Controllers.Greenfield
_invoiceActivator = invoiceActivator;
_pullPaymentService = pullPaymentService;
_dbContextFactory = dbContextFactory;
_paymentTypeRegistry = paymentTypeRegistry;
LanguageService = languageService;
}
@ -186,12 +184,16 @@ namespace BTCPayServer.Controllers.Greenfield
{
ModelState.AddModelError(nameof(request.Amount), "The amount should be 0 or more.");
}
if (request.Amount > GreenfieldConstants.MaxAmount)
{
ModelState.AddModelError(nameof(request.Amount), $"The amount should less than {GreenfieldConstants.MaxAmount}.");
}
request.Checkout ??= new CreateInvoiceRequest.CheckoutOptions();
if (request.Checkout.PaymentMethods?.Any() is true)
{
for (int i = 0; i < request.Checkout.PaymentMethods.Length; i++)
{
if (!_paymentTypeRegistry.TryParsePaymentMethod(request.Checkout.PaymentMethods[i], out _))
if (!PaymentMethodId.TryParse(request.Checkout.PaymentMethods[i], out _))
{
request.AddModelError(invoiceRequest => invoiceRequest.Checkout.PaymentMethods[i],
"Invalid payment method", this);
@ -338,7 +340,8 @@ namespace BTCPayServer.Controllers.Greenfield
{
return InvoiceNotFound();
}
if (_paymentTypeRegistry.TryParsePaymentMethod(paymentMethod, out var paymentMethodId))
if (PaymentMethodId.TryParse(paymentMethod, out var paymentMethodId))
{
await _invoiceActivator.ActivateInvoicePaymentMethod(paymentMethodId, invoice, store);
return Ok();
@ -379,7 +382,7 @@ namespace BTCPayServer.Controllers.Greenfield
}
PaymentMethod? invoicePaymentMethod = null;
PaymentMethodId? paymentMethodId = null;
if (request.PaymentMethod is not null && _paymentTypeRegistry.TryParsePaymentMethod(request.PaymentMethod, out paymentMethodId))
if (request.PaymentMethod is not null && PaymentMethodId.TryParse(request.PaymentMethod, out paymentMethodId))
{
invoicePaymentMethod = invoice.GetPaymentMethods().SingleOrDefault(method => method.GetId() == paymentMethodId);
}

@ -28,19 +28,17 @@ namespace BTCPayServer.Controllers.Greenfield
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
private readonly LightningClientFactoryService _lightningClientFactory;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
public GreenfieldStoreLightningNodeApiController(
IOptions<LightningNetworkOptions> lightningNetworkOptions,
LightningClientFactoryService lightningClientFactory, BTCPayNetworkProvider btcPayNetworkProvider,
PoliciesSettings policiesSettings,
IAuthorizationService authorizationService, PaymentTypeRegistry paymentTypeRegistry) : base(
IAuthorizationService authorizationService) : base(
btcPayNetworkProvider, policiesSettings, authorizationService)
{
_lightningNetworkOptions = lightningNetworkOptions;
_lightningClientFactory = lightningClientFactory;
_btcPayNetworkProvider = btcPayNetworkProvider;
_paymentTypeRegistry = paymentTypeRegistry;
}
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
@ -152,8 +150,8 @@ namespace BTCPayServer.Controllers.Greenfield
throw new JsonHttpException(StoreNotFound());
}
var id = new PaymentMethodId(cryptoCode, LightningPaymentType.Instance);
var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider, _paymentTypeRegistry)
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
.OfType<LightningSupportedPaymentMethod>()
.FirstOrDefault(d => d.PaymentId == id);
if (existing == null)

@ -255,7 +255,9 @@ namespace BTCPayServer.Controllers.Greenfield
Email = blob.Email,
AllowCustomPaymentAmounts = blob.AllowCustomPaymentAmounts,
EmbeddedCSS = blob.EmbeddedCSS,
CustomCSSLink = blob.CustomCSSLink
CustomCSSLink = blob.CustomCSSLink,
FormResponse = blob.FormResponse,
FormId = blob.FormId
};
}

@ -36,7 +36,6 @@ namespace BTCPayServer.Controllers.Greenfield
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
private readonly BTCPayNetworkProvider _networkProvider;
private readonly IAuthorizationService _authorizationService;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
public GreenfieldPullPaymentController(PullPaymentHostedService pullPaymentService,
LinkGenerator linkGenerator,
@ -45,8 +44,7 @@ namespace BTCPayServer.Controllers.Greenfield
Services.BTCPayNetworkJsonSerializerSettings serializerSettings,
IEnumerable<IPayoutHandler> payoutHandlers,
BTCPayNetworkProvider btcPayNetworkProvider,
IAuthorizationService authorizationService,
PaymentTypeRegistry paymentTypeRegistry)
IAuthorizationService authorizationService)
{
_pullPaymentService = pullPaymentService;
_linkGenerator = linkGenerator;
@ -56,7 +54,6 @@ namespace BTCPayServer.Controllers.Greenfield
_payoutHandlers = payoutHandlers;
_networkProvider = btcPayNetworkProvider;
_authorizationService = authorizationService;
_paymentTypeRegistry = paymentTypeRegistry;
}
[HttpGet("~/api/v1/stores/{storeId}/pull-payments")]
@ -127,7 +124,7 @@ namespace BTCPayServer.Controllers.Greenfield
{
paymentMethods = paymentMethodsStr.Select(s =>
{
_paymentTypeRegistry.TryParsePaymentMethod(s, out var pmi);
PaymentMethodId.TryParse(s, out var pmi);
return pmi;
}).ToArray();
var supported = (await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData())).ToArray();
@ -258,16 +255,15 @@ namespace BTCPayServer.Controllers.Greenfield
return PullPaymentNotFound();
var blob = pp.GetBlob();
var pms = blob.SupportedPaymentMethods.FirstOrDefault(id => id.PaymentType == LightningPaymentType.Instance && _networkProvider.DefaultNetwork.CryptoCode == id.CryptoCode);
if (pms is not null && blob.Currency.Equals(pms.CryptoCode, StringComparison.InvariantCultureIgnoreCase))
if (_pullPaymentService.SupportsLNURL(blob))
{
var lnurlEndpoint = new Uri(Url.Action("GetLNURLForPullPayment", "UILNURL", new
{
cryptoCode = _networkProvider.DefaultNetwork.CryptoCode,
pullPaymentId = pullPaymentId
pullPaymentId
}, Request.Scheme, Request.Host.ToString())!);
return base.Ok(new PullPaymentLNURL()
return base.Ok(new PullPaymentLNURL
{
LNURLBech32 = LNURL.LNURL.EncodeUri(lnurlEndpoint, "withdrawRequest", true).ToString(),
LNURLUri = LNURL.LNURL.EncodeUri(lnurlEndpoint, "withdrawRequest", false).ToString()
@ -301,7 +297,7 @@ namespace BTCPayServer.Controllers.Greenfield
[AllowAnonymous]
public async Task<IActionResult> CreatePayout(string pullPaymentId, CreatePayoutRequest request, CancellationToken cancellationToken)
{
if (!_paymentTypeRegistry.TryParsePaymentMethod(request?.PaymentMethod, out var paymentMethodId))
if (!PaymentMethodId.TryParse(request?.PaymentMethod, out var paymentMethodId))
{
ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method");
return this.CreateValidationError(ModelState);
@ -364,7 +360,7 @@ namespace BTCPayServer.Controllers.Greenfield
}
}
if (request is null || !_paymentTypeRegistry.TryParsePaymentMethod(request?.PaymentMethod, out var paymentMethodId))
if (request is null || !PaymentMethodId.TryParse(request?.PaymentMethod, out var paymentMethodId))
{
ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method");
return this.CreateValidationError(ModelState);

@ -24,14 +24,12 @@ namespace BTCPayServer.Controllers.Greenfield
{
private readonly PayoutProcessorService _payoutProcessorService;
private readonly EventAggregator _eventAggregator;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
public GreenfieldStoreAutomatedLightningPayoutProcessorsController(PayoutProcessorService payoutProcessorService,
EventAggregator eventAggregator, PaymentTypeRegistry paymentTypeRegistry)
EventAggregator eventAggregator)
{
_payoutProcessorService = payoutProcessorService;
_eventAggregator = eventAggregator;
_paymentTypeRegistry = paymentTypeRegistry;
}
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
@ -40,7 +38,7 @@ namespace BTCPayServer.Controllers.Greenfield
public async Task<IActionResult> GetStoreLightningAutomatedPayoutProcessors(
string storeId, string? paymentMethod)
{
paymentMethod = !string.IsNullOrEmpty(paymentMethod) ? _paymentTypeRegistry.ParsePaymentMethod(paymentMethod).ToString() : null;
paymentMethod = !string.IsNullOrEmpty(paymentMethod) ? PaymentMethodId.Parse(paymentMethod).ToString() : null;
var configured =
await _payoutProcessorService.GetProcessors(
new PayoutProcessorService.PayoutProcessorQuery()
@ -75,7 +73,7 @@ namespace BTCPayServer.Controllers.Greenfield
AutomatedPayoutConstants.ValidateInterval(ModelState, request.IntervalSeconds, nameof(request.IntervalSeconds));
if (!ModelState.IsValid)
return this.CreateValidationError(ModelState);
paymentMethod = _paymentTypeRegistry.ParsePaymentMethod(paymentMethod).ToString();
paymentMethod = PaymentMethodId.Parse(paymentMethod).ToString();
var activeProcessor =
(await _payoutProcessorService.GetProcessors(
new PayoutProcessorService.PayoutProcessorQuery()

@ -25,14 +25,12 @@ namespace BTCPayServer.Controllers.Greenfield
{
private readonly PayoutProcessorService _payoutProcessorService;
private readonly EventAggregator _eventAggregator;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
public GreenfieldStoreAutomatedOnChainPayoutProcessorsController(PayoutProcessorService payoutProcessorService,
EventAggregator eventAggregator, PaymentTypeRegistry paymentTypeRegistry)
EventAggregator eventAggregator)
{
_payoutProcessorService = payoutProcessorService;
_eventAggregator = eventAggregator;
_paymentTypeRegistry = paymentTypeRegistry;
}
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
@ -41,7 +39,7 @@ namespace BTCPayServer.Controllers.Greenfield
public async Task<IActionResult> GetStoreOnChainAutomatedPayoutProcessors(
string storeId, string? paymentMethod)
{
paymentMethod = !string.IsNullOrEmpty(paymentMethod) ? _paymentTypeRegistry.ParsePaymentMethod(paymentMethod).ToString() : null;
paymentMethod = !string.IsNullOrEmpty(paymentMethod) ? PaymentMethodId.Parse(paymentMethod).ToString() : null;
var configured =
await _payoutProcessorService.GetProcessors(
new PayoutProcessorService.PayoutProcessorQuery()
@ -84,7 +82,7 @@ namespace BTCPayServer.Controllers.Greenfield
ModelState.AddModelError(nameof(request.FeeBlockTarget), "The feeBlockTarget should be between 1 and 1000");
if (!ModelState.IsValid)
return this.CreateValidationError(ModelState);
paymentMethod = _paymentTypeRegistry.ParsePaymentMethod(paymentMethod).ToString();
paymentMethod = PaymentMethodId.Parse(paymentMethod).ToString();
var activeProcessor =
(await _payoutProcessorService.GetProcessors(
new PayoutProcessorService.PayoutProcessorQuery()

@ -32,26 +32,23 @@ namespace BTCPayServer.Controllers.Greenfield
private StoreData Store => HttpContext.GetStoreData();
private readonly StoreRepository _storeRepository;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
public GreenfieldStoreLNURLPayPaymentMethodsController(
StoreRepository storeRepository,
BTCPayNetworkProvider btcPayNetworkProvider,
PaymentTypeRegistry paymentTypeRegistry)
BTCPayNetworkProvider btcPayNetworkProvider)
{
_storeRepository = storeRepository;
_btcPayNetworkProvider = btcPayNetworkProvider;
_paymentTypeRegistry = paymentTypeRegistry;
}
public static IEnumerable<LNURLPayPaymentMethodData> GetLNURLPayPaymentMethods(StoreData store,
BTCPayNetworkProvider networkProvider, bool? enabled, PaymentTypeRegistry paymentTypeRegistry)
BTCPayNetworkProvider networkProvider, bool? enabled)
{
var blob = store.GetStoreBlob();
var excludedPaymentMethods = blob.GetExcludedPaymentMethods();
return store.GetSupportedPaymentMethods(networkProvider, paymentTypeRegistry)
.Where((method) => method.PaymentId.PaymentType == LNURLPayPaymentType.Instance)
return store.GetSupportedPaymentMethods(networkProvider)
.Where((method) => method.PaymentId.PaymentType == PaymentTypes.LNURLPay)
.OfType<LNURLPaySupportedPaymentMethod>()
.Select(paymentMethod =>
new LNURLPayPaymentMethodData(
@ -70,7 +67,7 @@ namespace BTCPayServer.Controllers.Greenfield
string storeId,
[FromQuery] bool? enabled)
{
return Ok(GetLNURLPayPaymentMethods(Store, _btcPayNetworkProvider, enabled, _paymentTypeRegistry));
return Ok(GetLNURLPayPaymentMethods(Store, _btcPayNetworkProvider, enabled));
}
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
@ -96,7 +93,7 @@ namespace BTCPayServer.Controllers.Greenfield
AssertCryptoCodeWallet(cryptoCode, out _);
var id = new PaymentMethodId(cryptoCode, LNURLPayPaymentType.Instance);
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
var store = Store;
store.SetSupportedPaymentMethod(id, null);
await _storeRepository.UpdateStore(store);
@ -108,12 +105,12 @@ namespace BTCPayServer.Controllers.Greenfield
public async Task<IActionResult> UpdateLNURLPayPaymentMethod(string storeId, string cryptoCode,
[FromBody] LNURLPayPaymentMethodData paymentMethodData)
{
var paymentMethodId = new PaymentMethodId(cryptoCode, LNURLPayPaymentType.Instance);
var paymentMethodId = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
AssertCryptoCodeWallet(cryptoCode, out _);
var lnMethod = GreenfieldStoreLightningNetworkPaymentMethodsController.GetExistingLightningLikePaymentMethod(_btcPayNetworkProvider,
cryptoCode, Store, _paymentTypeRegistry);
cryptoCode, Store);
if ((lnMethod is null || lnMethod.Enabled is false) && paymentMethodData.Enabled)
{
@ -144,9 +141,9 @@ namespace BTCPayServer.Controllers.Greenfield
{
store ??= Store;
var storeBlob = store.GetStoreBlob();
var id = new PaymentMethodId(cryptoCode, LNURLPayPaymentType.Instance);
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
var paymentMethod = store
.GetSupportedPaymentMethods(_btcPayNetworkProvider, _paymentTypeRegistry)
.GetSupportedPaymentMethods(_btcPayNetworkProvider)
.OfType<LNURLPaySupportedPaymentMethod>()
.FirstOrDefault(method => method.PaymentId == id);

@ -37,31 +37,28 @@ namespace BTCPayServer.Controllers.Greenfield
private readonly StoreRepository _storeRepository;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly IAuthorizationService _authorizationService;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
public GreenfieldStoreLightningNetworkPaymentMethodsController(
StoreRepository storeRepository,
BTCPayNetworkProvider btcPayNetworkProvider,
IAuthorizationService authorizationService,
ISettingsRepository settingsRepository,
PoliciesSettings policiesSettings,
PaymentTypeRegistry paymentTypeRegistry)
PoliciesSettings policiesSettings)
{
_storeRepository = storeRepository;
_btcPayNetworkProvider = btcPayNetworkProvider;
_authorizationService = authorizationService;
_paymentTypeRegistry = paymentTypeRegistry;
PoliciesSettings = policiesSettings;
}
public static IEnumerable<LightningNetworkPaymentMethodData> GetLightningPaymentMethods(StoreData store,
BTCPayNetworkProvider networkProvider, bool? enabled, PaymentTypeRegistry paymentTypeRegistry)
BTCPayNetworkProvider networkProvider, bool? enabled)
{
var blob = store.GetStoreBlob();
var excludedPaymentMethods = blob.GetExcludedPaymentMethods();
return store.GetSupportedPaymentMethods(networkProvider, paymentTypeRegistry )
.Where((method) => method.PaymentId.PaymentType == LightningPaymentType.Instance)
return store.GetSupportedPaymentMethods(networkProvider)
.Where((method) => method.PaymentId.PaymentType == PaymentTypes.LightningLike)
.OfType<LightningSupportedPaymentMethod>()
.Select(paymentMethod =>
new LightningNetworkPaymentMethodData(
@ -82,7 +79,7 @@ namespace BTCPayServer.Controllers.Greenfield
string storeId,
[FromQuery] bool? enabled)
{
return Ok(GetLightningPaymentMethods(Store, _btcPayNetworkProvider, enabled, _paymentTypeRegistry));
return Ok(GetLightningPaymentMethods(Store, _btcPayNetworkProvider, enabled));
}
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
@ -91,7 +88,7 @@ namespace BTCPayServer.Controllers.Greenfield
{
AssertSupportLightning(cryptoCode);
var method = GetExistingLightningLikePaymentMethod(_btcPayNetworkProvider, cryptoCode, Store, _paymentTypeRegistry);
var method = GetExistingLightningLikePaymentMethod(_btcPayNetworkProvider, cryptoCode, Store);
if (method is null)
{
throw ErrorPaymentMethodNotConfigured();
@ -113,7 +110,7 @@ namespace BTCPayServer.Controllers.Greenfield
{
AssertSupportLightning(cryptoCode);
var id = new PaymentMethodId(cryptoCode, LightningPaymentType.Instance);
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
var store = Store;
store.SetSupportedPaymentMethod(id, null);
await _storeRepository.UpdateStore(store);
@ -125,7 +122,7 @@ namespace BTCPayServer.Controllers.Greenfield
public async Task<IActionResult> UpdateLightningNetworkPaymentMethod(string storeId, string cryptoCode,
[FromBody] UpdateLightningNetworkPaymentMethodRequest request)
{
var paymentMethodId = new PaymentMethodId(cryptoCode, LightningPaymentType.Instance);
var paymentMethodId = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
AssertSupportLightning(cryptoCode);
if (string.IsNullOrEmpty(request.ConnectionString))
@ -140,7 +137,7 @@ namespace BTCPayServer.Controllers.Greenfield
LightningSupportedPaymentMethod? paymentMethod = null;
var store = Store;
var storeBlob = store.GetStoreBlob();
var existing = GetExistingLightningLikePaymentMethod(_btcPayNetworkProvider, cryptoCode, store, _paymentTypeRegistry);
var existing = GetExistingLightningLikePaymentMethod(_btcPayNetworkProvider, cryptoCode, store);
if (existing == null || existing.ConnectionString != request.ConnectionString)
{
if (request.ConnectionString == LightningSupportedPaymentMethod.InternalNode)
@ -190,17 +187,17 @@ namespace BTCPayServer.Controllers.Greenfield
storeBlob.SetExcluded(paymentMethodId, !request.Enabled);
store.SetStoreBlob(storeBlob);
await _storeRepository.UpdateStore(store);
return Ok(GetExistingLightningLikePaymentMethod(_btcPayNetworkProvider, cryptoCode, store, _paymentTypeRegistry));
return Ok(GetExistingLightningLikePaymentMethod(_btcPayNetworkProvider, cryptoCode, store));
}
public static LightningNetworkPaymentMethodData? GetExistingLightningLikePaymentMethod(BTCPayNetworkProvider btcPayNetworkProvider, string cryptoCode,
StoreData store, PaymentTypeRegistry paymentTypeRegistry)
StoreData store)
{
var storeBlob = store.GetStoreBlob();
var id = new PaymentMethodId(cryptoCode, LightningPaymentType.Instance);
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
var paymentMethod = store
.GetSupportedPaymentMethods(btcPayNetworkProvider, paymentTypeRegistry)
.GetSupportedPaymentMethods(btcPayNetworkProvider)
.OfType<LightningSupportedPaymentMethod>()
.FirstOrDefault(method => method.PaymentId == id);

@ -86,7 +86,7 @@ namespace BTCPayServer.Controllers.Greenfield
var store = Store;
var storeBlob = store.GetStoreBlob();
store.SetSupportedPaymentMethod(new PaymentMethodId(cryptoCode, BitcoinPaymentType.Instance),
store.SetSupportedPaymentMethod(new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike),
derivationSchemeSettings);
store.SetStoreBlob(storeBlob);
await _storeRepository.UpdateStore(store);

@ -38,7 +38,6 @@ namespace BTCPayServer.Controllers.Greenfield
private readonly IAuthorizationService _authorizationService;
private readonly ExplorerClientProvider _explorerClientProvider;
private readonly EventAggregator _eventAggregator;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
public GreenfieldStoreOnChainPaymentMethodsController(
StoreRepository storeRepository,
@ -47,8 +46,7 @@ namespace BTCPayServer.Controllers.Greenfield
IAuthorizationService authorizationService,
ExplorerClientProvider explorerClientProvider,
PoliciesSettings policiesSettings,
EventAggregator eventAggregator,
PaymentTypeRegistry paymentTypeRegistry)
EventAggregator eventAggregator)
{
_storeRepository = storeRepository;
_btcPayNetworkProvider = btcPayNetworkProvider;
@ -56,18 +54,17 @@ namespace BTCPayServer.Controllers.Greenfield
_authorizationService = authorizationService;
_explorerClientProvider = explorerClientProvider;
_eventAggregator = eventAggregator;
_paymentTypeRegistry = paymentTypeRegistry;
PoliciesSettings = policiesSettings;
}
public static IEnumerable<OnChainPaymentMethodData> GetOnChainPaymentMethods(StoreData store,
BTCPayNetworkProvider networkProvider, bool? enabled, PaymentTypeRegistry paymentTypeRegistry)
BTCPayNetworkProvider networkProvider, bool? enabled)
{
var blob = store.GetStoreBlob();
var excludedPaymentMethods = blob.GetExcludedPaymentMethods();
return store.GetSupportedPaymentMethods(networkProvider, paymentTypeRegistry)
.Where((method) => method.PaymentId.PaymentType == BitcoinPaymentType.Instance)
return store.GetSupportedPaymentMethods(networkProvider)
.Where((method) => method.PaymentId.PaymentType == PaymentTypes.BTCLike)
.OfType<DerivationSchemeSettings>()
.Select(strategy =>
new OnChainPaymentMethodData(strategy.PaymentId.CryptoCode,
@ -84,7 +81,7 @@ namespace BTCPayServer.Controllers.Greenfield
string storeId,
[FromQuery] bool? enabled)
{
return Ok(GetOnChainPaymentMethods(Store, _btcPayNetworkProvider, enabled, _paymentTypeRegistry));
return Ok(GetOnChainPaymentMethods(Store, _btcPayNetworkProvider, enabled));
}
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
@ -213,7 +210,7 @@ namespace BTCPayServer.Controllers.Greenfield
{
AssertCryptoCodeWallet(cryptoCode, out _, out _);
var id = new PaymentMethodId(cryptoCode, BitcoinPaymentType.Instance);
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
var store = Store;
store.SetSupportedPaymentMethod(id, null);
await _storeRepository.UpdateStore(store);
@ -231,7 +228,7 @@ namespace BTCPayServer.Controllers.Greenfield
string cryptoCode,
[FromBody] UpdateOnChainPaymentMethodRequest request)
{
var id = new PaymentMethodId(cryptoCode, BitcoinPaymentType.Instance);
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
AssertCryptoCodeWallet(cryptoCode, out var network, out var wallet);
if (string.IsNullOrEmpty(request?.DerivationScheme))
@ -296,9 +293,9 @@ namespace BTCPayServer.Controllers.Greenfield
{
store ??= Store;
var storeBlob = store.GetStoreBlob();
var id = new PaymentMethodId(cryptoCode, BitcoinPaymentType.Instance);
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
var paymentMethod = store
.GetSupportedPaymentMethods(_btcPayNetworkProvider, _paymentTypeRegistry)
.GetSupportedPaymentMethods(_btcPayNetworkProvider)
.OfType<DerivationSchemeSettings>()
.FirstOrDefault(method => method.PaymentId == id);

@ -14,7 +14,6 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.Models.WalletViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Payments.PayJoin;
using BTCPayServer.Payments.PayJoin.Sender;
using BTCPayServer.Services;
@ -55,7 +54,6 @@ namespace BTCPayServer.Controllers.Greenfield
private readonly WalletReceiveService _walletReceiveService;
private readonly IFeeProviderFactory _feeProviderFactory;
private readonly UTXOLocker _utxoLocker;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
public GreenfieldStoreOnChainWalletsController(
IAuthorizationService authorizationService,
@ -71,8 +69,7 @@ namespace BTCPayServer.Controllers.Greenfield
EventAggregator eventAggregator,
WalletReceiveService walletReceiveService,
IFeeProviderFactory feeProviderFactory,
UTXOLocker utxoLocker,
PaymentTypeRegistry paymentTypeRegistry
UTXOLocker utxoLocker
)
{
_authorizationService = authorizationService;
@ -89,7 +86,6 @@ namespace BTCPayServer.Controllers.Greenfield
_walletReceiveService = walletReceiveService;
_feeProviderFactory = feeProviderFactory;
_utxoLocker = utxoLocker;
_paymentTypeRegistry = paymentTypeRegistry;
}
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
@ -186,7 +182,8 @@ namespace BTCPayServer.Controllers.Greenfield
[FromQuery] TransactionStatus[]? statusFilter = null,
[FromQuery] string? labelFilter = null,
[FromQuery] int skip = 0,
[FromQuery] int limit = int.MaxValue
[FromQuery] int limit = int.MaxValue,
CancellationToken cancellationToken = default
)
{
if (IsInvalidWalletRequest(cryptoCode, out var network,
@ -201,7 +198,7 @@ namespace BTCPayServer.Controllers.Greenfield
if (statusFilter?.Any() is true || !string.IsNullOrWhiteSpace(labelFilter))
preFiltering = false;
var txs = await wallet.FetchTransactionHistory(derivationScheme.AccountDerivation, preFiltering ? skip : 0,
preFiltering ? limit : int.MaxValue);
preFiltering ? limit : int.MaxValue, cancellationToken: cancellationToken);
if (!preFiltering)
{
var filteredList = new List<TransactionHistoryLine>(txs.Count);
@ -805,10 +802,10 @@ namespace BTCPayServer.Controllers.Greenfield
private DerivationSchemeSettings? GetDerivationSchemeSettings(string cryptoCode)
{
var paymentMethod = Store
.GetSupportedPaymentMethods(_btcPayNetworkProvider, _paymentTypeRegistry)
.GetSupportedPaymentMethods(_btcPayNetworkProvider)
.OfType<DerivationSchemeSettings>()
.FirstOrDefault(p =>
p.PaymentId.PaymentType == Payments.BitcoinPaymentType.Instance &&
p.PaymentId.PaymentType == Payments.PaymentTypes.BTCLike &&
p.PaymentId.CryptoCode.Equals(cryptoCode, StringComparison.InvariantCultureIgnoreCase));
return paymentMethod;
}

@ -6,7 +6,6 @@ using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.Payments;
using BTCPayServer.Security;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
@ -23,13 +22,11 @@ namespace BTCPayServer.Controllers.Greenfield
private StoreData Store => HttpContext.GetStoreData();
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly IAuthorizationService _authorizationService;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
public GreenfieldStorePaymentMethodsController(BTCPayNetworkProvider btcPayNetworkProvider, IAuthorizationService authorizationService, PaymentTypeRegistry paymentTypeRegistry)
public GreenfieldStorePaymentMethodsController(BTCPayNetworkProvider btcPayNetworkProvider, IAuthorizationService authorizationService)
{
_btcPayNetworkProvider = btcPayNetworkProvider;
_authorizationService = authorizationService;
_paymentTypeRegistry = paymentTypeRegistry;
}
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
@ -43,7 +40,7 @@ namespace BTCPayServer.Controllers.Greenfield
var canModifyStore = (await _authorizationService.AuthorizeAsync(User, null,
new PolicyRequirement(Policies.CanModifyStoreSettings))).Succeeded;
;
return Ok(Store.GetSupportedPaymentMethods(_btcPayNetworkProvider, _paymentTypeRegistry)
return Ok(Store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
.Where(method =>
enabled is null || (enabled is false && excludedPaymentMethods.Match(method.PaymentId)))
.ToDictionary(

@ -27,20 +27,18 @@ namespace BTCPayServer.Controllers.Greenfield
{
private readonly StoreRepository _storeRepository;
private readonly UserManager<ApplicationUser> _userManager;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
public GreenfieldStoresController(StoreRepository storeRepository, UserManager<ApplicationUser> userManager, PaymentTypeRegistry paymentTypeRegistry)
public GreenfieldStoresController(StoreRepository storeRepository, UserManager<ApplicationUser> userManager)
{
_storeRepository = storeRepository;
_userManager = userManager;
_paymentTypeRegistry = paymentTypeRegistry;
}
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores")]
public Task<ActionResult<IEnumerable<Client.Models.StoreData>>> GetStores()
{
var stores = HttpContext.GetStoresData();
return Task.FromResult<ActionResult<IEnumerable<Client.Models.StoreData>>>(Ok(stores.Select(data => FromModel(data, _paymentTypeRegistry))));
return Task.FromResult<ActionResult<IEnumerable<Client.Models.StoreData>>>(Ok(stores.Select(FromModel)));
}
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
@ -52,7 +50,7 @@ namespace BTCPayServer.Controllers.Greenfield
{
return StoreNotFound();
}
return Ok(FromModel(store, _paymentTypeRegistry));
return Ok(FromModel(store));
}
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
@ -86,10 +84,10 @@ namespace BTCPayServer.Controllers.Greenfield
var store = new Data.StoreData();
_paymentTypeRegistry.TryParsePaymentMethod(request.DefaultPaymentMethod, out var defaultPaymentMethodId);
PaymentMethodId.TryParse(request.DefaultPaymentMethod, out var defaultPaymentMethodId);
ToModel(request, store, defaultPaymentMethodId);
await _storeRepository.CreateStore(_userManager.GetUserId(User), store);
return Ok(FromModel(store, _paymentTypeRegistry));
return Ok(FromModel(store));
}
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
@ -107,14 +105,14 @@ namespace BTCPayServer.Controllers.Greenfield
return validationResult;
}
_paymentTypeRegistry.TryParsePaymentMethod(request.DefaultPaymentMethod, out var defaultPaymentMethodId);
PaymentMethodId.TryParse(request.DefaultPaymentMethod, out var defaultPaymentMethodId);
ToModel(request, store, defaultPaymentMethodId);
await _storeRepository.UpdateStore(store);
return Ok(FromModel(store, _paymentTypeRegistry));
return Ok(FromModel(store));
}
internal static Client.Models.StoreData FromModel(StoreData data, PaymentTypeRegistry paymentTypeRegistry)
internal static Client.Models.StoreData FromModel(Data.StoreData data)
{
var storeBlob = data.GetStoreBlob();
return new Client.Models.StoreData
@ -124,13 +122,14 @@ namespace BTCPayServer.Controllers.Greenfield
Website = data.StoreWebsite,
SupportUrl = storeBlob.StoreSupportUrl,
SpeedPolicy = data.SpeedPolicy,
DefaultPaymentMethod = data.GetDefaultPaymentId(paymentTypeRegistry)?.ToStringNormalized(),
DefaultPaymentMethod = data.GetDefaultPaymentId()?.ToStringNormalized(),
//blob
//we do not include DefaultCurrencyPairs,Spread, PreferredExchange, RateScripting, RateScript in this model and instead opt to set it in stores/storeid/rates endpoints
//we do not include ExcludedPaymentMethods in this model and instead opt to set it in stores/storeid/payment-methods endpoints
//we do not include EmailSettings in this model and instead opt to set it in stores/storeid/email endpoints
//we do not include PaymentMethodCriteria because moving the CurrencyValueJsonConverter to the Client csproj is hard and requires a refactor (#1571 & #1572)
NetworkFeeMode = storeBlob.NetworkFeeMode,
DefaultCurrency = storeBlob.DefaultCurrency,
RequiresRefundEmail = storeBlob.RequiresRefundEmail,
CheckoutType = storeBlob.CheckoutType,
Receipt = InvoiceDataBase.ReceiptOptions.Merge(storeBlob.ReceiptOptions, null),
@ -208,7 +207,7 @@ namespace BTCPayServer.Controllers.Greenfield
Currency = criteria.CurrencyCode,
Value = criteria.Amount
},
PaymentMethod = _paymentTypeRegistry.ParsePaymentMethod(criteria.PaymentMethod)
PaymentMethod = PaymentMethodId.Parse(criteria.PaymentMethod)
}).ToList() ?? new List<PaymentMethodCriteria>();
blob.NormalizeToRelativeLinks(Request);
model.SetStoreBlob(blob);
@ -222,7 +221,7 @@ namespace BTCPayServer.Controllers.Greenfield
}
if (!string.IsNullOrEmpty(request.DefaultPaymentMethod) &&
!_paymentTypeRegistry.TryParsePaymentMethod(request.DefaultPaymentMethod, out var defaultPaymentMethodId))
!PaymentMethodId.TryParse(request.DefaultPaymentMethod, out var defaultPaymentMethodId))
{
ModelState.AddModelError(nameof(request.Name), "DefaultPaymentMethod is invalid");
}
@ -258,7 +257,7 @@ namespace BTCPayServer.Controllers.Greenfield
request.AddModelError(data => data.PaymentMethodCriteria[index].CurrencyCode, "CurrencyCode is invalid", this);
}
if (string.IsNullOrEmpty(pmc.PaymentMethod) || _paymentTypeRegistry.TryParsePaymentMethod(pmc.PaymentMethod) is null)
if (string.IsNullOrEmpty(pmc.PaymentMethod) || PaymentMethodId.TryParse(pmc.PaymentMethod) is null)
{
request.AddModelError(data => data.PaymentMethodCriteria[index].PaymentMethod, "Payment method was invalid", this);
}

@ -3,7 +3,6 @@ using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Client;
using BTCPayServer.Data;
using BTCPayServer.Payments;
using BTCPayServer.Security;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization;
@ -23,12 +22,14 @@ namespace BTCPayServer.Controllers.Greenfield
public class GreenfieldTestApiKeyController : ControllerBase
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
private readonly StoreRepository _storeRepository;
private readonly BTCPayServerClient _localBTCPayServerClient;
public GreenfieldTestApiKeyController(UserManager<ApplicationUser> userManager, PaymentTypeRegistry paymentTypeRegistry)
public GreenfieldTestApiKeyController(UserManager<ApplicationUser> userManager, StoreRepository storeRepository, BTCPayServerClient localBTCPayServerClient)
{
_userManager = userManager;
_paymentTypeRegistry = paymentTypeRegistry;
_storeRepository = storeRepository;
_localBTCPayServerClient = localBTCPayServerClient;
}
[HttpGet("me/id")]
@ -56,7 +57,7 @@ namespace BTCPayServer.Controllers.Greenfield
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public BTCPayServer.Client.Models.StoreData[] GetCurrentUserStores()
{
return this.HttpContext.GetStoresData().Select(data => Greenfield.GreenfieldStoresController.FromModel(data, _paymentTypeRegistry)).ToArray();
return this.HttpContext.GetStoresData().Select(Greenfield.GreenfieldStoresController.FromModel).ToArray();
}
[HttpGet("me/stores/{storeId}/can-view")]

@ -1,6 +1,9 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client;
using BTCPayServer.Data;
@ -8,6 +11,7 @@ using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
@ -21,17 +25,20 @@ namespace BTCPayServer.Controllers
public UIAppsController(
UserManager<ApplicationUser> userManager,
StoreRepository storeRepository,
IFileService fileService,
AppService appService,
IHtmlHelper html)
{
_userManager = userManager;
_storeRepository = storeRepository;
_fileService = fileService;
_appService = appService;
Html = html;
}
private readonly UserManager<ApplicationUser> _userManager;
private readonly StoreRepository _storeRepository;
private readonly IFileService _fileService;
private readonly AppService _appService;
public string CreatedAppId { get; set; }
@ -184,13 +191,50 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new { storeId = app.StoreDataId });
}
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[HttpPost("{appId}/upload-file")]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> FileUpload(IFormFile file)
{
var app = GetCurrentApp();
var userId = GetUserId();
if (app is null || userId is null)
return NotFound();
if (!file.ContentType.StartsWith("image/", StringComparison.InvariantCulture))
{
return Json(new { error = "The file needs to be an image" });
}
if (file.Length > 500_000)
{
return Json(new { error = "The image file size should be less than 0.5MB" });
}
var formFile = await file.Bufferize();
if (!FileTypeDetector.IsPicture(formFile.Buffer, formFile.FileName))
{
return Json(new { error = "The file needs to be an image" });
}
try
{
var storedFile = await _fileService.AddFile(file, userId);
var fileId = storedFile.Id;
var fileUrl = await _fileService.GetFileUrl(Request.GetAbsoluteRootUri(), fileId);
return Json(new { fileId, fileUrl });
}
catch (Exception e)
{
return Json(new { error = $"Could not save file: {e.Message}" });
}
}
async Task<string> GetStoreDefaultCurrentIfEmpty(string storeId, string currency)
{
if (string.IsNullOrWhiteSpace(currency))
{
currency = (await _storeRepository.FindStore(storeId)).GetStoreBlob().DefaultCurrency;
var store = await _storeRepository.FindStore(storeId);
currency = store?.GetStoreBlob().DefaultCurrency;
}
return currency.Trim().ToUpperInvariant();
return currency?.Trim().ToUpperInvariant();
}
private string GetUserId() => _userManager.GetUserId(User);

@ -40,7 +40,6 @@ namespace BTCPayServer.Controllers
private readonly BTCPayNetworkProvider _networkProvider;
private readonly LinkGenerator _linkGenerator;
private readonly FormDataService _formDataService;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
public UICustodianAccountsController(
DisplayFormatter displayFormatter,
@ -50,8 +49,7 @@ namespace BTCPayServer.Controllers
BTCPayServerClient btcPayServerClient,
BTCPayNetworkProvider networkProvider,
LinkGenerator linkGenerator,
FormDataService formDataService,
PaymentTypeRegistry paymentTypeRegistry
FormDataService formDataService
)
{
_displayFormatter = displayFormatter;
@ -61,7 +59,6 @@ namespace BTCPayServer.Controllers
_networkProvider = networkProvider;
_linkGenerator = linkGenerator;
_formDataService = formDataService;
_paymentTypeRegistry = paymentTypeRegistry;
}
public string CreatedCustodianAccountId { get; set; }
@ -508,13 +505,13 @@ namespace BTCPayServer.Controllers
await depositableCustodian.GetDepositAddressAsync(paymentMethod, config, default);
vm.Address = depositAddressResult.Address;
var paymentMethodObj = _paymentTypeRegistry.ParsePaymentMethod(paymentMethod);
var paymentMethodObj = PaymentMethodId.Parse(paymentMethod);
if (paymentMethodObj.IsBTCOnChain)
{
var network = _networkProvider.GetNetwork<BTCPayNetwork>("BTC");
var bip21 = network.GenerateBIP21(depositAddressResult.Address, null);
vm.Link = bip21.ToString();
var paymentMethodId = _paymentTypeRegistry.TryParsePaymentMethod(paymentMethod);
var paymentMethodId = PaymentMethodId.TryParse(paymentMethod);
if (paymentMethodId != null)
{
var walletId = new WalletId(storeId, paymentMethodId.CryptoCode);
@ -555,7 +552,7 @@ namespace BTCPayServer.Controllers
private string GetImage(PaymentMethodId paymentMethodId, BTCPayNetwork network)
{
// TODO this method was copy-pasted from BTCPayServer.Controllers.UIWalletsController.GetImage(). Maybe refactor this?
var res = paymentMethodId.PaymentType == BitcoinPaymentType.Instance
var res = paymentMethodId.PaymentType == PaymentTypes.BTCLike
? Url.Content(network.CryptoImagePath)
: Url.Content(network.LightningImagePath);
return Request.GetRelativePathOrAbsolute(res);

@ -172,7 +172,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("i/{invoiceId}/receipt")]
public async Task<IActionResult> InvoiceReceipt(string invoiceId)
public async Task<IActionResult> InvoiceReceipt(string invoiceId, [FromQuery] bool print = false)
{
var i = await _InvoiceRepository.GetInvoice(invoiceId);
if (i is null)
@ -255,7 +255,7 @@ namespace BTCPayServer.Controllers
vm.Payments = receipt.ShowPayments is false ? null : payments;
vm.AdditionalData = PosDataParser.ParsePosData(receiptData);
return View(vm);
return View(print ? "InvoiceReceiptPrint" : "InvoiceReceipt", vm);
}
private string? GetTransactionLink(PaymentMethodId paymentMethodId, string txId)
@ -358,7 +358,7 @@ namespace BTCPayServer.Controllers
//TODO: Make this clean
if (paymentMethod is null && paymentMethodId.PaymentType == LightningPaymentType.Instance)
{
paymentMethod = pms[new PaymentMethodId(paymentMethodId.CryptoCode, LNURLPayPaymentType.Instance)];
paymentMethod = pms[new PaymentMethodId(paymentMethodId.CryptoCode, PaymentTypes.LNURLPay)];
}
if (paymentMethod != null)
@ -456,7 +456,7 @@ namespace BTCPayServer.Controllers
model.Title = "How much to refund?";
model.RefundStep = RefundSteps.SelectRate;
if (isPaidOver)
if (!isPaidOver)
{
ModelState.AddModelError(nameof(model.SelectedRefundOption), "Invoice is not overpaid");
}
@ -466,7 +466,7 @@ namespace BTCPayServer.Controllers
}
if (!ModelState.IsValid)
{
return this.CreateValidationError(ModelState);
return View("_RefundModal", model);
}
createPullPayment.Currency = paymentMethodId.CryptoCode;
@ -777,8 +777,8 @@ namespace BTCPayServer.Controllers
}
if (paymentMethodId is null)
{
paymentMethodId = displayedPaymentMethods.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == BitcoinPaymentType.Instance) ??
displayedPaymentMethods.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType != LNURLPayPaymentType.Instance) ??
paymentMethodId = displayedPaymentMethods.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == PaymentTypes.BTCLike) ??
displayedPaymentMethods.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType != PaymentTypes.LNURLPay) ??
displayedPaymentMethods.FirstOrDefault();
}
isDefaultPaymentId = true;
@ -941,7 +941,7 @@ namespace BTCPayServer.Controllers
? pmName
: pmName.Replace("Bitcoin (", "").Replace(")", "").Replace("Lightning ", ""),
IsLightning =
kv.GetId().PaymentType == LightningPaymentType.Instance,
kv.GetId().PaymentType == PaymentTypes.LightningLike,
CryptoImage = Request.GetRelativePathOrAbsolute(availableCryptoHandler.GetCryptoImage(availableCryptoPaymentMethodId)),
Link = Url.Action(nameof(Checkout),
new

@ -17,6 +17,7 @@ using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Rating;
using BTCPayServer.Security;
using BTCPayServer.Security.Greenfield;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
@ -58,7 +59,6 @@ namespace BTCPayServer.Controllers
private readonly InvoiceActivator _invoiceActivator;
private readonly LinkGenerator _linkGenerator;
private readonly IAuthorizationService _authorizationService;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
private readonly AppService _appService;
public WebhookSender WebhookNotificationManager { get; }
@ -84,8 +84,7 @@ namespace BTCPayServer.Controllers
InvoiceActivator invoiceActivator,
LinkGenerator linkGenerator,
AppService appService,
IAuthorizationService authorizationService,
PaymentTypeRegistry paymentTypeRegistry)
IAuthorizationService authorizationService)
{
_displayFormatter = displayFormatter;
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
@ -106,7 +105,6 @@ namespace BTCPayServer.Controllers
_invoiceActivator = invoiceActivator;
_linkGenerator = linkGenerator;
_authorizationService = authorizationService;
_paymentTypeRegistry = paymentTypeRegistry;
_appService = appService;
}
@ -130,6 +128,14 @@ namespace BTCPayServer.Controllers
{
throw new BitpayHttpException(400, "The expirationTime is set too soon");
}
if (entity.Price < 0.0m)
{
throw new BitpayHttpException(400, "The price should be 0 or more.");
}
if (entity.Price > GreenfieldConstants.MaxAmount)
{
throw new BitpayHttpException(400, $"The price should less than {GreenfieldConstants.MaxAmount}.");
}
entity.Metadata.OrderId = invoice.OrderId;
entity.Metadata.PosDataLegacy = invoice.PosData;
entity.ServerUrl = serverUrl;
@ -182,7 +188,7 @@ namespace BTCPayServer.Controllers
{
var supportedTransactionCurrencies = invoice.SupportedTransactionCurrencies
.Where(c => c.Value.Enabled)
.Select(c => _paymentTypeRegistry.TryParsePaymentMethod(c.Key, out var p) ? p : null)
.Select(c => PaymentMethodId.TryParse(c.Key, out var p) ? p : null)
.Where(c => c != null)
.ToHashSet();
excludeFilter = PaymentFilter.Where(p => !supportedTransactionCurrencies.Contains(p));
@ -252,7 +258,7 @@ namespace BTCPayServer.Controllers
}
entity.SpeedPolicy = invoice.Checkout.SpeedPolicy ?? store.SpeedPolicy;
entity.DefaultLanguage = invoice.Checkout.DefaultLanguage;
entity.DefaultPaymentMethod = invoice.Checkout.DefaultPaymentMethod ?? store.GetDefaultPaymentId()?.ToStringNormalized() ?? new PaymentMethodId(_NetworkProvider.DefaultNetwork.CryptoCode, BitcoinPaymentType.Instance).ToStringNormalized();
entity.DefaultPaymentMethod = invoice.Checkout.DefaultPaymentMethod ?? store.GetDefaultPaymentId()?.ToStringNormalized() ?? new PaymentMethodId(_NetworkProvider.DefaultNetwork.CryptoCode, PaymentTypes.BTCLike).ToStringNormalized();
entity.RedirectAutomatically = invoice.Checkout.RedirectAutomatically ?? storeBlob.RedirectAutomatically;
entity.CheckoutType = invoice.Checkout.CheckoutType;
entity.RequiresRefundEmail = invoice.Checkout.RequiresRefundEmail;
@ -261,7 +267,7 @@ namespace BTCPayServer.Controllers
if (invoice.Checkout.PaymentMethods != null)
{
var supportedTransactionCurrencies = invoice.Checkout.PaymentMethods
.Select(c => _paymentTypeRegistry.TryParsePaymentMethod(c, out var p) ? p : null)
.Select(c => PaymentMethodId.TryParse(c, out var p) ? p : null)
.ToHashSet();
excludeFilter = PaymentFilter.Where(p => !supportedTransactionCurrencies.Contains(p));
}
@ -281,6 +287,7 @@ namespace BTCPayServer.Controllers
if (string.IsNullOrEmpty(entity.Currency))
entity.Currency = storeBlob.DefaultCurrency;
entity.Currency = entity.Currency.Trim().ToUpperInvariant();
entity.Price = Math.Min(GreenfieldConstants.MaxAmount, entity.Price);
entity.Price = Math.Max(0.0m, entity.Price);
var currencyInfo = _CurrencyNameTable.GetNumberFormatInfo(entity.Currency, false);
if (currencyInfo != null)

@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
@ -60,7 +61,6 @@ namespace BTCPayServer
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
private readonly IPluginHookService _pluginHookService;
private readonly InvoiceActivator _invoiceActivator;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
public UILNURLController(InvoiceRepository invoiceRepository,
EventAggregator eventAggregator,
@ -75,8 +75,7 @@ namespace BTCPayServer
PullPaymentHostedService pullPaymentHostedService,
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
IPluginHookService pluginHookService,
InvoiceActivator invoiceActivator,
PaymentTypeRegistry paymentTypeRegistry)
InvoiceActivator invoiceActivator)
{
_invoiceRepository = invoiceRepository;
_eventAggregator = eventAggregator;
@ -92,7 +91,6 @@ namespace BTCPayServer
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
_pluginHookService = pluginHookService;
_invoiceActivator = invoiceActivator;
_paymentTypeRegistry = paymentTypeRegistry;
}
[HttpGet("withdraw/pp/{pullPaymentId}")]
@ -104,7 +102,7 @@ namespace BTCPayServer
return NotFound();
}
var pmi = new PaymentMethodId(cryptoCode, LightningPaymentType.Instance);
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
var pp = await _pullPaymentHostedService.GetPullPayment(pullPaymentId, true);
if (!pp.IsRunning() || !pp.IsSupported(pmi))
{
@ -112,24 +110,24 @@ namespace BTCPayServer
}
var blob = pp.GetBlob();
if (!blob.Currency.Equals(cryptoCode, StringComparison.InvariantCultureIgnoreCase))
if (!_pullPaymentHostedService.SupportsLNURL(blob))
{
return NotFound();
}
var unit = blob.Currency == "SATS" ? LightMoneyUnit.Satoshi : LightMoneyUnit.BTC;
var progress = _pullPaymentHostedService.CalculatePullPaymentProgress(pp, DateTimeOffset.UtcNow);
var remaining = progress.Limit - progress.Completed - progress.Awaiting;
var request = new LNURLWithdrawRequest
{
MaxWithdrawable = LightMoney.FromUnit(remaining, LightMoneyUnit.BTC),
MaxWithdrawable = LightMoney.FromUnit(remaining, unit),
K1 = pullPaymentId,
BalanceCheck = new Uri(Request.GetCurrentUrl()),
CurrentBalance = LightMoney.FromUnit(remaining, LightMoneyUnit.BTC),
CurrentBalance = LightMoney.FromUnit(remaining, unit),
MinWithdrawable =
LightMoney.FromUnit(
Math.Min(await _lightningLikePayoutHandler.GetMinimumPayoutAmount(pmi, null), remaining),
LightMoneyUnit.BTC),
unit),
Tag = "withdrawRequest",
Callback = new Uri(Request.GetCurrentUrl()),
// It's not `pp.GetBlob().Description` because this would be HTML
@ -157,13 +155,13 @@ namespace BTCPayServer
return NotFound();
}
var claimResponse = await _pullPaymentHostedService.Claim(new ClaimRequest()
var claimResponse = await _pullPaymentHostedService.Claim(new ClaimRequest
{
Destination = new BoltInvoiceClaimDestination(pr, result),
PaymentMethodId = pmi,
PullPaymentId = pullPaymentId,
StoreId = pp.StoreId,
Value = result.MinimumAmount.ToDecimal(LightMoneyUnit.BTC)
Value = result.MinimumAmount.ToDecimal(unit)
});
if (claimResponse.Result != ClaimRequest.ClaimResult.Ok)
@ -286,7 +284,7 @@ namespace BTCPayServer
if (item is null ||
item.Inventory <= 0 ||
(item.PaymentMethods?.Any() is true &&
item.PaymentMethods?.Any(s => _paymentTypeRegistry.ParsePaymentMethod(s) == pmi) is false))
item.PaymentMethods?.Any(s => PaymentMethodId.Parse(s) == pmi) is false))
{
return NotFound();
}
@ -376,13 +374,52 @@ namespace BTCPayServer
return NotFound("Unknown username");
var store = await _storeRepository.FindStore(lightningAddressSettings.StoreDataId);
var cryptoCode = "BTC";
if (store is null)
return NotFound("Unknown username");
if (GetLNUrlPaymentMethodId(cryptoCode, store, out var lnUrlMethod) is null)
return NotFound("LNUrl not available for store");
var blob = lightningAddressSettings.GetBlob();
return await GetLNURLRequest(
"BTC",
var lnurlRequest = new LNURLPayRequest()
{
Tag = "payRequest",
MinSendable = blob?.Min is decimal min ? new LightMoney(min, LightMoneyUnit.Satoshi) : null,
MaxSendable = blob?.Max is decimal max ? new LightMoney(max, LightMoneyUnit.Satoshi) : null,
CommentAllowed = lnUrlMethod.LUD12Enabled ? 2000 : 0
};
NormalizeSendable(lnurlRequest);
var lnUrlMetadata = new Dictionary<string, string>()
{
["text/identifier"] = $"{username}@{Request.Host}"
};
SetLNUrlDescriptionMetadata(lnUrlMetadata, store, store.GetStoreBlob(), null);
lnurlRequest.Metadata =
JsonConvert.SerializeObject(lnUrlMetadata.Select(kv => new[] { kv.Key, kv.Value }));
lnurlRequest.Callback = new Uri(_linkGenerator.GetUriByAction(
action: nameof(GetLNURLForLightningAddress),
controller: "UILNURL",
values: new { cryptoCode, username }, Request.Scheme, Request.Host, Request.PathBase));
lnurlRequest = await _pluginHookService.ApplyFilter("modify-lnurlp-request", lnurlRequest) as LNURLPayRequest;
return Ok(lnurlRequest);
}
[HttpGet("pay/lnaddress/{username}")]
[EnableCors(CorsPolicies.All)]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> GetLNURLForLightningAddress(string cryptoCode, string username, [FromQuery] long? amount = null, string comment = null)
{
var lightningAddressSettings = await _lightningAddressService.ResolveByAddress(username);
if (lightningAddressSettings is null || username is null)
return NotFound("Unknown username");
var blob = lightningAddressSettings.GetBlob();
var store = await _storeRepository.FindStore(lightningAddressSettings.StoreDataId);
var result = await GetLNURLRequest(
cryptoCode,
store,
store.GetStoreBlob(),
new CreateInvoiceRequest()
@ -399,31 +436,44 @@ namespace BTCPayServer
{
{ "text/identifier", $"{username}@{Request.Host}" }
});
if (result is not OkObjectResult ok || ok.Value is not LNURLPayRequest payRequest)
return result;
var invoiceId = payRequest.Callback.AbsoluteUri.Split('/').Last();
return await GetLNURLForInvoice(invoiceId, cryptoCode, amount, comment);
}
[HttpGet("pay")]
[HttpGet("{storeId}/pay")]
[EnableCors(CorsPolicies.All)]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> GetLNUrlForStore(
string cryptoCode,
string storeId,
string currencyCode = null)
string currency = null,
string orderId = null,
decimal? amount = null)
{
var store = this.HttpContext.GetStoreData();
var store = await _storeRepository.FindStore(storeId);
if (store is null)
return NotFound();
var blob = store.GetStoreBlob();
var blob = store.GetStoreBlob();
if (!blob.AnyoneCanInvoice)
return NotFound("'Anyone can invoice' is turned off");
var metadata = new InvoiceMetadata();
if (!string.IsNullOrEmpty(orderId))
{
metadata.OrderId = orderId;
}
return await GetLNURLRequest(
cryptoCode,
store,
blob,
new CreateInvoiceRequest
{
Currency = currencyCode
Amount = amount,
Metadata = metadata.ToJObject(),
Currency = currency
});
}
@ -485,11 +535,7 @@ namespace BTCPayServer
if (!lnUrlMetadata.ContainsKey("text/plain"))
{
var invoiceDescription = blob.LightningDescriptionTemplate
.Replace("{StoreName}", store.StoreName ?? "", StringComparison.OrdinalIgnoreCase)
.Replace("{ItemDescription}", i.Metadata.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase)
.Replace("{OrderId}", i.Metadata.OrderId ?? "", StringComparison.OrdinalIgnoreCase);
lnUrlMetadata.Add("text/plain", invoiceDescription);
SetLNUrlDescriptionMetadata(lnUrlMetadata, store, blob, i.Metadata);
}
lnurlRequest.Tag = "payRequest";
@ -506,12 +552,7 @@ namespace BTCPayServer
lnurlRequest.MaxSendable = lnurlRequest.MinSendable;
}
// We don't think BTCPay handle well 0 sats payments, just in case make it minimum one sat.
if (lnurlRequest.MinSendable is null || lnurlRequest.MinSendable < LightMoney.Satoshis(1.0m))
lnurlRequest.MinSendable = LightMoney.Satoshis(1.0m);
if (lnurlRequest.MaxSendable is null)
lnurlRequest.MaxSendable = LightMoney.FromUnit(6.12m, LightMoneyUnit.BTC);
NormalizeSendable(lnurlRequest);
lnurlRequest = await _pluginHookService.ApplyFilter("modify-lnurlp-request", lnurlRequest) as LNURLPayRequest;
if (paymentMethodDetails.PayRequest is null)
@ -527,14 +568,33 @@ namespace BTCPayServer
return lnurlRequest;
}
private void SetLNUrlDescriptionMetadata(Dictionary<string, string> lnUrlMetadata, Data.StoreData store, StoreBlob blob, InvoiceMetadata invoiceMetadata)
{
var invoiceDescription = blob.LightningDescriptionTemplate
.Replace("{StoreName}", store.StoreName ?? "", StringComparison.OrdinalIgnoreCase)
.Replace("{ItemDescription}", invoiceMetadata?.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase)
.Replace("{OrderId}", invoiceMetadata?.OrderId ?? "", StringComparison.OrdinalIgnoreCase);
lnUrlMetadata.Add("text/plain", invoiceDescription);
}
private static void NormalizeSendable(LNURLPayRequest lnurlRequest)
{
// We don't think BTCPay handle well 0 sats payments, just in case make it minimum one sat.
if (lnurlRequest.MinSendable is null || lnurlRequest.MinSendable < LightMoney.Satoshis(1.0m))
lnurlRequest.MinSendable = LightMoney.Satoshis(1.0m);
if (lnurlRequest.MaxSendable is null)
lnurlRequest.MaxSendable = LightMoney.FromUnit(6.12m, LightMoneyUnit.BTC);
}
PaymentMethodId GetLNUrlPaymentMethodId(string cryptoCode, Data.StoreData store, out LNURLPaySupportedPaymentMethod lnUrlSettings)
{
lnUrlSettings = null;
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
if (network is null || !network.SupportLightning)
return null;
var pmi = new PaymentMethodId(cryptoCode, LNURLPayPaymentType.Instance);
var lnpmi = new PaymentMethodId(cryptoCode, LightningPaymentType.Instance);
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
var lnpmi = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
var methods = store.GetSupportedPaymentMethods(_btcPayNetworkProvider);
var lnUrlMethod =
methods.FirstOrDefault(method => method.PaymentId == pmi) as LNURLPaySupportedPaymentMethod;

@ -506,57 +506,57 @@ namespace BTCPayServer.Controllers
{
public static readonly Dictionary<string, (string Title, string Description)> PermissionDescriptions = new Dictionary<string, (string Title, string Description)>()
{
{Policies.Unrestricted, ("Unrestricted access", "The app will have unrestricted access to your account.")},
{Policies.CanViewUsers, ("View users", "The app will be able to see all users on this server.")},
{Policies.CanCreateUser, ("Create new users", "The app will be able to create new users on this server.")},
{Policies.CanManageUsers, ("Manage users", "The app will be able to create/delete API keys for users.")},
{Policies.CanDeleteUser, ("Delete user", "The app will be able to delete the user to whom it is assigned. Admin users can delete any user without this permission.")},
{Policies.CanModifyStoreSettings, ("Modify your stores", "The app will be able to manage invoices on all your stores and modify their settings.")},
{$"{Policies.CanModifyStoreSettings}:", ("Manage selected stores", "The app will be able to manage invoices on the selected stores and modify their settings.")},
{Policies.CanViewCustodianAccounts, ("View exchange accounts linked to your stores", "The app will be able to see exchange accounts linked to your stores.")},
{$"{Policies.CanViewCustodianAccounts}:", ("View exchange accounts linked to selected stores", "The app will be able to see exchange accounts linked to the selected stores.")},
{Policies.CanManageCustodianAccounts, ("Manage exchange accounts linked to your stores", "The app will be able to modify exchange accounts linked to your stores.")},
{$"{Policies.CanManageCustodianAccounts}:", ("Manage exchange accounts linked to selected stores", "The app will be able to modify exchange accounts linked to selected stores.")},
{Policies.CanDepositToCustodianAccounts, ("Deposit funds to exchange accounts linked to your stores", "The app will be able to deposit funds to your exchange accounts.")},
{$"{Policies.CanDepositToCustodianAccounts}:", ("Deposit funds to exchange accounts linked to selected stores", "The app will be able to deposit funds to selected store's exchange accounts.")},
{Policies.CanWithdrawFromCustodianAccounts, ("Withdraw funds from exchange accounts to your store", "The app will be able to withdraw funds from your exchange accounts to your store.")},
{$"{Policies.CanWithdrawFromCustodianAccounts}:", ("Withdraw funds from selected store's exchange accounts", "The app will be able to withdraw funds from your selected store's exchange accounts.")},
{Policies.CanTradeCustodianAccount, ("Trade funds on your store's exchange accounts", "The app will be able to trade funds on your store's exchange accounts.")},
{$"{Policies.CanTradeCustodianAccount}:", ("Trade funds on selected store's exchange accounts", "The app will be able to trade funds on selected store's exchange accounts.")},
{Policies.CanModifyStoreWebhooks, ("Modify stores webhooks", "The app will modify the webhooks of all your stores.")},
{$"{Policies.CanModifyStoreWebhooks}:", ("Modify selected stores' webhooks", "The app will modify the webhooks of the selected stores.")},
{Policies.CanViewStoreSettings, ("View your stores", "The app will be able to view stores settings.")},
{$"{Policies.CanViewStoreSettings}:", ("View your stores", "The app will be able to view the selected stores' settings.")},
{Policies.CanModifyServerSettings, ("Manage your server", "The app will have total control on the server settings of your server.")},
{Policies.CanViewProfile, ("View your profile", "The app will be able to view your user profile.")},
{Policies.CanModifyProfile, ("Manage your profile", "The app will be able to view and modify your user profile.")},
{Policies.CanManageNotificationsForUser, ("Manage your notifications", "The app will be able to view and modify your user notifications.")},
{Policies.CanViewNotificationsForUser, ("View your notifications", "The app will be able to view your user notifications.")},
{Policies.CanCreateInvoice, ("Create an invoice", "The app will be able to create new invoices.")},
{$"{Policies.CanCreateInvoice}:", ("Create an invoice", "The app will be able to create new invoices on the selected stores.")},
{Policies.CanViewInvoices, ("View invoices", "The app will be able to view invoices.")},
{Policies.CanModifyInvoices, ("Modify invoices", "The app will be able to modify and view invoices.")},
{$"{Policies.CanViewInvoices}:", ("View invoices", "The app will be able to view invoices on the selected stores.")},
{$"{Policies.CanModifyInvoices}:", ("Modify invoices", "The app will be able to modify and view invoices on the selected stores.")},
{Policies.CanModifyPaymentRequests, ("Modify your payment requests", "The app will be able to view, modify, delete and create new payment requests on all your stores.")},
{$"{Policies.CanModifyPaymentRequests}:", ("Manage selected stores' payment requests", "The app will be able to view, modify, delete and create new payment requests on the selected stores.")},
{Policies.CanViewPaymentRequests, ("View your payment requests", "The app will be able to view payment requests.")},
{$"{Policies.CanViewPaymentRequests}:", ("View your payment requests", "The app will be able to view the selected stores' payment requests.")},
{Policies.CanManagePullPayments, ("Manage your pull payments", "The app will be able to view, modify, delete and create pull payments on all your stores.")},
{$"{Policies.CanManagePullPayments}:", ("Manage selected stores' pull payments", "The app will be able to view, modify, delete and create new pull payments on the selected stores.")},
{Policies.CanCreatePullPayments, ("Create pull payments", "The app will be able to create pull payments on all your stores.")},
{$"{Policies.CanCreatePullPayments}:", ("Create pull payments in selected stores", "The app will be able to create new pull payments on the selected stores.")},
{Policies.CanCreateNonApprovedPullPayments, ("Create non-approved pull payments", "The app will be able to create pull payments without automatic approval on all your stores.")},
{$"{Policies.CanCreateNonApprovedPullPayments}:", ("Create non-approved pull payments in selected stores", "The app will be able to view, modify, delete and create pull payments without automatic approval on the selected stores.")},
{Policies.CanUseInternalLightningNode, ("Use the internal lightning node", "The app will be able to use the internal BTCPay Server lightning node to create BOLT11 invoices, connect to other nodes, open new channels and pay BOLT11 invoices.")},
{Policies.CanViewLightningInvoiceInternalNode, ("View invoices from internal lightning node", "The app will be able to use the internal BTCPay Server lightning node to view BOLT11 invoices.")},
{Policies.CanCreateLightningInvoiceInternalNode, ("Create invoices with internal lightning node", "The app will be able to use the internal BTCPay Server lightning node to create BOLT11 invoices.")},
{Policies.CanUseLightningNodeInStore, ("Use the lightning nodes associated with your stores", "The app will be able to use the lightning nodes connected to all your stores to create BOLT11 invoices, connect to other nodes, open new channels and pay BOLT11 invoices.")},
{Policies.CanViewLightningInvoiceInStore, ("View the lightning invoices associated with your stores", "The app will be able to view the lightning invoices connected to all your stores.")},
{Policies.CanCreateLightningInvoiceInStore, ("Create invoices from the lightning nodes associated with your stores", "The app will be able to use the lightning nodes connected to all your stores to create BOLT11 invoices.")},
{$"{Policies.CanUseLightningNodeInStore}:", ("Use the lightning nodes associated with your stores", "The app will be able to use the lightning nodes connected to the selected stores to create BOLT11 invoices, connect to other nodes, open new channels and pay BOLT11 invoices.")},
{$"{Policies.CanViewLightningInvoiceInStore}:", ("View the lightning invoices associated with your stores", "The app will be able to view the lightning invoices connected to the selected stores.")},
{$"{Policies.CanCreateLightningInvoiceInStore}:", ("Create invoices from the lightning nodes associated with your stores", "The app will be able to use the lightning nodes connected to the selected stores to create BOLT11 invoices.")},
{Policies.Unrestricted, ("Unrestricted access", "Grants unrestricted access to your account.")},
{Policies.CanViewUsers, ("View users", "Allows seeing all users on this server.")},
{Policies.CanCreateUser, ("Create new users", "Allows creating new users on this server.")},
{Policies.CanManageUsers, ("Manage users", "Allows creating/deleting API keys for users.")},
{Policies.CanDeleteUser, ("Delete user", "Allows deleting the user to whom it is assigned. Admin users can delete any user without this permission.")},
{Policies.CanModifyStoreSettings, ("Modify your stores", "Allows managing invoices on all your stores and modify their settings.")},
{$"{Policies.CanModifyStoreSettings}:", ("Manage selected stores", "Allows managing invoices on the selected stores and modify their settings.")},
{Policies.CanViewCustodianAccounts, ("View exchange accounts linked to your stores", "Allows seeing exchange accounts linked to your stores.")},
{$"{Policies.CanViewCustodianAccounts}:", ("View exchange accounts linked to selected stores", "Allows seeing exchange accounts linked to the selected stores.")},
{Policies.CanManageCustodianAccounts, ("Manage exchange accounts linked to your stores", "Allows modifying exchange accounts linked to your stores.")},
{$"{Policies.CanManageCustodianAccounts}:", ("Manage exchange accounts linked to selected stores", "Allows modifying exchange accounts linked to selected stores.")},
{Policies.CanDepositToCustodianAccounts, ("Deposit funds to exchange accounts linked to your stores", "Allows depositing funds to your exchange accounts.")},
{$"{Policies.CanDepositToCustodianAccounts}:", ("Deposit funds to exchange accounts linked to selected stores", "Allows depositing funds to selected store's exchange accounts.")},
{Policies.CanWithdrawFromCustodianAccounts, ("Withdraw funds from exchange accounts to your store", "Allows withdrawing funds from your exchange accounts to your store.")},
{$"{Policies.CanWithdrawFromCustodianAccounts}:", ("Withdraw funds from selected store's exchange accounts", "Allows withdrawing funds from your selected store's exchange accounts.")},
{Policies.CanTradeCustodianAccount, ("Trade funds on your store's exchange accounts", "Allows trading funds on your store's exchange accounts.")},
{$"{Policies.CanTradeCustodianAccount}:", ("Trade funds on selected store's exchange accounts", "Allows trading funds on selected store's exchange accounts.")},
{Policies.CanModifyStoreWebhooks, ("Modify stores webhooks", "Allows modifying the webhooks of all your stores.")},
{$"{Policies.CanModifyStoreWebhooks}:", ("Modify selected stores' webhooks", "Allows modifying the webhooks of the selected stores.")},
{Policies.CanViewStoreSettings, ("View your stores", "Allows viewing stores settings.")},
{$"{Policies.CanViewStoreSettings}:", ("View your stores", "Allows viewing the selected stores' settings.")},
{Policies.CanModifyServerSettings, ("Manage your server", "Grants total control on the server settings of your server.")},
{Policies.CanViewProfile, ("View your profile", "Allows viewing your user profile.")},
{Policies.CanModifyProfile, ("Manage your profile", "Allows viewing and modifying your user profile.")},
{Policies.CanManageNotificationsForUser, ("Manage your notifications", "Allows viewing and modifying your user notifications.")},
{Policies.CanViewNotificationsForUser, ("View your notifications", "Allows viewing your user notifications.")},
{Policies.CanCreateInvoice, ("Create an invoice", "Allows creating new invoices.")},
{$"{Policies.CanCreateInvoice}:", ("Create an invoice", "Allows creating new invoices on the selected stores.")},
{Policies.CanViewInvoices, ("View invoices", "Allows viewing invoices.")},
{Policies.CanModifyInvoices, ("Modify invoices", "Allows viewing and modifying invoices.")},
{$"{Policies.CanViewInvoices}:", ("View invoices", "Allows viewing invoices on the selected stores.")},
{$"{Policies.CanModifyInvoices}:", ("Modify invoices", "Allows viewing and modifying invoices on the selected stores.")},
{Policies.CanModifyPaymentRequests, ("Modify your payment requests", "Allows viewing, modifying, deleting and creating new payment requests on all your stores.")},
{$"{Policies.CanModifyPaymentRequests}:", ("Manage selected stores' payment requests", "Allows viewing, modifying, deleting and creating new payment requests on the selected stores.")},
{Policies.CanViewPaymentRequests, ("View your payment requests", "Allows viewing payment requests.")},
{$"{Policies.CanViewPaymentRequests}:", ("View your payment requests", "Allows viewing the selected stores' payment requests.")},
{Policies.CanManagePullPayments, ("Manage your pull payments", "Allows viewing, modifying, deleting and creating pull payments on all your stores.")},
{$"{Policies.CanManagePullPayments}:", ("Manage selected stores' pull payments", "Allows viewing, modifying, deleting and creating pull payments on the selected stores.")},
{Policies.CanCreatePullPayments, ("Create pull payments", "Allows creating pull payments on all your stores.")},
{$"{Policies.CanCreatePullPayments}:", ("Create pull payments in selected stores", "Allows creating pull payments on the selected stores.")},
{Policies.CanCreateNonApprovedPullPayments, ("Create non-approved pull payments", "Allows creating pull payments without automatic approval on all your stores.")},
{$"{Policies.CanCreateNonApprovedPullPayments}:", ("Create non-approved pull payments in selected stores", "Allows viewing, modifying, deleting and creating pull payments without automatic approval on the selected stores.")},
{Policies.CanUseInternalLightningNode, ("Use the internal lightning node", "Allows using the internal BTCPay Server lightning node to create BOLT11 invoices, connect to other nodes, open new channels and pay BOLT11 invoices.")},
{Policies.CanViewLightningInvoiceInternalNode, ("View invoices from internal lightning node", "Allows using the internal BTCPay Server lightning node to view BOLT11 invoices.")},
{Policies.CanCreateLightningInvoiceInternalNode, ("Create invoices with internal lightning node", "Allows using the internal BTCPay Server lightning node to create BOLT11 invoices.")},
{Policies.CanUseLightningNodeInStore, ("Use the lightning nodes associated with your stores", "Allows using the lightning nodes connected to all your stores to create BOLT11 invoices, connect to other nodes, open new channels and pay BOLT11 invoices.")},
{Policies.CanViewLightningInvoiceInStore, ("View the lightning invoices associated with your stores", "Allows viewing the lightning invoices connected to all your stores.")},
{Policies.CanCreateLightningInvoiceInStore, ("Create invoices from the lightning nodes associated with your stores", "Allows using the lightning nodes connected to all your stores to create BOLT11 invoices.")},
{$"{Policies.CanUseLightningNodeInStore}:", ("Use the lightning nodes associated with your stores", "Allows using the lightning nodes connected to the selected stores to create BOLT11 invoices, connect to other nodes, open new channels and pay BOLT11 invoices.")},
{$"{Policies.CanViewLightningInvoiceInStore}:", ("View the lightning invoices associated with your stores", "Allows viewing the lightning invoices connected to the selected stores.")},
{$"{Policies.CanCreateLightningInvoiceInStore}:", ("Create invoices from the lightning nodes associated with your stores", "Allows using the lightning nodes connected to the selected stores to create BOLT11 invoices.")},
};
public string Title
{

@ -78,16 +78,20 @@ namespace BTCPayServer.Controllers
model = this.ParseListQuery(model ?? new ListPaymentRequestsViewModel());
var store = GetCurrentStore();
var includeArchived = new SearchString(model.SearchTerm, model.TimezoneOffset ?? 0).GetFilterBool("includearchived") == true;
var fs = new SearchString(model.SearchTerm, model.TimezoneOffset ?? 0);
var result = await _PaymentRequestRepository.FindPaymentRequests(new PaymentRequestQuery
{
UserId = GetUserId(),
StoreId = store.Id,
Skip = model.Skip,
Count = model.Count,
IncludeArchived = includeArchived
Status = fs.GetFilterArray("status")?.Select(s => Enum.Parse<Client.Models.PaymentRequestData.PaymentRequestStatus>(s, true)).ToArray(),
IncludeArchived = fs.GetFilterBool("includearchived") ?? false
});
model.Search = fs;
model.SearchText = fs.TextSearch;
model.Items = result.Select(data =>
{
var blob = data.GetBlob();

@ -66,7 +66,7 @@ namespace BTCPayServer.Controllers
private LightningSupportedPaymentMethod GetExistingLightningSupportedPaymentMethod(string cryptoCode, StoreData store)
{
var id = new PaymentMethodId(cryptoCode, LightningPaymentType.Instance);
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
var existing = store.GetSupportedPaymentMethods(_BtcPayNetworkProvider)
.OfType<LightningSupportedPaymentMethod>()
.FirstOrDefault(d => d.PaymentId == id);
@ -76,7 +76,7 @@ namespace BTCPayServer.Controllers
private string GetImage(PaymentMethodId paymentMethodId, BTCPayNetwork network)
{
var res = paymentMethodId.PaymentType == BitcoinPaymentType.Instance
var res = paymentMethodId.PaymentType == PaymentTypes.BTCLike
? Url.Content(network.CryptoImagePath)
: Url.Content(network.LightningImagePath);
return "/" + res;

@ -29,19 +29,19 @@ namespace BTCPayServer.Controllers
private readonly CurrencyNameTable _currencyNameTable;
private readonly DisplayFormatter _displayFormatter;
private readonly PullPaymentHostedService _pullPaymentHostedService;
private readonly BTCPayNetworkProvider _networkProvider;
private readonly BTCPayNetworkJsonSerializerSettings _serializerSettings;
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
private readonly StoreRepository _storeRepository;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
public UIPullPaymentController(ApplicationDbContextFactory dbContextFactory,
CurrencyNameTable currencyNameTable,
DisplayFormatter displayFormatter,
PullPaymentHostedService pullPaymentHostedService,
BTCPayNetworkProvider networkProvider,
BTCPayNetworkJsonSerializerSettings serializerSettings,
IEnumerable<IPayoutHandler> payoutHandlers,
StoreRepository storeRepository,
PaymentTypeRegistry paymentTypeRegistry)
StoreRepository storeRepository)
{
_dbContextFactory = dbContextFactory;
_currencyNameTable = currencyNameTable;
@ -50,7 +50,7 @@ namespace BTCPayServer.Controllers
_serializerSettings = serializerSettings;
_payoutHandlers = payoutHandlers;
_storeRepository = storeRepository;
_paymentTypeRegistry = paymentTypeRegistry;
_networkProvider = networkProvider;
}
[AllowAnonymous]
@ -99,12 +99,19 @@ namespace BTCPayServer.Controllers
Currency = blob.Currency,
Status = entity.Entity.State,
Destination = entity.Blob.Destination,
PaymentMethod = _paymentTypeRegistry.ParsePaymentMethod(entity.Entity.PaymentMethodId),
PaymentMethod = PaymentMethodId.Parse(entity.Entity.PaymentMethodId),
Link = entity.ProofBlob?.Link,
TransactionId = entity.ProofBlob?.Id
}).ToList()
};
vm.IsPending &= vm.AmountDue > 0.0m;
if (_pullPaymentHostedService.SupportsLNURL(blob))
{
var url = Url.Action("GetLNURLForPullPayment", "UILNURL", new { cryptoCode = _networkProvider.DefaultNetwork.CryptoCode, pullPaymentId = vm.Id }, Request.Scheme, Request.Host.ToString());
vm.LnurlEndpoint = url != null ? new Uri(url) : null;
}
return View(nameof(ViewPullPayment), vm);
}

@ -29,6 +29,7 @@ using BTCPayServer.Storage.Services;
using BTCPayServer.Storage.Services.Providers;
using BTCPayServer.Validation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
@ -664,6 +665,8 @@ namespace BTCPayServer.Controllers
[Route("lnd-config/{configKey}/lnd.config")]
[AllowAnonymous]
[EnableCors(CorsPolicies.All)]
[IgnoreAntiforgeryToken]
public IActionResult GetLNDConfig(ulong configKey)
{
var conf = _LnConfigProvider.GetConfig(configKey);

@ -117,7 +117,7 @@ namespace BTCPayServer.Controllers
}
var network = _ExplorerProvider.GetNetwork(vm.CryptoCode);
var paymentMethodId = new PaymentMethodId(network.CryptoCode, LightningPaymentType.Instance);
var paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.LightningLike);
LightningSupportedPaymentMethod? paymentMethod = null;
if (vm.LightningNodeType == LightningNodeType.Internal)
@ -175,7 +175,7 @@ namespace BTCPayServer.Controllers
switch (command)
{
case "save":
var lnurl = new PaymentMethodId(vm.CryptoCode, LNURLPayPaymentType.Instance);
var lnurl = new PaymentMethodId(vm.CryptoCode, PaymentTypes.LNURLPay);
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);
store.SetSupportedPaymentMethod(lnurl, new LNURLPaySupportedPaymentMethod()
{
@ -275,7 +275,7 @@ namespace BTCPayServer.Controllers
blob.LightningAmountInSatoshi = vm.LightningAmountInSatoshi;
blob.LightningPrivateRouteHints = vm.LightningPrivateRouteHints;
blob.OnChainWithLnInvoiceFallback = vm.OnChainWithLnInvoiceFallback;
var lnurlId = new PaymentMethodId(vm.CryptoCode, LNURLPayPaymentType.Instance);
var lnurlId = new PaymentMethodId(vm.CryptoCode, PaymentTypes.LNURLPay);
blob.SetExcluded(lnurlId, !vm.LNURLEnabled);
var lnurl = GetExistingLNURLSupportedPaymentMethod(vm.CryptoCode, store);
@ -323,12 +323,12 @@ namespace BTCPayServer.Controllers
if (lightning == null)
return NotFound();
var paymentMethodId = new PaymentMethodId(network.CryptoCode, LightningPaymentType.Instance);
var paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.LightningLike);
var storeBlob = store.GetStoreBlob();
storeBlob.SetExcluded(paymentMethodId, !enabled);
if (!enabled)
{
storeBlob.SetExcluded(new PaymentMethodId(network.CryptoCode, LNURLPayPaymentType.Instance), true);
storeBlob.SetExcluded(new PaymentMethodId(network.CryptoCode, PaymentTypes.LNURLPay), true);
}
store.SetStoreBlob(storeBlob);
await _Repo.UpdateStore(store);
@ -360,7 +360,7 @@ namespace BTCPayServer.Controllers
private LightningSupportedPaymentMethod? GetExistingLightningSupportedPaymentMethod(string cryptoCode, StoreData store)
{
var id = new PaymentMethodId(cryptoCode, LightningPaymentType.Instance);
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
var existing = store.GetSupportedPaymentMethods(_NetworkProvider)
.OfType<LightningSupportedPaymentMethod>()
.FirstOrDefault(d => d.PaymentId == id);
@ -369,7 +369,7 @@ namespace BTCPayServer.Controllers
private LNURLPaySupportedPaymentMethod? GetExistingLNURLSupportedPaymentMethod(string cryptoCode, StoreData store)
{
var id = new PaymentMethodId(cryptoCode, LNURLPayPaymentType.Instance);
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
var existing = store.GetSupportedPaymentMethods(_NetworkProvider)
.OfType<LNURLPaySupportedPaymentMethod>()
.FirstOrDefault(d => d.PaymentId == id);

@ -1,4 +1,5 @@
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
@ -150,7 +151,7 @@ namespace BTCPayServer.Controllers
vm.Config = ProtectString(strategy.ToJson());
ModelState.Remove(nameof(vm.Config));
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, BitcoinPaymentType.Instance);
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
var storeBlob = store.GetStoreBlob();
if (vm.Confirmation)
{
@ -250,7 +251,7 @@ namespace BTCPayServer.Controllers
CryptoCode = cryptoCode,
Method = method,
SetupRequest = request,
Confirmation = string.IsNullOrEmpty(request.ExistingMnemonic),
Confirmation = !isImport,
Network = network,
Source = isImport ? "SeedImported" : "NBXplorerGenerated",
IsHotWallet = isImport ? request.SavePrivateKeys : method == WalletSetupMethod.HotWallet,
@ -311,7 +312,7 @@ namespace BTCPayServer.Controllers
var result = await UpdateWallet(vm);
if (!ModelState.IsValid || !(result is RedirectToActionResult))
if (!ModelState.IsValid || result is not RedirectToActionResult)
return result;
if (!isImport)
@ -719,7 +720,7 @@ namespace BTCPayServer.Controllers
return NotFound();
}
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, BitcoinPaymentType.Instance);
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
store.SetSupportedPaymentMethod(paymentMethodId, null);
await _Repo.UpdateStore(store);
@ -768,7 +769,7 @@ namespace BTCPayServer.Controllers
private DerivationSchemeSettings GetExistingDerivationStrategy(string cryptoCode, StoreData store)
{
var id = new PaymentMethodId(cryptoCode, BitcoinPaymentType.Instance);
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
var existing = store.GetSupportedPaymentMethods(_NetworkProvider)
.OfType<DerivationSchemeSettings>()
.FirstOrDefault(d => d.PaymentId == id);

@ -373,7 +373,7 @@ namespace BTCPayServer.Controllers
vm.PaymentMethodCriteria = CurrentStore.GetSupportedPaymentMethods(_NetworkProvider)
.Where(s => !storeBlob.GetExcludedPaymentMethods().Match(s.PaymentId))
.Where(s => _NetworkProvider.GetNetwork(s.PaymentId.CryptoCode) != null)
.Where(s => s.PaymentId.PaymentType != LNURLPayPaymentType.Instance)
.Where(s => s.PaymentId.PaymentType != PaymentTypes.LNURLPay)
.Select(method =>
{
var existing = storeBlob.PaymentMethodCriteria.SingleOrDefault(criteria =>
@ -440,8 +440,8 @@ namespace BTCPayServer.Controllers
var defaultChoice = defaultPaymentId is not null ? defaultPaymentId.FindNearest(enabled) : null;
if (defaultChoice is null)
{
defaultChoice = enabled.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == BitcoinPaymentType.Instance) ??
enabled.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == LightningPaymentType.Instance) ??
defaultChoice = enabled.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == PaymentTypes.BTCLike) ??
enabled.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == PaymentTypes.LightningLike) ??
enabled.FirstOrDefault();
}
var choices = GetEnabledPaymentMethodChoices(storeData);
@ -485,15 +485,15 @@ namespace BTCPayServer.Controllers
foreach (var newCriteria in model.PaymentMethodCriteria.ToList())
{
var paymentMethodId = PaymentMethodId.Parse(newCriteria.PaymentMethod);
if (paymentMethodId.PaymentType == LightningPaymentType.Instance)
if (paymentMethodId.PaymentType == PaymentTypes.LightningLike)
model.PaymentMethodCriteria.Add(new PaymentMethodCriteriaViewModel()
{
PaymentMethod = new PaymentMethodId(paymentMethodId.CryptoCode, LNURLPayPaymentType.Instance).ToString(),
PaymentMethod = new PaymentMethodId(paymentMethodId.CryptoCode, PaymentTypes.LNURLPay).ToString(),
Type = newCriteria.Type,
Value = newCriteria.Value
});
// Should not be able to set LNUrlPay criteria directly in UI
if (paymentMethodId.PaymentType == LNURLPayPaymentType.Instance)
if (paymentMethodId.PaymentType == PaymentTypes.LNURLPay)
model.PaymentMethodCriteria.Remove(newCriteria);
}
blob.PaymentMethodCriteria ??= new List<PaymentMethodCriteria>();

@ -398,7 +398,7 @@ askdevice:
var paymentMethod = CurrentStore
.GetSupportedPaymentMethods(Networks)
.OfType<DerivationSchemeSettings>()
.FirstOrDefault(p => p.PaymentId.PaymentType == Payments.BitcoinPaymentType.Instance && p.PaymentId.CryptoCode == walletId.CryptoCode);
.FirstOrDefault(p => p.PaymentId.PaymentType == Payments.PaymentTypes.BTCLike && p.PaymentId.CryptoCode == walletId.CryptoCode);
return paymentMethod;
}
}

@ -66,7 +66,6 @@ namespace BTCPayServer.Controllers
private readonly DelayedTransactionBroadcaster _broadcaster;
private readonly PayjoinClient _payjoinClient;
private readonly LabelService _labelService;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
private readonly PullPaymentHostedService _pullPaymentHostedService;
private readonly WalletHistogramService _walletHistogramService;
@ -90,12 +89,10 @@ namespace BTCPayServer.Controllers
PayjoinClient payjoinClient,
IServiceProvider serviceProvider,
PullPaymentHostedService pullPaymentHostedService,
LabelService labelService,
PaymentTypeRegistry paymentTypeRegistry)
LabelService labelService)
{
_currencyTable = currencyTable;
_labelService = labelService;
_paymentTypeRegistry = paymentTypeRegistry;
Repository = repo;
WalletRepository = walletRepository;
RateFetcher = rateProvider;
@ -174,7 +171,7 @@ namespace BTCPayServer.Controllers
var stores = await Repository.GetStoresByUserId(GetUserId());
var onChainWallets = stores
.SelectMany(s => s.GetSupportedPaymentMethods(NetworkProvider, _paymentTypeRegistry)
.SelectMany(s => s.GetSupportedPaymentMethods(NetworkProvider)
.OfType<DerivationSchemeSettings>()
.Select(d => ((Wallet: _walletProvider.GetWallet(d.Network),
DerivationStrategy: d.AccountDerivation,
@ -215,7 +212,9 @@ namespace BTCPayServer.Controllers
WalletId walletId,
string? labelFilter = null,
int skip = 0,
int count = 50
int count = 50,
bool loadTransactions = false,
CancellationToken cancellationToken = default
)
{
var paymentMethod = GetDerivationSchemeSettings(walletId);
@ -226,25 +225,25 @@ namespace BTCPayServer.Controllers
// We can't filter at the database level if we need to apply label filter
var preFiltering = string.IsNullOrEmpty(labelFilter);
var transactions = await wallet.FetchTransactionHistory(paymentMethod.AccountDerivation, preFiltering ? skip : null, preFiltering ? count : null);
var walletTransactionsInfo = await WalletRepository.GetWalletTransactionsInfo(walletId, transactions.Select(t => t.TransactionId.ToString()).ToArray());
var model = new ListTransactionsViewModel { Skip = skip, Count = count };
model.Labels.AddRange(
(await WalletRepository.GetWalletLabels(walletId))
.Select(c => (c.Label, c.Color, ColorPalette.Default.TextColor(c.Color))));
IList<TransactionHistoryLine>? transactions = null;
Dictionary<string, WalletTransactionInfo>? walletTransactionsInfo = null;
if (loadTransactions)
{
transactions = await wallet.FetchTransactionHistory(paymentMethod.AccountDerivation, preFiltering ? skip : null, preFiltering ? count : null, cancellationToken: cancellationToken);
walletTransactionsInfo = await WalletRepository.GetWalletTransactionsInfo(walletId, transactions.Select(t => t.TransactionId.ToString()).ToArray());
}
if (labelFilter != null)
{
model.PaginationQuery = new Dictionary<string, object> { { "labelFilter", labelFilter } };
}
if (transactions == null)
if (transactions == null || walletTransactionsInfo is null)
{
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message =
"There was an error retrieving the transactions list. Is NBXplorer configured correctly?"
});
model.Transactions = new List<ListTransactionsViewModel.TransactionViewModel>();
}
else
@ -1314,7 +1313,7 @@ namespace BTCPayServer.Controllers
[HttpGet("{walletId}/export")]
public async Task<IActionResult> Export(
[ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId,
string format, string? labelFilter = null)
string format, string? labelFilter = null, CancellationToken cancellationToken = default)
{
var paymentMethod = GetDerivationSchemeSettings(walletId);
if (paymentMethod == null)
@ -1322,7 +1321,7 @@ namespace BTCPayServer.Controllers
var wallet = _walletProvider.GetWallet(paymentMethod.Network);
var walletTransactionsInfoAsync = WalletRepository.GetWalletTransactionsInfo(walletId, (string[]?)null);
var input = await wallet.FetchTransactionHistory(paymentMethod.AccountDerivation);
var input = await wallet.FetchTransactionHistory(paymentMethod.AccountDerivation, cancellationToken: cancellationToken);
var walletTransactionsInfo = await walletTransactionsInfoAsync;
var export = new TransactionsExport(wallet, walletTransactionsInfo);
var res = export.Process(input, format);
@ -1408,7 +1407,7 @@ namespace BTCPayServer.Controllers
private string GetImage(PaymentMethodId paymentMethodId, BTCPayNetwork network)
{
var res = paymentMethodId.PaymentType == BitcoinPaymentType.Instance
var res = paymentMethodId.PaymentType == PaymentTypes.BTCLike
? Url.Content(network.CryptoImagePath)
: Url.Content(network.LightningImagePath);
return Request.GetRelativePathOrAbsolute(res);

@ -20,16 +20,16 @@ namespace BTCPayServer.Data
addressInvoiceData.Address = address + "#" + paymentMethodId.ToString();
return addressInvoiceData;
}
public static PaymentMethodId GetPaymentMethodId(this AddressInvoiceData addressInvoiceData, PaymentTypeRegistry paymentTypeRegistry)
public static PaymentMethodId GetPaymentMethodId(this AddressInvoiceData addressInvoiceData)
{
if (addressInvoiceData.Address == null)
return null;
var index = addressInvoiceData.Address.LastIndexOf("#", StringComparison.InvariantCulture);
// Legacy AddressInvoiceData does not have the paymentMethodId attached to the Address
if (index == -1)
return paymentTypeRegistry.ParsePaymentMethod("BTC");
return PaymentMethodId.Parse("BTC");
/////////////////////////
return paymentTypeRegistry.ParsePaymentMethod(addressInvoiceData.Address.Substring(index + 1));
return PaymentMethodId.Parse(addressInvoiceData.Address.Substring(index + 1));
}
#pragma warning restore CS0618
}

@ -38,7 +38,7 @@ namespace BTCPayServer.Data
#pragma warning restore CS0618 // Type or member is obsolete
if (paymentData.Blob2 is not null)
{
if (!_paymentTypeRegistry.TryParsePaymentMethod(paymentData.Type, out var pmi))
if (!PaymentMethodId.TryParse(paymentData.Type, out var pmi))
return null;
var network = networks.GetNetwork<BTCPayNetworkBase>(pmi.CryptoCode);
if (network is null)

@ -37,7 +37,6 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
private readonly ApplicationDbContextFactory _dbContextFactory;
private readonly NotificationSender _notificationSender;
private readonly Logs Logs;
private readonly PaymentTypeRegistry _paymentTypeRegistry;
public WalletRepository WalletRepository { get; }
@ -47,8 +46,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
BTCPayNetworkJsonSerializerSettings jsonSerializerSettings,
ApplicationDbContextFactory dbContextFactory,
NotificationSender notificationSender,
Logs logs,
PaymentTypeRegistry paymentTypeRegistry)
Logs logs)
{
_btcPayNetworkProvider = btcPayNetworkProvider;
WalletRepository = walletRepository;
@ -57,7 +55,6 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
_dbContextFactory = dbContextFactory;
_notificationSender = notificationSender;
this.Logs = logs;
_paymentTypeRegistry = paymentTypeRegistry;
}
@ -218,7 +215,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
Stores = new[] { storeId },
PayoutIds = payoutIds
}, context)).Where(data =>
_paymentTypeRegistry.TryParsePaymentMethod(data.PaymentMethodId, out var paymentMethodId) &&
PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId) &&
CanHandle(paymentMethodId))
.Select(data => (data, ParseProof(data) as PayoutTransactionOnChainBlob)).Where(tuple => tuple.Item2 != null && tuple.Item2.TransactionId != null && tuple.Item2.Accounted == false);
foreach (var valueTuple in payouts)
@ -243,7 +240,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
Stores = new[] { storeId },
PayoutIds = payoutIds
}, context)).Where(data =>
_paymentTypeRegistry.TryParsePaymentMethod(data.PaymentMethodId, out var paymentMethodId) &&
PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId) &&
CanHandle(paymentMethodId))
.Select(data => (data, ParseProof(data) as PayoutTransactionOnChainBlob)).Where(tuple => tuple.Item2 != null && tuple.Item2.TransactionId != null && tuple.Item2.Accounted == true);
foreach (var valueTuple in payouts)

@ -96,7 +96,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
{
await SetStoreContext();
var pmi = new PaymentMethodId(cryptoCode, LightningPaymentType.Instance);
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
await using var ctx = _applicationDbContextFactory.CreateContext();
var payouts = await GetPayouts(ctx, pmi, payoutIds);
@ -120,7 +120,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
{
await SetStoreContext();
var pmi = new PaymentMethodId(cryptoCode, LightningPaymentType.Instance);
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
var payoutHandler = (LightningLikePayoutHandler)_payoutHandlers.FindPayoutHandler(pmi);
await using var ctx = _applicationDbContextFactory.CreateContext();

@ -30,7 +30,7 @@ namespace BTCPayServer.Data
public static PaymentMethodId GetPaymentMethodId(this PayoutData data)
{
return _paymentTypeRegistry.TryParsePaymentMethod(data.PaymentMethodId, out var paymentMethodId) ? paymentMethodId : null;
return PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId) ? paymentMethodId : null;
}
public static PayoutBlob GetBlob(this PayoutData data, BTCPayNetworkJsonSerializerSettings serializers)

@ -199,7 +199,9 @@ namespace BTCPayServer.Data
{ "GTQ", "bitpay" },
{ "COP", "yadio" },
{ "JPY", "bitbank" },
{ "TRY", "btcturk" }
{ "TRY", "btcturk" },
{ "UGX", "exchangeratehost"},
{ "RSD", "bitpay"}
};
public string GetRecommendedExchange() =>

@ -18,25 +18,25 @@ namespace BTCPayServer.Data
{
#pragma warning disable CS0618
public static PaymentMethodId? GetDefaultPaymentId(this StoreData storeData, PaymentTypeRegistry paymentTypeRegistry)
public static PaymentMethodId? GetDefaultPaymentId(this StoreData storeData)
{
paymentTypeRegistry.TryParsePaymentMethod(storeData.DefaultCrypto, out var defaultPaymentId);
PaymentMethodId.TryParse(storeData.DefaultCrypto, out var defaultPaymentId);
return defaultPaymentId;
}
public static PaymentMethodId[] GetEnabledPaymentIds(this StoreData storeData, BTCPayNetworkProvider networks, PaymentTypeRegistry paymentTypeRegistry)
public static PaymentMethodId[] GetEnabledPaymentIds(this StoreData storeData, BTCPayNetworkProvider networks)
{
return GetEnabledPaymentMethods(storeData, networks, paymentTypeRegistry).Select(method => method.PaymentId).ToArray();
return GetEnabledPaymentMethods(storeData, networks).Select(method => method.PaymentId).ToArray();
}
public static ISupportedPaymentMethod[] GetEnabledPaymentMethods(this StoreData storeData, BTCPayNetworkProvider networks, PaymentTypeRegistry paymentTypeRegistry)
public static ISupportedPaymentMethod[] GetEnabledPaymentMethods(this StoreData storeData, BTCPayNetworkProvider networks)
{
var excludeFilter = storeData.GetStoreBlob().GetExcludedPaymentMethods();
var paymentMethodIds = storeData.GetSupportedPaymentMethods(networks, paymentTypeRegistry)
var paymentMethodIds = storeData.GetSupportedPaymentMethods(networks)
.Where(a => !excludeFilter.Match(a.PaymentId))
.OrderByDescending(a => a.PaymentId.CryptoCode == "BTC")
.ThenBy(a => a.PaymentId.CryptoCode)
.ThenBy(a => a.PaymentId.PaymentType == LightningPaymentType.Instance ? 1 : 0)
.ThenBy(a => a.PaymentId.PaymentType == PaymentTypes.LightningLike ? 1 : 0)
.ToArray();
return paymentMethodIds;
}
@ -69,7 +69,7 @@ namespace BTCPayServer.Data
return true;
}
public static IEnumerable<ISupportedPaymentMethod> GetSupportedPaymentMethods(this StoreData storeData, BTCPayNetworkProvider networks, PaymentTypeRegistry paymentTypeRegistry)
public static IEnumerable<ISupportedPaymentMethod> GetSupportedPaymentMethods(this StoreData storeData, BTCPayNetworkProvider networks)
{
ArgumentNullException.ThrowIfNull(storeData);
#pragma warning disable CS0618
@ -80,14 +80,14 @@ namespace BTCPayServer.Data
JObject strategies = JObject.Parse(storeData.DerivationStrategies);
foreach (var strat in strategies.Properties())
{
if (!paymentTypeRegistry.TryParsePaymentMethod(strat.Name, out var paymentMethodId))
if (!PaymentMethodId.TryParse(strat.Name, out var paymentMethodId))
{
continue;
}
var network = networks.GetNetwork<BTCPayNetworkBase>(paymentMethodId.CryptoCode);
if (network != null)
{
if (network == networks.BTC && paymentMethodId.PaymentType == BitcoinPaymentType.Instance && btcReturned)
if (network == networks.BTC && paymentMethodId.PaymentType == PaymentTypes.BTCLike && btcReturned)
continue;
if (strat.Value.Type == JTokenType.Null)
continue;
@ -127,8 +127,11 @@ namespace BTCPayServer.Data
bool existing = false;
foreach (var strat in strategies.Properties().ToList())
{
if (strat.Name == paymentMethodId.PaymentType.GetPaymentMethodId(paymentMethodId))
if (!PaymentMethodId.TryParse(strat.Name, out var stratId))
{
continue;
}
if (stratId == paymentMethodId)
{
if (supportedPaymentMethod == null)
{
@ -148,19 +151,19 @@ namespace BTCPayServer.Data
#pragma warning restore CS0618
}
public static bool IsLightningEnabled(this StoreData storeData, BTCPayNetworkProvider networks, string cryptoCode, PaymentTypeRegistry paymentTypeRegistry)
public static bool IsLightningEnabled(this StoreData storeData, BTCPayNetworkProvider networks, string cryptoCode)
{
return IsPaymentTypeEnabled(storeData, networks, cryptoCode, LightningPaymentType.Instance, paymentTypeRegistry);
return IsPaymentTypeEnabled(storeData, networks, cryptoCode, LightningPaymentType.Instance);
}
public static bool IsLNUrlEnabled(this StoreData storeData, BTCPayNetworkProvider networks, string cryptoCode, PaymentTypeRegistry paymentTypeRegistry)
public static bool IsLNUrlEnabled(this StoreData storeData, BTCPayNetworkProvider networks, string cryptoCode)
{
return IsPaymentTypeEnabled(storeData, networks, cryptoCode, LNURLPayPaymentType.Instance, paymentTypeRegistry);
return IsPaymentTypeEnabled(storeData, networks, cryptoCode, LNURLPayPaymentType.Instance);
}
private static bool IsPaymentTypeEnabled(this StoreData storeData, BTCPayNetworkProvider networks, string cryptoCode, PaymentType paymentType, PaymentTypeRegistry paymentTypeRegistry)
private static bool IsPaymentTypeEnabled(this StoreData storeData, BTCPayNetworkProvider networks, string cryptoCode, PaymentType paymentType)
{
var paymentMethods = storeData.GetSupportedPaymentMethods(networks, paymentTypeRegistry);
var paymentMethods = storeData.GetSupportedPaymentMethods(networks);
var excludeFilters = storeData.GetStoreBlob().GetExcludedPaymentMethods();
return paymentMethods.Any(method =>
method.PaymentId.CryptoCode == cryptoCode &&

@ -388,7 +388,7 @@ namespace BTCPayServer
public string Label { get; set; }
[JsonIgnore]
public PaymentMethodId PaymentId => new PaymentMethodId(Network.CryptoCode, BitcoinPaymentType.Instance);
public PaymentMethodId PaymentId => new PaymentMethodId(Network.CryptoCode, PaymentTypes.BTCLike);
public override string ToString()
{

@ -105,16 +105,23 @@ namespace BTCPayServer
public static decimal RoundUp(decimal value, int precision)
{
for (int i = 0; i < precision; i++)
try
{
value = value * 10m;
for (int i = 0; i < precision; i++)
{
value = value * 10m;
}
value = Math.Ceiling(value);
for (int i = 0; i < precision; i++)
{
value = value / 10m;
}
return value;
}
value = Math.Ceiling(value);
for (int i = 0; i < precision; i++)
catch (OverflowException)
{
value = value / 10m;
return value;
}
return value;
}
public static IServiceCollection AddScheduledTask<T>(this IServiceCollection services, TimeSpan every)
@ -125,9 +132,9 @@ namespace BTCPayServer
return services;
}
public static PaymentMethodId GetpaymentMethodId(this InvoiceCryptoInfo info, PaymentTypeRegistry paymentTypeRegistry)
public static PaymentMethodId GetpaymentMethodId(this InvoiceCryptoInfo info)
{
return new PaymentMethodId(info.CryptoCode, paymentTypeRegistry.Parse(info.PaymentType));
return new PaymentMethodId(info.CryptoCode, PaymentTypes.Parse(info.PaymentType));
}
public static async Task CloseSocket(this WebSocket webSocket)
@ -148,7 +155,7 @@ namespace BTCPayServer
public static IEnumerable<BitcoinLikePaymentData> GetAllBitcoinPaymentData(this InvoiceEntity invoice, bool accountedOnly)
{
return invoice.GetPayments(accountedOnly)
.Where(p => p.GetPaymentMethodId()?.PaymentType == BitcoinPaymentType.Instance)
.Where(p => p.GetPaymentMethodId()?.PaymentType == PaymentTypes.BTCLike)
.Select(p => (BitcoinLikePaymentData)p.GetCryptoPaymentData())
.Where(data => data != null);
}

@ -40,7 +40,7 @@ namespace BTCPayServer
var paymentMethod = store
.GetSupportedPaymentMethods(networkProvider)
.OfType<DerivationSchemeSettings>()
.FirstOrDefault(p => p.PaymentId.PaymentType == Payments.BitcoinPaymentType.Instance && p.PaymentId.CryptoCode == cryptoCode);
.FirstOrDefault(p => p.PaymentId.PaymentType == Payments.PaymentTypes.BTCLike && p.PaymentId.CryptoCode == cryptoCode);
return paymentMethod;
}
}

@ -108,7 +108,7 @@ namespace BTCPayServer.HostedServices
// We keep backward compatibility with bitpay by passing BTC info to the notification
// we don't pass other info, as it is a bad idea to use IPN data for logic processing (can be faked)
var btcCryptoInfo = dto.CryptoInfo.FirstOrDefault(c => c.GetpaymentMethodId() == new PaymentMethodId("BTC", Payments.BitcoinPaymentType.Instance) && !string.IsNullOrEmpty(c.Address));
var btcCryptoInfo = dto.CryptoInfo.FirstOrDefault(c => c.GetpaymentMethodId() == new PaymentMethodId("BTC", Payments.PaymentTypes.BTCLike) && !string.IsNullOrEmpty(c.Address));
if (btcCryptoInfo != null)
{
#pragma warning disable CS0618

@ -46,6 +46,8 @@ namespace BTCPayServer.HostedServices
public class PullPaymentHostedService : BaseAsyncService
{
private readonly string[] _lnurlSupportedCurrencies = { "BTC", "SATS" };
public class CancelRequest
{
public CancelRequest(string pullPaymentId)
@ -337,6 +339,14 @@ namespace BTCPayServer.HostedServices
}
}
public bool SupportsLNURL(PullPaymentBlob blob)
{
var pms = blob.SupportedPaymentMethods.FirstOrDefault(id =>
id.PaymentType == LightningPaymentType.Instance &&
_networkProvider.DefaultNetwork.CryptoCode == id.CryptoCode);
return pms is not null && _lnurlSupportedCurrencies.Contains(blob.Currency);
}
public Task<RateResult> GetRate(PayoutData payout, string explicitRateRule, CancellationToken cancellationToken)
{
var ppBlob = payout.PullPaymentData?.GetBlob();
@ -401,7 +411,7 @@ namespace BTCPayServer.HostedServices
return;
}
if (!_paymentTypeRegistry.TryParsePaymentMethod(payout.PaymentMethodId, out var paymentMethod))
if (!PaymentMethodId.TryParse(payout.PaymentMethodId, out var paymentMethod))
{
req.Completion.SetResult(new PayoutApproval.ApprovalResult(PayoutApproval.Result.NotFound, null));
return;

@ -520,6 +520,8 @@ namespace BTCPayServer.Hosting
services.AddRateProvider<BitflyerRateProvider>();
services.AddRateProvider<YadioRateProvider>();
services.AddRateProvider<BtcTurkRateProvider>();
services.AddRateProvider<FreeCurrencyRatesRateProvider>();
services.AddRateProvider<ExchangeRateHostRateProvider>();
// Broken
// Providers.Add("argoneum", new ArgoneumRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_ARGONEUM")));

@ -640,8 +640,6 @@ WHERE cte.""Id""=p.""Id""
await using var ctx = _DBContextFactory.CreateContext();
foreach (var app in await ctx.Apps.Include(data => data.StoreData).AsQueryable().ToArrayAsync())
{
ViewPointOfSaleViewModel.Item[] items;
string newTemplate;
switch (app.AppType)
{
case CrowdfundAppType.AppType:
@ -651,13 +649,6 @@ WHERE cte.""Id""=p.""Id""
settings1.TargetCurrency = app.StoreData.GetStoreBlob().DefaultCurrency;
app.SetSettings(settings1);
}
items = AppService.Parse(settings1.PerksTemplate);
newTemplate = AppService.SerializeTemplate(items);
if (settings1.PerksTemplate != newTemplate)
{
settings1.PerksTemplate = newTemplate;
app.SetSettings(settings1);
};
break;
case PointOfSaleAppType.AppType:
@ -668,13 +659,6 @@ WHERE cte.""Id""=p.""Id""
settings2.Currency = app.StoreData.GetStoreBlob().DefaultCurrency;
app.SetSettings(settings2);
}
items = AppService.Parse(settings2.Template);
newTemplate = AppService.SerializeTemplate(items);
if (settings2.Template != newTemplate)
{
settings2.Template = newTemplate;
app.SetSettings(settings2);
};
break;
}
}

@ -13,7 +13,7 @@ namespace BTCPayServer.JsonConverters
return null;
if (reader.TokenType != JsonToken.String)
throw new JsonObjectException("A payment method id should be a string", reader);
if (_paymentTypeRegistry.TryParsePaymentMethod((string)reader.Value, out var result))
if (PaymentMethodId.TryParse((string)reader.Value, out var result))
return result;
return null;
// We need to do this gracefully as we have removed support for a payment type in the past which will throw here on your store each time it is loaded.

@ -2,7 +2,6 @@ using System.Reflection;
using System.Threading.Tasks;
using BTCPayServer.Payments;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.DependencyInjection;
namespace BTCPayServer.ModelBinders
{
@ -22,8 +21,8 @@ namespace BTCPayServer.ModelBinders
{
return Task.CompletedTask;
}
var paymentTypeRegistry = bindingContext.HttpContext.RequestServices.GetRequiredService<PaymentTypeRegistry>();
if (paymentTypeRegistry.TryParsePaymentMethod(key, out var paymentId))
if (PaymentMethodId.TryParse(key, out var paymentId))
{
bindingContext.Result = ModelBindingResult.Success(paymentId);
}

@ -18,6 +18,9 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
{
public List<ViewPaymentRequestViewModel> Items { get; set; }
public override int CurrentPageCount => Items.Count;
public SearchString Search { get; set; }
public string SearchText { get; set; }
}
public class UpdatePaymentRequestViewModel

@ -1,3 +1,4 @@
using System.Collections.Generic;
using NBXplorer.Models;
namespace BTCPayServer.Models.StoreViewModels

@ -96,6 +96,7 @@ namespace BTCPayServer.Models
public DateTimeOffset StartDate { get; set; }
public DateTime LastRefreshed { get; set; }
public CurrencyData CurrencyData { get; set; }
public Uri LnurlEndpoint { get; set; }
public bool Archived { get; set; }
public bool AutoApprove { get; set; }

@ -8,7 +8,7 @@ namespace BTCPayServer.Payments.Bitcoin
{
public class BitcoinLikeOnChainPaymentMethod : IPaymentMethodDetails
{
public PaymentType GetPaymentType() => BitcoinPaymentType.Instance;
public PaymentType GetPaymentType() => PaymentTypes.BTCLike;
public string GetPaymentDestination()
{

@ -10,7 +10,7 @@ namespace BTCPayServer.Payments.Bitcoin
{
public PaymentType GetPaymentType()
{
return BitcoinPaymentType.Instance;
return PaymentTypes.BTCLike;
}
public BitcoinLikePaymentData()
{

@ -72,7 +72,7 @@ namespace BTCPayServer.Payments.Bitcoin
if (model.Activated && bip21Case)
{
var lightningInfo = invoiceResponse.CryptoInfo.FirstOrDefault(a =>
a.GetpaymentMethodId() == new PaymentMethodId(model.CryptoCode, LightningPaymentType.Instance));
a.GetpaymentMethodId() == new PaymentMethodId(model.CryptoCode, PaymentTypes.LightningLike));
if (lightningInfo is not null && !string.IsNullOrEmpty(lightningInfo.PaymentUrls?.BOLT11))
{
lightningFallback = lightningInfo.PaymentUrls.BOLT11;
@ -80,7 +80,7 @@ namespace BTCPayServer.Payments.Bitcoin
else
{
var lnurlInfo = invoiceResponse.CryptoInfo.FirstOrDefault(a =>
a.GetpaymentMethodId() == new PaymentMethodId(model.CryptoCode, LNURLPayPaymentType.Instance));
a.GetpaymentMethodId() == new PaymentMethodId(model.CryptoCode, PaymentTypes.LNURLPay));
if (lnurlInfo is not null)
{
lightningFallback = lnurlInfo.PaymentUrls?.AdditionalData["LNURLP"].ToObject<string>();
@ -169,7 +169,7 @@ namespace BTCPayServer.Payments.Bitcoin
return _networkProvider
.GetAll()
.OfType<BTCPayNetwork>()
.Select(network => new PaymentMethodId(network.CryptoCode, BitcoinPaymentType.Instance));
.Select(network => new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike));
}
private string GetPaymentMethodName(BTCPayNetworkBase network)
@ -194,7 +194,7 @@ namespace BTCPayServer.Payments.Bitcoin
};
}
public override PaymentType PaymentType => BitcoinPaymentType.Instance;
public override PaymentType PaymentType => PaymentTypes.BTCLike;
public override async Task<IPaymentMethodDetails> CreatePaymentMethodDetails(
InvoiceLogs logs,

@ -246,7 +246,7 @@ namespace BTCPayServer.Payments.Bitcoin
var paymentEntitiesByPrevOut = new Dictionary<OutPoint, PaymentEntity>();
foreach (var payment in invoice.GetPayments(wallet.Network, false))
{
if (payment.GetPaymentMethodId()?.PaymentType != BitcoinPaymentType.Instance)
if (payment.GetPaymentMethodId()?.PaymentType != PaymentTypes.BTCLike)
continue;
var paymentData = (BitcoinLikePaymentData)payment.GetCryptoPaymentData();
if (!transactions.TryGetValue(paymentData.Outpoint.Hash, out TransactionResult tx))
@ -365,7 +365,7 @@ namespace BTCPayServer.Payments.Bitcoin
var strategy = GetDerivationStrategy(invoice, network);
if (strategy == null)
continue;
var cryptoId = new PaymentMethodId(network.CryptoCode, BitcoinPaymentType.Instance);
var cryptoId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
if (!invoice.Support(cryptoId))
continue;
@ -402,7 +402,7 @@ namespace BTCPayServer.Payments.Bitcoin
private DerivationStrategyBase GetDerivationStrategy(InvoiceEntity invoice, BTCPayNetworkBase network)
{
return invoice.GetSupportedPaymentMethod<DerivationSchemeSettings>(new PaymentMethodId(network.CryptoCode, BitcoinPaymentType.Instance))
return invoice.GetSupportedPaymentMethod<DerivationSchemeSettings>(new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike))
.Select(d => d.AccountDerivation)
.FirstOrDefault();
}
@ -413,7 +413,7 @@ namespace BTCPayServer.Payments.Bitcoin
invoice = (await UpdatePaymentStates(wallet, invoice.Id));
if (invoice == null)
return null;
var paymentMethod = invoice.GetPaymentMethod(wallet.Network, BitcoinPaymentType.Instance);
var paymentMethod = invoice.GetPaymentMethod(wallet.Network, PaymentTypes.BTCLike);
wallet.InvalidateCache(strategy);
_Aggregator.Publish(new InvoiceEvent(invoice, InvoiceEvent.ReceivedPayment) { Payment = payment });
return invoice;

@ -38,7 +38,7 @@ namespace BTCPayServer.Payments.Lightning
Options = options;
}
public override PaymentType PaymentType => LightningPaymentType.Instance;
public override PaymentType PaymentType => PaymentTypes.LightningLike;
private const string UriScheme = "lightning:";
@ -49,7 +49,7 @@ namespace BTCPayServer.Payments.Lightning
LNURLPaySupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, Data.StoreData store,
BTCPayNetwork network, object preparePaymentObject, IEnumerable<PaymentMethodId> invoicePaymentMethods)
{
var lnPmi = new PaymentMethodId(supportedPaymentMethod.CryptoCode, LightningPaymentType.Instance);
var lnPmi = new PaymentMethodId(supportedPaymentMethod.CryptoCode, PaymentTypes.LightningLike);
var lnSupported = store.GetSupportedPaymentMethods(_networkProvider)
.OfType<LightningSupportedPaymentMethod>()
.SingleOrDefault(method => method.PaymentId == lnPmi);
@ -76,7 +76,7 @@ namespace BTCPayServer.Payments.Lightning
.GetAll()
.OfType<BTCPayNetwork>()
.Where(network => network.NBitcoinNetwork.Consensus.SupportSegwit && network.SupportLightning)
.Select(network => new PaymentMethodId(network.CryptoCode, LNURLPayPaymentType.Instance));
.Select(network => new PaymentMethodId(network.CryptoCode, PaymentTypes.LNURLPay));
}
public override void PreparePaymentModel(PaymentModel model, InvoiceResponse invoiceResponse,

@ -10,7 +10,7 @@ namespace BTCPayServer.Payments.Lightning
public string CryptoCode { get; set; } = string.Empty;
[JsonIgnore]
public PaymentMethodId PaymentId => new PaymentMethodId(CryptoCode, LNURLPayPaymentType.Instance);
public PaymentMethodId PaymentId => new PaymentMethodId(CryptoCode, PaymentTypes.LNURLPay);
public bool UseBech32Scheme { get; set; }

@ -41,7 +41,7 @@ namespace BTCPayServer.Payments.Lightning
public PaymentType GetPaymentType()
{
return string.IsNullOrEmpty(PaymentType) ? LightningPaymentType.Instance : PaymentTypes.Parse(PaymentType);
return string.IsNullOrEmpty(PaymentType) ? PaymentTypes.LightningLike : PaymentTypes.Parse(PaymentType);
}
public string[] GetSearchTerms()

@ -9,6 +9,7 @@ using BTCPayServer.Configuration;
using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.Lightning;
using BTCPayServer.Lightning.LndHub;
using BTCPayServer.Logging;
using BTCPayServer.Models;
using BTCPayServer.Models.InvoicingModels;
@ -44,7 +45,7 @@ namespace BTCPayServer.Payments.Lightning
Options = options;
}
public override PaymentType PaymentType => LightningPaymentType.Instance;
public override PaymentType PaymentType => PaymentTypes.LightningLike;
public IOptions<LightningNetworkOptions> Options { get; }
@ -122,11 +123,16 @@ namespace BTCPayServer.Payments.Lightning
{
if (!_Dashboard.IsFullySynched(network.CryptoCode, out var summary))
throw new PaymentMethodUnavailableException("Full node not available");
try
{
using var cts = new CancellationTokenSource(LightningTimeout);
var client = CreateLightningClient(supportedPaymentMethod, network);
// LNDhub-compatible implementations might not offer all of GetInfo data.
// Skip checks in those cases, see https://github.com/lnbits/lnbits/issues/1182
var isLndHub = client is LndHubLightningClient;
LightningNodeInformation info;
try
{
@ -136,6 +142,10 @@ namespace BTCPayServer.Payments.Lightning
{
throw new PaymentMethodUnavailableException("The lightning node did not reply in a timely manner");
}
catch (NotSupportedException) when (isLndHub)
{
return new NodeInfo[] {};
}
catch (Exception ex)
{
throw new PaymentMethodUnavailableException($"Error while connecting to the API: {ex.Message}" +
@ -146,9 +156,9 @@ namespace BTCPayServer.Payments.Lightning
var nodeInfo = preferOnion != null && info.NodeInfoList.Any(i => i.IsTor == preferOnion)
? info.NodeInfoList.Where(i => i.IsTor == preferOnion.Value).ToArray()
: info.NodeInfoList.Select(i => i).ToArray();
var blocksGap = summary.Status.ChainHeight - info.BlockHeight;
if (blocksGap > 10)
if (blocksGap > 10 && !(isLndHub && info.BlockHeight == 0))
{
throw new PaymentMethodUnavailableException($"The lightning node is not synched ({blocksGap} blocks left)");
}
@ -199,7 +209,7 @@ namespace BTCPayServer.Payments.Lightning
.GetAll()
.OfType<BTCPayNetwork>()
.Where(network => network.NBitcoinNetwork.Consensus.SupportSegwit && network.SupportLightning)
.Select(network => new PaymentMethodId(network.CryptoCode, LightningPaymentType.Instance));
.Select(network => new PaymentMethodId(network.CryptoCode, PaymentTypes.LightningLike));
}
public override void PreparePaymentModel(PaymentModel model, InvoiceResponse invoiceResponse,

@ -3,6 +3,7 @@ using System.Linq;
using BTCPayServer.Lightning;
using BTCPayServer.Services.Invoices;
using NBitcoin;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Payments.Lightning
@ -10,7 +11,9 @@ namespace BTCPayServer.Payments.Lightning
public class LightningLikePaymentMethodDetails : IPaymentMethodDetails
{
public string BOLT11 { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.UInt256JsonConverter))]
public uint256 PaymentHash { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.UInt256JsonConverter))]
public uint256 Preimage { get; set; }
public string InvoiceId { get; set; }
public string NodeInfo { get; set; }
@ -27,7 +30,7 @@ namespace BTCPayServer.Payments.Lightning
public virtual PaymentType GetPaymentType()
{
return LightningPaymentType.Instance;
return PaymentTypes.LightningLike;
}
public decimal GetNextNetworkFee()

@ -144,7 +144,7 @@ namespace BTCPayServer.Payments.Lightning
{
var listenedInvoices = new List<ListenedInvoice>();
foreach (var paymentMethod in invoice.GetPaymentMethods()
.Where(c => new[] { LightningPaymentType.Instance, LNURLPayPaymentType.Instance }.Contains(c.GetId().PaymentType)))
.Where(c => new[] { PaymentTypes.LightningLike, LNURLPayPaymentType.Instance }.Contains(c.GetId().PaymentType)))
{
LightningLikePaymentMethodDetails lightningMethod;
LightningSupportedPaymentMethod? lightningSupportedMethod;
@ -264,7 +264,7 @@ namespace BTCPayServer.Payments.Lightning
private async Task CreateNewLNInvoiceForBTCPayInvoice(InvoiceEntity invoice)
{
var paymentMethods = invoice.GetPaymentMethods()
.Where(method => new[] { LightningPaymentType.Instance, LNURLPayPaymentType.Instance }.Contains(method.GetId().PaymentType))
.Where(method => new[] { PaymentTypes.LightningLike, LNURLPayPaymentType.Instance }.Contains(method.GetId().PaymentType))
.ToArray();
var store = await _storeRepository.FindStore(invoice.StoreId);
if (store is null)

@ -53,7 +53,7 @@ public class LightningPendingPayoutListener : BaseAsyncService
var networks = _networkProvider.GetAll()
.OfType<BTCPayNetwork>()
.Where(network => network.SupportLightning)
.ToDictionary(network => new PaymentMethodId(network.CryptoCode, LightningPaymentType.Instance));
.ToDictionary(network => new PaymentMethodId(network.CryptoCode, PaymentTypes.LightningLike));
var payouts = await PullPaymentHostedService.GetPayouts(

@ -11,7 +11,7 @@ namespace BTCPayServer.Payments.Lightning
public string CryptoCode { get; set; } = string.Empty;
[JsonIgnore]
public PaymentMethodId PaymentId => new PaymentMethodId(CryptoCode, LightningPaymentType.Instance);
public PaymentMethodId PaymentId => new PaymentMethodId(CryptoCode, PaymentTypes.LightningLike);
[Obsolete("Use Get/SetLightningUrl")]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]

@ -246,7 +246,7 @@ namespace BTCPayServer.Payments.PayJoin
$"Provided transaction isn't mempool eligible {mempool.RPCCodeMessage}"));
}
var enforcedLowR = ctx.OriginalTransaction.Inputs.All(IsLowR);
var paymentMethodId = new PaymentMethodId(network.CryptoCode, BitcoinPaymentType.Instance);
var paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
Money? due = null;
Dictionary<OutPoint, UTXO> selectedUTXOs = new Dictionary<OutPoint, UTXO>();
PSBTOutput? originalPaymentOutput = null;

@ -8,7 +8,7 @@ namespace BTCPayServer.Payments
public static JToken Serialize(ISupportedPaymentMethod factory)
{
// Legacy
if (factory.PaymentId.PaymentType == BitcoinPaymentType.Instance)
if (factory.PaymentId.PaymentType == PaymentTypes.BTCLike)
{
var derivation = (DerivationSchemeSettings)factory;
var str = derivation.Network.NBXplorerNetwork.Serializer.ToString(derivation);

@ -30,7 +30,7 @@ namespace BTCPayServer.Payments
{
get
{
return CryptoCode == "BTC" && PaymentType == BitcoinPaymentType.Instance;
return CryptoCode == "BTC" && PaymentType == PaymentTypes.BTCLike;
}
}
@ -68,7 +68,7 @@ namespace BTCPayServer.Payments
public override string ToString()
{
//BTCLike case is special because it is in legacy mode.
return PaymentType == BitcoinPaymentType.Instance ? CryptoCode : $"{CryptoCode}_{PaymentType}";
return PaymentType == PaymentTypes.BTCLike ? CryptoCode : $"{CryptoCode}_{PaymentType}";
}
/// <summary>
/// A string we can expose to Greenfield API, not subjected to internal legacy
@ -76,13 +76,54 @@ namespace BTCPayServer.Payments
/// <returns></returns>
public string ToStringNormalized()
{
return PaymentType.GetPaymentMethodId(this);
if (PaymentType == PaymentTypes.BTCLike)
return CryptoCode;
#if ALTCOINS
if (CryptoCode == "XMR" && PaymentType == PaymentTypes.MoneroLike)
return CryptoCode;
if ((CryptoCode == "YEC" || CryptoCode == "ZEC") && PaymentType == PaymentTypes.ZcashLike)
return CryptoCode;
#endif
return $"{CryptoCode}-{PaymentType.ToStringNormalized()}";
}
public string ToPrettyString()
{
return $"{CryptoCode} ({PaymentType.ToPrettyString()})";
}
static char[] Separators = new[] { '_', '-' };
public static PaymentMethodId? TryParse(string? str)
{
TryParse(str, out var r);
return r;
}
public static bool TryParse(string? str, [MaybeNullWhen(false)] out PaymentMethodId paymentMethodId)
{
str ??= "";
paymentMethodId = null;
var parts = str.Split(Separators, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0 || parts.Length > 2)
return false;
PaymentType type = PaymentTypes.BTCLike;
#if ALTCOINS
if (parts[0].ToUpperInvariant() == "XMR")
type = PaymentTypes.MoneroLike;
if (parts[0].ToUpperInvariant() == "ZEC")
type = PaymentTypes.ZcashLike;
#endif
if (parts.Length == 2)
{
if (!PaymentTypes.TryParse(parts[1], out type))
return false;
}
paymentMethodId = new PaymentMethodId(parts[0], type);
return true;
}
public static PaymentMethodId Parse(string str)
{
if (!TryParse(str, out var result))
throw new FormatException("Invalid PaymentMethodId");
return result;
}
}
}

@ -1,57 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using AngleSharp.Common;
namespace BTCPayServer.Payments;
public class PaymentTypeRegistry
{
static char[] Separators = new[] { '_', '-' };
private readonly PaymentType[] _paymentTypes;
public PaymentTypeRegistry(IEnumerable<PaymentType> paymentTypes)
{
_paymentTypes = paymentTypes.ToArray();
}
public bool TryParse(string paymentType, out PaymentType type)
{
type = _paymentTypes.FirstOrDefault(type1 => type1.IsPaymentType(paymentType));
return type != null;
}
public PaymentType Parse(string paymentType)
{
if (!TryParse(paymentType, out var result))
throw new FormatException("Invalid payment type");
return result;
}
public PaymentMethodId TryParsePaymentMethod(string? str)
{
return TryParsePaymentMethod(str, out var result) ? result : null;
}
public bool TryParsePaymentMethod(string? str, [MaybeNullWhen(false)] out PaymentMethodId paymentMethodId)
{
str ??= "";
paymentMethodId = null;
var parts = str.Split(Separators, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0 || parts.Length > 2)
return false;
if (TryParse(parts.Last(), out var type))
{
paymentMethodId = new PaymentMethodId(parts[0], type);
return true;
}
return false;
}
public PaymentMethodId ParsePaymentMethod(string str)
{
if (!TryParsePaymentMethod(str, out var result))
throw new FormatException("Invalid PaymentMethodId");
return result;
}
}

@ -10,22 +10,58 @@ using Newtonsoft.Json.Linq;
namespace BTCPayServer.Payments
{
/// <summary>
/// The different ways to pay an invoice
/// </summary>
public static class PaymentTypes
{
private static PaymentType[] _paymentTypes =
{
BTCLike, LightningLike, LNURLPay,
#if ALTCOINS
MoneroLike, ZcashLike,
#endif
};
/// <summary>
/// On-Chain UTXO based, bitcoin compatible
/// </summary>
public static BitcoinPaymentType BTCLike => BitcoinPaymentType.Instance;
/// <summary>
/// Lightning payment
/// </summary>
public static LightningPaymentType LightningLike => LightningPaymentType.Instance;
/// <summary>
/// Lightning payment
/// </summary>
public static LNURLPayPaymentType LNURLPay => LNURLPayPaymentType.Instance;
#if ALTCOINS
/// <summary>
/// Monero payment
/// </summary>
public static MoneroPaymentType MoneroLike => MoneroPaymentType.Instance;
/// <summary>
/// Zcash payment
/// </summary>
public static ZcashPaymentType ZcashLike => ZcashPaymentType.Instance;
#endif
public static bool TryParse(string paymentType, out PaymentType type)
{
type = _paymentTypes.FirstOrDefault(type1 => type1.IsPaymentType(paymentType));
return type != null;
}
public static PaymentType Parse(string paymentType)
{
if (!TryParse(paymentType, out var result))
throw new FormatException("Invalid payment type");
return result;
}
}
public abstract class PaymentType
{
public abstract string ToPrettyString();
public virtual string GetPaymentMethodId(PaymentMethodId paymentMethodId)
{
if (paymentMethodId.PaymentType == BitcoinPaymentType.Instance)
return paymentMethodId.CryptoCode;
#if ALTCOINS
if (paymentMethodId.CryptoCode == "XMR" && paymentMethodId.PaymentType == MoneroPaymentType.Instance)
return paymentMethodId.CryptoCode;
if ((paymentMethodId.CryptoCode == "YEC" || paymentMethodId.CryptoCode == "ZEC") && paymentMethodId.PaymentType == ZcashPaymentType.Instance)
return paymentMethodId.CryptoCode;
#endif
return $"{paymentMethodId.CryptoCode}-{paymentMethodId.PaymentType.ToStringNormalized()}";
}
public override string ToString()
{
return GetId();

@ -17,7 +17,6 @@ using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using NBitpayClient;
@ -267,6 +266,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
if (app == null)
return NotFound();
vm.AppId = app.Id;
vm.TargetCurrency = await GetStoreDefaultCurrentIfEmpty(app.StoreDataId, vm.TargetCurrency);
if (_currencies.GetCurrencyData(vm.TargetCurrency, false) == null)
ModelState.AddModelError(nameof(vm.TargetCurrency), "Invalid currency");

@ -55,8 +55,8 @@ namespace BTCPayServer.Plugins.NFC
var methods = invoice.GetPaymentMethods();
PaymentMethod lnPaymentMethod = null;
if (!methods.TryGetValue(new PaymentMethodId("BTC", LNURLPayPaymentType.Instance), out var lnurlPaymentMethod) &&
!methods.TryGetValue(new PaymentMethodId("BTC", LightningPaymentType.Instance), out lnPaymentMethod))
if (!methods.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.LNURLPay), out var lnurlPaymentMethod) &&
!methods.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.LightningLike), out lnPaymentMethod))
{
return BadRequest("Destination for LNURL-Withdraw was not specified");
}

@ -60,6 +60,11 @@ namespace BTCPayServer.Plugins.PayButton.Controllers
}
var apps = await _appService.GetAllApps(_userManager.GetUserId(User), false, store.Id);
// unset app store data, because we don't need it and inclusion leads to circular references when serializing to JSON
foreach (var app in apps)
{
app.App.StoreData = null;
}
var appUrl = HttpContext.Request.GetAbsoluteRoot().WithTrailingSlash();
var model = new PayButtonViewModel
{

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