Compare commits

..

77 Commits

Author SHA1 Message Date
85a3dc4848 Plugins: Recommended plugins and github Remote config options
This allows external integrations ( btcpay docker fragments) to highlight specific plugins as recommended to be installed. Also moved the remote option to  a config option instead of a url query param to avoid messy situations where users could get deceived with a generated url. The dockerfiles also have an additional csproj to build and the plugin dir was renamed correctly from extensions to plugins
2020-11-04 08:17:39 +01:00
493b10393b Add warning for zero-conf accept option (#2038)
* Add warning for zero-conf accept option

close #2003

* Explicitly get element by id
2020-11-03 06:55:45 +01:00
b406f52670 Add warning to configure e-mail server (#2024)
Adds a warning to configure the e-mail server before "Requires a confirmation mail for registering" checkbox can be checked if e-mail server is not configured.

close #1889
2020-11-03 06:53:49 +01:00
ef3f314754 Remove QuadrigaCX references (#2021)
* Remove QuadrigaCX references

Quadriga has been dead for years. I've removed the references to them and replaced them with a different Canadian exchange (ndax) for example. 

The whole set of instructions could probably use an overhaul, but for now, at least let's get rid of Gerry's presence.

* Remove Quadriga from tests too

Co-authored-by: Kukks <evilkukka@gmail.com>
2020-11-02 13:10:55 +01:00
d2910686cd Fix link from app label (#2028) 2020-11-02 12:26:11 +01:00
793b1b56d9 Fix exception when saving checkout experience on new store (#2033) 2020-10-31 11:34:40 +01:00
d8f9075e2a Fix NRE 2020-10-30 00:19:23 +09:00
1199d4ead9 Merge pull request #2031 from bkiac/master
Fix failing generateaddress when address contains escape sequences in docker-bitcoin-generate.sh
2020-10-29 23:58:00 +09:00
4520e1bb3e Fix potential NRE in polling 2020-10-29 23:17:46 +09:00
afece8193e Fix possibly dirty generated bitcoin address 2020-10-29 14:16:23 +01:00
3b14585d1a Merge pull request #2027 from btcpayserver/missing-cs
Fix missing altcoins tab
2020-10-29 16:28:49 +09:00
9cd32a908f Fix missing altcoins tab 2020-10-28 18:04:19 +01:00
b9ca02088d Make a batch query for all pending invoice (Fix #2022) 2020-10-28 23:21:46 +09:00
440ce0221a Revert "Add thread limit for updating payment states for payment invoices in NBXplorerListener"
This reverts commit 5d9827fb6032a2716eb87ecdc3ce62d53c74e055.
2020-10-28 23:07:30 +09:00
caff7eda9f Revert "Add missing async/await keywords"
This reverts commit 8d0260b64490018c907f996d62f10b54c509dbc8.
2020-10-28 23:07:27 +09:00
aef1cefc18 changelog 1.0.5.9 2020-10-28 19:24:24 +09:00
1385c7cc9b Merge pull request #2023 from Rheopyrin/master
Add thread limit for updating payment states for payment invoices in NBXplorerListener
2020-10-28 18:43:50 +09:00
8d0260b644 Add missing async/await keywords 2020-10-27 21:17:56 +02:00
5d9827fb60 Add thread limit for updating payment states for payment invoices in NBXplorerListener 2020-10-27 21:09:53 +02:00
2083954aa5 add comment 2020-10-27 08:19:41 +01:00
66af258876 fix tests 2020-10-27 08:00:34 +01:00
4ba04031ef Fix specter image 2020-10-27 14:59:07 +09:00
a30456a92d Fix warnings 2020-10-26 14:19:05 +09:00
c8dd13577e Add nuget publishing for plugin packaer and abstractions (#2018) 2020-10-25 00:21:50 +09:00
fac35b46bb Display link for Pay button (#2017)
fixes #635
2020-10-24 23:52:39 +09:00
748cb778e0 Fix redirect when in modal (#2015)
fixes #2012
2020-10-24 23:47:05 +09:00
bbcca24bcc Fix typos and improve wording & formatting (#2013) 2020-10-24 23:46:35 +09:00
b8c52b1120 fix decimal entry in payment requests (#2016)
fixes #2014
2020-10-24 12:25:50 +02:00
b1b3ce48ee Toast messages in payment request (#2010)
* Hide toast messages in print

Fixes #2009.

* Optimize payment request toast messages
2020-10-24 10:20:19 +02:00
da864f4c6c bump nbx and nbitcoin 2020-10-24 15:27:48 +09:00
758f627e12 Partially paid invoices should be reused in payment requests. Cleanup the code. (#2008) 2020-10-23 21:00:23 +09:00
20322c6ab8 Improve payment print styles (#1977)
* Improve payment print styles

Allows for export as invoice PDF to be used in accounting. Closes #1957.

* Change Transaction ID wording

* Minor payment request UI improvements

* Add amount paid, rate and colorize payment status

* Display rate at invoice level

* Inherit text color in print

* Show full date in print view

* Rearrange payment details

* Add received date for payments

* Fix amount calculation

* Fix validInvoice assignment
2020-10-23 17:37:28 +09:00
7a711f0690 Merge pull request #1983 from Kukks/fix-email-search
fix email not included in textsearch
2020-10-23 17:26:10 +09:00
067b977ec8 Do not include maxadditionalfeecontribution if there is no change. (#2007) 2020-10-23 11:57:06 +09:00
58f0ca3d8a Improve plugins UI (#2005) 2020-10-22 10:58:22 +02:00
4176f3659b Add QR code scan/show for PSBT + Import wallet via QR (#1931)
* Add PSBT QR code scan/show

This PR introduces support to show and read PSBTs in BC-UR format via animated QR codes.  This allows you to use BTCPay with HW devices such as Cobo Vault and Blue wallet to sign transactions without ever exposing the keys outside of that device.
Spec: https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-005-ur.md
I've also bumped the QR code library we sue as it had a bug with large datasets.

* Reuse same code for all and allow wallet import via QR code scan

* remove unecessary js vendor files

* Allow export wallet from settings via QR

* formatting

* bundle

* fix wallet receive bundle
2020-10-21 14:03:11 +02:00
5979fe5eef BTCPay Extensions Part 2 (#2001)
* BTCPay Extensions Part 2

This PR cleans up the extension system a bit in that:
 * It renames the test extension to a more uniform name
 * Allows yo uto have system extensions, which are extensions but bundled by default with the release (and cannot be removed)
 * Adds a tool to help you generate an extension package from a csproj
 * Refactors the UI extension points to a view component
 * Moves some more interfaces to the Abstractions csproj

* Rename to plugins
2020-10-21 14:02:20 +02:00
362ba21567 fix unclosed div 2020-10-21 10:45:24 +02:00
71894ba245 Override Block Explorer Links (#2000)
* Override Block Explorer Links

closes #1953

* load overrides after save as well

* fix js
2020-10-21 09:53:05 +02:00
2b19e0fbc6 Remove SQLite as default option (#1987)
* Remove SQLite as default option

IF MERGED, STORES WITH NO DB CONFIG WILL NOT START UNTIL CONFIG IS UPDATED WITH `sqlitefile=sqlite.db`

* remove sqlite conn string option

* toggle between abs path or relative for sqlite db

* Update DefaultConfiguration.cs
2020-10-20 13:12:10 +02:00
4d0b402e8b Allow disabling notifications per user and disabling specific notifications per user (#1991)
* Allow disabling notifications per user and disabling specific notifications per use

closes #1974

* Add disable notifs for all users

* fix term generator for notifications

* sow checkboxes instead of multiselect when js is enabled

* remove js dependency

* fix notif conditions
2020-10-20 13:09:09 +02:00
933e0c30bf Remove empty box on post redirect (#1995)
In case JS is enabled the post redirect page showed an empty box. This box contains the explanation text for the non-JS text.

This changes it to only show the modal box in casee JS is disabled, because the page – even though only visible briefly –  looks weird for users with JS enabled.
2020-10-18 11:10:51 +02:00
b430afe3e1 Merge pull request #1990 from btcpayserver/feat/store-hints
Store hints/warnings
2020-10-18 03:11:36 -05:00
2f25f1790e Trying to prevent ocassional chrome crashes
Error: The process started from chrome location /usr/bin/chromium is no longer running, so ChromeDriver is assuming that Chrome has crashed.
2020-10-18 03:11:17 -05:00
d90fcf15bd Verifying presence of wallet warning on store list 2020-10-18 03:11:17 -05:00
be9cc41957 Dismissing lightning hint when connection is setup 2020-10-18 03:11:17 -05:00
ef99eeb300 Adding selenium test for store hints 2020-10-18 03:11:17 -05:00
1646241dfb Switch to POST for dismissing hints 2020-10-18 03:11:17 -05:00
543e628a8b Removing rates hint 2020-10-18 03:11:17 -05:00
36269cbed6 Consistent styling for Back to List 2020-10-18 03:11:17 -05:00
75dcdc72d2 Showing warning hint on stores listing page 2020-10-18 03:11:17 -05:00
f5c90bebdc Support for dismissing hints 2020-10-18 03:11:17 -05:00
15af66de55 Setting hints during store creation and update 2020-10-18 03:11:17 -05:00
17c8ac8248 Moving general store settings in same section 2020-10-18 03:11:17 -05:00
e5c6b9a979 Rearranging store sections and providing warnings for new stores 2020-10-18 03:11:17 -05:00
753849cedd Merge pull request #1994 from btcpayserver/forgotpassword
fix forgot password
2020-10-18 11:54:55 +09:00
b81e4c01ec clean altcoins docker compose 2020-10-17 16:09:34 +02:00
2a32b05df1 fix forgot password
If Email verification is turned off but you requested a forgot password form, it would ignore the request internally. Seems like it has been this way since the beginning
2020-10-17 09:25:48 +02:00
e3a0fe88c1 Fix LN invoices (#1955)
* Fix LN invoices

This commit adds more to the previous LN fix in the case of a partial payment to an invoice. While it generated a new LN invoice after 1 partial payment was made, there were some new issues uncovered:
* Any other subsequent partial payments was not listened to  and did not generate an invoice ( fixed by listeneing to received payment event and makng sure that the status was already set `to partialPaid`)
* Any other subsequent partial payments caused a DbConcurrency error and did not generate an invoice ( Fixed in `MarkUnassigned`)
2020-10-17 08:57:21 +02:00
ee3aa49eee Merge pull request #1993 from dennisreimann/api-keys-ui
API Keys UI: Properly align form items
2020-10-17 14:38:14 +09:00
dd27ad79bd API Keys UI: Properly align form items 2020-10-16 22:31:09 +02:00
cfd0f556a5 Update changelog 2020-10-16 20:44:41 +09:00
347e70f4ea bump 2020-10-16 20:44:32 +09:00
828aae35ce Revert "The send wallet, by default, include the previous transaction"
This reverts commit 0f743cec4132c7f97a2e94263c044ddefbdccf9b.
2020-10-16 20:29:29 +09:00
2f56783b7e fix domain mapping bug (#1992)
fixes #1988
2020-10-16 19:59:01 +09:00
dc4ecdaa38 better handling on remote being down 2020-10-15 15:04:11 +02:00
1440e8c55d BTCPay Server Extensions (#1925)
* BTCPay Server Extensions

![demo](https://i.imgur.com/2S00aL2.gif)

* cleanup

* fix

* Polish UI a bit,detect when docker deployment
2020-10-15 21:28:09 +09:00
51a072808f If a password fail to be reset by mail, show proper error (fix #1986) 2020-10-15 15:36:42 +09:00
0a8c2926ea reword review step for vault 2020-10-14 19:57:27 +09:00
5e79f567a7 Merge pull request #1982 from Kukks/hide-conn-string
Do not log the database connection string
2020-10-14 19:23:04 +09:00
e2eb26eb93 Use base65 instead of hex for BIP78 (#1985)
fixes #1984
2020-10-14 12:01:21 +02:00
740a50a26d fix email not included in textsearch
fixes #1979
2020-10-14 09:16:31 +02:00
be3f248a5a Do not log the database connection string
fixes #1980
2020-10-14 09:06:50 +02:00
f2870caed2 Payment redesign (#1967)
* Payment redesign

Guess who's back!

This reverts commit 4174fa648d994e098d8555bca0a5b3a255541cb5.

* Refactor PullPayment state string

Compatible with this one: https://github.com/btcpayserver/btcpayserver/pull/1834/files#diff-a9136096252382b110b9a7ac7747b95aR41

* Use unified copy to clipboard function

* Refactor status text class to helper function
2020-10-13 09:58:46 +02:00
ad22d3fd91 Custom redirect_url for PoS (#1924) 2020-10-13 09:51:28 +02:00
9dcd8b6edf Checkout: Overlay improvement and markup fixes (#1968)
* Checkout: Markup fixes

* Checkout: Less translucent overlay

More focus, as discussed in #1930: https://github.com/btcpayserver/btcpayserver/pull/1930#issuecomment-701298441
2020-10-12 17:52:21 +02:00
9607e0e55d Update PayButtonEnable.cshtml (#1952)
* Update PayButtonEnable.cshtml

Edit PayButton Title

* Update BTCPayServer/Views/Stores/PayButtonEnable.cshtml

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

* Apply suggestions from code review

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2020-10-11 17:46:11 +02:00
180 changed files with 3845 additions and 12033 deletions

View File

@ -24,15 +24,15 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.
**Your BTCPay Environment (please complete the following information):**
- BTCPay Server Version [available in the right bottom corner of footer]
- Deployment Method: [e.g. Docker, Manual, Third-Party-hoist]
- Browser [e.g. chrome, safari]
- BTCPay Server Version: [available in the right bottom corner of footer]
- Deployment Method: [e.g. Docker, Manual, Third-Party-host]
- Browser: [e.g. Chrome, Safari]
**Logs (if applicable)**
Basic logs can be found in Server Settings > Logs. More logs https://docs.btcpayserver.org/Troubleshooting/#2-looking-through-the-logs
**Setup Parameters**
If you're reporting a deployment issue run `. btcpay-setup.sh -i` and paste your the parameters by obscuring private information.
If you're reporting a deployment issue run `. btcpay-setup.sh -i` and paste the setup parameters here with your private information removed or obscured.
**Additional context**
Add any other context about the problem here.

1
.gitignore vendored
View File

@ -298,3 +298,4 @@ BTCPayServer/wwwroot/bundles/*
!.vscode/extensions.json
BTCPayServer/testpwd
.DS_Store
Packed Plugins

View File

@ -0,0 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Build and pack plugins" type="CompoundRunConfigurationType">
<toRun name="Pack Test Plugin" type="DotNetProject" />
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,21 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Pack Test Plugin" type="DotNetProject" factoryName=".NET Project" singleton="false">
<option name="EXE_PATH" value="$PROJECT_DIR$/BTCPayServer.PluginPacker/bin/Debug/netcoreapp3.1/BTCPayServer.PluginPacker.dll" />
<option name="PROGRAM_PARAMETERS" value="../../../../BTCPayServer.Plugins.Test\bin\Debug\netcoreapp3.1 BTCPayServer.Plugins.Test &quot;../../../../Packed Plugins&quot;" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/BTCPayServer.PluginPacker/bin/Debug/netcoreapp3.1" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/BTCPayServer.PluginPacker/BTCPayServer.PluginPacker.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
<method v="2">
<option name="Build" default="false" projectName="BTCPayServer.Plugins.Test" projectPath="C:\Git\btcpayserver\BTCPayServer.Plugins.Test\BTCPayServer.Plugins.Test.csproj" />
<option name="Build" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" />
<PropertyGroup>
<Company>BTCPay Server</Company>
<Copyright>Copyright © BTCPay Server 2020</Copyright>
<Description>A library for BTCPay Server plugin development</Description>
<PackageIcon>icon.png</PackageIcon>
<PackageTags>btcpay,btcpayserver</PackageTags>
<PackageProjectUrl>https://github.com/btcpayserver/btcpayserver/tree/master/BTCPayServer.Abstractions</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryUrl>https://github.com/btcpayserver/btcpayserver</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<DebugType>portable</DebugType>
<Optimize>true</Optimize>
<NoWarn>1591;1573;1572;1584;1570;3021</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup Condition=" '$(Configuration)' == 'Release' ">
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<None Include="icon.png" Pack="true" PackagePath="\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,21 @@
using System;
using System.Text.Json.Serialization;
using BTCPayServer.Abstractions.Converters;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace BTCPayServer.Contracts
{
public interface IBTCPayServerPlugin
{
public string Identifier { get; }
string Name { get; }
[JsonConverter(typeof(VersionConverter))]
Version Version { get; }
string Description { get; }
bool SystemPlugin { get; set; }
string[] Dependencies { get; }
void Execute(IApplicationBuilder applicationBuilder, IServiceProvider applicationBuilderApplicationServices);
void Execute(IServiceCollection applicationBuilder);
}
}

View File

@ -0,0 +1,27 @@
using System;
namespace BTCPayServer.Contracts
{
public abstract class BaseNotification
{
public abstract string Identifier { get; }
public abstract string NotificationType { get; }
}
public interface INotificationHandler
{
string NotificationType { get; }
Type NotificationBlobType { get; }
public (string identifier, string name)[] Meta { get; }
void FillViewModel(object notification, NotificationViewModel vm);
}
public class NotificationViewModel
{
public string Id { get; set; }
public DateTimeOffset Created { get; set; }
public string Body { get; set; }
public string ActionLink { get; set; }
public bool Seen { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using System.Threading;
using System.Threading.Tasks;
namespace BTCPayServer.Services
{
public interface ISettingsRepository
{
Task<T> GetSettingAsync<T>(string name = null);
Task UpdateSetting<T>(T obj, string name = null);
Task<T> WaitSettingsChanged<T>(CancellationToken cancellationToken = default);
}
}

View File

@ -0,0 +1,21 @@
namespace BTCPayServer.Contracts
{
public interface IUIExtension
{
string Partial { get; }
string Location { get; }
}
public class UIExtension: IUIExtension
{
public UIExtension(string partial, string location)
{
Partial = partial;
Location = location;
}
public string Partial { get; }
public string Location { get; }
}
}

View File

@ -6,5 +6,4 @@ namespace BTCPayServer.Contracts
string Partial { get; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace BTCPayServer.Abstractions.Converters
{
public class VersionConverter : JsonConverter<Version>
{
public override Version Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return Version.Parse(reader.GetString());
}
public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
}

View File

@ -0,0 +1,20 @@
using System.Text.Json;
using BTCPayServer.Models;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace BTCPayServer
{
public static class SetStatusMessageModelExtensions
{
public static void SetStatusMessageModel(this ITempDataDictionary tempData, StatusMessageModel statusMessage)
{
if (statusMessage == null)
{
tempData.Remove("StatusMessageModel");
return;
}
tempData["StatusMessageModel"] = JsonSerializer.Serialize(statusMessage, new JsonSerializerOptions());
}
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Reflection;
using BTCPayServer.Contracts;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace BTCPayServer.Models
{
public abstract class BaseBTCPayServerPlugin : IBTCPayServerPlugin
{
public abstract string Identifier { get; }
public abstract string Name { get; }
public virtual Version Version
{
get
{
return Assembly.GetAssembly(GetType())?.GetName().Version ?? new Version(1, 0, 0, 0);
}
}
public abstract string Description { get; }
public bool SystemPlugin { get; set; }
public bool SystemExtension { get; set; }
public virtual string[] Dependencies { get; } = Array.Empty<string>();
public virtual void Execute(IApplicationBuilder applicationBuilder,
IServiceProvider applicationBuilderApplicationServices)
{
}
public virtual void Execute(IServiceCollection applicationBuilder)
{
}
}
}

View File

@ -0,0 +1,7 @@
rm "bin\release\" -Recurse -Force
dotnet pack --configuration Release --include-symbols -p:SymbolPackageFormat=snupkg
$package=(ls .\bin\Release\*.nupkg).FullName
dotnet nuget push $package --source "https://api.nuget.org/v3/index.json"
$ver = ((ls .\bin\release\*.nupkg)[0].Name -replace '.*(\d+\.\d+\.\d+)\.nupkg','$1')
git tag -a "BTCPayServer.Abstractions/v$ver" -m "BTCPayServer.Abstractions/$ver"
git push origin "BTCPayServer.Abstractions/v$ver"

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -27,7 +27,7 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NBitcoin" Version="5.0.51" />
<PackageReference Include="NBitcoin" Version="5.0.60" />
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>

View File

@ -24,6 +24,8 @@ namespace BTCPayServer
var settings = new BTCPayDefaultSettings();
_Settings.Add(chainType, settings);
settings.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", NBXplorerDefaultSettings.GetFolderName(chainType));
settings.DefaultPluginDirectory =
StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", "Plugins");
settings.DefaultConfigurationFile = Path.Combine(settings.DefaultDataDirectory, "settings.config");
settings.DefaultPort = (chainType == NetworkType.Mainnet ? 23000 :
chainType == NetworkType.Regtest ? 23002 :
@ -39,6 +41,7 @@ namespace BTCPayServer
}
public string DefaultDataDirectory { get; set; }
public string DefaultPluginDirectory { get; set; }
public string DefaultConfigurationFile { get; set; }
public int DefaultPort { get; set; }
}
@ -127,9 +130,25 @@ namespace BTCPayServer
public abstract class BTCPayNetworkBase
{
private string _blockExplorerLink;
public bool ShowSyncSummary { get; set; } = true;
public string CryptoCode { get; internal set; }
public string BlockExplorerLink { get; internal set; }
public string BlockExplorerLink
{
get => _blockExplorerLink;
set
{
if (string.IsNullOrEmpty(BlockExplorerLinkDefault))
{
BlockExplorerLinkDefault = value;
}
_blockExplorerLink = value;
}
}
public string BlockExplorerLinkDefault { get; internal set; }
public string DisplayName { get; set; }
public int Divisibility { get; set; } = 8;
[Obsolete("Should not be needed")]

View File

@ -4,7 +4,7 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="NBXplorer.Client" Version="3.0.18" />
<PackageReference Include="NBXplorer.Client" Version="3.0.19" />
</ItemGroup>
<ItemGroup Condition="'$(Altcoins)' != 'true'">
<Compile Remove="Altcoins\**\*.cs"></Compile>

View File

@ -28,5 +28,6 @@ namespace BTCPayServer.Data
public List<U2FDevice> U2FDevices { get; set; }
public List<APIKeyData> APIKeys { get; set; }
public DateTimeOffset? Created { get; set; }
public string DisabledNotifications { get; set; }
}
}

View File

@ -35,6 +35,25 @@ namespace BTCPayServer.Data
return request.Where(p => false);
}
}
public static string GetStateString(this PayoutState state)
{
switch (state)
{
case PayoutState.AwaitingApproval:
return "Awaiting Approval";
case PayoutState.AwaitingPayment:
return "Awaiting Payment";
case PayoutState.InProgress:
return "In Progress";
case PayoutState.Completed:
return "Completed";
case PayoutState.Cancelled:
return "Cancelled";
default:
throw new ArgumentOutOfRangeException(nameof(state), state, null);
}
}
}
public class PullPaymentData
{

View File

@ -0,0 +1,26 @@
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20201015151438_AddDisabledNotificationsToUser")]
public partial class AddDisabledNotificationsToUser : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "DisabledNotifications",
table: "AspNetUsers",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "DisabledNotifications",
table: "AspNetUsers");
}
}
}

View File

@ -111,6 +111,9 @@ namespace BTCPayServer.Migrations
b.Property<DateTimeOffset?>("Created")
.HasColumnType("TEXT");
b.Property<string>("DisabledNotifications")
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasMaxLength(256);

View File

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Version>1.0.0.0</Version>
<PackAsTool>true</PackAsTool>
<ToolCommandName>btcpay-plugin</ToolCommandName>
<Company>BTCPay Server</Company>
<Copyright>Copyright © BTCPay Server 2020</Copyright>
<Description>A dotnet tool for packaging BTCPay Server plugins</Description>
<PackageIcon>icon.png</PackageIcon>
<PackageTags>btcpay,btcpayserver</PackageTags>
<PackageProjectUrl>https://github.com/btcpayserver/btcpayserver/tree/master/BTCPayServer.PluginPacker</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryUrl>https://github.com/btcpayserver/btcpayserver</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<DebugType>portable</DebugType>
<Optimize>true</Optimize>
<NoWarn>1591;1573;1572;1584;1570;3021</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
<None Include="icon.png" Pack="true" PackagePath="\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,55 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using BTCPayServer.Contracts;
namespace BTCPayServer.PluginPacker
{
class Program
{
static void Main(string[] args)
{
if (args.Length < 3)
{
Console.WriteLine("Usage: btcpay-plugin [directory of compiled plugin] [name of plugin] [packed plugin output directory]");
return;
}
var directory = args[0];
var name = args[1];
var outputDir = args[2];
var outputFile = Path.Combine(outputDir, name);
var rootDLLPath = Path.Combine(directory, name +".dll");
if (!File.Exists(rootDLLPath) )
{
throw new Exception($"{rootDLLPath} could not be found");
}
var assembly = Assembly.LoadFrom(rootDLLPath);
var extension = GetAllExtensionTypesFromAssembly(assembly).FirstOrDefault();
if (extension is null)
{
throw new Exception($"{rootDLLPath} is not a valid plugin");
}
var loadedPlugin = (IBTCPayServerPlugin)Activator.CreateInstance(extension);
var json = JsonSerializer.Serialize(loadedPlugin);
Directory.CreateDirectory(outputDir);
if (File.Exists(outputFile + ".btcpay"))
{
File.Delete(outputFile + ".btcpay");
}
ZipFile.CreateFromDirectory(directory, outputFile + ".btcpay", CompressionLevel.Optimal, false);
File.WriteAllText(outputFile + ".btcpay.json", json);
}
private static Type[] GetAllExtensionTypesFromAssembly(Assembly assembly)
{
return assembly.GetTypes().Where(type =>
typeof(IBTCPayServerPlugin).IsAssignableFrom(type) &&
!type.IsAbstract).ToArray();
}
}
}

View File

@ -0,0 +1,7 @@
rm "bin\release\" -Recurse -Force
dotnet pack --configuration Release --include-symbols -p:SymbolPackageFormat=snupkg
$package=(ls .\bin\Release\*.nupkg).FullName
dotnet nuget push $package --source "https://api.nuget.org/v3/index.json"
$ver = ((ls .\bin\release\*.nupkg)[0].Name -replace '.*(\d+\.\d+\.\d+)\.nupkg','$1')
git tag -a "BTCPayServer.PluginPacker/v$ver" -m "BTCPayServer.PluginPacker/$ver"
git push origin "BTCPayServer.PluginPacker/v$ver"

View File

@ -0,0 +1,4 @@
This tool makes it easy to create a BTCPay plugin package. To create a package you must:
* Build your BTCPay Plugin project
* open a terminal in this project
* run `dotnet run PATH_TO_PLUGIN_BUILD_DIRECTORY NAME_OF_PLUGIN BUILT_PACKAGE_OUTPUT_DIRECTORY`

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,44 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace BTCPayServer.Plugins.Test
{
public class ApplicationPartsLogger : IHostedService
{
private readonly ILogger<ApplicationPartsLogger> _logger;
private readonly ApplicationPartManager _partManager;
public ApplicationPartsLogger(ILogger<ApplicationPartsLogger> logger, ApplicationPartManager partManager)
{
_logger = logger;
_partManager = partManager;
}
public Task StartAsync(CancellationToken cancellationToken)
{
// Get the names of all the application parts. This is the short assembly name for AssemblyParts
var applicationParts = _partManager.ApplicationParts.Select(x => x.Name);
// Create a controller feature, and populate it from the application parts
var controllerFeature = new ControllerFeature();
_partManager.PopulateFeature(controllerFeature);
// Get the names of all of the controllers
var controllers = controllerFeature.Controllers.Select(x => x.Name);
// Log the application parts and controllers
_logger.LogInformation("Found the following application parts: '{ApplicationParts}' with the following controllers: '{Controllers}'",
string.Join(", ", applicationParts), string.Join(", ", controllers));
return Task.CompletedTask;
}
// Required by the interface
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<PreserveCompilationContext>false</PreserveCompilationContext>
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
<AssemblyVersion>1.0.0</AssemblyVersion>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
<EmbeddedResource Include="Resources\**" />
</ItemGroup>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -0,0 +1,19 @@
using BTCPayServer.Contracts;
using BTCPayServer.Models;
using Microsoft.Extensions.DependencyInjection;
namespace BTCPayServer.Plugins.Test
{
public class TestPlugin: BaseBTCPayServerPlugin
{
public override string Identifier { get; } = "BTCPayServer.Plugins.Test";
public override string Name { get; } = "Test Plugin!";
public override string Description { get; } = "This is a description of the loaded test extension!";
public override void Execute(IServiceCollection services)
{
services.AddSingleton<IUIExtension>(new UIExtension("TestExtensionNavExtension", "header-nav"));
services.AddHostedService<ApplicationPartsLogger>();
}
}
}

View File

@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Plugins.Test
{
[Route("extensions/test")]
public class TestExtensionController : Controller
{
// GET
public IActionResult Index()
{
return View();
}
}
}

View File

@ -0,0 +1,2 @@
<li class="nav-item"><a asp-controller="TestExtension" asp-action="Index" class="nav-link js-scroll-trigger" >Dear Nicolas Dorier</a></li>

View File

@ -0,0 +1,9 @@
<section>
<div class="container">
<h1>Challenge Completed!!</h1>
Here is also an image loaded from the plugin<br/>
<a href="https://twitter.com/NicolasDorier/status/1307221679014256640">
<img src="/Resources/img/screengrab.png"/>
</a>
</div>
</section>

View File

@ -0,0 +1 @@
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -6,7 +6,7 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.6.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="NBitcoin" Version="5.0.51" />
<PackageReference Include="NBitcoin" Version="5.0.60" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.6.3" />
</ItemGroup>

View File

@ -206,9 +206,9 @@ namespace BTCPayServer.Tests
bitflyerMock.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_JPY"), new BidAsk(700000m)));
rateProvider.Providers.Add("bitflyer", bitflyerMock);
var quadrigacx = new MockRateProvider();
quadrigacx.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_CAD"), new BidAsk(6000m)));
rateProvider.Providers.Add("quadrigacx", quadrigacx);
var ndax = new MockRateProvider();
ndax.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_CAD"), new BidAsk(6000m)));
rateProvider.Providers.Add("ndax", ndax);
var bittrex = new MockRateProvider();
bittrex.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("DOGE_BTC"), new BidAsk(0.004m)));

View File

@ -42,18 +42,19 @@ namespace BTCPayServer.Tests
{
await Server.StartAsync();
ChromeOptions options = new ChromeOptions();
var isDebug = !Server.PayTester.InContainer;
if (Server.PayTester.InContainer)
{
// this must be first option https://stackoverflow.com/questions/53073411/selenium-webdriverexceptionchrome-failed-to-start-crashed-as-google-chrome-is#comment102570662_53073789
options.AddArgument("no-sandbox");
}
var isDebug = !Server.PayTester.InContainer;
if (!isDebug)
{
options.AddArguments("headless"); // Comment to view browser
options.AddArguments("window-size=1200x1000"); // Comment to view browser
}
options.AddArgument("shm-size=2g");
if (Server.PayTester.InContainer)
{
options.AddArgument("no-sandbox");
}
Driver = new ChromeDriver(Server.PayTester.InContainer ? "/usr/bin" : Directory.GetCurrentDirectory(), options);
if (isDebug)
{
@ -228,7 +229,11 @@ namespace BTCPayServer.Tests
Driver.FindElement(By.Id("Email")).SendKeys(user);
Driver.FindElement(By.Id("Password")).SendKeys(password);
Driver.FindElement(By.Id("LoginButton")).Click();
}
public void GoToStores()
{
Driver.FindElement(By.Id("Stores")).Click();
}
public void GoToStore(string storeId, StoreNavPages storeNavPage = StoreNavPages.Index)
@ -303,7 +308,7 @@ namespace BTCPayServer.Tests
public string CreateInvoice(string storeName, decimal amount = 100, string currency = "USD", string refundEmail = "")
{
GoToInvoices();
Driver.FindElement(By.Id("CreateNewInvoice")).Click();
Driver.FindElement(By.Id("CreateNewInvoice")).Click(); // ocassionally gets stuck for some reason, tried force click and wait for element
Driver.FindElement(By.Id("Amount")).SendKeys(amount.ToString(CultureInfo.InvariantCulture));
var currencyEl = Driver.FindElement(By.Id("Currency"));
currencyEl.Clear();

View File

@ -300,14 +300,30 @@ namespace BTCPayServer.Tests
{
await s.StartAsync();
var alice = s.RegisterNewUser();
var store = s.CreateNewStore().storeName;
s.AddDerivationScheme();
var storeData = s.CreateNewStore();
// verify that hints are displayed on the store page
Assert.True(s.Driver.PageSource.Contains("Wallet not setup for the store, please provide Derviation Scheme"),
"Wallet hint not present");
Assert.True(s.Driver.PageSource.Contains("Review settings if you want to receive Lightning payments"),
"Lightning hint not present");
s.GoToStores();
Assert.True(s.Driver.PageSource.Contains("warninghint_" + storeData.storeId),
"Warning hint on list not present");
s.GoToStore(storeData.storeId);
s.AddDerivationScheme(); // wallet hint should be dismissed
s.Driver.AssertNoError();
Assert.Contains(store, s.Driver.PageSource);
Assert.False(s.Driver.PageSource.Contains("Wallet not setup for the store, please provide Derviation Scheme"),
"Wallet hint not dismissed on derivation scheme add");
s.Driver.FindElement(By.Id("dismissLightningHint")).Click(); // dismiss lightning hint
Assert.Contains(storeData.storeName, s.Driver.PageSource);
var storeUrl = s.Driver.Url;
s.ClickOnAllSideMenus();
s.GoToInvoices();
var invoiceId = s.CreateInvoice(store);
var invoiceId = s.CreateInvoice(storeData.storeName);
s.AssertHappyMessage();
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
var invoiceUrl = s.Driver.Url;
@ -362,6 +378,11 @@ namespace BTCPayServer.Tests
s.Logout();
LogIn(s, alice);
s.Driver.FindElement(By.Id("Stores")).Click();
// there shouldn't be any hints now
Assert.False(s.Driver.PageSource.Contains("Review settings if you want to receive Lightning payments"),
"Lightning hint should be dismissed at this point");
s.Driver.FindElement(By.LinkText("Remove")).Click();
s.Driver.FindElement(By.Id("continue")).Click();
s.Driver.FindElement(By.Id("Stores")).Click();
@ -806,7 +827,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter);
s.AssertHappyMessage();
Assert.Contains("AwaitingApproval", s.Driver.PageSource);
Assert.Contains("Awaiting Approval", s.Driver.PageSource);
var viewPullPaymentUrl = s.Driver.Url;
// This one should have nothing
@ -848,8 +869,7 @@ namespace BTCPayServer.Tests
s.Driver.Navigate().GoToUrl(viewPullPaymentUrl);
txs = s.Driver.FindElements(By.ClassName("transaction-link"));
Assert.Equal(2, txs.Count);
Assert.Contains("InProgress", s.Driver.PageSource);
Assert.Contains("In Progress", s.Driver.PageSource);
await s.Server.ExplorerNode.GenerateAsync(1);

View File

@ -1893,7 +1893,7 @@ namespace BTCPayServer.Tests
rateVm.ScriptTest = "BTC_USD,BTC_CAD,DOGE_USD,DOGE_CAD";
rateVm.Script = "DOGE_X = bittrex(DOGE_BTC) * BTC_X;\n" +
"X_CAD = quadrigacx(X_CAD);\n" +
"X_CAD = ndax(X_CAD);\n" +
"X_X = coingecko(X_X);";
rateVm.Spread = 50;
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(await store.Rates(rateVm, "Test"))

View File

@ -2,4 +2,5 @@
bitcoind_container_id="$(docker ps -q --filter label=com.docker.compose.project=btcpayservertests --filter label=com.docker.compose.service=bitcoind)"
address=$(docker exec -ti $bitcoind_container_id bitcoin-cli -datadir="/data" getnewaddress)
docker exec -ti $bitcoind_container_id bitcoin-cli -datadir="/data" generatetoaddress "$@" "$address"
clean_address="${address//[$'\t\r\n']}"
docker exec $bitcoind_container_id bitcoin-cli -datadir="/data" generatetoaddress "$@" "$clean_address"

View File

@ -82,7 +82,7 @@ services:
- customer_lnd
- merchant_lnd
nbxplorer:
image: nicolasdorier/nbxplorer:2.1.42
image: nicolasdorier/nbxplorer:2.1.45
restart: unless-stopped
ports:
- "32838:32838"
@ -213,52 +213,6 @@ services:
- "merchant_lightningd_datadir:/root/.lightning"
links:
- bitcoind
litecoind:
restart: unless-stopped
image: nicolasdorier/docker-litecoin:0.16.3
environment:
BITCOIN_EXTRA_ARGS: |-
rpcuser=ceiwHEbqWI83
rpcpassword=DwubwWsoo3
regtest=1
rpcport=43782
port=39388
whitelist=0.0.0.0/0
ports:
- "43783:43782"
expose:
- "43782" # RPC
- "39388" # P2P
elementsd-liquid:
restart: always
container_name: btcpayserver_elementsd_liquid
image: btcpayserver/elements:0.18.1.7
environment:
ELEMENTS_CHAIN: elementsregtest
ELEMENTS_EXTRA_ARGS: |
mainchainrpcport=43782
mainchainrpchost=bitcoind
mainchainrpcuser=liquid
mainchainrpcpassword=liquid
rpcport=19332
rpcbind=0.0.0.0:19332
rpcauth=liquid:c8bf1a8961d97f224cb21224aaa8235d$$402f4a8907683d057b8c58a42940b6e54d1638322a42986ae28ebb844e603ae6
port=19444
whitelist=0.0.0.0/0
validatepegin=0
initialfreecoins=210000000000000
con_dyna_deploy_start=99999999999
expose:
- "19332"
- "19444"
ports:
- "19332:19332"
- "19444:19444"
volumes:
- "elementsd_liquid_datadir:/data"
postgres:
image: postgres:9.6.5
ports:
@ -341,7 +295,7 @@ services:
- "torrcdir:/usr/local/etc/tor"
- "tor_servicesdir:/var/lib/tor/hidden_services"
monerod:
image: btcpayserver/monero:0.15.0.1-amd64
image: btcpayserver/monero:0.17.0.0-amd64
restart: unless-stopped
container_name: xmr_monerod
entrypoint: sleep 999999
@ -351,17 +305,16 @@ services:
ports:
- "18081:18081"
monero_wallet:
image: btcpayserver/monero:0.15.0.1-amd64
restart: unless-stopped
container_name: xmr_wallet_rpc
entrypoint: monero-wallet-rpc --testnet --rpc-bind-ip=0.0.0.0 --disable-rpc-login --confirm-external-bind --rpc-bind-port=18082 --non-interactive --trusted-daemon --daemon-address=monerod:18081 --wallet-file=/wallet/wallet.keys --password-file=/wallet/password --tx-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/tx?cryptoCode=xmr&hash=%s"
ports:
- "18082:18082"
volumes:
- "./monero_wallet:/wallet"
depends_on:
image: btcpayserver/monero:0.17.0.0-amd64
restart: unless-stopped
container_name: xmr_wallet_rpc
entrypoint: monero-wallet-rpc --testnet --rpc-bind-ip=0.0.0.0 --disable-rpc-login --confirm-external-bind --rpc-bind-port=18082 --non-interactive --trusted-daemon --daemon-address=monerod:18081 --wallet-file=/wallet/wallet.keys --password-file=/wallet/password --tx-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/tx?cryptoCode=xmr&hash=%s"
ports:
- "18082:18082"
volumes:
- "./monero_wallet:/wallet"
depends_on:
- monerod
litecoind:
restart: unless-stopped
image: nicolasdorier/docker-litecoin:0.16.3

View File

@ -79,7 +79,7 @@ services:
- customer_lnd
- merchant_lnd
nbxplorer:
image: nicolasdorier/nbxplorer:2.1.42
image: nicolasdorier/nbxplorer:2.1.45
restart: unless-stopped
ports:
- "32838:32838"

View File

@ -1,11 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<PreserveCompilationContext>true</PreserveCompilationContext>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Build\**" />
<Compile Remove="wwwroot\bundles\jqueryvalidate\**" />
<Compile Remove="wwwroot\vendor\jquery-nice-select\**" />
@ -52,6 +55,7 @@
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
<PackageReference Include="CsvHelper" Version="15.0.5" />
<PackageReference Include="HtmlSanitizer" Version="4.0.217" />
<PackageReference Include="McMaster.NETCore.Plugins.Mvc" Version="1.3.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.9.8">
<PrivateAssets>all</PrivateAssets>
@ -141,6 +145,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
<ProjectReference Include="..\BTCPayServer.Client\BTCPayServer.Client.csproj" />
<ProjectReference Include="..\BTCPayServer.Data\BTCPayServer.Data.csproj" />
<ProjectReference Include="..\BTCPayServer.Rating\BTCPayServer.Rating.csproj" />

View File

@ -1,4 +1,7 @@
@inject LinkGenerator linkGenerator
@inject UserManager<ApplicationUser> UserManager
@inject CssThemeManager CssThemeManager
@using BTCPayServer.HostedServices
@model BTCPayServer.Components.NotificationsDropdown.NotificationSummaryViewModel
@if (Model.UnseenCount > 0)
@ -32,36 +35,47 @@ else
</a>
</li>
}
<script type="text/javascript">
@{
var disabled = CssThemeManager.Policies.DisableInstantNotifications;
if (!disabled)
{
var user = await UserManager.GetUserAsync(User);
disabled = user.DisabledNotifications == "all";
}
}
@if (!disabled)
{
var supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
<script type="text/javascript">
var supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
if (supportsWebSockets) {
var loc = window.location, ws_uri;
if (loc.protocol === "https:") {
ws_uri = "wss:";
} else {
ws_uri = "ws:";
}
ws_uri += "//" + loc.host;
ws_uri += "@linkGenerator.GetPathByAction("SubscribeUpdates", "Notifications")";
var newDataEndpoint = "@linkGenerator.GetPathByAction("GetNotificationDropdownUI", "Notifications")";
try {
socket = new WebSocket(ws_uri);
socket.onmessage = function (e) {
$.get(newDataEndpoint, function(data){
$("#notifications-nav-item").replaceWith($(data));
});
};
socket.onerror = function (e) {
console.error("Error while connecting to websocket for notifications (callback)", e);
};
}
catch (e) {
console.error("Error while connecting to websocket for notifications", e);
var loc = window.location, ws_uri;
if (loc.protocol === "https:") {
ws_uri = "wss:";
} else {
ws_uri = "ws:";
}
ws_uri += "//" + loc.host;
ws_uri += "@linkGenerator.GetPathByAction("SubscribeUpdates", "Notifications")";
var newDataEndpoint = "@linkGenerator.GetPathByAction("GetNotificationDropdownUI", "Notifications")";
try {
socket = new WebSocket(ws_uri);
socket.onmessage = function (e) {
$.get(newDataEndpoint, function(data){
$("#notifications-nav-item").replaceWith($(data));
});
};
socket.onerror = function (e) {
console.error("Error while connecting to websocket for notifications (callback)", e);
};
}
catch (e) {
console.error("Error while connecting to websocket for notifications", e);
}
}
</script>
}
</script>

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Contracts;
using BTCPayServer.Models.NotificationViewModels;
namespace BTCPayServer.Components.NotificationsDropdown

View File

@ -0,0 +1,6 @@
@model IEnumerable<string>
@foreach (var partial in Model)
{
await Html.RenderPartialAsync(partial);
}

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using BTCPayServer.Contracts;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Components.UIExtensionPoint
{
public class UiExtensionPoint : ViewComponent
{
private readonly IEnumerable<IUIExtension> _uiExtensions;
public UiExtensionPoint(IEnumerable<IUIExtension> uiExtensions)
{
_uiExtensions = uiExtensions;
}
public IViewComponentResult Invoke(string location)
{
return View(_uiExtensions.Where(extension => extension.Location.Equals(location, StringComparison.InvariantCultureIgnoreCase)).Select(extension => extension.Partial));
}
}
}

View File

@ -80,6 +80,7 @@ namespace BTCPayServer.Configuration
{
NetworkType = DefaultConfiguration.GetNetworkType(conf);
DataDir = conf.GetDataDir(NetworkType);
PluginDir = conf.GetPluginDir(NetworkType);
Logs.Configuration.LogInformation("Network: " + NetworkType.ToString());
if (conf.GetOrDefault<bool>("launchsettings", false) && NetworkType != NetworkType.Regtest)
@ -165,7 +166,10 @@ namespace BTCPayServer.Configuration
PostgresConnectionString = conf.GetOrDefault<string>("postgres", null);
MySQLConnectionString = conf.GetOrDefault<string>("mysql", null);
SQLiteFileName = conf.GetOrDefault<string>("sqlitefile", null);
BundleJsCss = conf.GetOrDefault<bool>("bundlejscss", true);
DockerDeployment = conf.GetOrDefault<bool>("dockerdeployment", true);
AllowAdminRegistration = conf.GetOrDefault<bool>("allow-admin-registration", false);
TorrcFile = conf.GetOrDefault<string>("torrcfile", null);
@ -237,8 +241,14 @@ namespace BTCPayServer.Configuration
}
DisableRegistration = conf.GetOrDefault<bool>("disable-registration", true);
PluginRemote = conf.GetOrDefault("plugin-remote", "btcpayserver/btcpayserver-plugins");
RecommendedPlugins = conf.GetOrDefault("recommended-plugins", "").ToLowerInvariant().Split('\r','\n','\t',' ').Where(s => !string.IsNullOrEmpty(s)).Distinct().ToArray();
}
public string PluginDir { get; set; }
public string PluginRemote { get; set; }
public string[] RecommendedPlugins { get; set; }
private SSHSettings ParseSSHConfiguration(IConfiguration conf)
{
var settings = new SSHSettings();
@ -281,6 +291,7 @@ namespace BTCPayServer.Configuration
public ExternalServices ExternalServices { get; set; } = new ExternalServices();
public BTCPayNetworkProvider NetworkProvider { get; set; }
public bool DockerDeployment { get; set; }
public string PostgresConnectionString
{
get;
@ -291,6 +302,11 @@ namespace BTCPayServer.Configuration
get;
set;
}
public string SQLiteFileName
{
get;
set;
}
public bool BundleJsCss
{
get;

View File

@ -67,5 +67,11 @@ namespace BTCPayServer.Configuration
var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(networkType);
return configuration.GetOrDefault("datadir", defaultSettings.DefaultDataDirectory);
}
public static string GetPluginDir(this IConfiguration configuration, NetworkType networkType)
{
var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(networkType);
return configuration.GetOrDefault("plugindir", defaultSettings.DefaultPluginDirectory);
}
}
}

View File

@ -25,8 +25,9 @@ namespace BTCPayServer.Configuration
app.Option("--regtest | -regtest", $"Use regtest (deprecated, use --network instead)", CommandOptionType.BoolValue);
app.Option("--allow-admin-registration", $"For debug only, will show a checkbox when a new user register to add himself as admin. (default: false)", CommandOptionType.BoolValue);
app.Option("--chains | -c", $"Chains to support as a comma separated (default: btc; available: {chains})", CommandOptionType.SingleValue);
app.Option("--postgres", $"Connection string to a PostgreSQL database (default: SQLite)", CommandOptionType.SingleValue);
app.Option("--mysql", $"Connection string to a MySQL database (default: SQLite)", CommandOptionType.SingleValue);
app.Option("--postgres", $"Connection string to a PostgreSQL database", CommandOptionType.SingleValue);
app.Option("--mysql", $"Connection string to a MySQL database", CommandOptionType.SingleValue);
app.Option("--sqlitefile", $"File name to an SQLite database file inside the data directory", CommandOptionType.SingleValue);
app.Option("--externalservices", $"Links added to external services inside Server Settings / Services under the format service1:path2;service2:path2.(default: empty)", CommandOptionType.SingleValue);
app.Option("--bundlejscss", $"Bundle JavaScript and CSS files for better performance (default: true)", CommandOptionType.SingleValue);
app.Option("--rootpath", "The root path in the URL to access BTCPay (default: /)", CommandOptionType.SingleValue);
@ -42,6 +43,8 @@ namespace BTCPayServer.Configuration
app.Option("--debuglog", "A rolling log file for debug messages.", CommandOptionType.SingleValue);
app.Option("--debugloglevel", "The severity you log (default:information)", CommandOptionType.SingleValue);
app.Option("--disable-registration", "Disables new user registrations (default:true)", CommandOptionType.SingleValue);
app.Option("--plugin-remote", "Which github repository to fetch the available plugins list (default:btcpayserver/btcpayserver-plugins)", CommandOptionType.SingleValue);
app.Option("--recommended-plugins", "Plugins which would be marked as recommended to be installed. Separated by newline or space", CommandOptionType.MultipleValue);
app.Option("--xforwardedproto", "If specified, set X-Forwarded-Proto to the specified value, this may be useful if your reverse proxy handle https but is not configured to add X-Forwarded-Proto (example: --xforwardedproto https)", CommandOptionType.SingleValue);
foreach (var network in provider.GetAll().OfType<BTCPayNetwork>())
{
@ -49,11 +52,14 @@ namespace BTCPayServer.Configuration
app.Option($"--{crypto}explorerurl", $"URL of the NBXplorer for {network.CryptoCode} (default: {network.NBXplorerNetwork.DefaultSettings.DefaultUrl})", CommandOptionType.SingleValue);
app.Option($"--{crypto}explorercookiefile", $"Path to the cookie file (default: {network.NBXplorerNetwork.DefaultSettings.DefaultCookieFile})", CommandOptionType.SingleValue);
app.Option($"--{crypto}lightning", $"Easy configuration of lightning for the server administrator: Must be a UNIX socket of c-lightning (lightning-rpc) or URL to a charge server (default: empty)", CommandOptionType.SingleValue);
app.Option($"--{crypto}externallndgrpc", $"The LND gRPC configuration BTCPay will expose to easily connect to the internal lnd wallet from an external wallet (default: empty)", CommandOptionType.SingleValue);
app.Option($"--{crypto}externallndrest", $"The LND REST configuration BTCPay will expose to easily connect to the internal lnd wallet from an external wallet (default: empty)", CommandOptionType.SingleValue);
app.Option($"--{crypto}externalrtl", $"The Ride the Lightning configuration so BTCPay will expose to easily open it in server settings (default: empty)", CommandOptionType.SingleValue);
app.Option($"--{crypto}externalspark", $"Show spark information in Server settings / Server. The connection string to spark server (default: empty)", CommandOptionType.SingleValue);
app.Option($"--{crypto}externalcharge", $"Show lightning charge information in Server settings/Server. The connection string to charge server (default: empty)", CommandOptionType.SingleValue);
if (network.SupportLightning)
{
app.Option($"--{crypto}externallndgrpc", $"The LND gRPC configuration BTCPay will expose to easily connect to the internal lnd wallet from an external wallet (default: empty)", CommandOptionType.SingleValue);
app.Option($"--{crypto}externallndrest", $"The LND REST configuration BTCPay will expose to easily connect to the internal lnd wallet from an external wallet (default: empty)", CommandOptionType.SingleValue);
app.Option($"--{crypto}externalrtl", $"The Ride the Lightning configuration so BTCPay will expose to easily open it in server settings (default: empty)", CommandOptionType.SingleValue);
app.Option($"--{crypto}externalspark", $"Show spark information in Server settings / Server. The connection string to spark server (default: empty)", CommandOptionType.SingleValue);
app.Option($"--{crypto}externalcharge", $"Show lightning charge information in Server settings/Server. The connection string to charge server (default: empty)", CommandOptionType.SingleValue);
}
}
return app;
}
@ -118,14 +124,18 @@ namespace BTCPayServer.Configuration
builder.AppendLine("### Database ###");
builder.AppendLine("#postgres=User ID=root;Password=myPassword;Host=localhost;Port=5432;Database=myDataBase;");
builder.AppendLine("#mysql=User ID=root;Password=myPassword;Host=localhost;Port=3306;Database=myDataBase;");
builder.AppendLine("#sqlitefile=sqlite.db");
builder.AppendLine();
builder.AppendLine("### NBXplorer settings ###");
foreach (var n in new BTCPayNetworkProvider(networkType).GetAll().OfType<BTCPayNetwork>())
{
builder.AppendLine($"#{n.CryptoCode}.explorer.url={n.NBXplorerNetwork.DefaultSettings.DefaultUrl}");
builder.AppendLine($"#{n.CryptoCode}.explorer.cookiefile={ n.NBXplorerNetwork.DefaultSettings.DefaultCookieFile}");
builder.AppendLine($"#{n.CryptoCode}.lightning=/root/.lightning/lightning-rpc");
builder.AppendLine($"#{n.CryptoCode}.lightning=https://apitoken:API_TOKEN_SECRET@charge.example.com/");
if (n.SupportLightning)
{
builder.AppendLine($"#{n.CryptoCode}.lightning=/root/.lightning/lightning-rpc");
builder.AppendLine($"#{n.CryptoCode}.lightning=https://apitoken:API_TOKEN_SECRET@charge.example.com/");
}
}
return builder.ToString();
}

View File

@ -1,7 +0,0 @@
namespace BTCPayServer.Contracts
{
public interface IStoreNavExtension
{
string Partial { get; }
}
}

View File

@ -537,7 +537,7 @@ namespace BTCPayServer.Controllers
if (ModelState.IsValid)
{
var user = await _userManager.FindByEmailAsync(model.Email);
if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
if (user == null || (user.RequiresEmailConfirmation && !(await _userManager.IsEmailConfirmedAsync(user))))
{
// Don't reveal that the user does not exist or is not confirmed
return RedirectToAction(nameof(ForgotPasswordConfirmation));
@ -606,7 +606,7 @@ namespace BTCPayServer.Controllers
}
AddErrors(result);
return View();
return View(model);
}
[HttpGet]

View File

@ -82,6 +82,7 @@ namespace BTCPayServer.Controllers
public string Description { get; set; }
public string NotificationUrl { get; set; }
public string RedirectUrl { get; set; }
public bool? RedirectAutomatically { get; set; }
}
@ -115,6 +116,7 @@ namespace BTCPayServer.Controllers
EmbeddedCSS = settings.EmbeddedCSS,
Description = settings.Description,
NotificationUrl = settings.NotificationUrl,
RedirectUrl = settings.RedirectUrl,
SearchTerm = $"storeid:{app.StoreDataId}",
RedirectAutomatically = settings.RedirectAutomatically.HasValue ? settings.RedirectAutomatically.Value ? "true" : "false" : ""
};
@ -191,6 +193,7 @@ namespace BTCPayServer.Controllers
CustomTipPercentages = ListSplit(vm.CustomTipPercentages),
CustomCSSLink = vm.CustomCSSLink,
NotificationUrl = vm.NotificationUrl,
RedirectUrl = vm.RedirectUrl,
Description = vm.Description,
EmbeddedCSS = vm.EmbeddedCSS,
RedirectAutomatically = string.IsNullOrEmpty(vm.RedirectAutomatically) ? (bool?)null : bool.Parse(vm.RedirectAutomatically)

View File

@ -41,12 +41,13 @@ namespace BTCPayServer.Controllers
[HttpGet("/apps/{appId}")]
public async Task<IActionResult> RedirectToApp(string appId)
{
switch (await _AppService.GetAppInfo(appId))
switch ((await _AppService.GetApp(appId, null)).AppType)
{
case ViewCrowdfundViewModel _:
case nameof(AppType.Crowdfund):
return RedirectToAction("ViewCrowdfund", new {appId});
case ViewPointOfSaleViewModel _:
case nameof(AppType.PointOfSale):
return RedirectToAction("ViewPointOfSale", new {appId});
}
@ -203,7 +204,9 @@ namespace BTCPayServer.Controllers
OrderId = orderId,
NotificationURL =
string.IsNullOrEmpty(notificationUrl) ? settings.NotificationUrl : notificationUrl,
RedirectURL = redirectUrl ?? Request.GetDisplayUrl(),
RedirectURL = !string.IsNullOrEmpty(redirectUrl) ? redirectUrl
: !string.IsNullOrEmpty(settings.RedirectUrl) ? settings.RedirectUrl
: Request.GetDisplayUrl(),
FullNotifications = true,
ExtendedNotifications = true,
PosData = string.IsNullOrEmpty(posData) ? null : posData,

View File

@ -300,32 +300,35 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(PullPaymentController.ViewPullPayment),
"PullPayment",
new { pullPaymentId = ppId });
}
private InvoiceDetailsModel InvoicePopulatePayments(InvoiceEntity invoice)
{
var model = new InvoiceDetailsModel();
model.Archived = invoice.Archived;
model.Payments = invoice.GetPayments();
foreach (var data in invoice.GetPaymentMethods())
return new InvoiceDetailsModel
{
var accounting = data.Calculate();
var paymentMethodId = data.GetId();
var cryptoPayment = new InvoiceDetailsModel.CryptoPayment();
cryptoPayment.PaymentMethodId = paymentMethodId;
cryptoPayment.PaymentMethod = paymentMethodId.ToPrettyString();
cryptoPayment.Due = _CurrencyNameTable.DisplayFormatCurrency(accounting.Due.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
cryptoPayment.Paid = _CurrencyNameTable.DisplayFormatCurrency(accounting.CryptoPaid.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
cryptoPayment.Overpaid = _CurrencyNameTable.DisplayFormatCurrency(accounting.OverpaidHelper.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
var paymentMethodDetails = data.GetPaymentMethodDetails();
cryptoPayment.Address = paymentMethodDetails.GetPaymentDestination();
cryptoPayment.Rate = ExchangeRate(data);
model.CryptoPayments.Add(cryptoPayment);
}
return model;
Archived = invoice.Archived,
Payments = invoice.GetPayments(),
CryptoPayments = invoice.GetPaymentMethods().Select(
data =>
{
var accounting = data.Calculate();
var paymentMethodId = data.GetId();
return new InvoiceDetailsModel.CryptoPayment
{
PaymentMethodId = paymentMethodId,
PaymentMethod = paymentMethodId.ToPrettyString(),
Due = _CurrencyNameTable.DisplayFormatCurrency(accounting.Due.ToDecimal(MoneyUnit.BTC),
paymentMethodId.CryptoCode),
Paid = _CurrencyNameTable.DisplayFormatCurrency(
accounting.CryptoPaid.ToDecimal(MoneyUnit.BTC),
paymentMethodId.CryptoCode),
Overpaid = _CurrencyNameTable.DisplayFormatCurrency(
accounting.OverpaidHelper.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode),
Address = data.GetPaymentMethodDetails().GetPaymentDestination(),
Rate = ExchangeRate(data)
};
}).ToList()
};
}
[HttpPost("invoices/{invoiceId}/archive")]

View File

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Contracts;
using BTCPayServer.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace BTCPayServer.Controllers
{
public partial class ManageController
{
[HttpGet("notifications")]
public async Task<IActionResult> NotificationSettings([FromServices] IEnumerable<INotificationHandler> notificationHandlers)
{
var user = await _userManager.GetUserAsync(User);
if (user.DisabledNotifications == "all")
{
return View(new NotificationSettingsViewModel() {All = true});
}
var disabledNotifications =
user.DisabledNotifications?.Split(';', StringSplitOptions.RemoveEmptyEntries)?.ToList() ??
new List<string>();
var notifications = notificationHandlers.SelectMany(handler => handler.Meta.Select(tuple =>
new SelectListItem(tuple.name, tuple.identifier,
disabledNotifications.Contains(tuple.identifier, StringComparer.InvariantCultureIgnoreCase))))
.ToList();
return View(new NotificationSettingsViewModel() {DisabledNotifications = notifications});
}
[HttpPost("notifications")]
public async Task<IActionResult> NotificationSettings(NotificationSettingsViewModel vm, string command)
{
var user = await _userManager.GetUserAsync(User);
if (command == "disable-all")
{
user.DisabledNotifications = "all";
}
else if (command == "enable-all")
{
user.DisabledNotifications = "";
}
else if (command == "update")
{
var disabled = vm.DisabledNotifications.Where(item => item.Selected).Select(item => item.Value)
.ToArray();
user.DisabledNotifications = disabled.Any() is true
? string.Join(';', disabled) + ";"
: string.Empty;
}
await _userManager.UpdateAsync(user);
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Message = "Updated successfully.", Severity = StatusMessageModel.StatusSeverity.Success
});
return RedirectToAction("NotificationSettings");
}
public class NotificationSettingsViewModel
{
public bool All { get; set; }
public List<SelectListItem> DisabledNotifications { get; set; }
}
}
}

View File

@ -89,11 +89,11 @@ namespace BTCPayServer.Controllers
}
#if DEBUG
[HttpGet]
public async Task<IActionResult> GenerateJunk(int x = 100)
public async Task<IActionResult> GenerateJunk(int x = 100, bool admin=true)
{
for (int i = 0; i < x; i++)
{
await _notificationSender.SendNotification(new AdminScope(), new JunkNotification());
await _notificationSender.SendNotification(admin? (NotificationScope) new AdminScope(): new UserScope(_userManager.GetUserId(User)), new JunkNotification());
}
return RedirectToAction("Index");

View File

@ -233,19 +233,22 @@ namespace BTCPayServer.Controllers
return BadRequest("Payment Request has expired");
}
var statusesAllowedToDisplay = new List<InvoiceStatus>() { InvoiceStatus.New };
var validInvoice = result.Invoices.FirstOrDefault(invoice =>
Enum.TryParse<InvoiceStatus>(invoice.Status, true, out var status) &&
statusesAllowedToDisplay.Contains(status));
if (validInvoice != null)
var stateAllowedToDisplay = new HashSet<InvoiceState>()
{
new InvoiceState(InvoiceStatus.New, InvoiceExceptionStatus.None),
new InvoiceState(InvoiceStatus.New, InvoiceExceptionStatus.PaidPartial),
};
var currentInvoice = result
.Invoices
.FirstOrDefault(invoice => stateAllowedToDisplay.Contains(invoice.State));
if (currentInvoice != null)
{
if (redirectToInvoice)
{
return RedirectToAction("Checkout", "Invoice", new { Id = validInvoice.Id });
return RedirectToAction("Checkout", "Invoice", new { Id = currentInvoice.Id });
}
return Ok(validInvoice.Id);
return Ok(currentInvoice.Id);
}
if (result.AllowCustomPaymentAmounts && amount != null)
@ -297,8 +300,7 @@ namespace BTCPayServer.Controllers
}
var invoices = result.Invoices.Where(requestInvoice =>
requestInvoice.Status.Equals(InvoiceState.ToString(InvoiceStatus.New),
StringComparison.InvariantCulture) && !requestInvoice.Payments.Any());
requestInvoice.State.Status == InvoiceStatus.New && !requestInvoice.Payments.Any());
if (!invoices.Any())
{

View File

@ -1,6 +1,7 @@
using System;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using BTCPayServer;
using BTCPayServer.Data;
@ -69,13 +70,13 @@ namespace BTCPayServer.Controllers
CurrencyData = cd,
LastUpdated = DateTime.Now,
Payouts = payouts
.Select(entity => new ViewPullPaymentModel.PayoutLine()
.Select(entity => new ViewPullPaymentModel.PayoutLine
{
Id = entity.Entity.Id,
Amount = entity.Blob.Amount,
AmountFormatted = _currencyNameTable.FormatCurrency(entity.Blob.Amount, blob.Currency),
Currency = blob.Currency,
Status = entity.Entity.State.ToString(),
Status = entity.Entity.State.GetStateString(),
Destination = entity.Blob.Destination.Address.ToString(),
Link = GetTransactionLink(_networkProvider.GetNetwork<BTCPayNetwork>(entity.Entity.GetPaymentMethodId().CryptoCode), entity.TransactionId),
TransactionId = entity.TransactionId

View File

@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Configuration;
using BTCPayServer.Contracts;
using BTCPayServer.Models;
using BTCPayServer.Plugins;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Controllers
{
public partial class ServerController
{
[HttpGet("server/plugins")]
public async Task<IActionResult> ListPlugins(
[FromServices] PluginService pluginService,
[FromServices] BTCPayServerOptions btcPayServerOptions)
{
IEnumerable<PluginService.AvailablePlugin> availablePlugins;
try
{
availablePlugins = await pluginService.GetRemotePlugins();
}
catch (Exception)
{
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = "Remote plugins lookup failed. Try again later."
});
availablePlugins = Array.Empty<PluginService.AvailablePlugin>();
}
var res = new ListPluginsViewModel()
{
Installed = pluginService.LoadedPlugins,
Available = availablePlugins,
Commands = pluginService.GetPendingCommands(),
CanShowRestart = btcPayServerOptions.DockerDeployment
};
return View(res);
}
public class ListPluginsViewModel
{
public IEnumerable<IBTCPayServerPlugin> Installed { get; set; }
public IEnumerable<PluginService.AvailablePlugin> Available { get; set; }
public (string command, string plugin)[] Commands { get; set; }
public bool CanShowRestart { get; set; }
}
[HttpPost("server/plugins/uninstall")]
public IActionResult UnInstallPlugin(
[FromServices] PluginService pluginService, string plugin)
{
pluginService.UninstallPlugin(plugin);
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Message = "Plugin scheduled to be uninstalled.",
Severity = StatusMessageModel.StatusSeverity.Success
});
return RedirectToAction("ListPlugins");
}
[HttpPost("server/plugins/cancel")]
public IActionResult CancelPluginCommands(
[FromServices] PluginService pluginService, string plugin)
{
pluginService.CancelCommands(plugin);
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Message = "Plugin action cancelled.",
Severity = StatusMessageModel.StatusSeverity.Success
});
return RedirectToAction("ListPlugins");
}
[HttpPost("server/plugins/install")]
public async Task<IActionResult> InstallPlugin(
[FromServices] PluginService pluginService, string plugin)
{
try
{
await pluginService.DownloadRemotePlugin(plugin);
pluginService.InstallPlugin(plugin);
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Message = "Plugin scheduled to be installed.",
Severity = StatusMessageModel.StatusSeverity.Success
});
}
catch (Exception)
{
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Message = "The plugin could not be downloaded. Try again later.", Severity = StatusMessageModel.StatusSeverity.Error
});
}
return RedirectToAction("ListPlugins");
}
[HttpPost("server/plugins/upload")]
public async Task<IActionResult> UploadPlugin([FromServices] PluginService pluginService,
List<IFormFile> files)
{
foreach (var formFile in files.Where(file => file.Length > 0))
{
await pluginService.UploadPlugin(formFile);
pluginService.InstallPlugin(formFile.FileName.TrimEnd(PluginManager.BTCPayPluginSuffix,
StringComparison.InvariantCultureIgnoreCase));
}
return RedirectToAction("ListPlugins",
new {StatusMessage = "Files uploaded, restart server to load plugins"});
}
}
}

View File

@ -11,6 +11,7 @@ using BTCPayServer.Configuration;
using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.HostedServices;
using BTCPayServer.Hosting;
using BTCPayServer.Logging;
using BTCPayServer.Models;
using BTCPayServer.Models.AccountViewModels;
@ -105,6 +106,12 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> Maintenance(MaintenanceViewModel vm, string command)
{
vm.CanUseSSH = _sshState.CanUseSSH;
if (!vm.CanUseSSH)
{
TempData[WellKnownTempData.ErrorMessage] = "Maintenance feature requires access to SSH properly configured in BTCPayServer configuration.";
return View(vm);
}
if (!ModelState.IsValid)
return View(vm);
if (command == "changedomain")
@ -182,6 +189,13 @@ namespace BTCPayServer.Controllers
return error;
TempData[WellKnownTempData.SuccessMessage] = $"The old docker images will be cleaned soon...";
}
else if (command == "restart")
{
var error = await RunSSH(vm, $"btcpay-restart.sh");
if (error != null)
return error;
TempData[WellKnownTempData.SuccessMessage] = $"BTCPay will restart momentarily.";
}
else
{
return NotFound();
@ -260,8 +274,10 @@ namespace BTCPayServer.Controllers
[Route("server/policies")]
[HttpPost]
public async Task<IActionResult> Policies(PoliciesSettings settings, string command = "")
public async Task<IActionResult> Policies([FromServices] BTCPayNetworkProvider btcPayNetworkProvider,PoliciesSettings settings, string command = "")
{
ViewBag.UpdateUrlPresent = _Options.UpdateUrl != null;
ViewBag.AppsList = await GetAppSelectList();
if (command == "add-domain")
{
@ -277,6 +293,8 @@ namespace BTCPayServer.Controllers
return View(settings);
}
settings.BlockExplorerLinks = settings.BlockExplorerLinks.Where(tuple => btcPayNetworkProvider.GetNetwork(tuple.CryptoCode).BlockExplorerLinkDefault != tuple.Link).ToList();
if (!ModelState.IsValid)
{
return View(settings);
@ -308,6 +326,7 @@ namespace BTCPayServer.Controllers
}
await _SettingsRepository.UpdateSetting(settings);
BlockExplorerLinkStartupTask.SetLinkOnNetworks(settings.BlockExplorerLinks, btcPayNetworkProvider);
TempData[WellKnownTempData.SuccessMessage] = "Policies updated successfully";
return RedirectToAction(nameof(Policies));
}

View File

@ -102,7 +102,8 @@ namespace BTCPayServer.Controllers
if (vm.WalletFile != null)
{
if (!DerivationSchemeSettings.TryParseFromWalletFile(await ReadAllText(vm.WalletFile), network, out strategy))
if (!DerivationSchemeSettings.TryParseFromWalletFile(await ReadAllText(vm.WalletFile), network,
out strategy))
{
TempData.SetStatusMessageModel(new StatusMessageModel()
{
@ -113,6 +114,19 @@ namespace BTCPayServer.Controllers
return View(nameof(AddDerivationScheme), vm);
}
}
else if (!string.IsNullOrEmpty(vm.WalletFileContent))
{
if (!DerivationSchemeSettings.TryParseFromWalletFile(vm.WalletFileContent, network, out strategy))
{
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = "QR import was not in the correct format"
});
vm.Confirmation = false;
return View(nameof(AddDerivationScheme), vm);
}
}
else
{
try
@ -122,16 +136,24 @@ namespace BTCPayServer.Controllers
var newStrategy = ParseDerivationStrategy(vm.DerivationScheme, null, network);
if (newStrategy.AccountDerivation != strategy?.AccountDerivation)
{
var accountKey = string.IsNullOrEmpty(vm.AccountKey) ? null : new BitcoinExtPubKey(vm.AccountKey, network.NBitcoinNetwork);
var accountKey = string.IsNullOrEmpty(vm.AccountKey)
? null
: new BitcoinExtPubKey(vm.AccountKey, network.NBitcoinNetwork);
if (accountKey != null)
{
var accountSettings = newStrategy.AccountKeySettings.FirstOrDefault(a => a.AccountKey == accountKey);
var accountSettings =
newStrategy.AccountKeySettings.FirstOrDefault(a => a.AccountKey == accountKey);
if (accountSettings != null)
{
accountSettings.AccountKeyPath = vm.KeyPath == null ? null : KeyPath.Parse(vm.KeyPath);
accountSettings.RootFingerprint = string.IsNullOrEmpty(vm.RootFingerprint) ? (HDFingerprint?)null : new HDFingerprint(NBitcoin.DataEncoders.Encoders.Hex.DecodeData(vm.RootFingerprint));
accountSettings.AccountKeyPath =
vm.KeyPath == null ? null : KeyPath.Parse(vm.KeyPath);
accountSettings.RootFingerprint = string.IsNullOrEmpty(vm.RootFingerprint)
? (HDFingerprint?)null
: new HDFingerprint(
NBitcoin.DataEncoders.Encoders.Hex.DecodeData(vm.RootFingerprint));
}
}
strategy = newStrategy;
strategy.Source = vm.Source;
vm.DerivationScheme = strategy.AccountDerivation.ToString();
@ -163,11 +185,11 @@ namespace BTCPayServer.Controllers
var willBeExcluded = !vm.Enabled;
var showAddress = // Show addresses if:
// - If the user is testing the hint address in confirmation screen
// - If the user is testing the hint address in confirmation screen
(vm.Confirmation && !string.IsNullOrWhiteSpace(vm.HintAddress)) ||
// - The user is clicking on continue after changing the config
(!vm.Confirmation && oldConfig != vm.Config) ||
// - The user is clickingon continue without changing config nor enabling/disabling
// - The user is clicking on continue without changing config nor enabling/disabling
(!vm.Confirmation && oldConfig == vm.Config && willBeExcluded == wasExcluded);
showAddress = showAddress && strategy != null;
@ -179,6 +201,7 @@ namespace BTCPayServer.Controllers
await wallet.TrackAsync(strategy.AccountDerivation);
store.SetSupportedPaymentMethod(paymentMethodId, strategy);
storeBlob.SetExcluded(paymentMethodId, willBeExcluded);
storeBlob.Hints.Wallet = false;
store.SetStoreBlob(storeBlob);
}
catch
@ -188,21 +211,22 @@ namespace BTCPayServer.Controllers
}
await _Repo.UpdateStore(store);
_EventAggregator.Publish(new WalletChangedEvent()
{
WalletId = new WalletId(storeId, cryptoCode)
});
_EventAggregator.Publish(new WalletChangedEvent() {WalletId = new WalletId(storeId, cryptoCode)});
if (willBeExcluded != wasExcluded)
{
var label = willBeExcluded ? "disabled" : "enabled";
TempData[WellKnownTempData.SuccessMessage] = $"On-Chain payments for {network.CryptoCode} has been {label}.";
TempData[WellKnownTempData.SuccessMessage] =
$"On-Chain payments for {network.CryptoCode} has been {label}.";
}
else
{
TempData[WellKnownTempData.SuccessMessage] = $"Derivation settings for {network.CryptoCode} has been modified.";
TempData[WellKnownTempData.SuccessMessage] =
$"Derivation settings for {network.CryptoCode} has been modified.";
}
return RedirectToAction(nameof(UpdateStore), new { storeId = storeId });
// This is success case when derivation scheme is added to the store
return RedirectToAction(nameof(UpdateStore), new {storeId = storeId});
}
else if (!string.IsNullOrEmpty(vm.HintAddress))
{
@ -267,7 +291,7 @@ namespace BTCPayServer.Controllers
Severity = StatusMessageModel.StatusSeverity.Error,
Html = $"There was an error generating your wallet: {e.Message}"
});
return RedirectToAction(nameof(AddDerivationScheme), new { storeId, cryptoCode });
return RedirectToAction(nameof(AddDerivationScheme), new {storeId, cryptoCode});
}
if (response == null)
@ -277,7 +301,7 @@ namespace BTCPayServer.Controllers
Severity = StatusMessageModel.StatusSeverity.Error,
Html = "There was an error generating your wallet. Is your node available?"
});
return RedirectToAction(nameof(AddDerivationScheme), new { storeId, cryptoCode });
return RedirectToAction(nameof(AddDerivationScheme), new {storeId, cryptoCode});
}
var store = HttpContext.GetStoreData();
@ -313,7 +337,7 @@ namespace BTCPayServer.Controllers
Mnemonic = response.Mnemonic,
Passphrase = response.Passphrase,
IsStored = request.SavePrivateKeys,
ReturnUrl = Url.Action(nameof(UpdateStore), new { storeId })
ReturnUrl = Url.Action(nameof(UpdateStore), new {storeId})
};
return this.RedirectToRecoverySeedBackup(vm);
}
@ -330,7 +354,8 @@ namespace BTCPayServer.Controllers
private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet()
{
var isAdmin = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings)).Succeeded;
var isAdmin = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings))
.Succeeded;
if (isAdmin)
return (true, true);
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>();

View File

@ -137,6 +137,7 @@ namespace BTCPayServer.Controllers
case "save":
var storeBlob = store.GetStoreBlob();
storeBlob.SetExcluded(paymentMethodId, !vm.Enabled);
storeBlob.Hints.Lightning = false;
store.SetStoreBlob(storeBlob);
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);
await _Repo.UpdateStore(store);

View File

@ -372,8 +372,8 @@ namespace BTCPayServer.Controllers
new PaymentMethodCriteriaViewModel()
{
PaymentMethod = criteria.PaymentMethod.ToString(),
Type = criteria.Above? PaymentMethodCriteriaViewModel.CriteriaType.GreaterThan : PaymentMethodCriteriaViewModel.CriteriaType.LessThan,
Value = criteria.Value?.ToString()?? ""
Type = criteria.Above ? PaymentMethodCriteriaViewModel.CriteriaType.GreaterThan : PaymentMethodCriteriaViewModel.CriteriaType.LessThan,
Value = criteria.Value?.ToString() ?? ""
}).ToList();
vm.CustomCSS = storeBlob.CustomCSS;
vm.CustomLogo = storeBlob.CustomLogo;
@ -394,7 +394,9 @@ namespace BTCPayServer.Controllers
.Select(o =>
new CheckoutExperienceViewModel.Format()
{
Name = o.ToPrettyString(), Value = o.ToString(), PaymentId = o
Name = o.ToPrettyString(),
Value = o.ToString(),
PaymentId = o
}).ToArray();
var defaultPaymentId = storeData.GetDefaultPaymentId(_NetworkProvider);
@ -418,6 +420,7 @@ namespace BTCPayServer.Controllers
}
SetCryptoCurrencies(model, CurrentStore);
model.SetLanguages(_LangService, model.DefaultLang);
model.PaymentMethodCriteria??= new List<PaymentMethodCriteriaViewModel>();
for (var index = 0; index < model.PaymentMethodCriteria.Count; index++)
{
var methodCriterion = model.PaymentMethodCriteria[index];
@ -425,10 +428,12 @@ namespace BTCPayServer.Controllers
{
if (!CurrencyValue.TryParse(methodCriterion.Value, out var value))
{
model.AddModelError(viewModel => viewModel.PaymentMethodCriteria[index].Value, $"{methodCriterion.PaymentMethod}: invalid format (1.0 USD)", this);
model.AddModelError(viewModel => viewModel.PaymentMethodCriteria[index].Value,
$"{methodCriterion.PaymentMethod}: invalid format (1.0 USD)", this);
}
}
}
if (!ModelState.IsValid)
{
@ -439,7 +444,7 @@ namespace BTCPayServer.Controllers
.Where(viewModel => !string.IsNullOrEmpty(viewModel.Value)).Select(viewModel =>
{
CurrencyValue.TryParse(viewModel.Value, out var cv);
return new PaymentMethodCriteria() {Above = viewModel.Type == PaymentMethodCriteriaViewModel.CriteriaType.GreaterThan, Value = cv, PaymentMethod = PaymentMethodId.Parse(viewModel.PaymentMethod)};
return new PaymentMethodCriteria() { Above = viewModel.Type == PaymentMethodCriteriaViewModel.CriteriaType.GreaterThan, Value = cv, PaymentMethod = PaymentMethodId.Parse(viewModel.PaymentMethod) };
}).ToList();
#pragma warning disable 612
blob.LightningMaxValue = null;
@ -471,32 +476,6 @@ namespace BTCPayServer.Controllers
});
}
[HttpGet]
[Route("{storeId}")]
public IActionResult UpdateStore()
{
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
var storeBlob = store.GetStoreBlob();
var vm = new StoreViewModel();
vm.Id = store.Id;
vm.StoreName = store.StoreName;
vm.StoreWebsite = store.StoreWebsite;
vm.NetworkFeeMode = storeBlob.NetworkFeeMode;
vm.AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice;
vm.SpeedPolicy = store.SpeedPolicy;
vm.CanDelete = _Repo.CanDeleteStores();
AddPaymentMethods(store, storeBlob, vm);
vm.MonitoringExpiration = (int)storeBlob.MonitoringExpiration.TotalMinutes;
vm.InvoiceExpiration = (int)storeBlob.InvoiceExpiration.TotalMinutes;
vm.LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate;
vm.PaymentTolerance = storeBlob.PaymentTolerance;
vm.PayJoinEnabled = storeBlob.PayJoinEnabled;
return View(vm);
}
private void AddPaymentMethods(StoreData store, StoreBlob storeBlob, StoreViewModel vm)
{
@ -556,6 +535,36 @@ namespace BTCPayServer.Controllers
}
[HttpGet]
[Route("{storeId}")]
public IActionResult UpdateStore()
{
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
var storeBlob = store.GetStoreBlob();
var vm = new StoreViewModel();
vm.Id = store.Id;
vm.StoreName = store.StoreName;
vm.StoreWebsite = store.StoreWebsite;
vm.NetworkFeeMode = storeBlob.NetworkFeeMode;
vm.AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice;
vm.SpeedPolicy = store.SpeedPolicy;
vm.CanDelete = _Repo.CanDeleteStores();
AddPaymentMethods(store, storeBlob, vm);
vm.MonitoringExpiration = (int)storeBlob.MonitoringExpiration.TotalMinutes;
vm.InvoiceExpiration = (int)storeBlob.InvoiceExpiration.TotalMinutes;
vm.LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate;
vm.PaymentTolerance = storeBlob.PaymentTolerance;
vm.PayJoinEnabled = storeBlob.PayJoinEnabled;
vm.HintWallet = storeBlob.Hints.Wallet;
vm.HintLightning = storeBlob.Hints.Lightning;
return View(vm);
}
[HttpPost]
[Route("{storeId}")]
public async Task<IActionResult> UpdateStore(StoreViewModel model, string command = null)
@ -625,7 +634,6 @@ namespace BTCPayServer.Controllers
{
storeId = CurrentStore.Id
});
}
[HttpGet]
@ -974,10 +982,30 @@ namespace BTCPayServer.Controllers
{
storeId = CurrentStore.Id
});
}
[HttpPost]
[Route("{storeId}/dismissHint")]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> DismissHint(string id)
{
var blob = CurrentStore.GetStoreBlob();
if (id == "Wallet" || id == "Lightning")
{
try
{
var prop = blob.Hints.GetType().GetProperty(id);
prop.SetValue(blob.Hints, false);
}
// disregard parse errors
catch { }
if (CurrentStore.SetStoreBlob(blob))
{
await _Repo.UpdateStore(CurrentStore);
}
}
return Content("ack");
}
}
}

View File

@ -5,6 +5,7 @@ using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Security;
using BTCPayServer.Services.Stores;
using ExchangeSharp;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
@ -37,6 +38,23 @@ namespace BTCPayServer.Controllers
return View();
}
[HttpPost]
[Route("create")]
public async Task<IActionResult> CreateStore(CreateStoreViewModel vm)
{
if (!ModelState.IsValid)
{
return View(vm);
}
var store = await _Repo.CreateStore(GetUserId(), vm.Name);
CreatedStoreId = store.Id;
TempData[WellKnownTempData.SuccessMessage] = "Store successfully created";
return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new
{
storeId = store.Id
});
}
public string CreatedStoreId
{
get; set;
@ -108,34 +126,20 @@ namespace BTCPayServer.Controllers
for (int i = 0; i < stores.Length; i++)
{
var store = stores[i];
var blob = store.GetStoreBlob();
result.Stores.Add(new StoresViewModel.StoreViewModel()
{
Id = store.Id,
Name = store.StoreName,
WebSite = store.StoreWebsite,
IsOwner = store.Role == StoreRoles.Owner
IsOwner = store.Role == StoreRoles.Owner,
HintWalletWarning = blob.Hints.Wallet
});
}
return View(result);
}
[HttpPost]
[Route("create")]
public async Task<IActionResult> CreateStore(CreateStoreViewModel vm)
{
if (!ModelState.IsValid)
{
return View(vm);
}
var store = await _Repo.CreateStore(GetUserId(), vm.Name);
CreatedStoreId = store.Id;
TempData[WellKnownTempData.SuccessMessage] = "Store successfully created";
return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new
{
storeId = store.Id
});
}
private string GetUserId()
{
return _UserManager.GetUserId(User);

View File

@ -80,6 +80,7 @@ namespace BTCPayServer.Controllers
{
vm.Decoded = psbt.ToString();
vm.PSBT = psbt.ToBase64();
vm.PSBTHex = psbt.ToHex();
}
return View(nameof(WalletPSBT), vm ?? new WalletPSBTViewModel() { CryptoCode = walletId.CryptoCode });
@ -104,6 +105,8 @@ namespace BTCPayServer.Controllers
ModelState.AddModelError(nameof(vm.PSBT), "Invalid PSBT");
return View(vm);
}
vm.PSBTHex = psbt.ToHex();
var res = await TryHandleSigningCommands(walletId, psbt, command, new SigningContextModel(psbt));
if (res != null)
{
@ -117,6 +120,7 @@ namespace BTCPayServer.Controllers
ModelState.Remove(nameof(vm.FileName));
ModelState.Remove(nameof(vm.UploadedPSBTFile));
vm.PSBT = psbt.ToBase64();
vm.PSBTHex = psbt.ToHex();
vm.FileName = vm.UploadedPSBTFile?.FileName;
return View(vm);

View File

@ -188,6 +188,13 @@ namespace BTCPayServer.Data
public bool RedirectAutomatically { get; set; }
public bool PayJoinEnabled { get; set; }
public StoreHints Hints { get; set; }
public class StoreHints
{
public bool Wallet { get; set; }
public bool Lightning { get; set; }
}
public IPaymentFilter GetExcludedPaymentMethods()
{
#pragma warning disable CS0618 // Type or member is obsolete

View File

@ -46,6 +46,9 @@ namespace BTCPayServer.Data
var result = storeData.StoreBlob == null ? new StoreBlob() : new Serializer(null).ToObject<StoreBlob>(Encoding.UTF8.GetString(storeData.StoreBlob));
if (result.PreferredExchange == null)
result.PreferredExchange = CoinGeckoRateProvider.CoinGeckoName;
if (result.Hints == null)
result.Hints = new StoreBlob.StoreHints();
return result;
}

View File

@ -224,15 +224,7 @@ namespace BTCPayServer
return false;
}
public static void SetStatusMessageModel(this ITempDataDictionary tempData, StatusMessageModel statusMessage)
{
if (statusMessage == null)
{
tempData.Remove("StatusMessageModel");
return;
}
tempData["StatusMessageModel"] = JObject.FromObject(statusMessage).ToString(Formatting.None);
}
public static StatusMessageModel GetStatusMessageModel(this ITempDataDictionary tempData)
{

View File

@ -0,0 +1,18 @@
using System;
namespace BTCPayServer
{
public static class StringExtensions
{
public static string TrimEnd(this string input, string suffixToRemove,
StringComparison comparisonType)
{
if (input != null && suffixToRemove != null
&& input.EndsWith(suffixToRemove, comparisonType))
{
return input.Substring(0, input.Length - suffixToRemove.Length);
}
else return input;
}
}
}

View File

@ -29,12 +29,20 @@ namespace BTCPayServer.HostedServices
public List<object> Events { get; set; } = new List<object>();
bool _Dirty = false;
private bool _Unaffect;
public void MarkDirty()
{
_Dirty = true;
}
public void UnaffectAddresses()
{
_Unaffect = true;
}
public bool Dirty => _Dirty;
public bool Unaffect => _Unaffect;
}
readonly InvoiceRepository _InvoiceRepository;
@ -57,21 +65,18 @@ namespace BTCPayServer.HostedServices
readonly CompositeDisposable leases = new CompositeDisposable();
private async Task UpdateInvoice(UpdateInvoiceContext context)
private void UpdateInvoice(UpdateInvoiceContext context)
{
var invoice = context.Invoice;
if (invoice.Status == InvoiceStatus.New && invoice.ExpirationTime <= DateTimeOffset.UtcNow)
{
context.MarkDirty();
await _InvoiceRepository.UnaffectAddress(invoice.Id);
context.UnaffectAddresses();
invoice.Status = InvoiceStatus.Expired;
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.Expired));
if (invoice.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.ExpiredPaidPartial));
}
var payments = invoice.GetPayments().Where(p => p.Accounted).ToArray();
var allPaymentMethods = invoice.GetPaymentMethods();
var paymentMethod = GetNearestClearedPayment(allPaymentMethods, out var accounting);
if (paymentMethod == null)
@ -85,7 +90,7 @@ namespace BTCPayServer.HostedServices
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.PaidInFull));
invoice.Status = InvoiceStatus.Paid;
invoice.ExceptionStatus = accounting.Paid > accounting.TotalDue ? InvoiceExceptionStatus.PaidOver : InvoiceExceptionStatus.None;
await _InvoiceRepository.UnaffectAddress(invoice.Id);
context.UnaffectAddresses();
context.MarkDirty();
}
else if (invoice.Status == InvoiceStatus.Expired && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidLate)
@ -136,14 +141,14 @@ namespace BTCPayServer.HostedServices
// And not enough amount confirmed
(confirmedAccounting.Paid < accounting.MinimumTotalDue))
{
await _InvoiceRepository.UnaffectAddress(invoice.Id);
context.UnaffectAddresses();
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.FailedToConfirm));
invoice.Status = InvoiceStatus.Invalid;
context.MarkDirty();
}
else if (confirmedAccounting.Paid >= accounting.MinimumTotalDue)
{
await _InvoiceRepository.UnaffectAddress(invoice.Id);
context.UnaffectAddresses();
invoice.Status = InvoiceStatus.Confirmed;
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.Confirmed));
context.MarkDirty();
@ -278,7 +283,11 @@ namespace BTCPayServer.HostedServices
if (invoice == null)
break;
var updateContext = new UpdateInvoiceContext(invoice);
await UpdateInvoice(updateContext);
UpdateInvoice(updateContext);
if (updateContext.Unaffect)
{
await _InvoiceRepository.UnaffectAddress(invoice.Id);
}
if (updateContext.Dirty)
{
await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.GetInvoiceState());

View File

@ -12,6 +12,7 @@ using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Payments.PayJoin;
using BTCPayServer.Plugins;
using BTCPayServer.Security;
using BTCPayServer.Security.Bitpay;
using BTCPayServer.Security.GreenField;
@ -83,6 +84,7 @@ namespace BTCPayServer.Hosting
services.AddEthereumLike();
#endif
services.TryAddSingleton<SettingsRepository>();
services.TryAddSingleton<ISettingsRepository>(provider => provider.GetService<SettingsRepository>());
services.TryAddSingleton<LabelFactory>();
services.TryAddSingleton<TorServices>();
services.TryAddSingleton<SocketFactory>();
@ -91,6 +93,7 @@ namespace BTCPayServer.Hosting
services.TryAddSingleton<BTCPayServerOptions>(o =>
o.GetRequiredService<IOptions<BTCPayServerOptions>>().Value);
services.AddStartupTask<MigrationStartupTask>();
services.AddStartupTask<BlockExplorerLinkStartupTask>();
services.TryAddSingleton<InvoiceRepository>(o =>
{
var opts = o.GetRequiredService<BTCPayServerOptions>();
@ -110,24 +113,30 @@ namespace BTCPayServer.Hosting
{
var opts = o.GetRequiredService<BTCPayServerOptions>();
ApplicationDbContextFactory dbContext = null;
if (!String.IsNullOrEmpty(opts.PostgresConnectionString))
if (!string.IsNullOrEmpty(opts.PostgresConnectionString))
{
Logs.Configuration.LogInformation($"Postgres DB used ({opts.PostgresConnectionString})");
Logs.Configuration.LogInformation($"Postgres DB used");
dbContext = new ApplicationDbContextFactory(DatabaseType.Postgres, opts.PostgresConnectionString);
}
else if (!String.IsNullOrEmpty(opts.MySQLConnectionString))
else if (!string.IsNullOrEmpty(opts.MySQLConnectionString))
{
Logs.Configuration.LogInformation($"MySQL DB used ({opts.MySQLConnectionString})");
Logs.Configuration.LogInformation($"MySQL DB used");
Logs.Configuration.LogWarning("MySQL is not widely tested and should be considered experimental, we advise you to use postgres instead.");
dbContext = new ApplicationDbContextFactory(DatabaseType.MySQL, opts.MySQLConnectionString);
}
else
else if (!string.IsNullOrEmpty(opts.SQLiteFileName))
{
var connStr = "Data Source=" + Path.Combine(opts.DataDir, "sqllite.db");
Logs.Configuration.LogInformation($"SQLite DB used ({connStr})");
var connStr = "Data Source=" +(Path.IsPathRooted(opts.SQLiteFileName)
? opts.SQLiteFileName
: Path.Combine(opts.DataDir, opts.SQLiteFileName));
Logs.Configuration.LogInformation($"SQLite DB used");
Logs.Configuration.LogWarning("SQLite is not widely tested and should be considered experimental, we advise you to use postgres instead.");
dbContext = new ApplicationDbContextFactory(DatabaseType.Sqlite, connStr);
}
else
{
throw new ConfigException("No database option was configured.");
}
return dbContext;
});
@ -139,6 +148,7 @@ namespace BTCPayServer.Hosting
});
services.TryAddSingleton<AppService>();
services.AddSingleton<PluginService>();
services.TryAddTransient<Safe>();
services.TryAddSingleton<Ganss.XSS.HtmlSanitizer>(o =>
{

View File

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Services;
namespace BTCPayServer.Hosting
{
public class BlockExplorerLinkStartupTask : IStartupTask
{
private readonly SettingsRepository _settingsRepository;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
public BlockExplorerLinkStartupTask(SettingsRepository settingsRepository,
BTCPayNetworkProvider btcPayNetworkProvider)
{
_settingsRepository = settingsRepository;
_btcPayNetworkProvider = btcPayNetworkProvider;
}
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
{
var settings = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
if (settings?.BlockExplorerLinks?.Any() is true)
{
SetLinkOnNetworks(settings.BlockExplorerLinks, _btcPayNetworkProvider);
}
}
public static void SetLinkOnNetworks(List<PoliciesSettings.BlockExplorerOverrideItem> links,
BTCPayNetworkProvider networkProvider)
{
var networks = networkProvider.GetAll();
foreach (var network in networks)
{
var overrideLink = links.SingleOrDefault(item =>
item.CryptoCode.Equals(network.CryptoCode, StringComparison.InvariantCultureIgnoreCase));
network.BlockExplorerLink = overrideLink?.Link ?? network.BlockExplorerLinkDefault;
}
}
}
}

View File

@ -6,6 +6,7 @@ using BTCPayServer.Data;
using BTCPayServer.Filters;
using BTCPayServer.Logging;
using BTCPayServer.PaymentRequest;
using BTCPayServer.Plugins;
using BTCPayServer.Security;
using BTCPayServer.Services.Apps;
using BTCPayServer.Storage;
@ -56,7 +57,7 @@ namespace BTCPayServer.Hosting
services.AddProviderStorage();
services.AddSession();
services.AddSignalR();
services.AddMvc(o =>
var mvcBuilder= services.AddMvc(o =>
{
o.Filters.Add(new XFrameOptionsAttribute("DENY"));
o.Filters.Add(new XContentTypeOptionsAttribute("nosniff"));
@ -91,7 +92,11 @@ namespace BTCPayServer.Hosting
#if RAZOR_RUNTIME_COMPILE
.AddRazorRuntimeCompilation()
#endif
.AddPlugins(services, Configuration, LoggerFactory)
.AddControllersAsServices();
services.TryAddScoped<ContentSecurityPolicies>();
services.Configure<IdentityOptions>(options =>
{
@ -174,6 +179,7 @@ namespace BTCPayServer.Hosting
private static void ConfigureCore(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider prov, ILoggerFactory loggerFactory, BTCPayServerOptions options)
{
Logs.Configure(loggerFactory);
app.UsePlugins();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();

View File

@ -33,6 +33,9 @@ namespace BTCPayServer.Models.AppViewModels
[Display(Name = "Callback Notification Url")]
[Uri]
public string NotificationUrl { get; set; }
[Display(Name = "Redirect Url")]
[Uri]
public string RedirectUrl { get; set; }
[Required]
[MaxLength(30)]

View File

@ -1,5 +1,5 @@
using System;
using System.Collections.Generic;
using BTCPayServer.Contracts;
namespace BTCPayServer.Models.NotificationViewModels
{
@ -11,12 +11,5 @@ namespace BTCPayServer.Models.NotificationViewModels
public List<NotificationViewModel> Items { get; set; }
}
public class NotificationViewModel
{
public string Id { get; set; }
public DateTimeOffset Created { get; set; }
public string Body { get; set; }
public string ActionLink { get; set; }
public bool Seen { get; set; }
}
}

View File

@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
using Microsoft.AspNetCore.Mvc.Rendering;
using PaymentRequestData = BTCPayServer.Data.PaymentRequestData;
@ -139,7 +141,9 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
public DateTime ExpiryDate { get; set; }
public decimal Amount { get; set; }
public string AmountFormatted { get; set; }
public string Status { get; set; }
public InvoiceState State { get; set; }
public InvoiceStatus Status { get; set; }
public string StateFormatted { get; set; }
public List<PaymentRequestInvoicePayment> Payments { get; set; }
public string Currency { get; set; }
@ -149,6 +153,10 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
{
public string PaymentMethod { get; set; }
public decimal Amount { get; set; }
public string RateFormatted { get; set; }
public decimal Paid { get; set; }
public string PaidFormatted { get; set; }
public DateTime ReceivedDate { get; set; }
public string Link { get; set; }
public string Id { get; set; }
}

View File

@ -35,6 +35,8 @@ namespace BTCPayServer.Models.StoreViewModels
[Display(Name = "Wallet File")]
public IFormFile WalletFile { get; set; }
[Display(Name = "Wallet File Content")]
public string WalletFileContent { get; set; }
public string Config { get; set; }
public string Source { get; set; }
public string DerivationSchemeFormat { get; set; }

View File

@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using BTCPayServer.Client.Models;
using BTCPayServer.Validation;
using static BTCPayServer.Data.StoreBlob;
namespace BTCPayServer.Models.StoreViewModels
{
@ -90,6 +91,9 @@ namespace BTCPayServer.Models.StoreViewModels
[Display(Name = "Enable Payjoin/P2EP")]
public bool PayJoinEnabled { get; set; }
public bool HintWallet { get; set; }
public bool HintLightning { get; set; }
public class LightningNode
{
public string CryptoCode { get; set; }

View File

@ -4,32 +4,15 @@ namespace BTCPayServer.Models.StoreViewModels
{
public class StoresViewModel
{
public List<StoreViewModel> Stores
{
get; set;
} = new List<StoreViewModel>();
public List<StoreViewModel> Stores { get; set; } = new List<StoreViewModel>();
public class StoreViewModel
{
public string Name
{
get; set;
}
public string WebSite
{
get; set;
}
public string Id
{
get; set;
}
public bool IsOwner
{
get;
set;
}
public string Id { get; set; }
public string Name { get; set; }
public string WebSite { get; set; }
public bool IsOwner { get; set; }
public bool HintWalletWarning { get; set; }
}
}
}

View File

@ -13,6 +13,7 @@ namespace BTCPayServer.Models.WalletViewModels
public string CryptoCode { get; set; }
public string Decoded { get; set; }
string _FileName;
public string PSBTHex { get; set; }
public bool NBXSeedAvailable { get; set; }
public string FileName

View File

@ -53,7 +53,7 @@ namespace BTCPayServer.Models.WalletViewModels
public string RateError { get; set; }
public bool SupportRBF { get; set; }
[Display(Name = "Always include non-witness UTXO if available")]
public bool AlwaysIncludeNonWitnessUTXO { get; set; } = true;
public bool AlwaysIncludeNonWitnessUTXO { get; set; }
[Display(Name = "Allow fee increase (RBF)")]
public ThreeStateBool AllowFeeBump { get; set; }

View File

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;
namespace BTCPayServer.Models.WalletViewModels
{
@ -22,6 +23,7 @@ namespace BTCPayServer.Models.WalletViewModels
public class WalletSettingsAccountKeyViewModel
{
[JsonProperty("ExtPubKey")]
[DisplayName("Account key")]
public string AccountKey { get; set; }
[DisplayName("Master fingerprint")]

View File

@ -98,37 +98,51 @@ namespace BTCPayServer.PaymentRequest
AnyPendingInvoice = pendingInvoice != null,
PendingInvoiceHasPayments = pendingInvoice != null &&
pendingInvoice.ExceptionStatus != InvoiceExceptionStatus.None,
Invoices = invoices.Select(entity => new ViewPaymentRequestViewModel.PaymentRequestInvoice()
Invoices = invoices.Select(entity =>
{
Id = entity.Id,
Amount = entity.Price,
AmountFormatted = _currencies.FormatCurrency(entity.Price, blob.Currency),
Currency = entity.Currency,
ExpiryDate = entity.ExpirationTime.DateTime,
Status = entity.GetInvoiceState().ToString(),
Payments = entity
.GetPayments()
.Select(paymentEntity =>
var state = entity.GetInvoiceState();
return new ViewPaymentRequestViewModel.PaymentRequestInvoice
{
var paymentData = paymentEntity.GetCryptoPaymentData();
var paymentMethodId = paymentEntity.GetPaymentMethodId();
if (paymentData is null || paymentMethodId is null)
{
return null;
}
Id = entity.Id,
Amount = entity.Price,
AmountFormatted = _currencies.FormatCurrency(entity.Price, blob.Currency),
Currency = entity.Currency,
ExpiryDate = entity.ExpirationTime.DateTime,
State = state,
StateFormatted = state.ToString(),
Payments = entity
.GetPayments()
.Select(paymentEntity =>
{
var paymentData = paymentEntity.GetCryptoPaymentData();
var paymentMethodId = paymentEntity.GetPaymentMethodId();
if (paymentData is null || paymentMethodId is null)
{
return null;
}
string txId = paymentData.GetPaymentId();
string link = GetTransactionLink(paymentMethodId, txId);
return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment()
{
Amount = paymentData.GetValue(),
PaymentMethod = paymentMethodId.ToString(),
Link = link,
Id = txId
};
})
.Where(payment => payment != null)
.ToList()
string txId = paymentData.GetPaymentId();
string link = GetTransactionLink(paymentMethodId, txId);
var paymentMethod = entity.GetPaymentMethod(paymentMethodId);
var amount = paymentData.GetValue();
var rate = paymentMethod.Rate;
var paid = (amount - paymentEntity.NetworkFee) * rate;
return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment
{
Amount = amount,
Paid = paid,
ReceivedDate = paymentEntity.ReceivedTime.DateTime,
PaidFormatted = _currencies.FormatCurrency(paid, blob.Currency),
RateFormatted = _currencies.FormatCurrency(rate, blob.Currency),
PaymentMethod = paymentMethodId.ToPrettyString(),
Link = link,
Id = txId
};
})
.Where(payment => payment != null)
.ToList()
};
}).ToList()
};
}

View File

@ -138,9 +138,7 @@ namespace BTCPayServer.Payments.Bitcoin
switch (newEvent)
{
case NBXplorer.Models.NewBlockEvent evt:
await Task.WhenAll((await _InvoiceRepository.GetPendingInvoices())
.Select(invoiceId => UpdatePaymentStates(wallet, invoiceId))
.ToArray());
await UpdatePaymentStates(wallet, await _InvoiceRepository.GetPendingInvoices());
_Aggregator.Publish(new Events.NewBlockEvent() { CryptoCode = evt.CryptoCode });
break;
case NBXplorer.Models.NewTransactionEvent evt:
@ -206,11 +204,21 @@ namespace BTCPayServer.Payments.Bitcoin
}
}
async Task UpdatePaymentStates(BTCPayWallet wallet, string[] invoiceIds)
{
var invoices = await _InvoiceRepository.GetInvoices(invoiceIds);
await Task.WhenAll(invoices.Select(i => UpdatePaymentStates(wallet, i)).ToArray());
}
async Task<InvoiceEntity> UpdatePaymentStates(BTCPayWallet wallet, string invoiceId)
{
var invoice = await _InvoiceRepository.GetInvoice(invoiceId, false);
if (invoice == null)
return null;
return await UpdatePaymentStates(wallet, invoice);
}
async Task<InvoiceEntity> UpdatePaymentStates(BTCPayWallet wallet, InvoiceEntity invoice)
{
List<PaymentEntity> updatedPaymentEntities = new List<PaymentEntity>();
var transactions = await wallet.GetTransactions(invoice.GetAllBitcoinPaymentData()
.Select(p => p.Outpoint.Hash)
@ -363,7 +371,7 @@ namespace BTCPayServer.Payments.Bitcoin
var address = network.NBXplorerNetwork.CreateAddress(strategy, coin.KeyPath, coin.ScriptPubKey);
var paymentData = new BitcoinLikePaymentData(address, coin.Value, coin.OutPoint,
transaction.Transaction.RBF, coin.KeyPath);
transaction?.Transaction is null ? true : transaction.Transaction.RBF, coin.KeyPath);
var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin.Timestamp, paymentData, network).ConfigureAwait(false);
alreadyAccounted.Add(coin.OutPoint);

View File

@ -25,10 +25,5 @@ namespace BTCPayServer.Payments.Lightning
{
return 0.0m;
}
public void SetPaymentDetails(IPaymentMethodDetails newPaymentMethodDetails)
{
BOLT11 = newPaymentMethodDetails.GetPaymentDestination();
}
}
}

View File

@ -137,76 +137,32 @@ namespace BTCPayServer.Payments.Lightning
readonly CompositeDisposable leases = new CompositeDisposable();
public Task StartAsync(CancellationToken cancellationToken)
{
leases.Add(_Aggregator.Subscribe<Events.InvoiceEvent>(inv =>
leases.Add(_Aggregator.Subscribe<Events.InvoiceEvent>(async inv =>
{
if (inv.Name == InvoiceEvent.Created)
{
_CheckInvoices.Writer.TryWrite(inv.Invoice.Id);
}
if (inv.Name == InvoiceEvent.ReceivedPayment && inv.Invoice.Status == InvoiceStatus.New && inv.Invoice.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
{
var pm = inv.Invoice.GetPaymentMethods().First();
if (pm.Calculate().Due.GetValue(pm.Network as BTCPayNetwork) > 0m)
{
await CreateNewLNInvoiceForBTCPayInvoice(inv.Invoice);
}
}
}));
leases.Add(_Aggregator.Subscribe<Events.InvoiceDataChangedEvent>(async inv =>
{
if (inv.State.Status == InvoiceStatus.New &&
inv.State.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
{
var invoice = await _InvoiceRepository.GetInvoice(inv.InvoiceId);
var paymentMethods = invoice.GetPaymentMethods()
.Where(method => method.GetId().PaymentType == PaymentTypes.LightningLike).ToArray();
var store = await _storeRepository.FindStore(invoice.StoreId);
if (paymentMethods.Any())
{
var logs = new InvoiceLogs();
logs.Write("Partial payment detected, attempting to update all lightning payment methods with new bolt11 with correct due amount.", InvoiceEventData.EventSeverity.Info);
foreach (var paymentMethod in paymentMethods)
{
try
{
var supportedMethod =
invoice.GetSupportedPaymentMethod<LightningSupportedPaymentMethod>(
paymentMethod.GetId()).First();
var prepObj =
_lightningLikePaymentHandler.PreparePayment(supportedMethod, store,
paymentMethod.Network);
var newPaymentMethodDetails =
await _lightningLikePaymentHandler.CreatePaymentMethodDetails(
logs, supportedMethod,
paymentMethod, store, paymentMethod.Network, prepObj);
var instanceListenerKey = (paymentMethod.Network.CryptoCode,
supportedMethod.GetLightningUrl().ToString());
if (_InstanceListeners.TryGetValue(instanceListenerKey, out var instanceListener))
{
await _InvoiceRepository.NewPaymentDetails(invoice.Id, newPaymentMethodDetails,
paymentMethod.Network);
instanceListener.AddListenedInvoice(new ListenedInvoice()
{
Expiration = invoice.ExpirationTime,
Uri = supportedMethod.GetLightningUrl().BaseUri.AbsoluteUri,
PaymentMethodDetails = (LightningLikePaymentMethodDetails) newPaymentMethodDetails,
SupportedPaymentMethod = supportedMethod,
PaymentMethod = paymentMethod,
Network = (BTCPayNetwork) paymentMethod.Network,
InvoiceId = invoice.Id
});
_Aggregator.Publish(new Events.InvoiceNewPaymentDetailsEvent(invoice.Id,
newPaymentMethodDetails, paymentMethod.GetId()));
}
}
catch (Exception e)
{
logs.Write($"Could not update {paymentMethod.GetId().ToPrettyString()}: {e.Message}",
InvoiceEventData.EventSeverity.Error);
}
}
await _InvoiceRepository.AddInvoiceLogs(invoice.Id, logs);
_CheckInvoices.Writer.TryWrite(inv.InvoiceId);
}
await CreateNewLNInvoiceForBTCPayInvoice(invoice);
}
}));
_CheckingInvoice = CheckingInvoice(_Cts.Token);
_ListenPoller = new Timer(async s =>
@ -224,6 +180,66 @@ namespace BTCPayServer.Payments.Lightning
return Task.CompletedTask;
}
private async Task CreateNewLNInvoiceForBTCPayInvoice(InvoiceEntity invoice)
{
var paymentMethods = invoice.GetPaymentMethods()
.Where(method => method.GetId().PaymentType == PaymentTypes.LightningLike)
.ToArray();
var store = await _storeRepository.FindStore(invoice.StoreId);
if (paymentMethods.Any())
{
var logs = new InvoiceLogs();
logs.Write(
"Partial payment detected, attempting to update all lightning payment methods with new bolt11 with correct due amount.",
InvoiceEventData.EventSeverity.Info);
foreach (var paymentMethod in paymentMethods)
{
try
{
var supportedMethod = invoice
.GetSupportedPaymentMethod<LightningSupportedPaymentMethod>(paymentMethod.GetId()).First();
var prepObj =
_lightningLikePaymentHandler.PreparePayment(supportedMethod, store, paymentMethod.Network);
var newPaymentMethodDetails =
(LightningLikePaymentMethodDetails)(await _lightningLikePaymentHandler
.CreatePaymentMethodDetails(logs, supportedMethod, paymentMethod, store,
paymentMethod.Network, prepObj));
var instanceListenerKey = (paymentMethod.Network.CryptoCode,
supportedMethod.GetLightningUrl().ToString());
if (_InstanceListeners.TryGetValue(instanceListenerKey, out var instanceListener))
{
await _InvoiceRepository.NewPaymentDetails(invoice.Id, newPaymentMethodDetails,
paymentMethod.Network);
instanceListener.AddListenedInvoice(new ListenedInvoice()
{
Expiration = invoice.ExpirationTime,
Uri = supportedMethod.GetLightningUrl().BaseUri.AbsoluteUri,
PaymentMethodDetails = newPaymentMethodDetails,
SupportedPaymentMethod = supportedMethod,
PaymentMethod = paymentMethod,
Network = (BTCPayNetwork)paymentMethod.Network,
InvoiceId = invoice.Id
});
_Aggregator.Publish(new Events.InvoiceNewPaymentDetailsEvent(invoice.Id,
newPaymentMethodDetails, paymentMethod.GetId()));
}
}
catch (Exception e)
{
logs.Write($"Could not update {paymentMethod.GetId().ToPrettyString()}: {e.Message}",
InvoiceEventData.EventSeverity.Error);
}
}
await _InvoiceRepository.AddInvoiceLogs(invoice.Id, logs);
_CheckInvoices.Writer.TryWrite(invoice.Id);
}
}
TimeSpan _PollInterval = TimeSpan.FromMinutes(1.0);
public TimeSpan PollInterval
{

View File

@ -0,0 +1,184 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using BTCPayServer.Configuration;
using BTCPayServer.Contracts;
using McMaster.NETCore.Plugins;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
namespace BTCPayServer.Plugins
{
public static class PluginManager
{
public const string BTCPayPluginSuffix = ".btcpay";
private static readonly List<Assembly> _pluginAssemblies = new List<Assembly>();
private static ILogger _logger;
public static IMvcBuilder AddPlugins(this IMvcBuilder mvcBuilder, IServiceCollection serviceCollection,
IConfiguration config, ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger(typeof(PluginManager));
var pluginsFolder = config.GetPluginDir(DefaultConfiguration.GetNetworkType(config));
var plugins = new List<IBTCPayServerPlugin>();
_logger.LogInformation($"Loading plugins from {pluginsFolder}");
Directory.CreateDirectory(pluginsFolder);
ExecuteCommands(pluginsFolder);
List<(PluginLoader, Assembly, IFileProvider)> loadedPlugins = new List<(PluginLoader, Assembly, IFileProvider)>();
var systemExtensions = GetDefaultLoadedPluginAssemblies();
plugins.AddRange(systemExtensions.SelectMany(assembly =>
GetAllPluginTypesFromAssembly(assembly).Select(GetPluginInstanceFromType)));
foreach (IBTCPayServerPlugin btcPayServerExtension in plugins)
{
btcPayServerExtension.SystemPlugin = true;
}
foreach (var dir in Directory.GetDirectories(pluginsFolder))
{
var pluginName = Path.GetFileName(dir);
var plugin = PluginLoader.CreateFromAssemblyFile(
Path.Combine(dir, pluginName + ".dll"), // create a plugin from for the .dll file
config =>
// this ensures that the version of MVC is shared between this app and the plugin
config.PreferSharedTypes = true);
mvcBuilder.AddPluginLoader(plugin);
var pluginAssembly = plugin.LoadDefaultAssembly();
_pluginAssemblies.Add(pluginAssembly);
var fileProvider = CreateEmbeddedFileProviderForAssembly(pluginAssembly);
loadedPlugins.Add((plugin, pluginAssembly, fileProvider));
plugins.AddRange(GetAllPluginTypesFromAssembly(pluginAssembly)
.Select(GetPluginInstanceFromType));
}
foreach (var plugin in plugins)
{
try
{
_logger.LogInformation(
$"Adding and executing plugin {plugin.Identifier} - {plugin.Version}");
plugin.Execute(serviceCollection);
serviceCollection.AddSingleton(plugin);
}
catch (Exception e)
{
_logger.LogError($"Error when loading plugin {plugin.Identifier} - {plugin.Version}{Environment.NewLine}{e.Message}");
}
}
return mvcBuilder;
}
public static void UsePlugins(this IApplicationBuilder applicationBuilder)
{
foreach (var extension in applicationBuilder.ApplicationServices
.GetServices<IBTCPayServerPlugin>())
{
extension.Execute(applicationBuilder,
applicationBuilder.ApplicationServices);
}
var webHostEnvironment = applicationBuilder.ApplicationServices.GetService<IWebHostEnvironment>();
List<IFileProvider> providers = new List<IFileProvider>() {webHostEnvironment.WebRootFileProvider};
providers.AddRange(
_pluginAssemblies
.Select(CreateEmbeddedFileProviderForAssembly));
webHostEnvironment.WebRootFileProvider = new CompositeFileProvider(providers);
}
private static Assembly[] GetDefaultLoadedPluginAssemblies()
{
return AppDomain.CurrentDomain.GetAssemblies().Where(assembly =>
assembly?.FullName?.StartsWith("BTCPayServer.Plugins",
StringComparison.InvariantCultureIgnoreCase) is true)
.ToArray();
}
private static Type[] GetAllPluginTypesFromAssembly(Assembly assembly)
{
return assembly.GetTypes().Where(type =>
typeof(IBTCPayServerPlugin).IsAssignableFrom(type) &&
!type.IsAbstract).ToArray();
}
private static IBTCPayServerPlugin GetPluginInstanceFromType(Type type)
{
return (IBTCPayServerPlugin)Activator.CreateInstance(type, Array.Empty<object>());
}
private static IFileProvider CreateEmbeddedFileProviderForAssembly(Assembly assembly)
{
return new EmbeddedFileProvider(assembly);
}
private static void ExecuteCommands(string pluginsFolder)
{
var pendingCommands = GetPendingCommands(pluginsFolder);
foreach (var command in pendingCommands)
{
ExecuteCommand(command, pluginsFolder);
}
File.Delete(Path.Combine(pluginsFolder, "commands"));
}
private static void ExecuteCommand((string command, string extension) command, string pluginsFolder)
{
var dirName = Path.Combine(pluginsFolder, command.extension);
switch (command.command)
{
case "delete":
if (Directory.Exists(dirName))
{
Directory.Delete(dirName, true);
}
break;
case "install":
var fileName = dirName + BTCPayPluginSuffix;
if (File.Exists(fileName))
{
ZipFile.ExtractToDirectory(fileName, dirName, true);
File.Delete(fileName);
}
break;
}
}
public static (string command, string plugin)[] GetPendingCommands(string pluginsFolder)
{
if (!File.Exists(Path.Combine(pluginsFolder, "commands")))
return Array.Empty<(string command, string plugin)>();
var commands = File.ReadAllLines(Path.Combine(pluginsFolder, "commands"));
return commands.Select(s =>
{
var split = s.Split(':');
return (split[0].ToLower(CultureInfo.InvariantCulture), split[1]);
}).ToArray();
}
public static void QueueCommands(string pluginsFolder, params ( string action, string plugin)[] commands)
{
File.AppendAllLines(Path.Combine(pluginsFolder, "commands"),
commands.Select((tuple) => $"{tuple.action}:{tuple.plugin}"));
}
public static void CancelCommands(string pluginDir, string plugin)
{
var cmds = GetPendingCommands(pluginDir).Where(tuple =>
!tuple.plugin.Equals(plugin, StringComparison.InvariantCultureIgnoreCase)).ToArray();
File.Delete(Path.Combine(pluginDir, "commands"));
QueueCommands(pluginDir, cmds);
}
}
}

View File

@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using BTCPayServer.Configuration;
using BTCPayServer.Contracts;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
namespace BTCPayServer.Plugins
{
public class PluginService
{
private readonly BTCPayServerOptions _btcPayServerOptions;
private readonly HttpClient _githubClient;
public PluginService(IEnumerable<IBTCPayServerPlugin> btcPayServerPlugins,
IHttpClientFactory httpClientFactory, BTCPayServerOptions btcPayServerOptions)
{
LoadedPlugins = btcPayServerPlugins;
_githubClient = httpClientFactory.CreateClient();
_githubClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("btcpayserver", "1"));
_btcPayServerOptions = btcPayServerOptions;
}
public IEnumerable<IBTCPayServerPlugin> LoadedPlugins { get; }
public async Task<IEnumerable<AvailablePlugin>> GetRemotePlugins()
{
var resp = await _githubClient
.GetStringAsync(new Uri($"https://api.github.com/repos/{_btcPayServerOptions.PluginRemote}/contents"));
var files = JsonConvert.DeserializeObject<GithubFile[]>(resp);
return await Task.WhenAll(files.Where(file => file.Name.EndsWith($"{PluginManager.BTCPayPluginSuffix}.json", StringComparison.InvariantCulture)).Select(async file =>
{
return await _githubClient.GetStringAsync(file.DownloadUrl).ContinueWith(
task => JsonConvert.DeserializeObject<AvailablePlugin>(task.Result), TaskScheduler.Current);
}));
}
public async Task DownloadRemotePlugin(string plugin)
{
var dest = _btcPayServerOptions.PluginDir;
var resp = await _githubClient
.GetStringAsync(new Uri($"https://api.github.com/repos/{_btcPayServerOptions.PluginRemote}/contents"));
var files = JsonConvert.DeserializeObject<GithubFile[]>(resp);
var ext = files.SingleOrDefault(file => file.Name == $"{plugin}{PluginManager.BTCPayPluginSuffix}");
if (ext is null)
{
throw new Exception("Plugin not found on remote");
}
var filedest = Path.Combine(dest, ext.Name);
Directory.CreateDirectory(Path.GetDirectoryName(filedest));
new WebClient().DownloadFile(new Uri(ext.DownloadUrl), filedest);
}
public void InstallPlugin(string plugin)
{
var dest = _btcPayServerOptions.PluginDir;
UninstallPlugin(plugin);
PluginManager.QueueCommands(dest, ("install", plugin));
}
public async Task UploadPlugin(IFormFile plugin)
{
var dest = _btcPayServerOptions.PluginDir;
var filedest = Path.Combine(dest, plugin.FileName);
Directory.CreateDirectory(Path.GetDirectoryName(filedest));
if (Path.GetExtension(filedest) == PluginManager.BTCPayPluginSuffix)
{
await using var stream = new FileStream(filedest, FileMode.Create);
await plugin.CopyToAsync(stream);
}
}
public void UninstallPlugin(string plugin)
{
var dest = _btcPayServerOptions.PluginDir;
PluginManager.QueueCommands(dest, ("delete", plugin));
}
public class AvailablePlugin : IBTCPayServerPlugin
{
public string Identifier { get; set; }
public string Name { get; set; }
public Version Version { get; set; }
public string Description { get; set; }
public bool SystemPlugin { get; set; } = false;
public string[] Dependencies { get; } = Array.Empty<string>();
public void Execute(IApplicationBuilder applicationBuilder,
IServiceProvider applicationBuilderApplicationServices)
{
}
public void Execute(IServiceCollection applicationBuilder)
{
}
}
class GithubFile
{
[JsonProperty("name")] public string Name { get; set; }
[JsonProperty("sha")] public string Sha { get; set; }
[JsonProperty("download_url")] public string DownloadUrl { get; set; }
}
public (string command, string plugin)[] GetPendingCommands()
{
return PluginManager.GetPendingCommands(_btcPayServerOptions.PluginDir);
}
public void CancelCommands(string plugin)
{
PluginManager.CancelCommands(_btcPayServerOptions.PluginDir, plugin);
}
}
}

View File

@ -20,7 +20,8 @@
"BTCPAY_DEBUGLOG": "debug.log",
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
"BTCPAY_UPDATEURL": ""
"BTCPAY_UPDATEURL": "",
"BTCPAY_DOCKERDEPLOYMENT": "true"
},
"applicationUrl": "http://127.0.0.1:14142/"
},
@ -51,7 +52,8 @@
"BTCPAY_SSHPASSWORD": "opD3i2282D",
"BTCPAY_DEBUGLOG": "debug.log",
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
"BTCPAY_SOCKSENDPOINT": "localhost:9050"
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
"BTCPAY_DOCKERDEPLOYMENT": "true"
},
"applicationUrl": "https://localhost:14142/"
},
@ -84,7 +86,8 @@
"BTCPAY_SSHPASSWORD": "opD3i2282D",
"BTCPAY_DEBUGLOG": "debug.log",
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
"BTCPAY_SOCKSENDPOINT": "localhost:9050"
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
"BTCPAY_DOCKERDEPLOYMENT": "true"
},
"applicationUrl": "https://localhost:14142/"
}

View File

@ -21,7 +21,8 @@ namespace BTCPayServer.Services.Altcoins.Ethereum
serviceCollection.AddSingleton<IHostedService, EthereumService>(provider => provider.GetService<EthereumService>());
serviceCollection.AddSingleton<EthereumLikePaymentMethodHandler>();
serviceCollection.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<EthereumLikePaymentMethodHandler>());
serviceCollection.AddSingleton<IStoreNavExtension, EthereumStoreNavExtension>();
serviceCollection.AddSingleton<IUIExtension>(new UIExtension("Ethereum/StoreNavEthereumExtension", "store-nav"));
serviceCollection.AddTransient<NoRedirectHttpClientHandler>();
serviceCollection.AddSingleton<ISyncSummaryProvider, EthereumSyncSummaryProvider>();
serviceCollection.AddHttpClient(EthereumInvoiceCreateHttpClient)

View File

@ -1,11 +0,0 @@
#if ALTCOINS
using BTCPayServer.Contracts;
namespace BTCPayServer.Services.Altcoins.Ethereum
{
public class EthereumStoreNavExtension: IStoreNavExtension
{
public string Partial { get; } = "Ethereum/StoreNavEthereumExtension";
}
}
#endif

View File

@ -23,7 +23,7 @@ namespace BTCPayServer.Services.Altcoins.Monero
serviceCollection.AddHostedService<MoneroListener>();
serviceCollection.AddSingleton<MoneroLikePaymentMethodHandler>();
serviceCollection.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<MoneroLikePaymentMethodHandler>());
serviceCollection.AddSingleton<IStoreNavExtension, MoneroStoreNavExtension>();
serviceCollection.AddSingleton<IUIExtension>(new UIExtension("Monero/StoreNavMoneroExtension", "store-nav"));
serviceCollection.AddSingleton<ISyncSummaryProvider, MoneroSyncSummaryProvider>();
return serviceCollection;

View File

@ -1,11 +0,0 @@
#if ALTCOINS
using BTCPayServer.Contracts;
namespace BTCPayServer.Services.Altcoins.Monero
{
public class MoneroStoreNavExtension : IStoreNavExtension
{
public string Partial { get; } = "Monero/StoreNavMoneroExtension";
}
}
#endif

View File

@ -248,13 +248,13 @@ namespace BTCPayServer.Services.Apps
}
}
public async Task<AppData> GetApp(string appId, AppType appType, bool includeStore = false)
public async Task<AppData> GetApp(string appId, AppType? appType, bool includeStore = false)
{
using (var ctx = _ContextFactory.CreateContext())
{
var query = ctx.Apps
.Where(us => us.Id == appId &&
us.AppType == appType.ToString());
(appType == null || us.AppType == appType.ToString()));
if (includeStore)
{

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