Compare commits
7 Commits
v1.1.2
...
pj/no-invo
Author | SHA1 | Date | |
---|---|---|---|
c1fe473cc7 | |||
d3923da8e4 | |||
eac4d54466 | |||
717836e9e0 | |||
8d1ec4b5c0 | |||
1ee5c82b8b | |||
c775c3cc78 |
BTCPayServer.Client
BTCPayServer.Common
BTCPayServer.Data
BTCPayServer.Rating
BTCPayServer.Tests
AltcoinTests
BTCPayServer.Tests.csprojBTCPayServerTester.csCoinSwitchTests.csCrowdfundTests.csGreenfieldAPITests.csPayJoinTests.csSeleniumTester.csSeleniumTests.csTestAccount.csU2FTests.csUnitTest1.csBTCPayServer
BTCPayServer.csprojDerivationSchemeSettings.csEventAggregator.cs
Components/NotificationsDropdown
Configuration
Controllers
AccountController.csAppsController.Crowdfund.csAppsController.PointOfSale.cs
GreenField
GreenFieldUtils.csInvoiceController.csPullPaymentController.csStoreOnChainWalletsController.csStoreWebhooksController.cs
HomeController.csInvoiceController.UI.csManageController.APIKeys.csManageController.U2F.csManageController.csPullPaymentController.csServerController.Storage.csServerController.Users.csServerController.csStoresController.CoinSwitch.csStoresController.LightningLike.csStoresController.Onchain.csStoresController.csWalletsController.PullPayments.csWalletsController.csData
Payouts
BitcoinLike
AddressClaimDestination.csBitcoinLikePayoutHandler.csIBitcoinLikeClaimDestination.csPayoutTransactionOnChainBlob.csUriClaimDestination.cs
IClaimDestination.csIPayoutHandler.csPayoutBlob.csPayoutExtensions.csPullPayments
PullPaymentsExtensions.csStoreBlob.csEvents
Extensions.csFido2
HostedServices
InvoiceWatcher.csPullPaymentHostedService.csTorServicesHostedService.csTransactionLabelMarkerHostedService.csWebhookNotificationManager.cs
Hosting
ModelBinders
DateTimeOffsetModelBinder.csDefaultModelBinderProvider.csDerivationSchemeModelBinder.csPaymentMethodIdModelBinder.csWalletIdModelBinder.cs
Models
PaymentRequest
Payments
Bitcoin
Coinswitch
Lightning
PayJoin
Plugins
Program.csSecurity/GreenField
Services
Storage/ViewModels
U2F
Models
AddU2FDeviceViewModel.csLoginWithU2FViewModel.csServerChallenge.csServerRegisterResponse.csU2FAuthenticationViewModel.csU2FDeviceAuthenticationRequest.cs
U2FService.csViews
Account
ForgotPassword.cshtmlLogin.cshtmlLoginWith2fa.cshtmlLoginWithFido2.cshtmlLoginWithRecoveryCode.cshtmlLoginWithU2F.cshtmlRegister.cshtmlSecondaryLogin.cshtmlSetPassword.cshtml
Apps
AppsNavPages.csCreateApp.cshtmlListApps.cshtmlUpdateCrowdfund.cshtmlUpdatePointOfSale.cshtml_ViewImports.cshtml_ViewStart.cshtml
AppsPublic
EthereumLikeStore
Fido2
Home
Invoice
Manage
APIKeys.cshtmlAddApiKey.cshtmlAddU2FDevice.cshtmlAuthorizeAPIKey.cshtmlChangePassword.cshtmlConfirmAPIKey.cshtmlEnableAuthenticator.cshtmlIndex.cshtmlManageNavPages.csNotificationSettings.cshtmlSetPassword.cshtmlTwoFactorAuthentication.cshtmlU2FAuthentication.cshtml_Nav.cshtml_ViewStart.cshtml
MoneroLikeStore
Notifications
PaymentRequest
PullPayment
Server
CLightningRestServices.cshtmlConfiguratorService.cshtmlCreateTemporaryFileUrl.cshtmlCreateUser.cshtmlDynamicDnsService.cshtmlDynamicDnsServices.cshtmlEditAmazonS3StorageProvider.cshtmlEditAzureBlobStorageStorageProvider.cshtmlEditFilesystemStorageProvider.cshtmlEditGoogleCloudStorageStorageProvider.cshtmlEmails.cshtmlFiles.cshtmlLightningChargeServices.cshtmlLightningWalletServices.cshtmlListPlugins.cshtmlListUsers.cshtmlLndSeedBackup.cshtmlLndServices.cshtmlLogs.cshtmlMaintenance.cshtmlP2PService.cshtmlPolicies.cshtmlRPCService.cshtmlSSHService.cshtmlServices.cshtmlStorage.cshtmlTheme.cshtmlUser.cshtml_Nav.cshtml_ViewStart.cshtml
Shared
Bitcoin
CoinSwitch
CheckoutContentExtension.cshtmlCheckoutEnd.cshtmlCheckoutTabExtension.cshtmlStoreIntegrationCoinSwitchOption.cshtml
Confirm.cshtmlEmailsBody.cshtmlEthereum
Lightning
PostRedirect.cshtmlShopify
_Layout.cshtml_NavLayout.cshtmlShopify
Stores
AddLightningNode.cshtmlCheckoutExperience.cshtmlCreateToken.cshtmlEmails.cshtmlGenerateWallet.cshtmlGenerateWalletOptions.cshtml
ImportWallet
ImportWalletOptions.cshtmlIntegrations.cshtmlListTokens.cshtmlModifyWallet.cshtmlModifyWebhook.cshtmlPayButton.cshtmlPayButtonEnable.cshtmlRates.cshtmlRequestPairing.cshtmlSetupLightningNode.cshtmlSetupWallet.cshtmlShowToken.cshtmlStoreNavPages.csStoreUsers.cshtmlUpdateCoinSwitchSettings.cshtmlUpdateStore.cshtmlWebhooks.cshtml_LayoutWalletSetup.cshtml_Nav.cshtml_ViewStart.cshtmlUserStores
ViewsRazor.csWallets
CoinSelection.cshtmlListWallets.cshtmlNewPullPayment.cshtmlPayouts.cshtmlPullPayments.cshtmlSignWithSeed.cshtmlWalletPSBT.cshtmlWalletPSBTCombine.cshtmlWalletPSBTReady.cshtmlWalletReceive.cshtmlWalletRescan.cshtmlWalletSend.cshtmlWalletSendVault.cshtmlWalletSettings.cshtmlWalletSigningMenu.cshtmlWalletTransactions.cshtmlWalletsNavPages.cs_Nav.cshtml_ViewStart.cshtml
wwwroot
checkout
img
imlegacy
js/webauthn
main
paybutton
swagger/v1
vendor
Build
Changelog.mdREADME.md@ -13,7 +13,7 @@
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Version Condition=" '$(Version)' == '' ">1.4.0</Version>
|
||||
<Version Condition=" '$(Version)' == '' ">1.3.0</Version>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
@ -27,7 +27,7 @@
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NBitcoin" Version="5.0.77" />
|
||||
<PackageReference Include="NBitcoin" Version="5.0.73" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
|
@ -1,41 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<IEnumerable<InvoiceData>> GetInvoices(string storeId, string orderId = null, InvoiceStatus[] status = null,
|
||||
DateTimeOffset? startDate = null,
|
||||
DateTimeOffset? endDate = null,
|
||||
bool includeArchived = false,
|
||||
public virtual async Task<IEnumerable<InvoiceData>> GetInvoices(string storeId, bool includeArchived = false,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
Dictionary<string, object> queryPayload = new Dictionary<string, object>();
|
||||
queryPayload.Add(nameof(includeArchived), includeArchived);
|
||||
|
||||
if (startDate is DateTimeOffset s)
|
||||
queryPayload.Add(nameof(startDate), Utils.DateTimeToUnixTime(s));
|
||||
|
||||
if (endDate is DateTimeOffset e)
|
||||
queryPayload.Add(nameof(endDate), Utils.DateTimeToUnixTime(e));
|
||||
|
||||
if (orderId != null)
|
||||
queryPayload.Add(nameof(orderId), orderId);
|
||||
|
||||
if (status != null)
|
||||
queryPayload.Add(nameof(status), status.Select(s=> s.ToString().ToLower()).ToArray());
|
||||
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/invoices",
|
||||
queryPayload), token);
|
||||
new Dictionary<string, object>() {{nameof(includeArchived), includeArchived}}), token);
|
||||
return await HandleResponse<IEnumerable<InvoiceData>>(response);
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@ -70,13 +69,6 @@ namespace BTCPayServer.Client
|
||||
return JsonConvert.DeserializeObject<T>(str);
|
||||
}
|
||||
|
||||
public async Task<T> SendHttpRequest<T>(string path,
|
||||
Dictionary<string, object> queryPayload = null,
|
||||
HttpMethod method = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var resp = await _httpClient.SendAsync(CreateHttpRequest(path, queryPayload, method), cancellationToken);
|
||||
return await HandleResponse<T>(resp);
|
||||
}
|
||||
protected virtual HttpRequestMessage CreateHttpRequest(string path,
|
||||
Dictionary<string, object> queryPayload = null,
|
||||
HttpMethod method = null)
|
||||
|
@ -21,6 +21,5 @@ namespace BTCPayServer.Client.Models
|
||||
[JsonConverter(typeof(KeyPathJsonConverter))]
|
||||
public KeyPath KeyPath { get; set; }
|
||||
public string Address { get; set; }
|
||||
public int Confirmations { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -19,31 +19,7 @@ namespace BTCPayServer.Client.Models
|
||||
}
|
||||
public string DeliveryId { get; set; }
|
||||
public string WebhookId { get; set; }
|
||||
string _OriginalDeliveryId;
|
||||
public string OriginalDeliveryId
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_OriginalDeliveryId is null)
|
||||
{
|
||||
// Due to a typo in old version, we serialized `orignalDeliveryId` rather than `orignalDeliveryId`
|
||||
// We silently fix that here.
|
||||
// Note we can remove this code later on, as old webhook event are unlikely to be useful to anyone,
|
||||
// and having a null orignalDeliveryId is not end of the world
|
||||
if (AdditionalData != null &&
|
||||
AdditionalData.TryGetValue("orignalDeliveryId", out var tok))
|
||||
{
|
||||
_OriginalDeliveryId = tok.Value<string>();
|
||||
AdditionalData.Remove("orignalDeliveryId");
|
||||
}
|
||||
}
|
||||
return _OriginalDeliveryId;
|
||||
}
|
||||
set
|
||||
{
|
||||
_OriginalDeliveryId = value;
|
||||
}
|
||||
}
|
||||
public string OrignalDeliveryId { get; set; }
|
||||
public bool IsRedelivery { get; set; }
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public WebhookEventType Type { get; set; }
|
||||
|
@ -1,29 +0,0 @@
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public partial class BTCPayNetworkProvider
|
||||
{
|
||||
public void InitAlthash()
|
||||
{
|
||||
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("HTML");
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Althash",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.htmlcoin.com/api/tx/{0}" : "https://explorer.htmlcoin.com/api/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "htmlcoin",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"HTML_X = HTML_USD",
|
||||
"HTML_USD = hitbtc(HTML_USD)"
|
||||
},
|
||||
CryptoImagePath = "imlegacy/althash.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("88'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -11,8 +11,8 @@ namespace BTCPayServer
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "BitCore",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.bitcore.cc/tx/{0}" : "https://explorer.bitcore.cc/tx/{0}",
|
||||
DisplayName = "Bitcore",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://insight.bitcore.cc/tx/{0}" : "https://insight.bitcore.cc/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "bitcore",
|
||||
DefaultRateRules = new[]
|
||||
|
@ -51,7 +51,6 @@ namespace BTCPayServer
|
||||
InitMonacoin();
|
||||
InitDash();
|
||||
InitFeathercoin();
|
||||
InitAlthash();
|
||||
InitGroestlcoin();
|
||||
InitViacoin();
|
||||
InitMonero();
|
||||
@ -132,10 +131,5 @@ namespace BTCPayServer
|
||||
}
|
||||
return network as T;
|
||||
}
|
||||
public bool TryGetNetwork<T>(string cryptoCode, out T network) where T : BTCPayNetworkBase
|
||||
{
|
||||
network = GetNetwork<T>(cryptoCode);
|
||||
return network != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="3.0.21" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="3.0.20" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Altcoins)' != 'true'">
|
||||
<Compile Remove="Altcoins\**\*.cs"></Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
@ -55,7 +55,6 @@ namespace BTCPayServer.Data
|
||||
public DbSet<StoreWebhookData> StoreWebhooks { get; set; }
|
||||
public DbSet<StoreData> Stores { get; set; }
|
||||
public DbSet<U2FDevice> U2FDevices { get; set; }
|
||||
public DbSet<Fido2Credential> Fido2Credentials { get; set; }
|
||||
public DbSet<UserStore> UserStore { get; set; }
|
||||
public DbSet<WalletData> Wallets { get; set; }
|
||||
public DbSet<WalletTransactionData> WalletTransactions { get; set; }
|
||||
@ -100,7 +99,6 @@ namespace BTCPayServer.Data
|
||||
StoreWebhookData.OnModelCreating(builder);
|
||||
//StoreData.OnModelCreating(builder);
|
||||
U2FDevice.OnModelCreating(builder);
|
||||
Fido2Credential.OnModelCreating(builder);
|
||||
Data.UserStore.OnModelCreating(builder);
|
||||
//WalletData.OnModelCreating(builder);
|
||||
WalletTransactionData.OnModelCreating(builder);
|
||||
|
@ -9,7 +9,6 @@ namespace BTCPayServer.Data
|
||||
{
|
||||
public bool RequiresEmailConfirmation { get; set; }
|
||||
public List<StoredFile> StoredFiles { get; set; }
|
||||
[Obsolete("U2F support has been replace with FIDO2")]
|
||||
public List<U2FDevice> U2FDevices { get; set; }
|
||||
public List<APIKeyData> APIKeys { get; set; }
|
||||
public DateTimeOffset? Created { get; set; }
|
||||
@ -17,6 +16,5 @@ namespace BTCPayServer.Data
|
||||
|
||||
public List<NotificationData> Notifications { get; set; }
|
||||
public List<UserStore> UserStores { get; set; }
|
||||
public List<Fido2Credential> Fido2Credentials { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class Fido2Credential
|
||||
{
|
||||
public string Name { get; set; }
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public string Id { get; set; }
|
||||
|
||||
public string ApplicationUserId { get; set; }
|
||||
|
||||
public byte[] Blob { get; set; }
|
||||
public CredentialType Type { get; set; }
|
||||
public enum CredentialType
|
||||
{
|
||||
FIDO2
|
||||
}
|
||||
public static void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
builder.Entity<Fido2Credential>()
|
||||
.HasOne(o => o.ApplicationUser)
|
||||
.WithMany(i => i.Fido2Credentials)
|
||||
.HasForeignKey(i => i.ApplicationUserId).OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
}
|
||||
|
||||
public ApplicationUser ApplicationUser { get; set; }
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using BTCPayServer.Client.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
|
||||
@ -54,4 +53,13 @@ namespace BTCPayServer.Data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum PayoutState
|
||||
{
|
||||
AwaitingApproval,
|
||||
AwaitingPayment,
|
||||
InProgress,
|
||||
Completed,
|
||||
Cancelled
|
||||
}
|
||||
}
|
||||
|
@ -3,13 +3,11 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Client.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
|
||||
public class PullPaymentData
|
||||
{
|
||||
[Key]
|
||||
@ -88,6 +86,7 @@ namespace BTCPayServer.Data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class PayoutExtensions
|
||||
{
|
||||
public static IQueryable<PayoutData> GetPayoutInPeriod(this IQueryable<PayoutData> payouts, PullPaymentData pp)
|
||||
|
@ -23,12 +23,11 @@ namespace BTCPayServer.Data
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
builder.Entity<U2FDevice>()
|
||||
.HasOne(o => o.ApplicationUser)
|
||||
.WithMany(i => i.U2FDevices)
|
||||
.HasForeignKey(i => i.ApplicationUserId).OnDelete(DeleteBehavior.Cascade);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,47 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20210314092253_Fido2Credentials")]
|
||||
public partial class Fido2Credentials : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Fido2Credentials",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(nullable: false),
|
||||
Name = table.Column<string>(nullable: true),
|
||||
ApplicationUserId = table.Column<string>(nullable: true),
|
||||
Blob = table.Column<byte[]>(nullable: true),
|
||||
Type = table.Column<int>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Fido2Credentials", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Fido2Credentials_AspNetUsers_ApplicationUserId",
|
||||
column: x => x.ApplicationUserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Fido2Credentials_ApplicationUserId",
|
||||
table: "Fido2Credentials",
|
||||
column: "ApplicationUserId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Fido2Credentials");
|
||||
}
|
||||
}
|
||||
}
|
@ -170,31 +170,6 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("AspNetUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.Fido2Credential", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId");
|
||||
|
||||
b.ToTable("Fido2Credentials");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("InvoiceDataId")
|
||||
@ -983,14 +958,6 @@ namespace BTCPayServer.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.Fido2Credential", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("Fido2Credentials")
|
||||
.HasForeignKey("ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
|
@ -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.77" />
|
||||
<PackageReference Include="NBitcoin" Version="5.0.73" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.6.3" />
|
||||
</ItemGroup>
|
||||
|
@ -1266,13 +1266,6 @@
|
||||
"symbol":null,
|
||||
"crypto":true
|
||||
},
|
||||
{
|
||||
"name":"Althash",
|
||||
"code":"HTML",
|
||||
"divisibility":8,
|
||||
"symbol":null,
|
||||
"crypto":true
|
||||
},
|
||||
{
|
||||
"name":"CHC",
|
||||
"code":"CHC",
|
||||
|
@ -50,14 +50,13 @@ namespace BTCPayServer.Tests
|
||||
tester.ActivateLightning();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
var cryptoCode = "BTC";
|
||||
await user.GrantAccessAsync(true);
|
||||
user.RegisterDerivationScheme(cryptoCode);
|
||||
user.GrantAccess(true);
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
user.RegisterDerivationScheme("LTC");
|
||||
user.RegisterLightningNode(cryptoCode, LightningConnectionType.CLightning);
|
||||
var btcNetwork = tester.PayTester.Networks.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.CLightning);
|
||||
var btcNetwork = tester.PayTester.Networks.GetNetwork<BTCPayNetwork>("BTC");
|
||||
var invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
{
|
||||
Price = 1.5m,
|
||||
Currency = "USD",
|
||||
@ -70,43 +69,36 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(3, invoice.CryptoInfo.Length);
|
||||
|
||||
var controller = user.GetController<StoresController>();
|
||||
var lightningVm = (LightningNodeViewModel)Assert.IsType<ViewResult>(controller.SetupLightningNode(user.StoreId, cryptoCode)).Model;
|
||||
var lightningVm = (LightningNodeViewModel)Assert.IsType<ViewResult>(controller.AddLightningNode(user.StoreId, "BTC")).Model;
|
||||
Assert.True(lightningVm.Enabled);
|
||||
var response = await controller.SetLightningNodeEnabled(user.StoreId, cryptoCode, false);
|
||||
Assert.IsType<RedirectToActionResult>(response);
|
||||
|
||||
// Get enabled state from overview action
|
||||
StoreViewModel storeModel;
|
||||
response = controller.UpdateStore();
|
||||
storeModel = (StoreViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
var lnNode = storeModel.LightningNodes.Find(node => node.CryptoCode == cryptoCode);
|
||||
Assert.NotNull(lnNode);
|
||||
Assert.False(lnNode.Enabled);
|
||||
lightningVm.Enabled = false;
|
||||
controller.AddLightningNode(user.StoreId, lightningVm, "save", "BTC").GetAwaiter().GetResult();
|
||||
lightningVm = (LightningNodeViewModel)Assert.IsType<ViewResult>(controller.AddLightningNode(user.StoreId, "BTC")).Model;
|
||||
Assert.False(lightningVm.Enabled);
|
||||
|
||||
WalletSetupViewModel setupVm;
|
||||
var storeId = user.StoreId;
|
||||
response = await controller.GenerateWallet(storeId, cryptoCode, WalletSetupMethod.GenerateOptions, new GenerateWalletRequest());
|
||||
var cryptoCode = "BTC";
|
||||
var response = await controller.GenerateWallet(storeId, cryptoCode, WalletSetupMethod.GenerateOptions, new GenerateWalletRequest());
|
||||
Assert.IsType<ViewResult>(response);
|
||||
|
||||
// Get enabled state from overview action
|
||||
response = controller.UpdateStore();
|
||||
storeModel = (StoreViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
var derivationScheme = storeModel.DerivationSchemes.Find(scheme => scheme.Crypto == cryptoCode);
|
||||
Assert.NotNull(derivationScheme);
|
||||
Assert.True(derivationScheme.Enabled);
|
||||
// Get setup view model from modify action
|
||||
response = await controller.ModifyWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode });
|
||||
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.True(setupVm.Enabled);
|
||||
|
||||
// Disable wallet
|
||||
response = controller.SetWalletEnabled(storeId, cryptoCode, false).GetAwaiter().GetResult();
|
||||
// Only Enabling/Disabling the payment method must redirect to store page
|
||||
setupVm.Enabled = false;
|
||||
response = controller.UpdateWallet(setupVm).GetAwaiter().GetResult();
|
||||
Assert.IsType<RedirectToActionResult>(response);
|
||||
response = controller.UpdateStore();
|
||||
storeModel = (StoreViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
derivationScheme = storeModel.DerivationSchemes.Find(scheme => scheme.Crypto == cryptoCode);
|
||||
Assert.NotNull(derivationScheme);
|
||||
Assert.False(derivationScheme.Enabled);
|
||||
|
||||
var oldScheme = derivationScheme.Value;
|
||||
response = await controller.ModifyWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode });
|
||||
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.False(setupVm.Enabled);
|
||||
|
||||
invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
var oldScheme = setupVm.DerivationScheme;
|
||||
|
||||
invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice
|
||||
{
|
||||
Price = 1.5m,
|
||||
@ -121,7 +113,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode);
|
||||
|
||||
// Removing the derivation scheme, should redirect to store page
|
||||
response = controller.ConfirmDeleteWallet(user.StoreId, cryptoCode).GetAwaiter().GetResult();
|
||||
response = controller.ConfirmDeleteWallet(user.StoreId, "BTC").GetAwaiter().GetResult();
|
||||
Assert.IsType<RedirectToActionResult>(response);
|
||||
|
||||
// Setting it again should show the confirmation page
|
||||
@ -180,8 +172,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(expected.ToJson(), onchainBTC.ToJson());
|
||||
|
||||
// Let's check that the root hdkey and account key path are taken into account when making a PSBT
|
||||
invoice = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice
|
||||
invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
{
|
||||
Price = 1.5m,
|
||||
Currency = "USD",
|
||||
@ -192,7 +184,7 @@ namespace BTCPayServer.Tests
|
||||
}, Facade.Merchant);
|
||||
|
||||
tester.ExplorerNode.Generate(1);
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo.First(c => c.CryptoCode == cryptoCode).Address,
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo.First(c => c.CryptoCode == "BTC").Address,
|
||||
tester.ExplorerNode.Network);
|
||||
tester.ExplorerNode.SendToAddress(invoiceAddress, Money.Coins(1m));
|
||||
TestUtils.Eventually(() =>
|
||||
@ -204,9 +196,9 @@ namespace BTCPayServer.Tests
|
||||
var psbt = wallet.CreatePSBT(btcNetwork, onchainBTC,
|
||||
new WalletSendModel()
|
||||
{
|
||||
Outputs = new List<WalletSendModel.TransactionOutput>
|
||||
Outputs = new List<WalletSendModel.TransactionOutput>()
|
||||
{
|
||||
new WalletSendModel.TransactionOutput
|
||||
new WalletSendModel.TransactionOutput()
|
||||
{
|
||||
Amount = 0.5m,
|
||||
DestinationAddress = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, btcNetwork.NBitcoinNetwork)
|
||||
@ -312,7 +304,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var controller = tester.PayTester.GetController<InvoiceController>(null);
|
||||
var checkout =
|
||||
(Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id)
|
||||
(Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null)
|
||||
.GetAwaiter().GetResult()).Value;
|
||||
Assert.Single(checkout.AvailableCryptos);
|
||||
Assert.Equal("LTC", checkout.CryptoCode);
|
||||
@ -329,7 +321,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.Equal("paid", invoice.Status);
|
||||
checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id)
|
||||
checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null)
|
||||
.GetAwaiter().GetResult()).Value;
|
||||
Assert.Equal("paid", checkout.Status);
|
||||
});
|
||||
|
@ -53,8 +53,4 @@
|
||||
<ProjectReference Include="..\BTCPayServer\BTCPayServer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Pages" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -167,7 +167,6 @@ namespace BTCPayServer.Tests
|
||||
l.SetMinimumLevel(LogLevel.Information)
|
||||
.AddFilter("Microsoft", LogLevel.Error)
|
||||
.AddFilter("Hangfire", LogLevel.Error)
|
||||
.AddFilter("Fido2NetLib.DistributedCacheMetadataService", LogLevel.Error)
|
||||
.AddProvider(Logs.LogProvider);
|
||||
});
|
||||
})
|
||||
|
91
BTCPayServer.Tests/CoinSwitchTests.cs
Normal file
91
BTCPayServer.Tests/CoinSwitchTests.cs
Normal file
@ -0,0 +1,91 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments.CoinSwitch;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class CoinSwitchTests
|
||||
{
|
||||
public CoinSwitchTests(ITestOutputHelper helper)
|
||||
{
|
||||
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
|
||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanSetCoinSwitchPaymentMethod()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
var controller = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
|
||||
|
||||
|
||||
var storeBlob = controller.CurrentStore.GetStoreBlob();
|
||||
Assert.Null(storeBlob.CoinSwitchSettings);
|
||||
|
||||
var updateModel = new UpdateCoinSwitchSettingsViewModel()
|
||||
{
|
||||
MerchantId = "aaa",
|
||||
};
|
||||
|
||||
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
|
||||
await controller.UpdateCoinSwitchSettings(user.StoreId, updateModel, "save")).ActionName);
|
||||
|
||||
var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
|
||||
storeBlob = controller.CurrentStore.GetStoreBlob();
|
||||
Assert.NotNull(storeBlob.CoinSwitchSettings);
|
||||
Assert.NotNull(storeBlob.CoinSwitchSettings);
|
||||
Assert.IsType<CoinSwitchSettings>(storeBlob.CoinSwitchSettings);
|
||||
Assert.Equal(storeBlob.CoinSwitchSettings.MerchantId,
|
||||
updateModel.MerchantId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanToggleCoinSwitchPaymentMethod()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
var controller = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
|
||||
|
||||
var updateModel = new UpdateCoinSwitchSettingsViewModel()
|
||||
{
|
||||
MerchantId = "aaa",
|
||||
Enabled = true
|
||||
};
|
||||
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
|
||||
await controller.UpdateCoinSwitchSettings(user.StoreId, updateModel, "save")).ActionName);
|
||||
|
||||
|
||||
var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
|
||||
|
||||
Assert.True(store.GetStoreBlob().CoinSwitchSettings.Enabled);
|
||||
|
||||
updateModel.Enabled = false;
|
||||
|
||||
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
|
||||
await controller.UpdateCoinSwitchSettings(user.StoreId, updateModel, "save")).ActionName);
|
||||
|
||||
store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
|
||||
|
||||
Assert.False(store.GetStoreBlob().CoinSwitchSettings.Enabled);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ namespace BTCPayServer.Tests
|
||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanCreateAndDeleteCrowdfundApp()
|
||||
{
|
||||
@ -63,7 +63,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanContributeOnlyWhenAllowed()
|
||||
{
|
||||
@ -155,7 +155,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanComputeCrowdfundModel()
|
||||
{
|
||||
|
@ -471,25 +471,6 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Revision = payout.Revision
|
||||
}));
|
||||
|
||||
// Create one pull payment with an amount of 9 decimals
|
||||
var test3 = await client.CreatePullPayment(storeId, new Client.Models.CreatePullPaymentRequest()
|
||||
{
|
||||
Name = "Test 2",
|
||||
Amount = 12.303228134m,
|
||||
Currency = "BTC",
|
||||
PaymentMethods = new[] { "BTC" }
|
||||
});
|
||||
destination = (await tester.ExplorerNode.GetNewAddressAsync()).ToString();
|
||||
payout = await unauthenticated.CreatePayout(test3.Id, new CreatePayoutRequest()
|
||||
{
|
||||
Destination = destination,
|
||||
PaymentMethod = "BTC"
|
||||
});
|
||||
payout = await client.ApprovePayout(storeId, payout.Id, new ApprovePayoutRequest());
|
||||
// The payout should round the value of the payment down to the network of the payment method
|
||||
Assert.Equal(12.30322814m, payout.PaymentMethodAmount);
|
||||
Assert.Equal(12.303228134m, payout.Amount);
|
||||
}
|
||||
}
|
||||
|
||||
@ -706,7 +687,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.NotNull(newDelivery);
|
||||
Assert.Equal(404, newDelivery.HttpCode);
|
||||
var req = await clientProfile.GetWebhookDeliveryRequest(user.StoreId, hook.Id, newDeliveryId);
|
||||
Assert.Equal(delivery.Id, req.OriginalDeliveryId);
|
||||
Assert.Equal(delivery.Id, req.OrignalDeliveryId);
|
||||
Assert.True(req.IsRedelivery);
|
||||
Assert.Equal(WebhookDeliveryStatus.HttpError, newDelivery.Status);
|
||||
});
|
||||
@ -970,7 +951,7 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
var newInvoice = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest() { Currency = "USD", Amount = 1, Metadata = JObject.Parse("{\"itemCode\": \"testitem\", \"orderId\": \"testOrder\"}"), Checkout = new CreateInvoiceRequest.CheckoutOptions()
|
||||
new CreateInvoiceRequest() { Currency = "USD", Amount = 1, Metadata = JObject.Parse("{\"itemCode\": \"testitem\"}"), Checkout = new CreateInvoiceRequest.CheckoutOptions()
|
||||
{
|
||||
RedirectAutomatically = true
|
||||
}});
|
||||
@ -983,62 +964,6 @@ namespace BTCPayServer.Tests
|
||||
Assert.Single(invoices);
|
||||
Assert.Equal(newInvoice.Id, invoices.First().Id);
|
||||
|
||||
//list Filtered
|
||||
var invoicesFiltered = await viewOnly.GetInvoices(user.StoreId,
|
||||
orderId: null, status: null, DateTimeOffset.Now.AddHours(-1),
|
||||
DateTimeOffset.Now.AddHours(1));
|
||||
|
||||
Assert.NotNull(invoicesFiltered);
|
||||
Assert.Single(invoicesFiltered);
|
||||
Assert.Equal(newInvoice.Id, invoicesFiltered.First().Id);
|
||||
|
||||
//list Yesterday
|
||||
var invoicesYesterday = await viewOnly.GetInvoices(user.StoreId,
|
||||
orderId: null, status: null, DateTimeOffset.Now.AddDays(-2),
|
||||
DateTimeOffset.Now.AddDays(-1));
|
||||
Assert.NotNull(invoicesYesterday);
|
||||
Assert.Empty(invoicesYesterday);
|
||||
|
||||
// Error, startDate and endDate inverted
|
||||
await AssertValidationError(new[] { "startDate", "endDate" },
|
||||
() => viewOnly.GetInvoices(user.StoreId,
|
||||
orderId: null, status: null, DateTimeOffset.Now.AddDays(-1),
|
||||
DateTimeOffset.Now.AddDays(-2)));
|
||||
|
||||
await AssertValidationError(new[] { "startDate" },
|
||||
() => viewOnly.SendHttpRequest<Client.Models.InvoiceData[]>($"api/v1/stores/{user.StoreId}/invoices", new Dictionary<string, object>()
|
||||
{
|
||||
{ "startDate", "blah" }
|
||||
}));
|
||||
|
||||
|
||||
//list Existing OrderId
|
||||
var invoicesExistingOrderId =
|
||||
await viewOnly.GetInvoices(user.StoreId, orderId: newInvoice.Metadata["orderId"].ToString());
|
||||
Assert.NotNull(invoicesExistingOrderId);
|
||||
Assert.Single(invoicesFiltered);
|
||||
Assert.Equal(newInvoice.Id, invoicesFiltered.First().Id);
|
||||
|
||||
//list NonExisting OrderId
|
||||
var invoicesNonExistingOrderId =
|
||||
await viewOnly.GetInvoices(user.StoreId, orderId: "NonExistingOrderId");
|
||||
Assert.NotNull(invoicesNonExistingOrderId);
|
||||
Assert.Empty(invoicesNonExistingOrderId);
|
||||
|
||||
//list Existing Status
|
||||
var invoicesExistingStatus =
|
||||
await viewOnly.GetInvoices(user.StoreId, status:new []{newInvoice.Status});
|
||||
Assert.NotNull(invoicesExistingStatus);
|
||||
Assert.Single(invoicesExistingStatus);
|
||||
Assert.Equal(newInvoice.Id, invoicesExistingStatus.First().Id);
|
||||
|
||||
//list NonExisting Status
|
||||
var invoicesNonExistingStatus = await viewOnly.GetInvoices(user.StoreId,
|
||||
status: new []{BTCPayServer.Client.Models.InvoiceStatus.Invalid});
|
||||
Assert.NotNull(invoicesNonExistingStatus);
|
||||
Assert.Empty(invoicesNonExistingStatus);
|
||||
|
||||
|
||||
//get
|
||||
var invoice = await viewOnly.GetInvoice(user.StoreId, newInvoice.Id);
|
||||
Assert.Equal(newInvoice.Metadata, invoice.Metadata);
|
||||
|
@ -320,7 +320,7 @@ namespace BTCPayServer.Tests
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var invoice = await invoiceRepository.GetInvoice(invoiceId);
|
||||
var payments = invoice.GetPayments(false);
|
||||
var payments = invoice.GetPayments();
|
||||
Assert.Equal(2, payments.Count);
|
||||
var originalPayment = payments[0];
|
||||
var coinjoinPayment = payments[1];
|
||||
@ -1088,7 +1088,7 @@ retry:
|
||||
{
|
||||
var invoiceEntity = await tester.PayTester.GetService<InvoiceRepository>().GetInvoice(invoice7.Id);
|
||||
Assert.Equal(InvoiceStatusLegacy.Paid, invoiceEntity.Status);
|
||||
Assert.Contains(invoiceEntity.GetPayments(false), p => p.Accounted &&
|
||||
Assert.Contains(invoiceEntity.GetPayments(), p => p.Accounted &&
|
||||
((BitcoinLikePaymentData)p.GetCryptoPaymentData()).PayjoinInformation is null);
|
||||
});
|
||||
////Assert.Contains(receiverWalletPayJoinState.GetRecords(), item => item.InvoiceId == invoice7.Id && item.TxSeen);
|
||||
@ -1117,8 +1117,8 @@ retry:
|
||||
{
|
||||
var invoiceEntity = await tester.PayTester.GetService<InvoiceRepository>().GetInvoice(invoice7.Id);
|
||||
Assert.Equal(InvoiceStatusLegacy.New, invoiceEntity.Status);
|
||||
Assert.True(invoiceEntity.GetPayments(false).All(p => !p.Accounted));
|
||||
ourOutpoint = invoiceEntity.GetAllBitcoinPaymentData(false).First().PayjoinInformation.ContributedOutPoints[0];
|
||||
Assert.True(invoiceEntity.GetPayments().All(p => !p.Accounted));
|
||||
ourOutpoint = invoiceEntity.GetAllBitcoinPaymentData().First().PayjoinInformation.ContributedOutPoints[0];
|
||||
});
|
||||
var payjoinRepository = tester.PayTester.GetService<PayJoinRepository>();
|
||||
// The outpoint should now be available for next pj selection
|
||||
|
@ -63,12 +63,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
options.AddArguments($"window-size={windowSize.Width}x{windowSize.Height}");
|
||||
options.AddArgument("shm-size=2g");
|
||||
|
||||
var cds = ChromeDriverService.CreateDefaultService(chromeDriverPath);
|
||||
cds.Port = Utils.FreeTcpPort();
|
||||
cds.HostName = "127.0.0.1";
|
||||
cds.Start();
|
||||
Driver = new ChromeDriver(cds, options,
|
||||
Driver = new ChromeDriver(chromeDriverPath, options,
|
||||
// A bit less than test timeout
|
||||
TimeSpan.FromSeconds(50));
|
||||
|
||||
@ -217,25 +212,20 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Driver.FindElement(By.CssSelector("label[for=\"LightningNodeType-Custom\"]")).Click();
|
||||
Driver.FindElement(By.Id("ConnectionString")).SendKeys(connectionString);
|
||||
|
||||
Driver.FindElement(By.Id("test")).Click();
|
||||
Assert.Contains("Connection to the Lightning node successful.", FindAlertMessage().Text);
|
||||
}
|
||||
|
||||
var enabled = Driver.FindElement(By.Id("Enabled"));
|
||||
if (!enabled.Selected) enabled.Click();
|
||||
|
||||
Driver.FindElement(By.Id("test")).Click();
|
||||
Assert.Contains("Connection to the Lightning node succeeded.", FindAlertMessage().Text);
|
||||
|
||||
Driver.FindElement(By.Id("save")).Click();
|
||||
Assert.Contains($"{cryptoCode} Lightning node updated.", FindAlertMessage().Text);
|
||||
|
||||
var enabled = Driver.FindElement(By.Id($"{cryptoCode}LightningEnabled"));
|
||||
if (enabled.Text == "Enable")
|
||||
{
|
||||
enabled.Click();
|
||||
Assert.Contains($"{cryptoCode} Lightning payments are now enabled for this store.", FindAlertMessage().Text);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClickOnAllSideMenus()
|
||||
{
|
||||
var links = Driver.FindElements(By.CssSelector(".nav .nav-link")).Select(c => c.GetAttribute("href")).ToList();
|
||||
var links = Driver.FindElements(By.CssSelector(".nav-pills .nav-link")).Select(c => c.GetAttribute("href")).ToList();
|
||||
Driver.AssertNoError();
|
||||
Assert.NotEmpty(links);
|
||||
foreach (var l in links)
|
||||
@ -378,14 +368,14 @@ namespace BTCPayServer.Tests
|
||||
private void CheckForJSErrors()
|
||||
{
|
||||
//wait for seleniun update: https://stackoverflow.com/questions/57520296/selenium-webdriver-3-141-0-driver-manage-logs-availablelogtypes-throwing-syste
|
||||
// var errorStrings = new List<string>
|
||||
// {
|
||||
// "SyntaxError",
|
||||
// "EvalError",
|
||||
// "ReferenceError",
|
||||
// "RangeError",
|
||||
// "TypeError",
|
||||
// "URIError"
|
||||
// var errorStrings = new List<string>
|
||||
// {
|
||||
// "SyntaxError",
|
||||
// "EvalError",
|
||||
// "ReferenceError",
|
||||
// "RangeError",
|
||||
// "TypeError",
|
||||
// "URIError"
|
||||
// };
|
||||
//
|
||||
// var jsErrors = Driver.Manage().Logs.GetLog(LogType.Browser).Where(x => errorStrings.Any(e => x.Message.Contains(e)));
|
||||
@ -412,7 +402,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, relativeUrl));
|
||||
}
|
||||
|
||||
|
||||
public void GoToServer(ServerNavPages navPages = ServerNavPages.Index)
|
||||
{
|
||||
Driver.FindElement(By.Id("ServerSettings")).Click();
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@ -8,7 +7,6 @@ using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Views.Manage;
|
||||
@ -214,10 +212,6 @@ namespace BTCPayServer.Tests
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
var settings = s.Server.PayTester.GetService<SettingsRepository>();
|
||||
var policies = await settings.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
|
||||
policies.DisableSSHService = false;
|
||||
await settings.UpdateSetting(policies);
|
||||
s.RegisterNewUser(isAdmin: true);
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services"));
|
||||
Assert.Contains("server/services/ssh", s.Driver.PageSource);
|
||||
@ -246,17 +240,6 @@ namespace BTCPayServer.Tests
|
||||
|
||||
text = s.Driver.FindElement(By.Id("SSHKeyFileContent")).Text;
|
||||
Assert.DoesNotContain("test2", text);
|
||||
|
||||
// Let's try to disable it now
|
||||
s.Driver.FindElement(By.Id("disable")).Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
policies = await settings.GetSettingAsync<PoliciesSettings>();
|
||||
Assert.True(policies.DisableSSHService);
|
||||
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/ssh"));
|
||||
Assert.True(s.Driver.PageSource.Contains("404 - Page not found", StringComparison.OrdinalIgnoreCase));
|
||||
policies.DisableSSHService = false;
|
||||
await settings.UpdateSetting(policies);
|
||||
}
|
||||
}
|
||||
|
||||
@ -366,7 +349,7 @@ namespace BTCPayServer.Tests
|
||||
s.AddLightningNode();
|
||||
s.Driver.AssertNoError();
|
||||
var successAlert = s.FindAlertMessage();
|
||||
Assert.Contains("BTC Lightning node updated.", successAlert.Text);
|
||||
Assert.Contains("BTC Lightning node modified.", successAlert.Text);
|
||||
Assert.False(s.Driver.PageSource.Contains(offchainHint), "Lightning hint should be dismissed at this point");
|
||||
|
||||
var storeUrl = s.Driver.Url;
|
||||
@ -981,18 +964,15 @@ namespace BTCPayServer.Tests
|
||||
var payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
||||
Assert.Equal(2, payouts.Count);
|
||||
payouts[1].Click();
|
||||
Assert.Empty(s.Driver.FindElements(By.ClassName("payout")));
|
||||
Assert.Contains("No payout waiting for approval", s.Driver.PageSource);
|
||||
|
||||
// PP2 should have payouts
|
||||
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
|
||||
payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
||||
payouts[0].Click();
|
||||
|
||||
Assert.NotEmpty(s.Driver.FindElements(By.ClassName("payout")));
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click();
|
||||
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click();
|
||||
|
||||
Assert.DoesNotContain("No payout waiting for approval", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Id("selectAllCheckbox")).Click();
|
||||
s.Driver.FindElement(By.Id("payCommand")).Click();
|
||||
s.Driver.FindElement(By.Id("SendMenu")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
|
||||
@ -1007,14 +987,13 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal("payout", s.Driver.FindElement(By.ClassName("transactionLabel")).Text);
|
||||
|
||||
s.GoToWallet(navPages: WalletsNavPages.Payouts);
|
||||
ReadOnlyCollection<IWebElement> txs;
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
|
||||
txs = s.Driver.FindElements(By.ClassName("transaction-link"));
|
||||
Assert.Equal(2, txs.Count);
|
||||
Assert.Contains("No payout waiting for approval", s.Driver.PageSource);
|
||||
});
|
||||
var txs = s.Driver.FindElements(By.ClassName("transaction-link"));
|
||||
Assert.Equal(2, txs.Count);
|
||||
|
||||
s.Driver.Navigate().GoToUrl(viewPullPaymentUrl);
|
||||
txs = s.Driver.FindElements(By.ClassName("transaction-link"));
|
||||
@ -1035,7 +1014,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
using var ctx = s.Server.PayTester.GetService<ApplicationDbContextFactory>().CreateContext();
|
||||
var payoutsData = await ctx.Payouts.Where(p => p.PullPaymentDataId == pullPaymentId).ToListAsync();
|
||||
Assert.True(payoutsData.All(p => p.State == PayoutState.Completed));
|
||||
Assert.True(payoutsData.All(p => p.State == Data.PayoutState.Completed));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -273,8 +273,8 @@ namespace BTCPayServer.Tests
|
||||
var connectionString = parent.GetLightningConnectionString(connectionType, isMerchant);
|
||||
var nodeType = connectionString == LightningSupportedPaymentMethod.InternalNode ? LightningNodeType.Internal : LightningNodeType.Custom;
|
||||
|
||||
await storeController.SetupLightningNode(storeId ?? StoreId,
|
||||
new LightningNodeViewModel { ConnectionString = connectionString, LightningNodeType = nodeType, SkipPortTest = true }, "save", cryptoCode);
|
||||
await storeController.AddLightningNode(storeId ?? StoreId,
|
||||
new LightningNodeViewModel { ConnectionString = connectionString, LightningNodeType = nodeType, SkipPortTest = true }, "save", "BTC");
|
||||
if (storeController.ModelState.ErrorCount != 0)
|
||||
Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
|
||||
}
|
||||
|
132
BTCPayServer.Tests/U2FTests.cs
Normal file
132
BTCPayServer.Tests/U2FTests.cs
Normal file
@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.AccountViewModels;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.U2F;
|
||||
using BTCPayServer.U2F.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using U2F.Core.Models;
|
||||
using U2F.Core.Utils;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class U2FTests
|
||||
{
|
||||
public const int TestTimeout = 60_000;
|
||||
|
||||
public U2FTests(ITestOutputHelper helper)
|
||||
{
|
||||
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
|
||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task U2ftest()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
|
||||
var accountController = tester.PayTester.GetController<AccountController>();
|
||||
var manageController = user.GetController<ManageController>();
|
||||
var mock = new MockU2FService(tester.PayTester.GetService<ApplicationDbContextFactory>());
|
||||
manageController._u2FService = mock;
|
||||
accountController._u2FService = mock;
|
||||
|
||||
Assert
|
||||
.IsType<RedirectToActionResult>(await accountController.Login(new LoginViewModel()
|
||||
{
|
||||
Email = user.RegisterDetails.Email,
|
||||
Password = user.RegisterDetails.Password
|
||||
}));
|
||||
|
||||
Assert.Empty(Assert.IsType<U2FAuthenticationViewModel>(Assert
|
||||
.IsType<ViewResult>(await manageController.U2FAuthentication()).Model).Devices);
|
||||
|
||||
var addDeviceVM = Assert.IsType<AddU2FDeviceViewModel>(Assert
|
||||
.IsType<ViewResult>(manageController.AddU2FDevice("testdevice")).Model);
|
||||
|
||||
Assert.NotEmpty(addDeviceVM.Challenge);
|
||||
Assert.Equal("testdevice", addDeviceVM.Name);
|
||||
Assert.NotEmpty(addDeviceVM.Version);
|
||||
Assert.Null(addDeviceVM.DeviceResponse);
|
||||
|
||||
var devReg = new DeviceRegistration(Guid.NewGuid().ToByteArray(), Guid.NewGuid().ToByteArray(),
|
||||
Guid.NewGuid().ToByteArray(), 1);
|
||||
|
||||
mock.GetDevReg = () => devReg;
|
||||
mock.StartedAuthentication = () =>
|
||||
new StartedAuthentication("chocolate", addDeviceVM.AppId,
|
||||
devReg.KeyHandle.ByteArrayToBase64String());
|
||||
addDeviceVM.DeviceResponse = new RegisterResponse("ss",
|
||||
Convert.ToBase64String(Encoding.UTF8.GetBytes("{typ:'x', challenge: 'fff'}"))).ToJson();
|
||||
Assert
|
||||
.IsType<RedirectToActionResult>(await manageController.AddU2FDevice(addDeviceVM));
|
||||
|
||||
Assert.Single(Assert.IsType<U2FAuthenticationViewModel>(Assert
|
||||
.IsType<ViewResult>(await manageController.U2FAuthentication()).Model).Devices);
|
||||
|
||||
var secondaryLoginViewModel = Assert.IsType<SecondaryLoginViewModel>(Assert
|
||||
.IsType<ViewResult>(await accountController.Login(new LoginViewModel()
|
||||
{
|
||||
Email = user.RegisterDetails.Email,
|
||||
Password = user.RegisterDetails.Password
|
||||
})).Model);
|
||||
Assert.NotNull(secondaryLoginViewModel.LoginWithU2FViewModel);
|
||||
Assert.Single(secondaryLoginViewModel.LoginWithU2FViewModel.Challenges);
|
||||
Assert.Equal(secondaryLoginViewModel.LoginWithU2FViewModel.Challenge,
|
||||
secondaryLoginViewModel.LoginWithU2FViewModel.Challenges.First().challenge);
|
||||
|
||||
secondaryLoginViewModel.LoginWithU2FViewModel.DeviceResponse = new AuthenticateResponse(
|
||||
Convert.ToBase64String(Encoding.UTF8.GetBytes(
|
||||
"{typ:'x', challenge: '" + secondaryLoginViewModel.LoginWithU2FViewModel.Challenge + "'}")),
|
||||
"dd", devReg.KeyHandle.ByteArrayToBase64String()).ToJson();
|
||||
Assert
|
||||
.IsType<RedirectToActionResult>(
|
||||
await accountController.LoginWithU2F(secondaryLoginViewModel.LoginWithU2FViewModel));
|
||||
}
|
||||
}
|
||||
|
||||
public class MockU2FService : U2FService
|
||||
{
|
||||
public Func<DeviceRegistration> GetDevReg;
|
||||
public Func<StartedAuthentication> StartedAuthentication;
|
||||
|
||||
public MockU2FService(ApplicationDbContextFactory contextFactory) : base(contextFactory)
|
||||
{
|
||||
}
|
||||
|
||||
protected override StartedRegistration StartDeviceRegistrationCore(string appId)
|
||||
{
|
||||
return global::U2F.Core.Crypto.U2F.StartRegistration(appId);
|
||||
}
|
||||
|
||||
protected override DeviceRegistration FinishRegistrationCore(StartedRegistration startedRegistration,
|
||||
RegisterResponse registerResponse)
|
||||
{
|
||||
return GetDevReg();
|
||||
}
|
||||
|
||||
protected override StartedAuthentication StartAuthenticationCore(string appId, U2FDevice registeredDevice)
|
||||
{
|
||||
return StartedAuthentication();
|
||||
}
|
||||
|
||||
protected override void FinishAuthenticationCore(StartedAuthentication authentication,
|
||||
AuthenticateResponse authenticateResponse, DeviceRegistration registration)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -19,8 +19,6 @@ using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Fido2;
|
||||
using BTCPayServer.Fido2.Models;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Lightning;
|
||||
@ -44,15 +42,12 @@ using BTCPayServer.Services.Labels;
|
||||
using BTCPayServer.Services.Mails;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.U2F.Models;
|
||||
using BTCPayServer.Validation;
|
||||
using ExchangeSharp;
|
||||
using Fido2NetLib;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Configuration.Memory;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitcoin.Payment;
|
||||
@ -72,7 +67,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
public class UnitTest1
|
||||
{
|
||||
public const int LongRunningTestTimeout = 60_000; // 60s
|
||||
public const int TestTimeout = 60_000;
|
||||
|
||||
public UnitTest1(ITestOutputHelper helper)
|
||||
{
|
||||
@ -875,55 +870,18 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact]
|
||||
[Trait("Fast", "Fast")]
|
||||
public async Task CanEnumerateTorServices()
|
||||
{
|
||||
var tor = new TorServices(new BTCPayNetworkProvider(ChainName.Regtest),
|
||||
new OptionsWrapper<BTCPayServerOptions>(new BTCPayServerOptions()
|
||||
{
|
||||
TorrcFile = TestUtils.GetTestDataFullPath("Tor/torrc")
|
||||
}));
|
||||
new BTCPayServerOptions() { TorrcFile = TestUtils.GetTestDataFullPath("Tor/torrc") });
|
||||
await tor.Refresh();
|
||||
|
||||
Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.BTCPayServer));
|
||||
Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.P2P));
|
||||
Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.RPC));
|
||||
Assert.True(tor.Services.Count(t => t.ServiceType == TorServiceType.Other) > 1);
|
||||
|
||||
tor = new TorServices(new BTCPayNetworkProvider(ChainName.Regtest),
|
||||
new OptionsWrapper<BTCPayServerOptions>(new BTCPayServerOptions()
|
||||
{
|
||||
TorrcFile = null,
|
||||
TorServices = "btcpayserver:host.onion:80;btc-p2p:host2.onion:81,BTC-RPC:host3.onion:82,UNKNOWN:host4.onion:83,INVALID:ddd".Split(new[] {';', ','}, StringSplitOptions.RemoveEmptyEntries)
|
||||
}));
|
||||
await Task.WhenAll(tor.StartAsync(CancellationToken.None));
|
||||
|
||||
var btcpayS = Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.BTCPayServer));
|
||||
Assert.Null(btcpayS.Network);
|
||||
Assert.Equal("host.onion", btcpayS.OnionHost);
|
||||
Assert.Equal(80, btcpayS.VirtualPort);
|
||||
|
||||
var p2p = Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.P2P));
|
||||
Assert.NotNull(p2p.Network);
|
||||
Assert.Equal("BTC", p2p.Network.CryptoCode);
|
||||
Assert.Equal("host2.onion", p2p.OnionHost);
|
||||
Assert.Equal(81, p2p.VirtualPort);
|
||||
|
||||
var rpc = Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.RPC));
|
||||
Assert.NotNull(p2p.Network);
|
||||
Assert.Equal("BTC", rpc.Network.CryptoCode);
|
||||
Assert.Equal("host3.onion", rpc.OnionHost);
|
||||
Assert.Equal(82, rpc.VirtualPort);
|
||||
|
||||
var unknown = Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.Other));
|
||||
Assert.Null(unknown.Network);
|
||||
Assert.Equal("host4.onion", unknown.OnionHost);
|
||||
Assert.Equal(83, unknown.VirtualPort);
|
||||
Assert.Equal("UNKNOWN", unknown.Name);
|
||||
|
||||
Assert.Equal(4, tor.Services.Length);
|
||||
|
||||
Assert.True(tor.Services.Where(t => t.ServiceType == TorServiceType.Other).Count() > 1);
|
||||
}
|
||||
|
||||
|
||||
@ -990,9 +948,9 @@ namespace BTCPayServer.Tests
|
||||
user.GrantAccess(true);
|
||||
var storeController = user.GetController<StoresController>();
|
||||
Assert.IsType<ViewResult>(storeController.UpdateStore());
|
||||
Assert.IsType<ViewResult>(storeController.SetupLightningNode(user.StoreId, "BTC"));
|
||||
Assert.IsType<ViewResult>(storeController.AddLightningNode(user.StoreId, "BTC"));
|
||||
|
||||
var testResult = storeController.SetupLightningNode(user.StoreId, new LightningNodeViewModel
|
||||
var testResult = storeController.AddLightningNode(user.StoreId, new LightningNodeViewModel()
|
||||
{
|
||||
ConnectionString = $"type=charge;server={tester.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true",
|
||||
SkipPortTest = true // We can't test this as the IP can't be resolved by the test host :(
|
||||
@ -1001,19 +959,19 @@ namespace BTCPayServer.Tests
|
||||
storeController.TempData.Clear();
|
||||
Assert.True(storeController.ModelState.IsValid);
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(storeController.SetupLightningNode(user.StoreId,
|
||||
new LightningNodeViewModel
|
||||
Assert.IsType<RedirectToActionResult>(storeController.AddLightningNode(user.StoreId,
|
||||
new LightningNodeViewModel()
|
||||
{
|
||||
ConnectionString = $"type=charge;server={tester.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true"
|
||||
}, "save", "BTC").GetAwaiter().GetResult());
|
||||
|
||||
// Make sure old connection string format does not work
|
||||
Assert.IsType<RedirectToActionResult>(storeController.SetupLightningNode(user.StoreId,
|
||||
new LightningNodeViewModel { ConnectionString = tester.MerchantCharge.Client.Uri.AbsoluteUri },
|
||||
Assert.IsType<ViewResult>(storeController.AddLightningNode(user.StoreId,
|
||||
new LightningNodeViewModel() { ConnectionString = tester.MerchantCharge.Client.Uri.AbsoluteUri },
|
||||
"save", "BTC").GetAwaiter().GetResult());
|
||||
|
||||
var storeVm =
|
||||
Assert.IsType<StoreViewModel>(Assert
|
||||
Assert.IsType<Models.StoreViewModels.StoreViewModel>(Assert
|
||||
.IsType<ViewResult>(storeController.UpdateStore()).Model);
|
||||
Assert.Single(storeVm.LightningNodes.Where(l => !string.IsNullOrEmpty(l.Address)));
|
||||
}
|
||||
@ -1088,7 +1046,7 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanUseServerInitiatedPairingCode()
|
||||
{
|
||||
@ -1115,7 +1073,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanSendIPN()
|
||||
{
|
||||
@ -1185,7 +1143,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CantPairTwiceWithSamePubkey()
|
||||
{
|
||||
@ -1209,7 +1167,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public void CanSolveTheDogesRatesOnKraken()
|
||||
{
|
||||
@ -1227,7 +1185,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanUseTorClient()
|
||||
{
|
||||
@ -1280,7 +1238,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanRescanWallet()
|
||||
{
|
||||
@ -1382,7 +1340,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanListInvoices()
|
||||
{
|
||||
@ -1433,7 +1391,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanListNotifications()
|
||||
{
|
||||
@ -1639,8 +1597,8 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
var i = await tester.PayTester.InvoiceRepository.GetInvoice(invoice2.Id);
|
||||
Assert.Equal(InvoiceStatusLegacy.New, i.Status);
|
||||
Assert.Single(i.GetPayments(false));
|
||||
Assert.False(i.GetPayments(false).First().Accounted);
|
||||
Assert.Single(i.GetPayments());
|
||||
Assert.False(i.GetPayments().First().Accounted);
|
||||
});
|
||||
|
||||
Logs.Tester.LogInformation(
|
||||
@ -1672,8 +1630,8 @@ namespace BTCPayServer.Tests
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var invoiceEntity = await tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id);
|
||||
var btcPayments = invoiceEntity.GetAllBitcoinPaymentData(false).ToArray();
|
||||
var payments = invoiceEntity.GetPayments(false).ToArray();
|
||||
var btcPayments = invoiceEntity.GetAllBitcoinPaymentData().ToArray();
|
||||
var payments = invoiceEntity.GetPayments().ToArray();
|
||||
Assert.Equal(tx1, btcPayments[0].Outpoint.Hash);
|
||||
Assert.False(payments[0].Accounted);
|
||||
Assert.Equal(tx1Bump, payments[1].Outpoint.Hash);
|
||||
@ -1717,7 +1675,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.NotNull(paymentData.KeyPath);
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void CanParseFilter()
|
||||
{
|
||||
@ -1745,7 +1703,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal("hekki", search.TextSearch);
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void CanParseFingerprint()
|
||||
{
|
||||
@ -1762,7 +1720,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(f1.ToString(), f2.ToString());
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async void CheckCORSSetOnBitpayAPI()
|
||||
{
|
||||
@ -1797,7 +1755,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task TestAccessBitpayAPI()
|
||||
{
|
||||
@ -1876,7 +1834,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanUseExchangeSpecificRate()
|
||||
{
|
||||
@ -1921,7 +1879,7 @@ namespace BTCPayServer.Tests
|
||||
return invoice2.CryptoInfo[0].Rate;
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanUseAnyoneCanCreateInvoice()
|
||||
{
|
||||
@ -1973,7 +1931,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanTweakRate()
|
||||
{
|
||||
@ -2020,7 +1978,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanModifyRates()
|
||||
{
|
||||
@ -2349,7 +2307,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(client.WaitAllRunning(default).Wait(100));
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void PosDataParser_ParsesCorrectly()
|
||||
{
|
||||
@ -2366,9 +2324,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
("{ invalidjson file here}",
|
||||
new Dictionary<string, object>() {{String.Empty, "{ invalidjson file here}"}})
|
||||
},
|
||||
// Duplicate keys should not crash things
|
||||
{("{ \"key\": true, \"key\": true}", new Dictionary<string, object>() {{"key", "True"}})}
|
||||
}
|
||||
};
|
||||
|
||||
testCases.ForEach(tuple =>
|
||||
@ -2377,7 +2333,7 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task PosDataParser_ParsesCorrectly_Slower()
|
||||
{
|
||||
@ -2427,7 +2383,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanExportInvoicesJson()
|
||||
{
|
||||
@ -2506,7 +2462,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanChangeNetworkFeeMode()
|
||||
{
|
||||
@ -2597,7 +2553,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanExportInvoicesCsv()
|
||||
{
|
||||
@ -2639,7 +2595,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanCreateAndDeleteApps()
|
||||
{
|
||||
@ -2677,7 +2633,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanCreateStrangeInvoice()
|
||||
{
|
||||
@ -2723,7 +2679,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task InvoiceFlowThroughDifferentStatesCorrectly()
|
||||
{
|
||||
@ -2913,7 +2869,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public void CanQueryDirectProviders()
|
||||
{
|
||||
@ -2984,7 +2940,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanExportBackgroundFetcherState()
|
||||
{
|
||||
@ -3026,7 +2982,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public void CanGetRateCryptoCurrenciesByDefault()
|
||||
{
|
||||
@ -3078,7 +3034,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CheckLogsRoute()
|
||||
{
|
||||
@ -3161,7 +3117,7 @@ namespace BTCPayServer.Tests
|
||||
return name;
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void CanCheckFileNameValid()
|
||||
{
|
||||
@ -3179,19 +3135,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Trait("Fast", "Fast")]
|
||||
[Fact]
|
||||
public void CanFixupWebhookEventPropertyName()
|
||||
{
|
||||
string legacy = "{\"orignalDeliveryId\":\"blahblah\"}";
|
||||
var obj = JsonConvert.DeserializeObject<WebhookEvent>(legacy, WebhookEvent.DefaultSerializerSettings);
|
||||
Assert.Equal("blahblah", obj.OriginalDeliveryId);
|
||||
var serialized = JsonConvert.SerializeObject(obj, WebhookEvent.DefaultSerializerSettings);
|
||||
Assert.DoesNotContain("orignalDeliveryId", serialized);
|
||||
Assert.Contains("originalDeliveryId", serialized);
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Fast", "Fast")]
|
||||
public async Task CanCreateSqlitedb()
|
||||
{
|
||||
@ -3203,7 +3147,7 @@ namespace BTCPayServer.Tests
|
||||
await new ApplicationDbContext(builder.Options).Database.MigrateAsync();
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void CanUsePermission()
|
||||
{
|
||||
@ -3228,7 +3172,7 @@ namespace BTCPayServer.Tests
|
||||
.Contains(Permission.Create(Policies.CanModifyStoreSettings)));
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void CheckRatesProvider()
|
||||
{
|
||||
@ -3316,7 +3260,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanLoginWithNoSecondaryAuthSystemsOrRequestItWhenAdded()
|
||||
{
|
||||
@ -3328,7 +3272,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var accountController = tester.PayTester.GetController<AccountController>();
|
||||
|
||||
//no 2fa or fido2 enabled, login should work
|
||||
//no 2fa or u2f enabled, login should work
|
||||
Assert.Equal(nameof(HomeController.Index),
|
||||
Assert.IsType<RedirectToActionResult>(await accountController.Login(new LoginViewModel()
|
||||
{
|
||||
@ -3336,46 +3280,48 @@ namespace BTCPayServer.Tests
|
||||
Password = user.RegisterDetails.Password
|
||||
})).ActionName);
|
||||
|
||||
var manageController = user.GetController<Fido2Controller>();
|
||||
var manageController = user.GetController<ManageController>();
|
||||
|
||||
//by default no fido2 devices available
|
||||
//by default no u2f devices available
|
||||
Assert.Empty(Assert
|
||||
.IsType<Fido2AuthenticationViewModel>(Assert
|
||||
.IsType<ViewResult>(await manageController.List()).Model).Credentials);
|
||||
Assert.IsType<CredentialCreateOptions>(Assert
|
||||
.IsType<ViewResult>(await manageController.Create(new AddFido2CredentialViewModel()
|
||||
{
|
||||
Name = "label"
|
||||
})).Model);
|
||||
.IsType<U2FAuthenticationViewModel>(Assert
|
||||
.IsType<ViewResult>(await manageController.U2FAuthentication()).Model).Devices);
|
||||
var addRequest =
|
||||
Assert.IsType<AddU2FDeviceViewModel>(Assert
|
||||
.IsType<ViewResult>(manageController.AddU2FDevice("label")).Model);
|
||||
//name should match the one provided in beginning
|
||||
Assert.Equal("label", addRequest.Name);
|
||||
|
||||
//sending an invalid response model back to server, should error out
|
||||
Assert.IsType<RedirectToActionResult>(await manageController.CreateResponse("sdsdsa", "sds"));
|
||||
Assert.IsType<RedirectToActionResult>(await manageController.AddU2FDevice(addRequest));
|
||||
var statusModel = manageController.TempData.GetStatusMessageModel();
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Error, statusModel.Severity);
|
||||
|
||||
var contextFactory = tester.PayTester.GetService<ApplicationDbContextFactory>();
|
||||
|
||||
//add a fake fido2 device in db directly since emulating a fido2 device is hard and annoying
|
||||
//add a fake u2f device in db directly since emulating a u2f device is hard and annoying
|
||||
using (var context = contextFactory.CreateContext())
|
||||
{
|
||||
var newDevice = new Fido2Credential()
|
||||
var newDevice = new U2FDevice()
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Name = "fake",
|
||||
Type = Fido2Credential.CredentialType.FIDO2,
|
||||
Counter = 0,
|
||||
KeyHandle = UTF8Encoding.UTF8.GetBytes("fake"),
|
||||
PublicKey = UTF8Encoding.UTF8.GetBytes("fake"),
|
||||
AttestationCert = UTF8Encoding.UTF8.GetBytes("fake"),
|
||||
ApplicationUserId = user.UserId
|
||||
};
|
||||
newDevice.SetBlob(new Fido2CredentialBlob() { });
|
||||
await context.Fido2Credentials.AddAsync(newDevice);
|
||||
await context.U2FDevices.AddAsync(newDevice);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
Assert.NotNull(newDevice.Id);
|
||||
Assert.NotEmpty(Assert
|
||||
.IsType<Fido2AuthenticationViewModel>(Assert
|
||||
.IsType<ViewResult>(await manageController.List()).Model).Credentials);
|
||||
.IsType<U2FAuthenticationViewModel>(Assert
|
||||
.IsType<ViewResult>(await manageController.U2FAuthentication()).Model).Devices);
|
||||
}
|
||||
|
||||
//check if we are showing the fido2 login screen now
|
||||
//check if we are showing the u2f login screen now
|
||||
var secondLoginResult = Assert.IsType<ViewResult>(await accountController.Login(new LoginViewModel()
|
||||
{
|
||||
Email = user.RegisterDetails.Email,
|
||||
@ -3386,11 +3332,11 @@ namespace BTCPayServer.Tests
|
||||
var vm = Assert.IsType<SecondaryLoginViewModel>(secondLoginResult.Model);
|
||||
//2fa was never enabled for user so this should be empty
|
||||
Assert.Null(vm.LoginWith2FaViewModel);
|
||||
Assert.NotNull(vm.LoginWithFido2ViewModel);
|
||||
Assert.NotNull(vm.LoginWithU2FViewModel);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async void CheckOnionlocationForNonOnionHtmlRequests()
|
||||
{
|
||||
@ -3436,7 +3382,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanCheckForNewVersion()
|
||||
{
|
||||
@ -3482,7 +3428,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanDoLightningInternalNodeMigration()
|
||||
{
|
||||
@ -3561,7 +3507,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanDoInvoiceMigrations()
|
||||
{
|
||||
@ -3642,7 +3588,7 @@ namespace BTCPayServer.Tests
|
||||
await migrationStartupTask.ExecuteAsync();
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task EmailSenderTests()
|
||||
{
|
||||
|
@ -51,8 +51,6 @@
|
||||
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435" />
|
||||
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
|
||||
<PackageReference Include="CsvHelper" Version="15.0.5" />
|
||||
<PackageReference Include="Fido2" Version="2.0.1" />
|
||||
<PackageReference Include="Fido2.AspNet" Version="2.0.1" />
|
||||
<PackageReference Include="HtmlSanitizer" Version="5.0.372" />
|
||||
<PackageReference Include="McMaster.NETCore.Plugins.Mvc" Version="1.3.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
|
||||
@ -84,6 +82,7 @@
|
||||
<PackageReference Include="TwentyTwenty.Storage.Azure" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Google" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Local" Version="2.12.1" />
|
||||
<PackageReference Include="U2F.Core" Version="1.0.4" />
|
||||
<PackageReference Include="YamlDotNet" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.1" Condition="'$(RazorCompileOnBuild)' != 'true'" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.1" />
|
||||
@ -135,9 +134,11 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Views\Stores\Integrations" />
|
||||
<Folder Include="wwwroot\vendor\clipboard.js\" />
|
||||
<Folder Include="wwwroot\vendor\highlightjs\" />
|
||||
<Folder Include="wwwroot\vendor\summernote" />
|
||||
<Folder Include="wwwroot\vendor\u2f" />
|
||||
<Folder Include="wwwroot\vendor\vue-qrcode-reader" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -3,17 +3,17 @@
|
||||
@inject CssThemeManager CssThemeManager
|
||||
@using BTCPayServer.HostedServices
|
||||
@using BTCPayServer.Views.Notifications
|
||||
@using Microsoft.AspNetCore.Http.Extensions
|
||||
@model BTCPayServer.Components.NotificationsDropdown.NotificationSummaryViewModel
|
||||
@addTagHelper *, BundlerMinifier.TagHelpers
|
||||
|
||||
@if (Model.UnseenCount > 0)
|
||||
{
|
||||
<li class="nav-item dropdown" id="notifications-nav-item">
|
||||
<a class="nav-link js-scroll-trigger @ViewData.IsActiveCategory(typeof(NotificationsNavPages))" href="#" id="navbarDropdown" role="button" data-toggle="dropdown">
|
||||
<span class="d-inline-block d-lg-none">Notifications</span>
|
||||
<i class="fa fa-bell d-lg-inline-block d-none"></i>
|
||||
<span class="notification-badge badge badge-pill badge-danger">@Model.UnseenCount</span>
|
||||
<a class="nav-link js-scroll-trigger @ViewData.IsActiveCategory(typeof(NotificationsNavPages))" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" id="Notifications">
|
||||
<span class="d-lg-none d-sm-block">Notifications</span><i class="fa fa-bell d-lg-inline-block d-none"></i>
|
||||
</a>
|
||||
<span class="alerts-badge badge badge-pill badge-danger">@Model.UnseenCount</span>
|
||||
<div class="dropdown-menu dropdown-menu-right text-center notification-dropdown" aria-labelledby="navbarDropdown">
|
||||
<div class="d-flex align-items-center justify-content-between py-3 px-4 border-bottom border-light">
|
||||
<h5 class="m-0">Notifications</h5>
|
||||
@ -57,7 +57,7 @@ else
|
||||
if (!disabled)
|
||||
{
|
||||
var user = await UserManager.GetUserAsync(User);
|
||||
disabled = user?.DisabledNotifications == "all";
|
||||
disabled = user.DisabledNotifications == "all";
|
||||
}
|
||||
}
|
||||
@if (!disabled)
|
||||
|
@ -69,12 +69,7 @@ namespace BTCPayServer.Configuration
|
||||
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);
|
||||
TorServices = conf.GetOrDefault<string>("torservices", null)
|
||||
?.Split(new[] {';', ','}, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (!string.IsNullOrEmpty(TorrcFile) && TorServices != null)
|
||||
throw new ConfigException($"torrcfile or torservices should be provided, but not both");
|
||||
|
||||
var socksEndpointString = conf.GetOrDefault<string>("socksendpoint", null);
|
||||
if (!string.IsNullOrEmpty(socksEndpointString))
|
||||
@ -200,7 +195,6 @@ namespace BTCPayServer.Configuration
|
||||
set;
|
||||
}
|
||||
public string TorrcFile { get; set; }
|
||||
public string[] TorServices { get; set; }
|
||||
public Uri UpdateUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,6 @@ namespace BTCPayServer.Configuration
|
||||
app.Option("--sshauthorizedkeys", "Path to a authorized_keys file that BTCPayServer can modify from the website (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--sshtrustedfingerprints", "SSH Host public key fingerprint or sha256 (default: empty, it will allow untrusted connections)", CommandOptionType.SingleValue);
|
||||
app.Option("--torrcfile", "Path to torrc file containing hidden services directories (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--torservices", "Tor hostnames of available services added to Server Settings (and sets onion header for btcpay). Format: btcpayserver:host.onion:80;btc-p2p:host2.onion:81,BTC-RPC:host3.onion:82,UNKNOWN:host4.onion:83. (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--socksendpoint", "Socks endpoint to connect to onion urls (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--updateurl", $"Url used for once a day new release version check. Check performed only if value is not empty (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--debuglog", "A rolling log file for debug messages.", CommandOptionType.SingleValue);
|
||||
|
@ -1,24 +1,26 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Security.Policy;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Fido2;
|
||||
using BTCPayServer.Fido2.Models;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.AccountViewModels;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using Fido2NetLib;
|
||||
using BTCPayServer.U2F;
|
||||
using BTCPayServer.U2F.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NicolasDorier.RateLimits;
|
||||
using U2F.Core.Exceptions;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -32,7 +34,8 @@ namespace BTCPayServer.Controllers
|
||||
readonly SettingsRepository _SettingsRepository;
|
||||
readonly Configuration.BTCPayServerOptions _Options;
|
||||
private readonly BTCPayServerEnvironment _btcPayServerEnvironment;
|
||||
private readonly Fido2Service _fido2Service;
|
||||
public U2FService _u2FService;
|
||||
private readonly RateLimitService _rateLimitService;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
readonly ILogger _logger;
|
||||
|
||||
@ -43,8 +46,9 @@ namespace BTCPayServer.Controllers
|
||||
SettingsRepository settingsRepository,
|
||||
Configuration.BTCPayServerOptions options,
|
||||
BTCPayServerEnvironment btcPayServerEnvironment,
|
||||
EventAggregator eventAggregator,
|
||||
Fido2Service fido2Service)
|
||||
U2FService u2FService,
|
||||
RateLimitService rateLimitService,
|
||||
EventAggregator eventAggregator)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_signInManager = signInManager;
|
||||
@ -52,7 +56,8 @@ namespace BTCPayServer.Controllers
|
||||
_SettingsRepository = settingsRepository;
|
||||
_Options = options;
|
||||
_btcPayServerEnvironment = btcPayServerEnvironment;
|
||||
_fido2Service = fido2Service;
|
||||
_u2FService = u2FService;
|
||||
_rateLimitService = rateLimitService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_logger = Logs.PayServer;
|
||||
}
|
||||
@ -120,8 +125,7 @@ namespace BTCPayServer.Controllers
|
||||
return View(model);
|
||||
}
|
||||
|
||||
var fido2Devices = await _fido2Service.HasCredentials(user.Id);
|
||||
if (!await _userManager.IsLockedOutAsync(user) && fido2Devices)
|
||||
if (!await _userManager.IsLockedOutAsync(user) && await _u2FService.HasDevices(user.Id))
|
||||
{
|
||||
if (await _userManager.CheckPasswordAsync(user, model.Password))
|
||||
{
|
||||
@ -140,7 +144,7 @@ namespace BTCPayServer.Controllers
|
||||
return View("SecondaryLogin", new SecondaryLoginViewModel()
|
||||
{
|
||||
LoginWith2FaViewModel = twoFModel,
|
||||
LoginWithFido2ViewModel = fido2Devices? await BuildFido2ViewModel(model.RememberMe, user): null,
|
||||
LoginWithU2FViewModel = await BuildU2FViewModel(model.RememberMe, user)
|
||||
});
|
||||
}
|
||||
else
|
||||
@ -156,7 +160,7 @@ namespace BTCPayServer.Controllers
|
||||
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: true);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
_logger.LogInformation($"User '{user.Id}' logged in.");
|
||||
_logger.LogInformation("User logged in.");
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
if (result.RequiresTwoFactor)
|
||||
@ -171,7 +175,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
if (result.IsLockedOut)
|
||||
{
|
||||
_logger.LogWarning($"User '{user.Id}' account locked out.");
|
||||
_logger.LogWarning("User account locked out.");
|
||||
return RedirectToAction(nameof(Lockout));
|
||||
}
|
||||
else
|
||||
@ -185,29 +189,31 @@ namespace BTCPayServer.Controllers
|
||||
return View(model);
|
||||
}
|
||||
|
||||
private async Task<LoginWithFido2ViewModel> BuildFido2ViewModel(bool rememberMe, ApplicationUser user)
|
||||
private async Task<LoginWithU2FViewModel> BuildU2FViewModel(bool rememberMe, ApplicationUser user)
|
||||
{
|
||||
if (_btcPayServerEnvironment.IsSecure)
|
||||
{
|
||||
var r = await _fido2Service.RequestLogin(user.Id);
|
||||
if (r is null)
|
||||
var u2fChallenge = await _u2FService.GenerateDeviceChallenges(user.Id,
|
||||
Request.GetAbsoluteUriNoPathBase().ToString().TrimEnd('/'));
|
||||
|
||||
return new LoginWithU2FViewModel()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new LoginWithFido2ViewModel()
|
||||
{
|
||||
Data = r,
|
||||
Version = u2fChallenge[0].version,
|
||||
Challenge = u2fChallenge[0].challenge,
|
||||
Challenges = u2fChallenge,
|
||||
AppId = u2fChallenge[0].appId,
|
||||
UserId = user.Id,
|
||||
RememberMe = rememberMe
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> LoginWithFido2(LoginWithFido2ViewModel viewModel, string returnUrl = null)
|
||||
public async Task<IActionResult> LoginWithU2F(LoginWithU2FViewModel viewModel, string returnUrl = null)
|
||||
{
|
||||
if (!CanLoginOrRegister())
|
||||
{
|
||||
@ -225,25 +231,24 @@ namespace BTCPayServer.Controllers
|
||||
var errorMessage = string.Empty;
|
||||
try
|
||||
{
|
||||
if (await _fido2Service.CompleteLogin(viewModel.UserId, JObject.Parse(viewModel.Response).ToObject<AuthenticatorAssertionRawResponse>()))
|
||||
if (await _u2FService.AuthenticateUser(viewModel.UserId, viewModel.DeviceResponse))
|
||||
{
|
||||
await _signInManager.SignInAsync(user, viewModel.RememberMe, "FIDO2");
|
||||
await _signInManager.SignInAsync(user, viewModel.RememberMe, "U2F");
|
||||
_logger.LogInformation("User logged in.");
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
|
||||
errorMessage = "Invalid login attempt.";
|
||||
}
|
||||
catch (Fido2VerificationException e)
|
||||
catch (U2fException e)
|
||||
{
|
||||
errorMessage = e.Message;
|
||||
}
|
||||
|
||||
ModelState.AddModelError(string.Empty, errorMessage);
|
||||
viewModel.Response = null;
|
||||
return View("SecondaryLogin", new SecondaryLoginViewModel()
|
||||
{
|
||||
LoginWithFido2ViewModel = viewModel,
|
||||
LoginWithU2FViewModel = viewModel,
|
||||
LoginWith2FaViewModel = !user.TwoFactorEnabled
|
||||
? null
|
||||
: new LoginWith2faViewModel()
|
||||
@ -252,6 +257,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> LoginWith2fa(bool rememberMe, string returnUrl = null)
|
||||
@ -274,7 +280,7 @@ namespace BTCPayServer.Controllers
|
||||
return View("SecondaryLogin", new SecondaryLoginViewModel()
|
||||
{
|
||||
LoginWith2FaViewModel = new LoginWith2faViewModel { RememberMe = rememberMe },
|
||||
LoginWithFido2ViewModel = (await _fido2Service.HasCredentials(user.Id)) ? await BuildFido2ViewModel(rememberMe, user) : null,
|
||||
LoginWithU2FViewModel = (await _u2FService.HasDevices(user.Id)) ? await BuildU2FViewModel(rememberMe, user) : null
|
||||
});
|
||||
}
|
||||
|
||||
@ -320,7 +326,7 @@ namespace BTCPayServer.Controllers
|
||||
return View("SecondaryLogin", new SecondaryLoginViewModel()
|
||||
{
|
||||
LoginWith2FaViewModel = model,
|
||||
LoginWithFido2ViewModel = (await _fido2Service.HasCredentials(user.Id)) ? await BuildFido2ViewModel(rememberMe, user) : null,
|
||||
LoginWithU2FViewModel = (await _u2FService.HasDevices(user.Id)) ? await BuildU2FViewModel(rememberMe, user) : null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -16,22 +16,23 @@ namespace BTCPayServer.Controllers
|
||||
public string StoreId { get; set; }
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Empty;
|
||||
return String.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{appId}/settings/crowdfund")]
|
||||
|
||||
[HttpGet]
|
||||
[Route("{appId}/settings/crowdfund")]
|
||||
public async Task<IActionResult> UpdateCrowdfund(string appId)
|
||||
{
|
||||
var app = await GetOwnedApp(appId, AppType.Crowdfund);
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
var settings = app.GetSettings<CrowdfundSettings>();
|
||||
var vm = new UpdateCrowdfundViewModel
|
||||
var vm = new UpdateCrowdfundViewModel()
|
||||
{
|
||||
Title = settings.Title,
|
||||
StoreId = app.StoreDataId,
|
||||
StoreName = app.StoreData?.StoreName,
|
||||
Enabled = settings.Enabled,
|
||||
EnforceTargetAmount = settings.EnforceTargetAmount,
|
||||
StartDate = settings.StartDate,
|
||||
|
@ -97,11 +97,10 @@ namespace BTCPayServer.Controllers
|
||||
settings.DefaultView = settings.EnableShoppingCart ? PosViewType.Cart : settings.DefaultView;
|
||||
settings.EnableShoppingCart = false;
|
||||
|
||||
var vm = new UpdatePointOfSaleViewModel
|
||||
var vm = new UpdatePointOfSaleViewModel()
|
||||
{
|
||||
Id = appId,
|
||||
StoreId = app.StoreDataId,
|
||||
StoreName = app.StoreData?.StoreName,
|
||||
Title = settings.Title,
|
||||
DefaultView = settings.DefaultView,
|
||||
ShowCustomAmount = settings.ShowCustomAmount,
|
||||
@ -184,7 +183,7 @@ namespace BTCPayServer.Controllers
|
||||
var app = await GetOwnedApp(appId, AppType.PointOfSale);
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
app.SetSettings(new PointOfSaleSettings
|
||||
app.SetSettings(new PointOfSaleSettings()
|
||||
{
|
||||
Title = vm.Title,
|
||||
DefaultView = vm.DefaultView,
|
||||
|
@ -8,11 +8,6 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
public static class GreenFieldUtils
|
||||
{
|
||||
public static IActionResult CreateValidationError(this ControllerBase controller, ModelStateDictionary modelState)
|
||||
{
|
||||
return controller.UnprocessableEntity(modelState.ToGreenfieldValidationError());
|
||||
}
|
||||
|
||||
public static List<GreenfieldValidationError> ToGreenfieldValidationError(this ModelStateDictionary modelState)
|
||||
{
|
||||
List<GreenfieldValidationError> errors = new List<GreenfieldValidationError>();
|
||||
foreach (var error in modelState)
|
||||
@ -22,17 +17,11 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
errors.Add(new GreenfieldValidationError(error.Key, errorMessage.ErrorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
return controller.UnprocessableEntity(errors.ToArray());
|
||||
}
|
||||
|
||||
public static IActionResult CreateAPIError(this ControllerBase controller, string errorCode, string errorMessage)
|
||||
{
|
||||
return controller.BadRequest(new GreenfieldAPIError(errorCode, errorMessage));
|
||||
}
|
||||
public static IActionResult CreateAPIError(this ControllerBase controller, int httpCode, string errorCode, string errorMessage)
|
||||
{
|
||||
return controller.StatusCode(httpCode, new GreenfieldAPIError(errorCode, errorMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Globalization;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
@ -48,43 +47,25 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
[Authorize(Policy = Policies.CanViewInvoices,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/invoices")]
|
||||
public async Task<IActionResult> GetInvoices(string storeId, [FromQuery] string[] orderId = null, [FromQuery] string[] status = null,
|
||||
[FromQuery]
|
||||
[ModelBinder(typeof(ModelBinders.DateTimeOffsetModelBinder))]
|
||||
DateTimeOffset? startDate = null,
|
||||
[FromQuery]
|
||||
[ModelBinder(typeof(ModelBinders.DateTimeOffsetModelBinder))]
|
||||
DateTimeOffset? endDate = null, [FromQuery] bool includeArchived = false)
|
||||
public async Task<IActionResult> GetInvoices(string storeId, bool includeArchived = false)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
{
|
||||
return StoreNotFound();
|
||||
}
|
||||
if (startDate is DateTimeOffset s &&
|
||||
endDate is DateTimeOffset e &&
|
||||
s > e)
|
||||
{
|
||||
this.ModelState.AddModelError(nameof(startDate), "startDate should not be above endDate");
|
||||
this.ModelState.AddModelError(nameof(endDate), "endDate should not be below startDate");
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
var invoices =
|
||||
await _invoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
StoreId = new[] {store.Id},
|
||||
IncludeArchived = includeArchived,
|
||||
StartDate = startDate,
|
||||
EndDate = endDate,
|
||||
OrderId = orderId,
|
||||
Status = status
|
||||
StoreId = new[] { store.Id },
|
||||
IncludeArchived = includeArchived
|
||||
});
|
||||
|
||||
return Ok(invoices.Select(ToModel));
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Authorize(Policy = Policies.CanViewInvoices,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/invoices/{invoiceId}")]
|
||||
@ -93,13 +74,13 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
{
|
||||
return InvoiceNotFound();
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||
if (invoice?.StoreId != store.Id)
|
||||
{
|
||||
return InvoiceNotFound();
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(ToModel(invoice));
|
||||
@ -113,13 +94,9 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
{
|
||||
return InvoiceNotFound();
|
||||
}
|
||||
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||
if (invoice?.StoreId != store.Id)
|
||||
{
|
||||
return InvoiceNotFound();
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
await _invoiceRepository.ToggleInvoiceArchival(invoiceId, true, storeId);
|
||||
return Ok();
|
||||
}
|
||||
@ -132,7 +109,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
{
|
||||
return InvoiceNotFound();
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var result = await _invoiceRepository.UpdateInvoiceMetadata(invoiceId, storeId, request.Metadata);
|
||||
@ -141,7 +118,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
return Ok(ToModel(result));
|
||||
}
|
||||
|
||||
return InvoiceNotFound();
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanCreateInvoice,
|
||||
@ -152,7 +129,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
{
|
||||
return StoreNotFound();
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (request.Amount < 0.0m)
|
||||
@ -229,13 +206,13 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
{
|
||||
return InvoiceNotFound();
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||
if (invoice.StoreId != store.Id)
|
||||
{
|
||||
return InvoiceNotFound();
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (!await _invoiceRepository.MarkInvoiceStatus(invoice.Id, request.Status))
|
||||
@ -258,13 +235,13 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
{
|
||||
return InvoiceNotFound();
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||
if (invoice.StoreId != store.Id)
|
||||
{
|
||||
return InvoiceNotFound();
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (!invoice.Archived)
|
||||
@ -283,21 +260,21 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
[Authorize(Policy = Policies.CanViewInvoices,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods")]
|
||||
public async Task<IActionResult> GetInvoicePaymentMethods(string storeId, string invoiceId, bool onlyAccountedPayments = true)
|
||||
public async Task<IActionResult> GetInvoicePaymentMethods(string storeId, string invoiceId)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
{
|
||||
return InvoiceNotFound();
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||
if (invoice?.StoreId != store.Id)
|
||||
{
|
||||
return InvoiceNotFound();
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(ToPaymentMethodModels(invoice, onlyAccountedPayments));
|
||||
return Ok(ToPaymentMethodModels(invoice));
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewInvoices,
|
||||
@ -308,13 +285,13 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
{
|
||||
return InvoiceNotFound();
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||
if (invoice?.StoreId != store.Id)
|
||||
{
|
||||
return InvoiceNotFound();
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (PaymentMethodId.TryParse(paymentMethod, out var paymentMethodId))
|
||||
@ -323,27 +300,17 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
_paymentMethodHandlerDictionary, store, invoice, paymentMethodId);
|
||||
return Ok();
|
||||
}
|
||||
ModelState.AddModelError(nameof(paymentMethod), "Invalid payment method");
|
||||
return this.CreateValidationError(ModelState);
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
private IActionResult InvoiceNotFound()
|
||||
{
|
||||
return this.CreateAPIError(404, "invoice-not-found", "The invoice was not found");
|
||||
}
|
||||
private IActionResult StoreNotFound()
|
||||
{
|
||||
return this.CreateAPIError(404, "store-not-found", "The store was not found");
|
||||
}
|
||||
|
||||
private InvoicePaymentMethodDataModel[] ToPaymentMethodModels(InvoiceEntity entity, bool includeAccountedPaymentOnly)
|
||||
|
||||
private InvoicePaymentMethodDataModel[] ToPaymentMethodModels(InvoiceEntity entity)
|
||||
{
|
||||
return entity.GetPaymentMethods().Select(
|
||||
method =>
|
||||
{
|
||||
var accounting = method.Calculate();
|
||||
var details = method.GetPaymentMethodDetails();
|
||||
var payments = method.ParentEntity.GetPayments(includeAccountedPaymentOnly).Where(paymentEntity =>
|
||||
var payments = method.ParentEntity.GetPayments().Where(paymentEntity =>
|
||||
paymentEntity.GetPaymentMethodId() == method.GetId());
|
||||
|
||||
return new InvoicePaymentMethodDataModel()
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -32,15 +31,13 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
private readonly CurrencyNameTable _currencyNameTable;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _serializerSettings;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
||||
|
||||
public GreenfieldPullPaymentController(PullPaymentHostedService pullPaymentService,
|
||||
LinkGenerator linkGenerator,
|
||||
ApplicationDbContextFactory dbContextFactory,
|
||||
CurrencyNameTable currencyNameTable,
|
||||
Services.BTCPayNetworkJsonSerializerSettings serializerSettings,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
IEnumerable<IPayoutHandler> payoutHandlers)
|
||||
BTCPayNetworkProvider networkProvider)
|
||||
{
|
||||
_pullPaymentService = pullPaymentService;
|
||||
_linkGenerator = linkGenerator;
|
||||
@ -48,7 +45,6 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
_currencyNameTable = currencyNameTable;
|
||||
_serializerSettings = serializerSettings;
|
||||
_networkProvider = networkProvider;
|
||||
_payoutHandlers = payoutHandlers;
|
||||
}
|
||||
|
||||
[HttpGet("~/api/v1/stores/{storeId}/pull-payments")]
|
||||
@ -182,7 +178,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
return NotFound();
|
||||
using var ctx = _dbContextFactory.CreateContext();
|
||||
var payouts = await ctx.Payouts.Where(p => p.PullPaymentDataId == pullPaymentId)
|
||||
.Where(p => p.State != PayoutState.Cancelled || includeCancelled)
|
||||
.Where(p => p.State != Data.PayoutState.Cancelled || includeCancelled)
|
||||
.ToListAsync();
|
||||
var cd = _currencyNameTable.GetCurrencyData(pp.GetBlob().Currency, false);
|
||||
return base.Ok(payouts
|
||||
@ -200,9 +196,14 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
Amount = blob.Amount,
|
||||
PaymentMethodAmount = blob.CryptoAmount,
|
||||
Revision = blob.Revision,
|
||||
State = p.State
|
||||
State = p.State == Data.PayoutState.AwaitingPayment ? Client.Models.PayoutState.AwaitingPayment :
|
||||
p.State == Data.PayoutState.AwaitingApproval ? Client.Models.PayoutState.AwaitingApproval :
|
||||
p.State == Data.PayoutState.Cancelled ? Client.Models.PayoutState.Cancelled :
|
||||
p.State == Data.PayoutState.Completed ? Client.Models.PayoutState.Completed :
|
||||
p.State == Data.PayoutState.InProgress ? Client.Models.PayoutState.InProgress :
|
||||
throw new NotSupportedException(),
|
||||
};
|
||||
model.Destination = blob.Destination;
|
||||
model.Destination = blob.Destination.ToString();
|
||||
model.PaymentMethod = p.PaymentMethodId;
|
||||
return model;
|
||||
}
|
||||
@ -213,14 +214,10 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
if (request is null)
|
||||
return NotFound();
|
||||
if (!PaymentMethodId.TryParse(request?.PaymentMethod, out var paymentMethodId))
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
var payoutHandler = _payoutHandlers.FirstOrDefault(handler => handler.CanHandle(paymentMethodId));
|
||||
if (payoutHandler is null)
|
||||
|
||||
var network = request?.PaymentMethod is string paymentMethod ?
|
||||
this._networkProvider.GetNetwork<BTCPayNetwork>(paymentMethod) : null;
|
||||
if (network is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method");
|
||||
return this.CreateValidationError(ModelState);
|
||||
@ -231,8 +228,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
if (pp is null)
|
||||
return NotFound();
|
||||
var ppBlob = pp.GetBlob();
|
||||
IClaimDestination destination = await payoutHandler.ParseClaimDestination(paymentMethodId,request.Destination);
|
||||
if (destination is null)
|
||||
if (request.Destination is null || !ClaimDestination.TryParse(request.Destination, network, out var destination))
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Destination), "The destination must be an address or a BIP21 URI");
|
||||
return this.CreateValidationError(ModelState);
|
||||
@ -249,7 +245,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
Destination = destination,
|
||||
PullPaymentId = pullPaymentId,
|
||||
Value = request.Amount,
|
||||
PaymentMethodId = paymentMethodId
|
||||
PaymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike)
|
||||
});
|
||||
switch (result.Result)
|
||||
{
|
||||
|
@ -239,7 +239,6 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
coin.OutPoint.Hash.ToString()),
|
||||
Timestamp = coin.Timestamp,
|
||||
KeyPath = coin.KeyPath,
|
||||
Confirmations = coin.Confirmations,
|
||||
Address = network.NBXplorerNetwork.CreateAddress(derivationScheme.AccountDerivation, coin.KeyPath, coin.ScriptPubKey).ToString()
|
||||
};
|
||||
}).ToList()
|
||||
|
@ -48,7 +48,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
var w = await StoreRepository.GetWebhook(CurrentStoreId, webhookId);
|
||||
if (w is null)
|
||||
return WebhookNotFound();
|
||||
return NotFound();
|
||||
return Ok(FromModel(w, false));
|
||||
}
|
||||
}
|
||||
@ -70,7 +70,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
var webhookId = await StoreRepository.CreateWebhook(CurrentStoreId, ToModel(create));
|
||||
var w = await StoreRepository.GetWebhook(CurrentStoreId, webhookId);
|
||||
if (w is null)
|
||||
return WebhookNotFound();
|
||||
return NotFound();
|
||||
return Ok(FromModel(w, true));
|
||||
}
|
||||
|
||||
@ -88,7 +88,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
return this.CreateValidationError(ModelState);
|
||||
var w = await StoreRepository.GetWebhook(CurrentStoreId, webhookId);
|
||||
if (w is null)
|
||||
return WebhookNotFound();
|
||||
return NotFound();
|
||||
await StoreRepository.UpdateWebhook(storeId, webhookId, ToModel(update));
|
||||
return await ListWebhooks(webhookId);
|
||||
}
|
||||
@ -97,19 +97,10 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
var w = await StoreRepository.GetWebhook(CurrentStoreId, webhookId);
|
||||
if (w is null)
|
||||
return WebhookNotFound();
|
||||
return NotFound();
|
||||
await StoreRepository.DeleteWebhook(CurrentStoreId, webhookId);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
IActionResult WebhookNotFound()
|
||||
{
|
||||
return this.CreateAPIError(404, "webhook-not-found", "The webhook was not found");
|
||||
}
|
||||
IActionResult WebhookDeliveryNotFound()
|
||||
{
|
||||
return this.CreateAPIError(404, "webhookdelivery-not-found", "The webhook delivery was not found");
|
||||
}
|
||||
private WebhookBlob ToModel(StoreWebhookBaseData create)
|
||||
{
|
||||
return new WebhookBlob()
|
||||
@ -142,7 +133,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
var delivery = await StoreRepository.GetWebhookDelivery(CurrentStoreId, webhookId, deliveryId);
|
||||
if (delivery is null)
|
||||
return WebhookDeliveryNotFound();
|
||||
return NotFound();
|
||||
return Ok(FromModel(delivery));
|
||||
}
|
||||
}
|
||||
@ -151,7 +142,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
var delivery = await StoreRepository.GetWebhookDelivery(CurrentStoreId, webhookId, deliveryId);
|
||||
if (delivery is null)
|
||||
return WebhookDeliveryNotFound();
|
||||
return NotFound();
|
||||
return this.Ok(new JValue(await WebhookNotificationManager.Redeliver(deliveryId)));
|
||||
}
|
||||
|
||||
@ -160,7 +151,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
var delivery = await StoreRepository.GetWebhookDelivery(CurrentStoreId, webhookId, deliveryId);
|
||||
if (delivery is null)
|
||||
return WebhookDeliveryNotFound();
|
||||
return NotFound();
|
||||
return File(delivery.GetBlob().Request, "application/json");
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[Route("misc/lang")]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie + "," + AuthenticationSchemes.Greenfield)]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public IActionResult Languages()
|
||||
{
|
||||
return Json(LanguageService.GetLanguages(), new JsonSerializerSettings() { Formatting = Formatting.Indented });
|
||||
|
@ -19,8 +19,8 @@ using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.CoinSwitch;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Plugins.CoinSwitch;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
@ -359,7 +359,7 @@ namespace BTCPayServer.Controllers
|
||||
return new InvoiceDetailsModel
|
||||
{
|
||||
Archived = invoice.Archived,
|
||||
Payments = invoice.GetPayments(false),
|
||||
Payments = invoice.GetPayments(),
|
||||
CryptoPayments = invoice.GetPaymentMethods().Select(
|
||||
data =>
|
||||
{
|
||||
@ -525,6 +525,12 @@ namespace BTCPayServer.Controllers
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var accounting = paymentMethod.Calculate();
|
||||
|
||||
CoinSwitchSettings coinswitch = (storeBlob.CoinSwitchSettings != null && storeBlob.CoinSwitchSettings.Enabled &&
|
||||
storeBlob.CoinSwitchSettings.IsConfigured())
|
||||
? storeBlob.CoinSwitchSettings
|
||||
: null;
|
||||
|
||||
|
||||
var paymentMethodHandler = _paymentMethodHandlerDictionary[paymentMethodId];
|
||||
|
||||
var divisibility = _CurrencyNameTable.GetNumberFormatInfo(paymentMethod.GetId().CryptoCode, false)?.CurrencyDecimalDigits;
|
||||
@ -561,7 +567,11 @@ namespace BTCPayServer.Controllers
|
||||
Status = invoice.StatusString,
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
NetworkFee = paymentMethodDetails.GetNextNetworkFee(),
|
||||
IsMultiCurrency = invoice.GetPayments(false).Select(p => p.GetPaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1,
|
||||
IsMultiCurrency = invoice.GetPayments().Select(p => p.GetPaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1,
|
||||
CoinSwitchEnabled = coinswitch != null,
|
||||
CoinSwitchAmountMarkupPercentage = coinswitch?.AmountMarkupPercentage ?? 0,
|
||||
CoinSwitchMerchantId = coinswitch?.MerchantId,
|
||||
CoinSwitchMode = coinswitch?.Mode,
|
||||
StoreId = store.Id,
|
||||
AvailableCryptos = invoice.GetPaymentMethods()
|
||||
.Where(i => i.Network != null)
|
||||
@ -903,20 +913,21 @@ namespace BTCPayServer.Controllers
|
||||
var jObject = JObject.Parse(posData);
|
||||
foreach (var item in jObject)
|
||||
{
|
||||
|
||||
switch (item.Value.Type)
|
||||
{
|
||||
case JTokenType.Array:
|
||||
var items = item.Value.AsEnumerable().ToList();
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
result.TryAdd($"{item.Key}[{i}]", ParsePosData(items[i].ToString()));
|
||||
result.Add($"{item.Key}[{i}]", ParsePosData(items[i].ToString()));
|
||||
}
|
||||
break;
|
||||
case JTokenType.Object:
|
||||
result.TryAdd(item.Key, ParsePosData(item.Value.ToString()));
|
||||
result.Add(item.Key, ParsePosData(item.Value.ToString()));
|
||||
break;
|
||||
default:
|
||||
result.TryAdd(item.Key, item.Value.ToString());
|
||||
result.Add(item.Key, item.Value.ToString());
|
||||
break;
|
||||
}
|
||||
|
||||
@ -924,7 +935,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
catch
|
||||
{
|
||||
result.TryAdd(string.Empty, posData);
|
||||
result.Add(string.Empty, posData);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -472,8 +472,8 @@ namespace BTCPayServer.Controllers
|
||||
{BTCPayServer.Client.Policies.CanCreateUser, ("Create new users", "The app will be able to create new users on this server.")},
|
||||
{BTCPayServer.Client.Policies.CanModifyStoreSettings, ("Modify your stores", "The app will be able to view, modify, delete and create new invoices on all your stores.")},
|
||||
{$"{BTCPayServer.Client.Policies.CanModifyStoreSettings}:", ("Manage selected stores", "The app will be able to view, modify, delete and create new invoices on the selected stores.")},
|
||||
{BTCPayServer.Client.Policies.CanModifyStoreWebhooks, ("Modify stores webhooks", "The app will modify the webhooks of all your stores.")},
|
||||
{$"{BTCPayServer.Client.Policies.CanModifyStoreWebhooks}:", ("Modify selected stores' webhooks", "The app will modify the webhooks of the selected stores.")},
|
||||
{BTCPayServer.Client.Policies.CanModifyStoreWebhooks, ("Modify stores webhooks", "The app will be mofidy the webhooks of all your stores.")},
|
||||
{$"{BTCPayServer.Client.Policies.CanModifyStoreWebhooks}:", ("Modify selected stores' webhooks", "The app will be mofidy the webhooks of the selected stores.")},
|
||||
{BTCPayServer.Client.Policies.CanViewStoreSettings, ("View your stores", "The app will be able to view stores settings.")},
|
||||
{$"{BTCPayServer.Client.Policies.CanViewStoreSettings}:", ("View your stores", "The app will be able to view the selected stores' settings.")},
|
||||
{BTCPayServer.Client.Policies.CanModifyServerSettings, ("Manage your server", "The app will have total control on the server settings of your server")},
|
||||
|
83
BTCPayServer/Controllers/ManageController.U2F.cs
Normal file
83
BTCPayServer/Controllers/ManageController.U2F.cs
Normal file
@ -0,0 +1,83 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.U2F.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using U2F.Core.Exceptions;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class ManageController
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> U2FAuthentication()
|
||||
{
|
||||
return View(new U2FAuthenticationViewModel()
|
||||
{
|
||||
Devices = await _u2FService.GetDevices(_userManager.GetUserId(User))
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> RemoveU2FDevice(string id)
|
||||
{
|
||||
await _u2FService.RemoveDevice(id, _userManager.GetUserId(User));
|
||||
return RedirectToAction("U2FAuthentication", new
|
||||
{
|
||||
StatusMessage = "Device removed"
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult AddU2FDevice(string name)
|
||||
{
|
||||
if (!_btcPayServerEnvironment.IsSecure)
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = "Cannot register U2F device while not on https or tor"
|
||||
});
|
||||
return RedirectToAction("U2FAuthentication");
|
||||
}
|
||||
|
||||
var serverRegisterResponse = _u2FService.StartDeviceRegistration(_userManager.GetUserId(User),
|
||||
Request.GetAbsoluteUriNoPathBase().ToString().TrimEnd('/'));
|
||||
|
||||
return View(new AddU2FDeviceViewModel()
|
||||
{
|
||||
AppId = serverRegisterResponse.AppId,
|
||||
Challenge = serverRegisterResponse.Challenge,
|
||||
Version = serverRegisterResponse.Version,
|
||||
Name = name
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> AddU2FDevice(AddU2FDeviceViewModel viewModel)
|
||||
{
|
||||
var errorMessage = string.Empty;
|
||||
try
|
||||
{
|
||||
if (await _u2FService.CompleteRegistration(_userManager.GetUserId(User), viewModel.DeviceResponse,
|
||||
string.IsNullOrEmpty(viewModel.Name) ? "Unlabelled U2F Device" : viewModel.Name))
|
||||
{
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Device added!";
|
||||
return RedirectToAction("U2FAuthentication");
|
||||
}
|
||||
}
|
||||
catch (U2fException e)
|
||||
{
|
||||
errorMessage = e.Message;
|
||||
}
|
||||
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = string.IsNullOrEmpty(errorMessage) ? "Could not add device." : errorMessage
|
||||
});
|
||||
return RedirectToAction("U2FAuthentication");
|
||||
}
|
||||
}
|
||||
}
|
@ -4,11 +4,13 @@ using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.ManageViewModels;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Security.GreenField;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Mails;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.U2F;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
@ -27,6 +29,8 @@ namespace BTCPayServer.Controllers
|
||||
private readonly EmailSenderFactory _EmailSenderFactory;
|
||||
private readonly ILogger _logger;
|
||||
private readonly UrlEncoder _urlEncoder;
|
||||
readonly IWebHostEnvironment _Env;
|
||||
public U2FService _u2FService;
|
||||
private readonly BTCPayServerEnvironment _btcPayServerEnvironment;
|
||||
private readonly APIKeyRepository _apiKeyRepository;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
@ -44,6 +48,7 @@ namespace BTCPayServer.Controllers
|
||||
BTCPayWalletProvider walletProvider,
|
||||
StoreRepository storeRepository,
|
||||
IWebHostEnvironment env,
|
||||
U2FService u2FService,
|
||||
BTCPayServerEnvironment btcPayServerEnvironment,
|
||||
APIKeyRepository apiKeyRepository,
|
||||
IAuthorizationService authorizationService,
|
||||
@ -55,6 +60,8 @@ namespace BTCPayServer.Controllers
|
||||
_EmailSenderFactory = emailSenderFactory;
|
||||
_logger = logger;
|
||||
_urlEncoder = urlEncoder;
|
||||
_Env = env;
|
||||
_u2FService = u2FService;
|
||||
_btcPayServerEnvironment = btcPayServerEnvironment;
|
||||
_apiKeyRepository = apiKeyRepository;
|
||||
_authorizationService = authorizationService;
|
||||
|
@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Models;
|
||||
@ -26,21 +26,18 @@ namespace BTCPayServer.Controllers
|
||||
private readonly CurrencyNameTable _currencyNameTable;
|
||||
private readonly PullPaymentHostedService _pullPaymentHostedService;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _serializerSettings;
|
||||
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
||||
|
||||
public PullPaymentController(ApplicationDbContextFactory dbContextFactory,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
CurrencyNameTable currencyNameTable,
|
||||
PullPaymentHostedService pullPaymentHostedService,
|
||||
BTCPayNetworkJsonSerializerSettings serializerSettings,
|
||||
IEnumerable<IPayoutHandler> payoutHandlers)
|
||||
BTCPayServer.Services.BTCPayNetworkJsonSerializerSettings serializerSettings)
|
||||
{
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_networkProvider = networkProvider;
|
||||
_currencyNameTable = currencyNameTable;
|
||||
_pullPaymentHostedService = pullPaymentHostedService;
|
||||
_serializerSettings = serializerSettings;
|
||||
_payoutHandlers = payoutHandlers;
|
||||
}
|
||||
[Route("pull-payments/{pullPaymentId}")]
|
||||
public async Task<IActionResult> ViewPullPayment(string pullPaymentId)
|
||||
@ -58,7 +55,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
Entity = o,
|
||||
Blob = o.GetBlob(_serializerSettings),
|
||||
ProofBlob = _payoutHandlers.FirstOrDefault(handler => handler.CanHandle(o.GetPaymentMethodId()))?.ParseProof(o)
|
||||
TransactionId = o.GetProofBlob(_serializerSettings)?.TransactionId?.ToString()
|
||||
});
|
||||
var cd = _currencyNameTable.GetCurrencyData(blob.Currency, false);
|
||||
var totalPaid = payouts.Where(p => p.Entity.State != PayoutState.Cancelled).Select(p => p.Blob.Amount).Sum();
|
||||
@ -82,10 +79,10 @@ namespace BTCPayServer.Controllers
|
||||
Amount = entity.Blob.Amount,
|
||||
AmountFormatted = _currencyNameTable.FormatCurrency(entity.Blob.Amount, blob.Currency),
|
||||
Currency = blob.Currency,
|
||||
Status = entity.Entity.State,
|
||||
Destination = entity.Blob.Destination,
|
||||
Link = entity.ProofBlob?.Link,
|
||||
TransactionId = entity.ProofBlob?.Id
|
||||
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
|
||||
}).ToList()
|
||||
};
|
||||
vm.IsPending &= vm.AmountDue > 0.0m;
|
||||
@ -102,14 +99,11 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
ModelState.AddModelError(nameof(pullPaymentId), "This pull payment does not exists");
|
||||
}
|
||||
|
||||
var ppBlob = pp.GetBlob();
|
||||
var network = _networkProvider.GetNetwork<BTCPayNetwork>(ppBlob.SupportedPaymentMethods.Single().CryptoCode);
|
||||
|
||||
var paymentMethodId = ppBlob.SupportedPaymentMethods.Single();
|
||||
var payoutHandler = _payoutHandlers.FirstOrDefault(handler => handler.CanHandle(paymentMethodId));
|
||||
IClaimDestination destination = await payoutHandler?.ParseClaimDestination(paymentMethodId, vm.Destination);
|
||||
if (destination is null)
|
||||
IClaimDestination destination = null;
|
||||
if (network != null &&
|
||||
(!ClaimDestination.TryParse(vm.Destination, network, out destination) || destination is null))
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.Destination), $"Invalid destination");
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
@ -20,8 +19,6 @@ using BTCPayServer.Storage.ViewModels;
|
||||
using BTCPayServer.Views;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
@ -33,14 +30,13 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var fileUrl = string.IsNullOrEmpty(fileId) ? null : await _FileService.GetFileUrl(Request.GetAbsoluteRootUri(), fileId);
|
||||
|
||||
var model = new ViewFilesViewModel()
|
||||
return View(new ViewFilesViewModel()
|
||||
{
|
||||
Files = await _StoredFileRepository.GetFiles(),
|
||||
SelectedFileId = string.IsNullOrEmpty(fileUrl) ? null : fileId,
|
||||
DirectFileUrl = fileUrl,
|
||||
StorageConfigured = (await _SettingsRepository.GetSettingAsync<StorageSettings>()) != null
|
||||
};
|
||||
return View(model);
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("server/files/{fileId}/delete")]
|
||||
@ -178,13 +174,8 @@ namespace BTCPayServer.Controllers
|
||||
var savedSettings = await _SettingsRepository.GetSettingAsync<StorageSettings>();
|
||||
if (forceChoice || savedSettings == null)
|
||||
{
|
||||
var providersList = _StorageProviderServices.Select(a =>
|
||||
new SelectListItem(a.StorageProvider().ToString(), a.StorageProvider().ToString())
|
||||
);
|
||||
|
||||
return View(new ChooseStorageViewModel()
|
||||
{
|
||||
ProvidersList = providersList,
|
||||
ShowChangeWarning = savedSettings != null,
|
||||
Provider = savedSettings?.Provider ?? BTCPayServer.Storage.Models.StorageProvider.FileSystem
|
||||
});
|
||||
|
@ -29,10 +29,7 @@ namespace BTCPayServer.Controllers
|
||||
var usersQuery = _UserManager.Users;
|
||||
if (!string.IsNullOrWhiteSpace(model.SearchTerm))
|
||||
{
|
||||
#pragma warning disable CA1307 // Specify StringComparison
|
||||
// Entity Framework don't support StringComparison
|
||||
usersQuery = usersQuery.Where(u => u.Email.Contains(model.SearchTerm));
|
||||
#pragma warning restore CA1307 // Specify StringComparison
|
||||
}
|
||||
|
||||
if (sortOrder != null)
|
||||
|
@ -31,7 +31,6 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
@ -76,7 +75,7 @@ namespace BTCPayServer.Controllers
|
||||
CheckConfigurationHostedService sshState,
|
||||
EventAggregator eventAggregator,
|
||||
CssThemeManager cssThemeManager,
|
||||
IOptions<ExternalServicesOptions> externalServiceOptions)
|
||||
IOptions<ExternalServicesOptions> externalServiceOptions)
|
||||
{
|
||||
_Options = options;
|
||||
_StoredFileRepository = storedFileRepository;
|
||||
@ -268,7 +267,7 @@ namespace BTCPayServer.Controllers
|
||||
sshClient.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public IHttpClientFactory HttpClientFactory { get; }
|
||||
|
||||
[Route("server/policies")]
|
||||
@ -282,9 +281,9 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[Route("server/policies")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Policies([FromServices] BTCPayNetworkProvider btcPayNetworkProvider, 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")
|
||||
@ -302,7 +301,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
settings.BlockExplorerLinks = settings.BlockExplorerLinks.Where(tuple => btcPayNetworkProvider.GetNetwork(tuple.CryptoCode).BlockExplorerLinkDefault != tuple.Link).ToList();
|
||||
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(settings);
|
||||
@ -354,7 +353,7 @@ namespace BTCPayServer.Controllers
|
||||
Link = this.Request.GetAbsoluteUriNoPathBase(externalService.Value).AbsoluteUri
|
||||
});
|
||||
}
|
||||
if (await CanShowSSHService())
|
||||
if (CanShowSSHService())
|
||||
{
|
||||
result.OtherExternalServices.Add(new ServicesViewModel.OtherExternalService()
|
||||
{
|
||||
@ -851,7 +850,7 @@ namespace BTCPayServer.Controllers
|
||||
[Route("server/services/ssh")]
|
||||
public async Task<IActionResult> SSHService()
|
||||
{
|
||||
if (!await CanShowSSHService())
|
||||
if (!CanShowSSHService())
|
||||
return NotFound();
|
||||
|
||||
var settings = _Options.SSHSettings;
|
||||
@ -889,11 +888,9 @@ namespace BTCPayServer.Controllers
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
async Task<bool> CanShowSSHService()
|
||||
bool CanShowSSHService()
|
||||
{
|
||||
var policies = await _SettingsRepository.GetSettingAsync<PoliciesSettings>();
|
||||
return !(policies?.DisableSSHService is true) &&
|
||||
_Options.SSHSettings != null && (_sshState.CanUseSSH || CanAccessAuthorizedKeyFile());
|
||||
return _Options.SSHSettings != null && (_sshState.CanUseSSH || CanAccessAuthorizedKeyFile());
|
||||
}
|
||||
|
||||
private bool CanAccessAuthorizedKeyFile()
|
||||
@ -903,88 +900,55 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[Route("server/services/ssh")]
|
||||
public async Task<IActionResult> SSHService(SSHServiceViewModel viewModel, string command = null)
|
||||
public async Task<IActionResult> SSHService(SSHServiceViewModel viewModel)
|
||||
{
|
||||
if (!await CanShowSSHService())
|
||||
return NotFound();
|
||||
string newContent = viewModel?.SSHKeyFileContent ?? string.Empty;
|
||||
newContent = newContent.Replace("\r\n", "\n", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (command is "Save")
|
||||
bool updated = false;
|
||||
Exception exception = null;
|
||||
// Let's try to just write the file
|
||||
if (CanAccessAuthorizedKeyFile())
|
||||
{
|
||||
string newContent = viewModel?.SSHKeyFileContent ?? string.Empty;
|
||||
newContent = newContent.Replace("\r\n", "\n", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
bool updated = false;
|
||||
Exception exception = null;
|
||||
// Let's try to just write the file
|
||||
if (CanAccessAuthorizedKeyFile())
|
||||
{
|
||||
try
|
||||
{
|
||||
await System.IO.File.WriteAllTextAsync(_Options.SSHSettings.AuthorizedKeysFile, newContent);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "authorized_keys has been updated";
|
||||
updated = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
}
|
||||
|
||||
// If that fail, fallback to ssh
|
||||
if (!updated && _sshState.CanUseSSH)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var sshClient = await _Options.SSHSettings.ConnectAsync())
|
||||
{
|
||||
await sshClient.RunBash($"mkdir -p ~/.ssh && echo '{newContent.EscapeSingleQuotes()}' > ~/.ssh/authorized_keys", TimeSpan.FromSeconds(10));
|
||||
}
|
||||
updated = true;
|
||||
exception = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
}
|
||||
|
||||
if (exception is null)
|
||||
try
|
||||
{
|
||||
await System.IO.File.WriteAllTextAsync(_Options.SSHSettings.AuthorizedKeysFile, newContent);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "authorized_keys has been updated";
|
||||
updated = true;
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = exception.Message;
|
||||
exception = ex;
|
||||
}
|
||||
return RedirectToAction(nameof(SSHService));
|
||||
}
|
||||
else if (command is "disable")
|
||||
{
|
||||
return RedirectToAction(nameof(SSHServiceDisable));
|
||||
}
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
[Route("server/services/ssh/disable")]
|
||||
public IActionResult SSHServiceDisable()
|
||||
{
|
||||
return View("Confirm", new ConfirmModel()
|
||||
// If that fail, fallback to ssh
|
||||
if (!updated && _sshState.CanUseSSH)
|
||||
{
|
||||
Action = "Disable",
|
||||
Title = "Disable modification of SSH settings",
|
||||
Description = "This action is permanent and will remove the ability to change the SSH settings via the BTCPay Server user interface.",
|
||||
ButtonClass = "btn-danger"
|
||||
});
|
||||
}
|
||||
[Route("server/services/ssh/disable")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> SSHServiceDisablePost()
|
||||
{
|
||||
var policies = await _SettingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
|
||||
policies.DisableSSHService = true;
|
||||
await _SettingsRepository.UpdateSetting(policies);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Changes to the SSH settings are now permanently disabled in the BTCPay Server user interface";
|
||||
return RedirectToAction(nameof(Services));
|
||||
try
|
||||
{
|
||||
using (var sshClient = await _Options.SSHSettings.ConnectAsync())
|
||||
{
|
||||
await sshClient.RunBash($"mkdir -p ~/.ssh && echo '{newContent.EscapeSingleQuotes()}' > ~/.ssh/authorized_keys", TimeSpan.FromSeconds(10));
|
||||
}
|
||||
updated = true;
|
||||
exception = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
}
|
||||
|
||||
if (exception is null)
|
||||
{
|
||||
TempData[WellKnownTempData.SuccessMessage] = "authorized_keys has been updated";
|
||||
}
|
||||
else
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = exception.Message;
|
||||
}
|
||||
return RedirectToAction(nameof(SSHService));
|
||||
}
|
||||
|
||||
[Route("server/theme")]
|
||||
@ -1014,7 +978,7 @@ namespace BTCPayServer.Controllers
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Emails(EmailsViewModel model, string command)
|
||||
{
|
||||
|
||||
|
||||
if (command == "Test")
|
||||
{
|
||||
try
|
||||
|
@ -1,27 +1,15 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using BTCPayServer.Payments.CoinSwitch;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Plugins.CoinSwitch
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Route("plugins/{storeId}/coinswitch")]
|
||||
public class CoinSwitchController : Controller
|
||||
public partial class StoresController
|
||||
{
|
||||
private readonly StoreRepository _storeRepository;
|
||||
|
||||
public CoinSwitchController(StoreRepository storeRepository)
|
||||
{
|
||||
_storeRepository = storeRepository;
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
[HttpGet]
|
||||
[Route("{storeId}/coinswitch")]
|
||||
public IActionResult UpdateCoinSwitchSettings(string storeId)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
@ -34,7 +22,8 @@ namespace BTCPayServer.Plugins.CoinSwitch
|
||||
|
||||
private void SetExistingValues(StoreData store, UpdateCoinSwitchSettingsViewModel vm)
|
||||
{
|
||||
var existing = store.GetStoreBlob().GetCoinSwitchSettings();
|
||||
|
||||
var existing = store.GetStoreBlob().CoinSwitchSettings;
|
||||
if (existing == null)
|
||||
return;
|
||||
vm.MerchantId = existing.MerchantId;
|
||||
@ -43,7 +32,8 @@ namespace BTCPayServer.Plugins.CoinSwitch
|
||||
vm.AmountMarkupPercentage = existing.AmountMarkupPercentage;
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
[HttpPost]
|
||||
[Route("{storeId}/coinswitch")]
|
||||
public async Task<IActionResult> UpdateCoinSwitchSettings(string storeId, UpdateCoinSwitchSettingsViewModel vm,
|
||||
string command)
|
||||
{
|
||||
@ -70,11 +60,14 @@ namespace BTCPayServer.Plugins.CoinSwitch
|
||||
{
|
||||
case "save":
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
storeBlob.SetCoinSwitchSettings(coinSwitchSettings);
|
||||
storeBlob.CoinSwitchSettings = coinSwitchSettings;
|
||||
store.SetStoreBlob(storeBlob);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
await _Repo.UpdateStore(store);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "CoinSwitch settings modified";
|
||||
return RedirectToAction(nameof(UpdateCoinSwitchSettings), new {storeId});
|
||||
return RedirectToAction(nameof(UpdateStore), new
|
||||
{
|
||||
storeId
|
||||
});
|
||||
|
||||
default:
|
||||
return View(vm);
|
@ -15,7 +15,7 @@ namespace BTCPayServer.Controllers
|
||||
public partial class StoresController
|
||||
{
|
||||
[HttpGet("{storeId}/lightning/{cryptoCode}")]
|
||||
public IActionResult SetupLightningNode(string storeId, string cryptoCode)
|
||||
public IActionResult AddLightningNode(string storeId, string cryptoCode)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
@ -31,7 +31,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[HttpPost("{storeId}/lightning/{cryptoCode}")]
|
||||
public async Task<IActionResult> SetupLightningNode(string storeId, LightningNodeViewModel vm, string command, string cryptoCode)
|
||||
public async Task<IActionResult> AddLightningNode(string storeId, LightningNodeViewModel vm, string command, string cryptoCode)
|
||||
{
|
||||
vm.CryptoCode = cryptoCode;
|
||||
var store = HttpContext.GetStoreData();
|
||||
@ -48,8 +48,6 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
var paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.LightningLike);
|
||||
var lightning = GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store);
|
||||
|
||||
LightningSupportedPaymentMethod paymentMethod = null;
|
||||
if (vm.LightningNodeType == LightningNodeType.Internal)
|
||||
{
|
||||
@ -98,11 +96,12 @@ 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);
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"{network.CryptoCode} Lightning node updated.";
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"{network.CryptoCode} Lightning node modified.";
|
||||
return RedirectToAction(nameof(UpdateStore), new { storeId });
|
||||
|
||||
case "test":
|
||||
@ -115,7 +114,7 @@ namespace BTCPayServer.Controllers
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
|
||||
await handler.TestConnection(info, cts.Token);
|
||||
}
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Connection to the Lightning node successful. Your node address: {info}";
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Connection to the Lightning node succeeded. Your node address: {info}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -129,31 +128,6 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("{storeId}/lightning/{cryptoCode}/status")]
|
||||
public async Task<IActionResult> SetLightningNodeEnabled(string storeId, string cryptoCode, bool enabled)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
|
||||
var network = cryptoCode == null ? null : _ExplorerProvider.GetNetwork(cryptoCode);
|
||||
if (network == null)
|
||||
return NotFound();
|
||||
|
||||
var lightning = GetExistingLightningSupportedPaymentMethod(cryptoCode, store);
|
||||
if (lightning == null)
|
||||
return NotFound();
|
||||
|
||||
var paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.LightningLike);
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
storeBlob.SetExcluded(paymentMethodId, !enabled);
|
||||
store.SetStoreBlob(storeBlob);
|
||||
await _Repo.UpdateStore(store);
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"{network.CryptoCode} Lightning payments are now {(enabled ? "enabled" : "disabled")} for this store.";
|
||||
|
||||
return RedirectToAction(nameof(UpdateStore), new { storeId });
|
||||
}
|
||||
|
||||
private bool CanUseInternalLightning()
|
||||
{
|
||||
return User.IsInRole(Roles.ServerAdmin) || _CssThemeManager.AllowLightningInternalNodeForAll;
|
||||
@ -161,17 +135,14 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
private void SetExistingValues(StoreData store, LightningNodeViewModel vm)
|
||||
{
|
||||
vm.CanUseInternalNode = CanUseInternalLightning();
|
||||
var lightning = GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store);
|
||||
if (lightning != null)
|
||||
{
|
||||
vm.LightningNodeType = lightning.IsInternalNode ? LightningNodeType.Internal : LightningNodeType.Custom;
|
||||
vm.ConnectionString = lightning.GetDisplayableConnectionString();
|
||||
}
|
||||
else
|
||||
{
|
||||
vm.LightningNodeType = vm.CanUseInternalNode ? LightningNodeType.Internal : LightningNodeType.Custom;
|
||||
}
|
||||
vm.Enabled = !store.GetStoreBlob().IsExcluded(new PaymentMethodId(vm.CryptoCode, PaymentTypes.LightningLike)) && lightning != null;
|
||||
vm.CanUseInternalNode = CanUseInternalLightning();
|
||||
}
|
||||
|
||||
private LightningSupportedPaymentMethod GetExistingLightningSupportedPaymentMethod(string cryptoCode, StoreData store)
|
||||
|
@ -157,7 +157,9 @@ namespace BTCPayServer.Controllers
|
||||
var configChanged = oldConfig != vm.Config;
|
||||
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var wasExcluded = storeBlob.GetExcludedPaymentMethods().Match(paymentMethodId);
|
||||
var willBeExcluded = !vm.Enabled;
|
||||
var excludedChanged = willBeExcluded != wasExcluded;
|
||||
|
||||
var showAddress = // Show addresses if:
|
||||
// - If the user is testing the hint address in confirmation screen
|
||||
@ -186,7 +188,17 @@ namespace BTCPayServer.Controllers
|
||||
await _Repo.UpdateStore(store);
|
||||
_EventAggregator.Publish(new WalletChangedEvent {WalletId = new WalletId(vm.StoreId, vm.CryptoCode)});
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Derivation settings for {network.CryptoCode} have been updated.";
|
||||
if (excludedChanged)
|
||||
{
|
||||
var label = willBeExcluded ? "disabled" : "enabled";
|
||||
TempData[WellKnownTempData.SuccessMessage] =
|
||||
$"On-Chain payments for {network.CryptoCode} have been {label}.";
|
||||
}
|
||||
else
|
||||
{
|
||||
TempData[WellKnownTempData.SuccessMessage] =
|
||||
$"Derivation settings for {network.CryptoCode} have been modified.";
|
||||
}
|
||||
|
||||
// This is success case when derivation scheme is added to the store
|
||||
return RedirectToAction(nameof(UpdateStore), new {storeId = vm.StoreId});
|
||||
@ -377,7 +389,8 @@ namespace BTCPayServer.Controllers
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Derivation settings for {network.CryptoCode} have been updated.";
|
||||
TempData[WellKnownTempData.SuccessMessage] =
|
||||
$"Derivation settings for {network.CryptoCode} have been modified.";
|
||||
|
||||
return RedirectToAction(nameof(UpdateStore), new {storeId});
|
||||
}
|
||||
@ -493,40 +506,6 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("{storeId}/onchain/{cryptoCode}/status")]
|
||||
public async Task<IActionResult> SetWalletEnabled(string storeId, string cryptoCode, bool enabled)
|
||||
{
|
||||
var checkResult = IsAvailable(cryptoCode, out var store, out var network);
|
||||
if (checkResult != null)
|
||||
{
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
var derivation = GetExistingDerivationStrategy(cryptoCode, store);
|
||||
if (derivation == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var wallet = _WalletProvider.GetWallet(network);
|
||||
if (wallet == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
storeBlob.SetExcluded(paymentMethodId, !enabled);
|
||||
store.SetStoreBlob(storeBlob);
|
||||
await _Repo.UpdateStore(store);
|
||||
_EventAggregator.Publish(new WalletChangedEvent {WalletId = new WalletId(storeId, cryptoCode)});
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] =
|
||||
$"{network.CryptoCode} on-chain payments are now {(enabled ? "enabled" : "disabled")} for this store.";
|
||||
|
||||
return RedirectToAction(nameof(UpdateStore), new {storeId});
|
||||
}
|
||||
|
||||
[HttpPost("{storeId}/onchain/{cryptoCode}/delete")]
|
||||
public async Task<IActionResult> ConfirmDeleteWallet(string storeId, string cryptoCode)
|
||||
{
|
||||
|
@ -544,16 +544,24 @@ namespace BTCPayServer.Controllers
|
||||
break;
|
||||
case LightningPaymentType _:
|
||||
var lightning = lightningByCryptoCode.TryGet(paymentMethodId.CryptoCode);
|
||||
var isEnabled = !excludeFilters.Match(paymentMethodId) && lightning != null;
|
||||
vm.LightningNodes.Add(new StoreViewModel.LightningNode
|
||||
vm.LightningNodes.Add(new StoreViewModel.LightningNode()
|
||||
{
|
||||
CryptoCode = paymentMethodId.CryptoCode,
|
||||
Address = lightning?.GetDisplayableConnectionString(),
|
||||
Enabled = isEnabled
|
||||
Enabled = !excludeFilters.Match(paymentMethodId) && lightning != null
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var coinSwitchEnabled = storeBlob.CoinSwitchSettings != null && storeBlob.CoinSwitchSettings.Enabled;
|
||||
vm.ThirdPartyPaymentMethods.Add(new StoreViewModel.AdditionalPaymentMethod()
|
||||
{
|
||||
Enabled = coinSwitchEnabled,
|
||||
Action = nameof(UpdateCoinSwitchSettings),
|
||||
Provider = "CoinSwitch"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -849,12 +857,6 @@ namespace BTCPayServer.Controllers
|
||||
if (string.IsNullOrWhiteSpace(userId))
|
||||
return Challenge(AuthenticationSchemes.Cookie);
|
||||
var storeId = CurrentStore?.Id;
|
||||
if (storeId != null)
|
||||
{
|
||||
var store = await _Repo.FindStore(storeId, userId);
|
||||
if (store != null)
|
||||
HttpContext.SetStoreData(store);
|
||||
}
|
||||
var model = new CreateTokenViewModel();
|
||||
ViewBag.HidePublicKey = true;
|
||||
ViewBag.ShowStores = true;
|
||||
@ -911,14 +913,6 @@ namespace BTCPayServer.Controllers
|
||||
return Challenge(AuthenticationSchemes.Cookie);
|
||||
if (pairingCode == null)
|
||||
return NotFound();
|
||||
if (selectedStore != null)
|
||||
{
|
||||
var store = await _Repo.FindStore(selectedStore, userId);
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
HttpContext.SetStoreData(store);
|
||||
ViewBag.ShowStores = false;
|
||||
}
|
||||
var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
|
||||
if (pairing == null)
|
||||
{
|
||||
@ -928,7 +922,7 @@ namespace BTCPayServer.Controllers
|
||||
else
|
||||
{
|
||||
var stores = await _Repo.GetStoresByUserId(userId);
|
||||
return View(new PairingModel
|
||||
return View(new PairingModel()
|
||||
{
|
||||
Id = pairing.Id,
|
||||
Label = pairing.Label,
|
||||
@ -987,6 +981,8 @@ namespace BTCPayServer.Controllers
|
||||
return _UserManager.GetUserId(User);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// TODO: Need to have talk about how architect default currency implementation
|
||||
// For now we have also hardcoded USD for Store creation and then Invoice creation
|
||||
const string DEFAULT_CURRENCY = "USD";
|
||||
@ -1015,7 +1011,7 @@ namespace BTCPayServer.Controllers
|
||||
ButtonType = 0,
|
||||
Min = 1,
|
||||
Max = 20,
|
||||
Step = "1",
|
||||
Step = 1,
|
||||
Apps = apps
|
||||
};
|
||||
return View(model);
|
||||
|
@ -6,7 +6,6 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.ModelBinders;
|
||||
@ -18,7 +17,6 @@ using BTCPayServer.Views;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using PayoutData = BTCPayServer.Data.PayoutData;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -191,9 +189,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
var storeId = walletId.StoreId;
|
||||
var paymentMethodId = new PaymentMethodId(walletId.CryptoCode, PaymentTypes.BTCLike);
|
||||
|
||||
var commandState = Enum.Parse<PayoutState>(vm.Command.Split("-").First());
|
||||
var payoutIds = vm.GetSelectedPayouts(commandState);
|
||||
var payoutIds = vm.WaitingForApproval.Where(p => p.Selected).Select(p => p.PayoutId).ToArray();
|
||||
if (payoutIds.Length == 0)
|
||||
{
|
||||
this.TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
@ -207,121 +203,93 @@ namespace BTCPayServer.Controllers
|
||||
pullPaymentId = vm.PullPaymentId
|
||||
});
|
||||
}
|
||||
|
||||
var command = vm.Command.Substring(vm.Command.IndexOf('-', StringComparison.InvariantCulture) + 1);
|
||||
|
||||
switch (command)
|
||||
if (vm.Command == "pay")
|
||||
{
|
||||
|
||||
case "approve-pay":
|
||||
case "approve":
|
||||
{
|
||||
await using var ctx = this._dbContextFactory.CreateContext();
|
||||
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
var payouts = await GetPayoutsForPaymentMethod(walletId.GetPaymentMethodId(), ctx, payoutIds, storeId, cancellationToken);
|
||||
|
||||
for (int i = 0; i < payouts.Count; i++)
|
||||
{
|
||||
var payout = payouts[i];
|
||||
if (payout.State != PayoutState.AwaitingApproval)
|
||||
continue;
|
||||
var rateResult = await _pullPaymentService.GetRate(payout, null, cancellationToken);
|
||||
if (rateResult.BidAsk == null)
|
||||
{
|
||||
this.TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = $"Rate unavailable: {rateResult.EvaluatedRule}",
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
});
|
||||
return RedirectToAction(nameof(Payouts), new
|
||||
{
|
||||
walletId = walletId.ToString(),
|
||||
pullPaymentId = vm.PullPaymentId
|
||||
});
|
||||
}
|
||||
var approveResult = await _pullPaymentService.Approve(new HostedServices.PullPaymentHostedService.PayoutApproval()
|
||||
{
|
||||
PayoutId = payout.Id,
|
||||
Revision = payout.GetBlob(_jsonSerializerSettings).Revision,
|
||||
Rate = rateResult.BidAsk.Ask
|
||||
});
|
||||
if (approveResult != HostedServices.PullPaymentHostedService.PayoutApproval.Result.Ok)
|
||||
{
|
||||
this.TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = PullPaymentHostedService.PayoutApproval.GetErrorMessage(approveResult),
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
});
|
||||
return RedirectToAction(nameof(Payouts), new
|
||||
{
|
||||
walletId = walletId.ToString(),
|
||||
pullPaymentId = vm.PullPaymentId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (command == "approve-pay")
|
||||
{
|
||||
goto case "pay";
|
||||
}
|
||||
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Payouts approved", Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
return RedirectToAction(nameof(Payouts),
|
||||
new {walletId = walletId.ToString(), pullPaymentId = vm.PullPaymentId});
|
||||
}
|
||||
|
||||
case "pay":
|
||||
{
|
||||
await using var ctx = this._dbContextFactory.CreateContext();
|
||||
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
var payouts = await GetPayoutsForPaymentMethod(walletId.GetPaymentMethodId(), ctx, payoutIds, storeId, cancellationToken);
|
||||
|
||||
var walletSend = (WalletSendModel)((ViewResult)(await this.WalletSend(walletId))).Model;
|
||||
walletSend.Outputs.Clear();
|
||||
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
|
||||
List<string> bip21 = new List<string>();
|
||||
foreach (var payout in payouts)
|
||||
{
|
||||
var blob = payout.GetBlob(_jsonSerializerSettings);
|
||||
if (payout.GetPaymentMethodId() != paymentMethodId)
|
||||
continue;
|
||||
bip21.Add(network.GenerateBIP21(payout.Destination, new Money(blob.CryptoAmount.Value, MoneyUnit.BTC)));
|
||||
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(WalletSend), new {walletId, bip21});
|
||||
}
|
||||
|
||||
case "cancel":
|
||||
await _pullPaymentService.Cancel(
|
||||
new HostedServices.PullPaymentHostedService.CancelRequest(payoutIds));
|
||||
this.TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Payouts archived", Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
return RedirectToAction(nameof(Payouts),
|
||||
new {walletId = walletId.ToString(), pullPaymentId = vm.PullPaymentId});
|
||||
}
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
private static async Task<List<PayoutData>> GetPayoutsForPaymentMethod(PaymentMethodId paymentMethodId,
|
||||
ApplicationDbContext ctx, string[] payoutIds,
|
||||
string storeId, CancellationToken cancellationToken)
|
||||
{
|
||||
var payouts = (await ctx.Payouts
|
||||
using var ctx = this._dbContextFactory.CreateContext();
|
||||
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
var payouts = (await ctx.Payouts
|
||||
.Include(p => p.PullPaymentData)
|
||||
.Include(p => p.PullPaymentData.StoreData)
|
||||
.Where(p => payoutIds.Contains(p.Id))
|
||||
.Where(p => p.PullPaymentData.StoreId == storeId && !p.PullPaymentData.Archived)
|
||||
.ToListAsync(cancellationToken))
|
||||
.Where(p => p.GetPaymentMethodId() == paymentMethodId)
|
||||
.ToList();
|
||||
return payouts;
|
||||
.ToListAsync())
|
||||
.Where(p => p.GetPaymentMethodId() == walletId.GetPaymentMethodId())
|
||||
.ToList();
|
||||
|
||||
for (int i = 0; i < payouts.Count; i++)
|
||||
{
|
||||
var payout = payouts[i];
|
||||
if (payout.State != PayoutState.AwaitingApproval)
|
||||
continue;
|
||||
var rateResult = await _pullPaymentService.GetRate(payout, null, cancellationToken);
|
||||
if (rateResult.BidAsk == null)
|
||||
{
|
||||
this.TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = $"Rate unavailable: {rateResult.EvaluatedRule}",
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
});
|
||||
return RedirectToAction(nameof(Payouts), new
|
||||
{
|
||||
walletId = walletId.ToString(),
|
||||
pullPaymentId = vm.PullPaymentId
|
||||
});
|
||||
}
|
||||
var approveResult = await _pullPaymentService.Approve(new HostedServices.PullPaymentHostedService.PayoutApproval()
|
||||
{
|
||||
PayoutId = payout.Id,
|
||||
Revision = payout.GetBlob(_jsonSerializerSettings).Revision,
|
||||
Rate = rateResult.BidAsk.Ask
|
||||
});
|
||||
if (approveResult != HostedServices.PullPaymentHostedService.PayoutApproval.Result.Ok)
|
||||
{
|
||||
this.TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = PullPaymentHostedService.PayoutApproval.GetErrorMessage(approveResult),
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
});
|
||||
return RedirectToAction(nameof(Payouts), new
|
||||
{
|
||||
walletId = walletId.ToString(),
|
||||
pullPaymentId = vm.PullPaymentId
|
||||
});
|
||||
}
|
||||
payouts[i] = await ctx.Payouts.FindAsync(payouts[i].Id);
|
||||
}
|
||||
var walletSend = (WalletSendModel)((ViewResult)(await this.WalletSend(walletId))).Model;
|
||||
walletSend.Outputs.Clear();
|
||||
foreach (var payout in payouts)
|
||||
{
|
||||
var blob = payout.GetBlob(_jsonSerializerSettings);
|
||||
if (payout.GetPaymentMethodId() != paymentMethodId)
|
||||
continue;
|
||||
var output = new WalletSendModel.TransactionOutput()
|
||||
{
|
||||
Amount = blob.CryptoAmount,
|
||||
DestinationAddress = blob.Destination.Address.ToString()
|
||||
};
|
||||
walletSend.Outputs.Add(output);
|
||||
}
|
||||
return View(nameof(walletSend), walletSend);
|
||||
}
|
||||
else if (vm.Command == "cancel")
|
||||
{
|
||||
await _pullPaymentService.Cancel(new HostedServices.PullPaymentHostedService.CancelRequest(payoutIds));
|
||||
this.TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Payouts archived",
|
||||
Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
return RedirectToAction(nameof(Payouts), new
|
||||
{
|
||||
walletId = walletId.ToString(),
|
||||
pullPaymentId = vm.PullPaymentId
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@ -331,11 +299,9 @@ namespace BTCPayServer.Controllers
|
||||
WalletId walletId, PayoutsModel vm = null)
|
||||
{
|
||||
vm ??= new PayoutsModel();
|
||||
vm.PayoutStateSets ??= ((PayoutState[]) Enum.GetValues(typeof(PayoutState))).Select(state =>
|
||||
new PayoutsModel.PayoutStateSet() {State = state, Payouts = new List<PayoutsModel.PayoutModel>()}).ToList();
|
||||
using var ctx = this._dbContextFactory.CreateContext();
|
||||
var storeId = walletId.StoreId;
|
||||
vm.PaymentMethodId = new PaymentMethodId(walletId.CryptoCode, PaymentTypes.BTCLike);
|
||||
var paymentMethodId = new PaymentMethodId(walletId.CryptoCode, PaymentTypes.BTCLike);
|
||||
var payoutRequest = ctx.Payouts.Where(p => p.PullPaymentData.StoreId == storeId && !p.PullPaymentData.Archived);
|
||||
if (vm.PullPaymentId != null)
|
||||
{
|
||||
@ -347,42 +313,34 @@ namespace BTCPayServer.Controllers
|
||||
Payout = o,
|
||||
PullPayment = o.PullPaymentData
|
||||
}).ToListAsync();
|
||||
foreach (var stateSet in payouts.GroupBy(arg => arg.Payout.State))
|
||||
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
|
||||
vm.WaitingForApproval = new List<PayoutsModel.PayoutModel>();
|
||||
vm.Other = new List<PayoutsModel.PayoutModel>();
|
||||
foreach (var item in payouts)
|
||||
{
|
||||
var state = vm.PayoutStateSets.SingleOrDefault(set => set.State == stateSet.Key);
|
||||
if (state == null)
|
||||
if (item.Payout.GetPaymentMethodId() != paymentMethodId)
|
||||
continue;
|
||||
var ppBlob = item.PullPayment.GetBlob();
|
||||
var payoutBlob = item.Payout.GetBlob(_jsonSerializerSettings);
|
||||
var m = new PayoutsModel.PayoutModel();
|
||||
m.PullPaymentId = item.PullPayment.Id;
|
||||
m.PullPaymentName = ppBlob.Name ?? item.PullPayment.Id;
|
||||
m.Date = item.Payout.Date;
|
||||
m.PayoutId = item.Payout.Id;
|
||||
m.Amount = _currencyTable.DisplayFormatCurrency(payoutBlob.Amount, ppBlob.Currency);
|
||||
m.Destination = payoutBlob.Destination.Address.ToString();
|
||||
if (item.Payout.State == PayoutState.AwaitingPayment || item.Payout.State == PayoutState.AwaitingApproval)
|
||||
{
|
||||
state = new PayoutsModel.PayoutStateSet()
|
||||
{
|
||||
Payouts = new List<PayoutsModel.PayoutModel>(), State = stateSet.Key
|
||||
};
|
||||
vm.PayoutStateSets.Add(state);
|
||||
vm.WaitingForApproval.Add(m);
|
||||
}
|
||||
|
||||
foreach (var item in stateSet)
|
||||
else
|
||||
{
|
||||
|
||||
if (item.Payout.GetPaymentMethodId() != vm.PaymentMethodId)
|
||||
continue;
|
||||
var ppBlob = item.PullPayment.GetBlob();
|
||||
var payoutBlob = item.Payout.GetBlob(_jsonSerializerSettings);
|
||||
var m = new PayoutsModel.PayoutModel();
|
||||
m.PullPaymentId = item.PullPayment.Id;
|
||||
m.PullPaymentName = ppBlob.Name ?? item.PullPayment.Id;
|
||||
m.Date = item.Payout.Date;
|
||||
m.PayoutId = item.Payout.Id;
|
||||
m.Amount = _currencyTable.DisplayFormatCurrency(payoutBlob.Amount, ppBlob.Currency);
|
||||
m.Destination = payoutBlob.Destination;
|
||||
var handler = _payoutHandlers
|
||||
.FirstOrDefault(handler => handler.CanHandle(item.Payout.GetPaymentMethodId()));
|
||||
var proofBlob = handler?.ParseProof(item.Payout);
|
||||
m.TransactionLink = proofBlob?.Link;
|
||||
state.Payouts.Add(m);
|
||||
|
||||
if (item.Payout.GetPaymentMethodId().PaymentType == PaymentTypes.BTCLike &&
|
||||
item.Payout.GetProofBlob(this._jsonSerializerSettings)?.TransactionId is uint256 txId)
|
||||
m.TransactionLink = string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, txId);
|
||||
vm.Other.Add(m);
|
||||
}
|
||||
}
|
||||
|
||||
vm.PayoutStateSets = vm.PayoutStateSets.Where(set => set.Payouts?.Any() is true).ToList();
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.ModelBinders;
|
||||
@ -15,6 +16,7 @@ using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Labels;
|
||||
using BTCPayServer.Services.Rates;
|
||||
@ -60,7 +62,6 @@ namespace BTCPayServer.Controllers
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _jsonSerializerSettings;
|
||||
private readonly PullPaymentHostedService _pullPaymentService;
|
||||
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
||||
|
||||
public RateFetcher RateFetcher { get; }
|
||||
|
||||
@ -85,8 +86,7 @@ namespace BTCPayServer.Controllers
|
||||
LabelFactory labelFactory,
|
||||
ApplicationDbContextFactory dbContextFactory,
|
||||
BTCPayNetworkJsonSerializerSettings jsonSerializerSettings,
|
||||
HostedServices.PullPaymentHostedService pullPaymentService,
|
||||
IEnumerable<IPayoutHandler> payoutHandlers)
|
||||
HostedServices.PullPaymentHostedService pullPaymentService)
|
||||
{
|
||||
_currencyTable = currencyTable;
|
||||
Repository = repo;
|
||||
@ -109,7 +109,6 @@ namespace BTCPayServer.Controllers
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_jsonSerializerSettings = jsonSerializerSettings;
|
||||
_pullPaymentService = pullPaymentService;
|
||||
_payoutHandlers = payoutHandlers;
|
||||
}
|
||||
|
||||
// Borrowed from https://github.com/ManageIQ/guides/blob/master/labels.md
|
||||
@ -343,8 +342,6 @@ namespace BTCPayServer.Controllers
|
||||
model.Transactions = model.Transactions.OrderByDescending(t => t.Timestamp).Skip(skip).Take(count).ToList();
|
||||
}
|
||||
|
||||
model.CryptoCode = walletId.CryptoCode;
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
@ -427,7 +424,7 @@ namespace BTCPayServer.Controllers
|
||||
[Route("{walletId}/send")]
|
||||
public async Task<IActionResult> WalletSend(
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, string defaultDestination = null, string defaultAmount = null, string[] bip21 = null)
|
||||
WalletId walletId, string defaultDestination = null, string defaultAmount = null, string bip21 = null)
|
||||
{
|
||||
if (walletId?.StoreId == null)
|
||||
return NotFound();
|
||||
@ -445,29 +442,19 @@ namespace BTCPayServer.Controllers
|
||||
double.TryParse(defaultAmount, out var amount);
|
||||
var model = new WalletSendModel()
|
||||
{
|
||||
CryptoCode = walletId.CryptoCode
|
||||
};
|
||||
if (bip21?.Any() is true)
|
||||
{
|
||||
foreach (var link in bip21)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(link))
|
||||
{
|
||||
|
||||
LoadFromBIP21(model, link, network);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!(model.Outputs?.Any() is true))
|
||||
{
|
||||
model.Outputs = new List<WalletSendModel.TransactionOutput>()
|
||||
Outputs = new List<WalletSendModel.TransactionOutput>()
|
||||
{
|
||||
new WalletSendModel.TransactionOutput()
|
||||
{
|
||||
Amount = Convert.ToDecimal(amount), DestinationAddress = defaultDestination
|
||||
Amount = Convert.ToDecimal(amount),
|
||||
DestinationAddress = defaultDestination
|
||||
}
|
||||
};
|
||||
},
|
||||
CryptoCode = walletId.CryptoCode
|
||||
};
|
||||
if (!string.IsNullOrEmpty(bip21))
|
||||
{
|
||||
LoadFromBIP21(model, bip21, network);
|
||||
}
|
||||
var feeProvider = _feeRateProvider.CreateFeeProvider(network);
|
||||
var recommendedFees =
|
||||
@ -551,7 +538,6 @@ namespace BTCPayServer.Controllers
|
||||
vm.NBXSeedAvailable = await GetSeed(walletId, network) != null;
|
||||
if (!string.IsNullOrEmpty(bip21))
|
||||
{
|
||||
vm.Outputs?.Clear();
|
||||
LoadFromBIP21(vm, bip21, network);
|
||||
}
|
||||
|
||||
@ -576,8 +562,7 @@ namespace BTCPayServer.Controllers
|
||||
Amount = coin.Value.GetValue(network),
|
||||
Comment = info?.Comment,
|
||||
Labels = info == null ? null : _labelFactory.ColorizeTransactionLabels(walletBlobAsync, info, Request),
|
||||
Link = string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, coin.OutPoint.Hash.ToString()),
|
||||
Confirmations = coin.Confirmations
|
||||
Link = string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, coin.OutPoint.Hash.ToString())
|
||||
};
|
||||
}).ToArray();
|
||||
}
|
||||
@ -590,10 +575,6 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
if (!string.IsNullOrEmpty(bip21))
|
||||
{
|
||||
if (!vm.Outputs.Any())
|
||||
{
|
||||
vm.Outputs.Add(new WalletSendModel.TransactionOutput());
|
||||
}
|
||||
return View(vm);
|
||||
}
|
||||
if (command == "add-output")
|
||||
@ -737,7 +718,6 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
private void LoadFromBIP21(WalletSendModel vm, string bip21, BTCPayNetwork network)
|
||||
{
|
||||
vm.Outputs ??= new List<WalletSendModel.TransactionOutput>();
|
||||
try
|
||||
{
|
||||
if (bip21.StartsWith(network.UriScheme, StringComparison.InvariantCultureIgnoreCase))
|
||||
@ -746,13 +726,15 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
var uriBuilder = new NBitcoin.Payment.BitcoinUrlBuilder(bip21, network.NBitcoinNetwork);
|
||||
|
||||
vm.Outputs.Add(new WalletSendModel.TransactionOutput()
|
||||
vm.Outputs = new List<WalletSendModel.TransactionOutput>()
|
||||
{
|
||||
Amount = uriBuilder.Amount?.ToDecimal(MoneyUnit.BTC),
|
||||
DestinationAddress = uriBuilder.Address.ToString(),
|
||||
SubtractFeesFromOutput = false
|
||||
});
|
||||
new WalletSendModel.TransactionOutput()
|
||||
{
|
||||
Amount = uriBuilder.Amount?.ToDecimal(MoneyUnit.BTC),
|
||||
DestinationAddress = uriBuilder.Address.ToString(),
|
||||
SubtractFeesFromOutput = false
|
||||
}
|
||||
};
|
||||
if (!string.IsNullOrEmpty(uriBuilder.Label) || !string.IsNullOrEmpty(uriBuilder.Message))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
@ -770,11 +752,13 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
try
|
||||
{
|
||||
vm.Outputs.Add(new WalletSendModel.TransactionOutput()
|
||||
vm.Outputs = new List<WalletSendModel.TransactionOutput>()
|
||||
{
|
||||
new WalletSendModel.TransactionOutput()
|
||||
{
|
||||
DestinationAddress = BitcoinAddress.Create(bip21, network.NBitcoinNetwork).ToString()
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@ -1,22 +0,0 @@
|
||||
using System;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class AddressClaimDestination : IBitcoinLikeClaimDestination
|
||||
{
|
||||
public BitcoinAddress _bitcoinAddress;
|
||||
|
||||
public AddressClaimDestination(BitcoinAddress bitcoinAddress)
|
||||
{
|
||||
if (bitcoinAddress == null)
|
||||
throw new ArgumentNullException(nameof(bitcoinAddress));
|
||||
_bitcoinAddress = bitcoinAddress;
|
||||
}
|
||||
public BitcoinAddress Address => _bitcoinAddress;
|
||||
public override string ToString()
|
||||
{
|
||||
return _bitcoinAddress.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,278 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Payment;
|
||||
using NBitcoin.RPC;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
using NewBlockEvent = BTCPayServer.Events.NewBlockEvent;
|
||||
using PayoutData = BTCPayServer.Data.PayoutData;
|
||||
|
||||
public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
{
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly ExplorerClientProvider _explorerClientProvider;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _jsonSerializerSettings;
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
|
||||
public BitcoinLikePayoutHandler(BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
ExplorerClientProvider explorerClientProvider, BTCPayNetworkJsonSerializerSettings jsonSerializerSettings,
|
||||
ApplicationDbContextFactory dbContextFactory, EventAggregator eventAggregator)
|
||||
{
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_explorerClientProvider = explorerClientProvider;
|
||||
_jsonSerializerSettings = jsonSerializerSettings;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_eventAggregator = eventAggregator;
|
||||
}
|
||||
|
||||
public bool CanHandle(PaymentMethodId paymentMethod)
|
||||
{
|
||||
return paymentMethod.PaymentType == BitcoinPaymentType.Instance &&
|
||||
_btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethod.CryptoCode)?.ReadonlyWallet is false;
|
||||
}
|
||||
|
||||
public Task<IClaimDestination> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination)
|
||||
{
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
destination = destination.Trim();
|
||||
try
|
||||
{
|
||||
if (destination.StartsWith($"{network.UriScheme}:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Task.FromResult<IClaimDestination>(new UriClaimDestination(new BitcoinUrlBuilder(destination, network.NBitcoinNetwork)));
|
||||
}
|
||||
|
||||
return Task.FromResult<IClaimDestination>(new AddressClaimDestination(BitcoinAddress.Create(destination, network.NBitcoinNetwork)));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Task.FromResult<IClaimDestination>(null);
|
||||
}
|
||||
}
|
||||
|
||||
public IPayoutProof ParseProof(PayoutData payout)
|
||||
{
|
||||
if (payout?.Proof is null)
|
||||
return null;
|
||||
var paymentMethodId = payout.GetPaymentMethodId();
|
||||
var res = JsonConvert.DeserializeObject<PayoutTransactionOnChainBlob>(Encoding.UTF8.GetString(payout.Proof), _jsonSerializerSettings.GetSerializer(paymentMethodId.CryptoCode));
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
res.LinkTemplate = network.BlockExplorerLink;
|
||||
return res;
|
||||
}
|
||||
|
||||
public void StartBackgroundCheck(Action<Type[]> subscribe)
|
||||
{
|
||||
subscribe(new[] {typeof(NewOnChainTransactionEvent), typeof(NewBlockEvent)});
|
||||
}
|
||||
|
||||
public async Task BackgroundCheck(object o)
|
||||
{
|
||||
if (o is NewOnChainTransactionEvent newTransaction)
|
||||
{
|
||||
await UpdatePayoutsAwaitingForPayment(newTransaction);
|
||||
}
|
||||
|
||||
if (o is NewBlockEvent || o is NewOnChainTransactionEvent)
|
||||
{
|
||||
await UpdatePayoutsInProgress();
|
||||
}
|
||||
}
|
||||
|
||||
public Task<decimal> GetMinimumPayoutAmount(PaymentMethodId paymentMethodId, IClaimDestination claimDestination)
|
||||
{
|
||||
if (_btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode)?
|
||||
.NBitcoinNetwork?
|
||||
.Consensus?
|
||||
.ConsensusFactory?
|
||||
.CreateTxOut() is TxOut txout &&
|
||||
claimDestination is IBitcoinLikeClaimDestination bitcoinLikeClaimDestination)
|
||||
{
|
||||
txout.ScriptPubKey = bitcoinLikeClaimDestination.Address.ScriptPubKey;
|
||||
return Task.FromResult(txout.GetDustThreshold(new FeeRate(1.0m)).ToDecimal(MoneyUnit.BTC));
|
||||
}
|
||||
|
||||
return Task.FromResult(0m);
|
||||
}
|
||||
|
||||
private async Task UpdatePayoutsInProgress()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var ctx = _dbContextFactory.CreateContext();
|
||||
var payouts = await ctx.Payouts
|
||||
.Include(p => p.PullPaymentData)
|
||||
.Where(p => p.State == PayoutState.InProgress)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var payout in payouts)
|
||||
{
|
||||
var proof = ParseProof(payout) as PayoutTransactionOnChainBlob;
|
||||
var payoutBlob = payout.GetBlob(this._jsonSerializerSettings);
|
||||
if (proof is null || proof.Accounted is false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
foreach (var txid in proof.Candidates.ToList())
|
||||
{
|
||||
var explorer = _explorerClientProvider.GetExplorerClient(payout.GetPaymentMethodId().CryptoCode);
|
||||
var tx = await explorer.GetTransactionAsync(txid);
|
||||
if (tx is null)
|
||||
{
|
||||
proof.Candidates.Remove(txid);
|
||||
}
|
||||
else if (tx.Confirmations >= payoutBlob.MinimumConfirmation)
|
||||
{
|
||||
payout.State = PayoutState.Completed;
|
||||
proof.TransactionId = tx.TransactionHash;
|
||||
payout.Destination = null;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
var rebroadcasted = await explorer.BroadcastAsync(tx.Transaction);
|
||||
if (rebroadcasted.RPCCode == RPCErrorCode.RPC_TRANSACTION_ERROR ||
|
||||
rebroadcasted.RPCCode == RPCErrorCode.RPC_TRANSACTION_REJECTED)
|
||||
{
|
||||
proof.Candidates.Remove(txid);
|
||||
}
|
||||
else
|
||||
{
|
||||
payout.State = PayoutState.InProgress;
|
||||
proof.TransactionId = tx.TransactionHash;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (proof.TransactionId is null && !proof.Candidates.Contains(proof.TransactionId))
|
||||
{
|
||||
proof.TransactionId = null;
|
||||
}
|
||||
|
||||
if (proof.Candidates.Count == 0)
|
||||
{
|
||||
payout.State = PayoutState.AwaitingPayment;
|
||||
}
|
||||
else if (proof.TransactionId is null)
|
||||
{
|
||||
proof.TransactionId = proof.Candidates.First();
|
||||
}
|
||||
|
||||
if (payout.State == PayoutState.Completed)
|
||||
proof.Candidates = null;
|
||||
SetProofBlob(payout, proof);
|
||||
}
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logs.PayServer.LogWarning(ex, "Error while processing an update in the pull payment hosted service");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdatePayoutsAwaitingForPayment(NewOnChainTransactionEvent newTransaction)
|
||||
{
|
||||
try
|
||||
{
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(newTransaction.CryptoCode);
|
||||
Dictionary<string, decimal> destinations;
|
||||
if (newTransaction.NewTransactionEvent.TrackedSource is AddressTrackedSource addressTrackedSource)
|
||||
{
|
||||
destinations = new Dictionary<string, decimal>()
|
||||
{
|
||||
{
|
||||
addressTrackedSource.Address.ToString(),
|
||||
newTransaction.NewTransactionEvent.Outputs.Sum(output => output.Value.GetValue(network))
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
destinations = newTransaction.NewTransactionEvent.TransactionData.Transaction.Outputs
|
||||
.GroupBy(txout => txout.ScriptPubKey)
|
||||
.ToDictionary(
|
||||
txoutSet => txoutSet.Key.GetDestinationAddress(network.NBitcoinNetwork).ToString(),
|
||||
txoutSet => txoutSet.Sum(txout => txout.Value.ToDecimal(MoneyUnit.BTC)));
|
||||
}
|
||||
|
||||
var paymentMethodId = new PaymentMethodId(newTransaction.CryptoCode, BitcoinPaymentType.Instance);
|
||||
|
||||
using var ctx = _dbContextFactory.CreateContext();
|
||||
var payouts = await ctx.Payouts
|
||||
.Include(o => o.PullPaymentData)
|
||||
.Where(p => p.State == PayoutState.AwaitingPayment)
|
||||
.Where(p => p.PaymentMethodId == paymentMethodId.ToString())
|
||||
.Where(p => destinations.Keys.Contains(p.Destination))
|
||||
.ToListAsync();
|
||||
var payoutByDestination = payouts.ToDictionary(p => p.Destination);
|
||||
foreach (var destination in destinations)
|
||||
{
|
||||
if (!payoutByDestination.TryGetValue(destination.Key, out var payout))
|
||||
continue;
|
||||
var payoutBlob = payout.GetBlob(_jsonSerializerSettings);
|
||||
if (payoutBlob.CryptoAmount is null ||
|
||||
// The round up here is not strictly necessary, this is temporary to fix existing payout before we
|
||||
// were properly roundup the crypto amount
|
||||
destination.Value != BTCPayServer.Extensions.RoundUp(payoutBlob.CryptoAmount.Value, network.Divisibility))
|
||||
continue;
|
||||
var proof = ParseProof(payout) as PayoutTransactionOnChainBlob;
|
||||
if (proof is null)
|
||||
{
|
||||
proof = new PayoutTransactionOnChainBlob()
|
||||
{
|
||||
Accounted = !(newTransaction.NewTransactionEvent.TrackedSource is AddressTrackedSource ),
|
||||
};
|
||||
}
|
||||
var txId = newTransaction.NewTransactionEvent.TransactionData.TransactionHash;
|
||||
if (proof.Candidates.Add(txId))
|
||||
{
|
||||
if (proof.Accounted is true)
|
||||
{
|
||||
payout.State = PayoutState.InProgress;
|
||||
var walletId = new WalletId(payout.PullPaymentData.StoreId, newTransaction.CryptoCode);
|
||||
_eventAggregator.Publish(new UpdateTransactionLabel(walletId,
|
||||
newTransaction.NewTransactionEvent.TransactionData.TransactionHash,
|
||||
UpdateTransactionLabel.PayoutTemplate(payout.Id,payout.PullPaymentDataId, walletId.ToString())));
|
||||
}
|
||||
if (proof.TransactionId is null)
|
||||
proof.TransactionId = txId;
|
||||
SetProofBlob(payout, proof);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logs.PayServer.LogWarning(ex, "Error while processing a transaction in the pull payment hosted service");
|
||||
}
|
||||
}
|
||||
|
||||
private void SetProofBlob(PayoutData data, PayoutTransactionOnChainBlob blob)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(blob, _jsonSerializerSettings.GetSerializer(data.GetPaymentMethodId().CryptoCode)));
|
||||
// We only update the property if the bytes actually changed, this prevent from hammering the DB too much
|
||||
if (data.Proof is null || bytes.Length != data.Proof.Length || !bytes.SequenceEqual(data.Proof))
|
||||
{
|
||||
data.Proof = bytes;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public interface IBitcoinLikeClaimDestination : IClaimDestination
|
||||
{
|
||||
BitcoinAddress Address { get; }
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class PayoutTransactionOnChainBlob: IPayoutProof
|
||||
{
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.UInt256JsonConverter))]
|
||||
public uint256 TransactionId { get; set; }
|
||||
[JsonProperty(ItemConverterType = typeof(NBitcoin.JsonConverters.UInt256JsonConverter), NullValueHandling = NullValueHandling.Ignore)]
|
||||
public HashSet<uint256> Candidates { get; set; } = new HashSet<uint256>();
|
||||
|
||||
[JsonIgnore] public string LinkTemplate { get; set; }
|
||||
[JsonIgnore]
|
||||
public string Link
|
||||
{
|
||||
get { return Id != null ? string.Format(CultureInfo.InvariantCulture, LinkTemplate, Id) : null; }
|
||||
}
|
||||
public bool? Accounted { get; set; }//nullable to be backwards compatible. if null, accounted is true
|
||||
[JsonIgnore]
|
||||
public string Id { get { return TransactionId?.ToString(); } }
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
using System;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Payment;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class UriClaimDestination : IBitcoinLikeClaimDestination
|
||||
{
|
||||
private readonly BitcoinUrlBuilder _bitcoinUrl;
|
||||
|
||||
public UriClaimDestination(BitcoinUrlBuilder bitcoinUrl)
|
||||
{
|
||||
if (bitcoinUrl == null)
|
||||
throw new ArgumentNullException(nameof(bitcoinUrl));
|
||||
if (bitcoinUrl.Address is null)
|
||||
throw new ArgumentException(nameof(bitcoinUrl));
|
||||
_bitcoinUrl = bitcoinUrl;
|
||||
}
|
||||
public BitcoinUrlBuilder BitcoinUrl => _bitcoinUrl;
|
||||
|
||||
public BitcoinAddress Address => _bitcoinUrl.Address;
|
||||
public override string ToString()
|
||||
{
|
||||
return _bitcoinUrl.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public interface IClaimDestination
|
||||
{
|
||||
}
|
||||
|
||||
public interface IPayoutProof
|
||||
{
|
||||
string Link { get; }
|
||||
string Id { get; }
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Payments;
|
||||
|
||||
public interface IPayoutHandler
|
||||
{
|
||||
public bool CanHandle(PaymentMethodId paymentMethod);
|
||||
//Allows payout handler to parse payout destinations on its own
|
||||
public Task<IClaimDestination> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination);
|
||||
public IPayoutProof ParseProof(PayoutData payout);
|
||||
//Allows you to subscribe the main pull payment hosted service to events and prepare the handler
|
||||
void StartBackgroundCheck(Action<Type[]> subscribe);
|
||||
//allows you to process events that the main pull payment hosted service is subscribed to
|
||||
Task BackgroundCheck(object o);
|
||||
Task<decimal> GetMinimumPayoutAmount(PaymentMethodId paymentMethod, IClaimDestination claimDestination);
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class PayoutBlob
|
||||
{
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Amount { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal? CryptoAmount { get; set; }
|
||||
public int MinimumConfirmation { get; set; } = 1;
|
||||
public string Destination { get; set; }
|
||||
public int Revision { get; set; }
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public static class PayoutExtensions
|
||||
{
|
||||
public static async Task<PayoutData> GetPayout(this DbSet<PayoutData> payouts, string payoutId, string storeId, bool includePullPayment = false, bool includeStore = false)
|
||||
{
|
||||
IQueryable<PayoutData> query = payouts;
|
||||
if (includePullPayment)
|
||||
query = query.Include(p => p.PullPaymentData);
|
||||
if (includeStore)
|
||||
query = query.Include(p => p.PullPaymentData.StoreData);
|
||||
var payout = await query.Where(p => p.Id == payoutId &&
|
||||
p.PullPaymentData.StoreId == storeId).FirstOrDefaultAsync();
|
||||
if (payout is null)
|
||||
return null;
|
||||
return payout;
|
||||
}
|
||||
|
||||
public static PaymentMethodId GetPaymentMethodId(this PayoutData data)
|
||||
{
|
||||
return PaymentMethodId.Parse(data.PaymentMethodId);
|
||||
}
|
||||
public static PayoutBlob GetBlob(this PayoutData data, BTCPayNetworkJsonSerializerSettings serializers)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<PayoutBlob>(Encoding.UTF8.GetString(data.Blob), serializers.GetSerializer(data.GetPaymentMethodId().CryptoCode));
|
||||
}
|
||||
public static void SetBlob(this PayoutData data, PayoutBlob blob, BTCPayNetworkJsonSerializerSettings serializers)
|
||||
{
|
||||
data.Blob = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(blob, serializers.GetSerializer(data.GetPaymentMethodId().CryptoCode)));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
using System;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using BTCPayServer.Payments;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class PullPaymentBlob
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Currency { get; set; }
|
||||
public int Divisibility { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Limit { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal MinimumClaim { get; set; }
|
||||
public PullPaymentView View { get; set; } = new PullPaymentView();
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
|
||||
public TimeSpan? Period { get; set; }
|
||||
|
||||
[JsonProperty(ItemConverterType = typeof(PaymentMethodIdJsonConverter))]
|
||||
public PaymentMethodId[] SupportedPaymentMethods { get; set; }
|
||||
|
||||
public class PullPaymentView
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string EmbeddedCSS { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string CustomCSSLink { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public static class PullPaymentsExtensions
|
||||
{
|
||||
|
||||
public static PullPaymentBlob GetBlob(this PullPaymentData data)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<PullPaymentBlob>(Encoding.UTF8.GetString(data.Blob));
|
||||
}
|
||||
public static void SetBlob(this PullPaymentData data, PullPaymentBlob blob)
|
||||
{
|
||||
data.Blob = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(blob));
|
||||
}
|
||||
|
||||
public static bool IsSupported(this PullPaymentData data, BTCPayServer.Payments.PaymentMethodId paymentId)
|
||||
{
|
||||
return data.GetBlob().SupportedPaymentMethods.Contains(paymentId);
|
||||
}
|
||||
}
|
||||
}
|
212
BTCPayServer/Data/PullPaymentsExtensions.cs
Normal file
212
BTCPayServer/Data/PullPaymentsExtensions.cs
Normal file
@ -0,0 +1,212 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using NBitcoin.JsonConverters;
|
||||
using NBitcoin.Payment;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public static class PullPaymentsExtensions
|
||||
{
|
||||
public static async Task<PayoutData> GetPayout(this DbSet<PayoutData> payouts, string payoutId, string storeId, bool includePullPayment = false, bool includeStore = false)
|
||||
{
|
||||
IQueryable<PayoutData> query = payouts;
|
||||
if (includePullPayment)
|
||||
query = query.Include(p => p.PullPaymentData);
|
||||
if (includeStore)
|
||||
query = query.Include(p => p.PullPaymentData.StoreData);
|
||||
var payout = await query.Where(p => p.Id == payoutId &&
|
||||
p.PullPaymentData.StoreId == storeId).FirstOrDefaultAsync();
|
||||
if (payout is null)
|
||||
return null;
|
||||
return payout;
|
||||
}
|
||||
public static PullPaymentBlob GetBlob(this PullPaymentData data)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<PullPaymentBlob>(Encoding.UTF8.GetString(data.Blob));
|
||||
}
|
||||
public static void SetBlob(this PullPaymentData data, PullPaymentBlob blob)
|
||||
{
|
||||
data.Blob = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(blob));
|
||||
}
|
||||
public static PaymentMethodId GetPaymentMethodId(this PayoutData data)
|
||||
{
|
||||
return PaymentMethodId.Parse(data.PaymentMethodId);
|
||||
}
|
||||
public static PayoutBlob GetBlob(this PayoutData data, BTCPayNetworkJsonSerializerSettings serializers)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<PayoutBlob>(Encoding.UTF8.GetString(data.Blob), serializers.GetSerializer(data.GetPaymentMethodId().CryptoCode));
|
||||
}
|
||||
public static void SetBlob(this PayoutData data, PayoutBlob blob, BTCPayNetworkJsonSerializerSettings serializers)
|
||||
{
|
||||
data.Blob = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(blob, serializers.GetSerializer(data.GetPaymentMethodId().CryptoCode)));
|
||||
}
|
||||
|
||||
public static bool IsSupported(this PullPaymentData data, BTCPayServer.Payments.PaymentMethodId paymentId)
|
||||
{
|
||||
return data.GetBlob().SupportedPaymentMethods.Contains(paymentId);
|
||||
}
|
||||
|
||||
public static PayoutTransactionOnChainBlob GetProofBlob(this PayoutData data, BTCPayNetworkJsonSerializerSettings serializers)
|
||||
{
|
||||
if (data.Proof is null)
|
||||
return null;
|
||||
return JsonConvert.DeserializeObject<PayoutTransactionOnChainBlob>(Encoding.UTF8.GetString(data.Proof), serializers.GetSerializer(data.GetPaymentMethodId().CryptoCode));
|
||||
}
|
||||
public static void SetProofBlob(this PayoutData data, PayoutTransactionOnChainBlob blob, BTCPayNetworkJsonSerializerSettings serializers)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(blob, serializers.GetSerializer(data.GetPaymentMethodId().CryptoCode)));
|
||||
// We only update the property if the bytes actually changed, this prevent from hammering the DB too much
|
||||
if (data.Proof is null || bytes.Length != data.Proof.Length || !bytes.SequenceEqual(data.Proof))
|
||||
{
|
||||
data.Proof = bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class PayoutTransactionOnChainBlob
|
||||
{
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.UInt256JsonConverter))]
|
||||
public uint256 TransactionId { get; set; }
|
||||
[JsonProperty(ItemConverterType = typeof(NBitcoin.JsonConverters.UInt256JsonConverter), NullValueHandling = NullValueHandling.Ignore)]
|
||||
public HashSet<uint256> Candidates { get; set; } = new HashSet<uint256>();
|
||||
}
|
||||
public interface IClaimDestination
|
||||
{
|
||||
BitcoinAddress Address { get; }
|
||||
}
|
||||
public static class ClaimDestination
|
||||
{
|
||||
public static bool TryParse(string destination, BTCPayNetwork network, out IClaimDestination claimDestination)
|
||||
{
|
||||
if (destination == null)
|
||||
throw new ArgumentNullException(nameof(destination));
|
||||
destination = destination.Trim();
|
||||
try
|
||||
{
|
||||
if (destination.StartsWith($"{network.UriScheme}:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
claimDestination = new UriClaimDestination(new BitcoinUrlBuilder(destination, network.NBitcoinNetwork));
|
||||
}
|
||||
else
|
||||
{
|
||||
claimDestination = new AddressClaimDestination(BitcoinAddress.Create(destination, network.NBitcoinNetwork));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
claimDestination = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
public class AddressClaimDestination : IClaimDestination
|
||||
{
|
||||
private readonly BitcoinAddress _bitcoinAddress;
|
||||
|
||||
public AddressClaimDestination(BitcoinAddress bitcoinAddress)
|
||||
{
|
||||
if (bitcoinAddress == null)
|
||||
throw new ArgumentNullException(nameof(bitcoinAddress));
|
||||
_bitcoinAddress = bitcoinAddress;
|
||||
}
|
||||
public BitcoinAddress BitcoinAdress => _bitcoinAddress;
|
||||
public BitcoinAddress Address => _bitcoinAddress;
|
||||
public override string ToString()
|
||||
{
|
||||
return _bitcoinAddress.ToString();
|
||||
}
|
||||
}
|
||||
public class UriClaimDestination : IClaimDestination
|
||||
{
|
||||
private readonly BitcoinUrlBuilder _bitcoinUrl;
|
||||
|
||||
public UriClaimDestination(BitcoinUrlBuilder bitcoinUrl)
|
||||
{
|
||||
if (bitcoinUrl == null)
|
||||
throw new ArgumentNullException(nameof(bitcoinUrl));
|
||||
if (bitcoinUrl.Address is null)
|
||||
throw new ArgumentException(nameof(bitcoinUrl));
|
||||
_bitcoinUrl = bitcoinUrl;
|
||||
}
|
||||
public BitcoinUrlBuilder BitcoinUrl => _bitcoinUrl;
|
||||
|
||||
public BitcoinAddress Address => _bitcoinUrl.Address;
|
||||
public override string ToString()
|
||||
{
|
||||
return _bitcoinUrl.ToString();
|
||||
}
|
||||
}
|
||||
public class PayoutBlob
|
||||
{
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Amount { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal? CryptoAmount { get; set; }
|
||||
public int MinimumConfirmation { get; set; } = 1;
|
||||
public IClaimDestination Destination { get; set; }
|
||||
public int Revision { get; set; }
|
||||
}
|
||||
public class ClaimDestinationJsonConverter : JsonConverter<IClaimDestination>
|
||||
{
|
||||
private readonly BTCPayNetwork _network;
|
||||
|
||||
public ClaimDestinationJsonConverter(BTCPayNetwork network)
|
||||
{
|
||||
if (network == null)
|
||||
throw new ArgumentNullException(nameof(network));
|
||||
_network = network;
|
||||
}
|
||||
|
||||
public override IClaimDestination ReadJson(JsonReader reader, Type objectType, IClaimDestination existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
return null;
|
||||
if (reader.TokenType != JsonToken.String)
|
||||
throw new JsonObjectException("Expected string for IClaimDestination", reader);
|
||||
if (ClaimDestination.TryParse((string)reader.Value, _network, out var v))
|
||||
return v;
|
||||
throw new JsonObjectException("Invalid IClaimDestination", reader);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, IClaimDestination value, JsonSerializer serializer)
|
||||
{
|
||||
if (value is IClaimDestination v)
|
||||
writer.WriteValue(v.ToString());
|
||||
}
|
||||
}
|
||||
public class PullPaymentBlob
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Currency { get; set; }
|
||||
public int Divisibility { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Limit { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal MinimumClaim { get; set; }
|
||||
public PullPaymentView View { get; set; } = new PullPaymentView();
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
|
||||
public TimeSpan? Period { get; set; }
|
||||
|
||||
[JsonProperty(ItemConverterType = typeof(PaymentMethodIdJsonConverter))]
|
||||
public PaymentMethodId[] SupportedPaymentMethods { get; set; }
|
||||
}
|
||||
public class PullPaymentView
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string EmbeddedCSS { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string CustomCSSLink { get; set; }
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Plugins.CoinSwitch;
|
||||
using BTCPayServer.Payments.CoinSwitch;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services.Mails;
|
||||
using BTCPayServer.Services.Rates;
|
||||
@ -90,6 +90,8 @@ namespace BTCPayServer.Data
|
||||
|
||||
public bool AnyoneCanInvoice { get; set; }
|
||||
|
||||
public CoinSwitchSettings CoinSwitchSettings { get; set; }
|
||||
|
||||
string _LightningDescriptionTemplate;
|
||||
public string LightningDescriptionTemplate
|
||||
{
|
||||
|
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using BTCPayServer.Payments;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
@ -99,10 +98,6 @@ namespace BTCPayServer
|
||||
JObject jobj = null;
|
||||
try
|
||||
{
|
||||
if (HexEncoder.IsWellFormed(fileContents))
|
||||
{
|
||||
fileContents = Encoding.UTF8.GetString(Encoders.Hex.DecodeData(fileContents));
|
||||
}
|
||||
jobj = JObject.Parse(fileContents);
|
||||
}
|
||||
catch
|
||||
|
@ -111,13 +111,6 @@ namespace BTCPayServer
|
||||
s.Act = (o) => subscription(s, (T)o);
|
||||
return Subscribe(eventType, s);
|
||||
}
|
||||
|
||||
public IEventAggregatorSubscription Subscribe(Type eventType, Action<IEventAggregatorSubscription, object> subscription)
|
||||
{
|
||||
var s = new Subscription(this, eventType);
|
||||
s.Act = (o) => subscription(s, o);
|
||||
return Subscribe(eventType, s);
|
||||
}
|
||||
|
||||
private IEventAggregatorSubscription Subscribe(Type eventType, Subscription subscription)
|
||||
{
|
||||
|
@ -1,4 +1,4 @@
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
|
||||
namespace BTCPayServer.Events
|
||||
{
|
||||
|
@ -1,24 +0,0 @@
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
|
||||
namespace BTCPayServer.Events
|
||||
{
|
||||
public class InvoicePaymentMethodActivated : IHasInvoiceId
|
||||
{
|
||||
public PaymentMethodId PaymentMethodId { get; }
|
||||
public InvoiceEntity InvoiceEntity { get; }
|
||||
|
||||
public InvoicePaymentMethodActivated(PaymentMethodId paymentMethodId, InvoiceEntity invoiceEntity)
|
||||
{
|
||||
PaymentMethodId = paymentMethodId;
|
||||
InvoiceEntity = invoiceEntity;
|
||||
}
|
||||
|
||||
public string InvoiceId => InvoiceEntity.Id;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
@ -133,9 +133,9 @@ namespace BTCPayServer
|
||||
finally { try { webSocket.Dispose(); } catch { } }
|
||||
}
|
||||
|
||||
public static IEnumerable<BitcoinLikePaymentData> GetAllBitcoinPaymentData(this InvoiceEntity invoice, bool accountedOnly)
|
||||
public static IEnumerable<BitcoinLikePaymentData> GetAllBitcoinPaymentData(this InvoiceEntity invoice)
|
||||
{
|
||||
return invoice.GetPayments(accountedOnly)
|
||||
return invoice.GetPayments()
|
||||
.Where(p => p.GetPaymentMethodId()?.PaymentType == PaymentTypes.BTCLike)
|
||||
.Select(p => (BitcoinLikePaymentData)p.GetCryptoPaymentData())
|
||||
.Where(data => data != null);
|
||||
|
@ -1,103 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Fido2.Models;
|
||||
using BTCPayServer.Models;
|
||||
using Fido2NetLib;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Fido2
|
||||
{
|
||||
|
||||
[Route("fido2")]
|
||||
[Authorize]
|
||||
public class Fido2Controller : Controller
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly Fido2Service _fido2Service;
|
||||
|
||||
public Fido2Controller(UserManager<ApplicationUser> userManager, Fido2Service fido2Service)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_fido2Service = fido2Service;
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
public async Task<IActionResult> List()
|
||||
{
|
||||
return View(new Fido2AuthenticationViewModel()
|
||||
{
|
||||
Credentials = await _fido2Service.GetCredentials( _userManager.GetUserId(User))
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("{id}/delete")]
|
||||
public IActionResult Remove(string id)
|
||||
{
|
||||
return View("Confirm", new ConfirmModel("Are you sure you want to remove FIDO2 credential?", "Your account will no longer have this credential as an option for MFA.", "Remove"));
|
||||
}
|
||||
|
||||
[HttpPost("{id}/delete")]
|
||||
public async Task<IActionResult> RemoveP(string id)
|
||||
{
|
||||
|
||||
await _fido2Service.Remove(id, _userManager.GetUserId(User));
|
||||
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
Html = $"FIDO2 Credentials were removed successfully."
|
||||
});
|
||||
|
||||
return RedirectToAction(nameof(List));
|
||||
}
|
||||
|
||||
[HttpGet("register")]
|
||||
public async Task<IActionResult> Create(AddFido2CredentialViewModel viewModel)
|
||||
{
|
||||
var options = await _fido2Service.RequestCreation(_userManager.GetUserId(User));
|
||||
if (options is null)
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Html = $"FIDO2 Credentials could not be saved."
|
||||
});
|
||||
|
||||
return RedirectToAction(nameof(List));
|
||||
}
|
||||
|
||||
ViewData["CredentialName"] = viewModel.Name ?? "";
|
||||
return View(options);
|
||||
}
|
||||
|
||||
[HttpPost("register")]
|
||||
public async Task<IActionResult> CreateResponse([FromForm] string data, [FromForm] string name)
|
||||
{
|
||||
if (await _fido2Service.CompleteCreation(_userManager.GetUserId(User), name, data))
|
||||
{
|
||||
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
Html = $"FIDO2 Credentials were saved successfully."
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Html = $"FIDO2 Credentials could not be saved."
|
||||
});
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(List));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,218 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Fido2.Models;
|
||||
using ExchangeSharp;
|
||||
using Fido2NetLib;
|
||||
using Fido2NetLib.Objects;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Fido2
|
||||
{
|
||||
public class Fido2Service
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, CredentialCreateOptions> CreationStore =
|
||||
new ConcurrentDictionary<string, CredentialCreateOptions>();
|
||||
private static readonly ConcurrentDictionary<string, AssertionOptions> LoginStore =
|
||||
new ConcurrentDictionary<string, AssertionOptions>();
|
||||
private readonly ApplicationDbContextFactory _contextFactory;
|
||||
private readonly IFido2 _fido2;
|
||||
private readonly Fido2Configuration _fido2Configuration;
|
||||
|
||||
public Fido2Service(ApplicationDbContextFactory contextFactory, IFido2 fido2, Fido2Configuration fido2Configuration)
|
||||
{
|
||||
_contextFactory = contextFactory;
|
||||
_fido2 = fido2;
|
||||
_fido2Configuration = fido2Configuration;
|
||||
}
|
||||
|
||||
public async Task<CredentialCreateOptions> RequestCreation(string userId)
|
||||
{
|
||||
await using var dbContext = _contextFactory.CreateContext();
|
||||
var user = await dbContext.Users.Include(applicationUser => applicationUser.Fido2Credentials)
|
||||
.FirstOrDefaultAsync(applicationUser => applicationUser.Id == userId);
|
||||
if (user == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. Get user existing keys by username
|
||||
var existingKeys =
|
||||
user.Fido2Credentials
|
||||
.Where(credential => credential.Type == Fido2Credential.CredentialType.FIDO2)
|
||||
.Select(c => c.GetBlob().Descriptor).ToList();
|
||||
|
||||
// 3. Create options
|
||||
var authenticatorSelection = new AuthenticatorSelection
|
||||
{
|
||||
RequireResidentKey = false, UserVerification = UserVerificationRequirement.Preferred
|
||||
};
|
||||
|
||||
var exts = new AuthenticationExtensionsClientInputs()
|
||||
{
|
||||
Extensions = true,
|
||||
UserVerificationIndex = true,
|
||||
Location = true,
|
||||
UserVerificationMethod = true,
|
||||
BiometricAuthenticatorPerformanceBounds = new AuthenticatorBiometricPerfBounds
|
||||
{
|
||||
FAR = float.MaxValue, FRR = float.MaxValue
|
||||
},
|
||||
};
|
||||
|
||||
var options = _fido2.RequestNewCredential(
|
||||
new Fido2User() {DisplayName = user.UserName, Name = user.UserName, Id = user.Id.ToBytesUTF8()},
|
||||
existingKeys, authenticatorSelection, AttestationConveyancePreference.None, exts);
|
||||
|
||||
// options.Rp = new PublicKeyCredentialRpEntity(Request.Host.Host, options.Rp.Name, "");
|
||||
CreationStore.AddOrReplace(userId, options);
|
||||
return options;
|
||||
}
|
||||
|
||||
public async Task<bool> CompleteCreation(string userId, string name, string data)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var attestationResponse = JObject.Parse(data).ToObject<AuthenticatorAttestationRawResponse>();
|
||||
await using var dbContext = _contextFactory.CreateContext();
|
||||
var user = await dbContext.Users.Include(applicationUser => applicationUser.Fido2Credentials)
|
||||
.FirstOrDefaultAsync(applicationUser => applicationUser.Id == userId);
|
||||
if (user == null || !CreationStore.TryGetValue(userId, out var options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Verify and make the credentials
|
||||
var success =
|
||||
await _fido2.MakeNewCredentialAsync(attestationResponse, options, args => Task.FromResult(true));
|
||||
|
||||
// 3. Store the credentials in db
|
||||
var newCredential = new Fido2Credential() {Name = name, ApplicationUserId = userId};
|
||||
|
||||
newCredential.SetBlob(new Fido2CredentialBlob()
|
||||
{
|
||||
Descriptor = new PublicKeyCredentialDescriptor(success.Result.CredentialId),
|
||||
PublicKey = success.Result.PublicKey,
|
||||
UserHandle = success.Result.User.Id,
|
||||
SignatureCounter = success.Result.Counter,
|
||||
CredType = success.Result.CredType,
|
||||
AaGuid = success.Result.Aaguid.ToString(),
|
||||
});
|
||||
|
||||
await dbContext.Fido2Credentials.AddAsync(newCredential);
|
||||
await dbContext.SaveChangesAsync();
|
||||
CreationStore.Remove(userId, out _);
|
||||
return true;
|
||||
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<Fido2Credential>> GetCredentials(string userId)
|
||||
{
|
||||
await using var context = _contextFactory.CreateContext();
|
||||
return await context.Fido2Credentials
|
||||
.Where(device => device.ApplicationUserId == userId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task Remove(string id, string userId)
|
||||
{
|
||||
await using var context = _contextFactory.CreateContext();
|
||||
var device = await context.Fido2Credentials.FindAsync( id);
|
||||
if (device == null || !device.ApplicationUserId.Equals(userId, StringComparison.InvariantCulture))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
context.Fido2Credentials.Remove(device);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> HasCredentials(string userId)
|
||||
{
|
||||
await using var context = _contextFactory.CreateContext();
|
||||
return await context.Fido2Credentials.Where(fDevice => fDevice.ApplicationUserId == userId).AnyAsync();
|
||||
}
|
||||
|
||||
public async Task<AssertionOptions> RequestLogin(string userId)
|
||||
{
|
||||
await using var dbContext = _contextFactory.CreateContext();
|
||||
var user = await dbContext.Users.Include(applicationUser => applicationUser.Fido2Credentials)
|
||||
.FirstOrDefaultAsync(applicationUser => applicationUser.Id == userId);
|
||||
if (!(user?.Fido2Credentials?.Any() is true))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var existingCredentials = user.Fido2Credentials
|
||||
.Where(credential => credential.Type == Fido2Credential.CredentialType.FIDO2)
|
||||
.Select(c => c.GetBlob().Descriptor)
|
||||
.ToList();
|
||||
var exts = new AuthenticationExtensionsClientInputs()
|
||||
{
|
||||
SimpleTransactionAuthorization = "FIDO",
|
||||
GenericTransactionAuthorization = new TxAuthGenericArg
|
||||
{
|
||||
ContentType = "text/plain",
|
||||
Content = new byte[] { 0x46, 0x49, 0x44, 0x4F }
|
||||
},
|
||||
UserVerificationIndex = true,
|
||||
Location = true,
|
||||
UserVerificationMethod = true ,
|
||||
Extensions = true,
|
||||
AppID = _fido2Configuration.Origin
|
||||
};
|
||||
|
||||
// 3. Create options
|
||||
var options = _fido2.GetAssertionOptions(
|
||||
existingCredentials,
|
||||
UserVerificationRequirement.Discouraged,
|
||||
exts
|
||||
);
|
||||
LoginStore.AddOrReplace(userId, options);
|
||||
return options;
|
||||
}
|
||||
|
||||
public async Task<bool> CompleteLogin(string userId, AuthenticatorAssertionRawResponse response){
|
||||
await using var dbContext = _contextFactory.CreateContext();
|
||||
var user = await dbContext.Users.Include(applicationUser => applicationUser.Fido2Credentials)
|
||||
.FirstOrDefaultAsync(applicationUser => applicationUser.Id == userId);
|
||||
if (user == null || !LoginStore.TryGetValue(userId, out var options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var credential = user.Fido2Credentials
|
||||
.Where(fido2Credential => fido2Credential.Type is Fido2Credential.CredentialType.FIDO2)
|
||||
.Select(fido2Credential => (fido2Credential, fido2Credential.GetBlob()))
|
||||
.FirstOrDefault(fido2Credential => fido2Credential.Item2.Descriptor.Id.SequenceEqual(response.Id));
|
||||
if (credential.Item2 is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. Make the assertion
|
||||
var res = await _fido2.MakeAssertionAsync(response, options, credential.Item2.PublicKey,
|
||||
credential.Item2.SignatureCounter, x => Task.FromResult(true));
|
||||
|
||||
// 6. Store the updated counter
|
||||
credential.Item2.SignatureCounter = res.Counter;
|
||||
credential.fido2Credential.SetBlob(credential.Item2);
|
||||
await dbContext.SaveChangesAsync();
|
||||
LoginStore.Remove(userId, out _);
|
||||
|
||||
// 7. return OK to client
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Fido2.Models;
|
||||
using NBXplorer;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Fido2
|
||||
{
|
||||
public static class Fido2Extensions
|
||||
{
|
||||
public static Fido2CredentialBlob GetBlob(this Fido2Credential credential)
|
||||
{
|
||||
var result = credential.Blob == null
|
||||
? new Fido2CredentialBlob()
|
||||
: JObject.Parse(ZipUtils.Unzip(credential.Blob)).ToObject<Fido2CredentialBlob>();
|
||||
return result;
|
||||
}
|
||||
public static bool SetBlob(this Fido2Credential credential, Fido2CredentialBlob descriptor)
|
||||
{
|
||||
var original = new Serializer(null).ToString(credential.GetBlob());
|
||||
var newBlob = new Serializer(null).ToString(descriptor);
|
||||
if (original == newBlob)
|
||||
return false;
|
||||
credential.Type = Fido2Credential.CredentialType.FIDO2;
|
||||
credential.Blob = ZipUtils.Zip(newBlob);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
using Fido2NetLib.Objects;
|
||||
|
||||
namespace BTCPayServer.Fido2.Models
|
||||
{
|
||||
public class AddFido2CredentialViewModel
|
||||
{
|
||||
public AuthenticatorAttachment? AuthenticatorAttachment { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Data;
|
||||
|
||||
namespace BTCPayServer.Fido2.Models
|
||||
{
|
||||
public class Fido2AuthenticationViewModel
|
||||
{
|
||||
public List<Fido2Credential> Credentials { get; set; }
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
using Fido2NetLib;
|
||||
using Fido2NetLib.Objects;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Fido2.Models
|
||||
{
|
||||
public class Fido2CredentialBlob
|
||||
{
|
||||
public PublicKeyCredentialDescriptor Descriptor { get; set; }
|
||||
[JsonConverter(typeof(Base64UrlConverter))]
|
||||
public byte[] PublicKey { get; set; }
|
||||
[JsonConverter(typeof(Base64UrlConverter))]
|
||||
public byte[] UserHandle { get; set; }
|
||||
public uint SignatureCounter { get; set; }
|
||||
public string CredType { get; set; }
|
||||
public string AaGuid { get; set; }
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
using Fido2NetLib;
|
||||
|
||||
namespace BTCPayServer.Fido2.Models
|
||||
{
|
||||
public class LoginWithFido2ViewModel
|
||||
{
|
||||
public string UserId { get; set; }
|
||||
|
||||
public bool RememberMe { get; set; }
|
||||
public AssertionOptions Data { get; set; }
|
||||
public string Response { get; set; }
|
||||
}
|
||||
}
|
@ -101,7 +101,7 @@ namespace BTCPayServer.HostedServices
|
||||
}
|
||||
}
|
||||
|
||||
if (accounting.Paid < accounting.MinimumTotalDue && invoice.GetPayments(true).Count != 0 && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidPartial)
|
||||
if (accounting.Paid < accounting.MinimumTotalDue && invoice.GetPayments().Count != 0 && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidPartial)
|
||||
{
|
||||
invoice.ExceptionStatus = InvoiceExceptionStatus.PaidPartial;
|
||||
context.MarkDirty();
|
||||
@ -335,7 +335,7 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
bool extendInvoiceMonitoring = false;
|
||||
var updateConfirmationCountIfNeeded = invoice
|
||||
.GetPayments(false)
|
||||
.GetPayments()
|
||||
.Select<PaymentEntity, Task<PaymentEntity>>(async payment =>
|
||||
{
|
||||
var paymentData = payment.GetCryptoPaymentData();
|
||||
|
@ -4,19 +4,20 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Notifications;
|
||||
using BTCPayServer.Services.Notifications.Blobs;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBXplorer;
|
||||
using PayoutData = BTCPayServer.Data.PayoutData;
|
||||
|
||||
using NBitcoin.RPC;
|
||||
|
||||
namespace BTCPayServer.HostedServices
|
||||
{
|
||||
@ -107,7 +108,7 @@ namespace BTCPayServer.HostedServices
|
||||
Limit = create.Amount,
|
||||
Period = o.Period is long periodSeconds ? (TimeSpan?)TimeSpan.FromSeconds(periodSeconds) : null,
|
||||
SupportedPaymentMethods = create.PaymentMethodIds,
|
||||
View = new PullPaymentBlob.PullPaymentView()
|
||||
View = new PullPaymentView()
|
||||
{
|
||||
Title = create.Name ?? string.Empty,
|
||||
Description = string.Empty,
|
||||
@ -145,19 +146,19 @@ namespace BTCPayServer.HostedServices
|
||||
BTCPayNetworkJsonSerializerSettings jsonSerializerSettings,
|
||||
CurrencyNameTable currencyNameTable,
|
||||
EventAggregator eventAggregator,
|
||||
ExplorerClientProvider explorerClientProvider,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
NotificationSender notificationSender,
|
||||
RateFetcher rateFetcher,
|
||||
IEnumerable<IPayoutHandler> payoutHandlers)
|
||||
RateFetcher rateFetcher)
|
||||
{
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_jsonSerializerSettings = jsonSerializerSettings;
|
||||
_currencyNameTable = currencyNameTable;
|
||||
_eventAggregator = eventAggregator;
|
||||
_explorerClientProvider = explorerClientProvider;
|
||||
_networkProvider = networkProvider;
|
||||
_notificationSender = notificationSender;
|
||||
_rateFetcher = rateFetcher;
|
||||
_payoutHandlers = payoutHandlers;
|
||||
}
|
||||
|
||||
Channel<object> _Channel;
|
||||
@ -165,30 +166,19 @@ namespace BTCPayServer.HostedServices
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _jsonSerializerSettings;
|
||||
private readonly CurrencyNameTable _currencyNameTable;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly ExplorerClientProvider _explorerClientProvider;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly NotificationSender _notificationSender;
|
||||
private readonly RateFetcher _rateFetcher;
|
||||
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
||||
private readonly CompositeDisposable _subscriptions = new CompositeDisposable();
|
||||
|
||||
internal override Task[] InitializeTasks()
|
||||
{
|
||||
_Channel = Channel.CreateUnbounded<object>();
|
||||
foreach (IPayoutHandler payoutHandler in _payoutHandlers)
|
||||
{
|
||||
payoutHandler.StartBackgroundCheck(Subscribe);
|
||||
}
|
||||
_eventAggregator.Subscribe<NewOnChainTransactionEvent>(o => _Channel.Writer.TryWrite(o));
|
||||
_eventAggregator.Subscribe<NewBlockEvent>(o => _Channel.Writer.TryWrite(o));
|
||||
return new[] { Loop() };
|
||||
}
|
||||
|
||||
private void Subscribe(params Type[] events)
|
||||
{
|
||||
foreach (Type @event in events)
|
||||
{
|
||||
_eventAggregator.Subscribe(@event, (subscription, o) => _Channel.Writer.TryWrite(o));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Loop()
|
||||
{
|
||||
await foreach (var o in _Channel.Reader.ReadAllAsync())
|
||||
@ -202,13 +192,18 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
await HandleApproval(approv);
|
||||
}
|
||||
|
||||
if (o is NewOnChainTransactionEvent newTransaction)
|
||||
{
|
||||
await UpdatePayoutsAwaitingForPayment(newTransaction);
|
||||
}
|
||||
if (o is CancelRequest cancel)
|
||||
{
|
||||
await HandleCancel(cancel);
|
||||
}
|
||||
foreach (IPayoutHandler payoutHandler in _payoutHandlers)
|
||||
}
|
||||
if (o is NewBlockEvent || o is NewOnChainTransactionEvent)
|
||||
{
|
||||
await payoutHandler.BackgroundCheck(o);
|
||||
await UpdatePayoutsInProgress();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -271,18 +266,15 @@ namespace BTCPayServer.HostedServices
|
||||
var paymentMethod = PaymentMethodId.Parse(payout.PaymentMethodId);
|
||||
if (paymentMethod.CryptoCode == payout.PullPaymentData.GetBlob().Currency)
|
||||
req.Rate = 1.0m;
|
||||
var cryptoAmount = payoutBlob.Amount / req.Rate;
|
||||
var payoutHandler = _payoutHandlers.First(handler => handler.CanHandle(paymentMethod));
|
||||
var dest = await payoutHandler.ParseClaimDestination(paymentMethod, payoutBlob.Destination);
|
||||
|
||||
decimal minimumCryptoAmount = await payoutHandler.GetMinimumPayoutAmount(paymentMethod, dest);
|
||||
if (cryptoAmount < minimumCryptoAmount)
|
||||
var cryptoAmount = Money.Coins(payoutBlob.Amount / req.Rate);
|
||||
Money mininumCryptoAmount = GetMinimumCryptoAmount(paymentMethod, payoutBlob.Destination.Address.ScriptPubKey);
|
||||
if (cryptoAmount < mininumCryptoAmount)
|
||||
{
|
||||
req.Completion.TrySetResult(PayoutApproval.Result.TooLowAmount);
|
||||
return;
|
||||
}
|
||||
payoutBlob.CryptoAmount = BTCPayServer.Extensions.RoundUp(cryptoAmount, _networkProvider.GetNetwork(paymentMethod.CryptoCode).Divisibility);
|
||||
payout.SetBlob(payoutBlob, _jsonSerializerSettings);
|
||||
payoutBlob.CryptoAmount = cryptoAmount.ToDecimal(MoneyUnit.BTC);
|
||||
payout.SetBlob(payoutBlob, this._jsonSerializerSettings);
|
||||
await ctx.SaveChangesAsync();
|
||||
req.Completion.SetResult(PayoutApproval.Result.Ok);
|
||||
}
|
||||
@ -297,9 +289,8 @@ namespace BTCPayServer.HostedServices
|
||||
try
|
||||
{
|
||||
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||
await using var ctx = _dbContextFactory.CreateContext();
|
||||
using var ctx = _dbContextFactory.CreateContext();
|
||||
var pp = await ctx.PullPayments.FindAsync(req.ClaimRequest.PullPaymentId);
|
||||
|
||||
if (pp is null || pp.Archived)
|
||||
{
|
||||
req.Completion.TrySetResult(new ClaimRequest.ClaimResponse(ClaimRequest.ClaimResult.Archived));
|
||||
@ -316,9 +307,7 @@ namespace BTCPayServer.HostedServices
|
||||
return;
|
||||
}
|
||||
var ppBlob = pp.GetBlob();
|
||||
var payoutHandler =
|
||||
_payoutHandlers.FirstOrDefault(handler => handler.CanHandle(req.ClaimRequest.PaymentMethodId));
|
||||
if (!ppBlob.SupportedPaymentMethods.Contains(req.ClaimRequest.PaymentMethodId) || payoutHandler is null)
|
||||
if (!ppBlob.SupportedPaymentMethods.Contains(req.ClaimRequest.PaymentMethodId))
|
||||
{
|
||||
req.Completion.TrySetResult(new ClaimRequest.ClaimResponse(ClaimRequest.ClaimResult.PaymentMethodNotSupported));
|
||||
return;
|
||||
@ -347,7 +336,7 @@ namespace BTCPayServer.HostedServices
|
||||
State = PayoutState.AwaitingApproval,
|
||||
PullPaymentDataId = req.ClaimRequest.PullPaymentId,
|
||||
PaymentMethodId = req.ClaimRequest.PaymentMethodId.ToString(),
|
||||
Destination = req.ClaimRequest.Destination.ToString()
|
||||
Destination = GetDestination(req.ClaimRequest.Destination.Address.ScriptPubKey)
|
||||
};
|
||||
if (claimed < ppBlob.MinimumClaim || claimed == 0.0m)
|
||||
{
|
||||
@ -357,10 +346,11 @@ namespace BTCPayServer.HostedServices
|
||||
var payoutBlob = new PayoutBlob()
|
||||
{
|
||||
Amount = claimed,
|
||||
Destination = req.ClaimRequest.Destination.ToString()
|
||||
Destination = req.ClaimRequest.Destination
|
||||
};
|
||||
payout.SetBlob(payoutBlob, _jsonSerializerSettings);
|
||||
await ctx.Payouts.AddAsync(payout);
|
||||
payout.SetProofBlob(new PayoutTransactionOnChainBlob(), _jsonSerializerSettings);
|
||||
ctx.Payouts.Add(payout);
|
||||
try
|
||||
{
|
||||
await ctx.SaveChangesAsync();
|
||||
@ -383,6 +373,54 @@ namespace BTCPayServer.HostedServices
|
||||
req.Completion.TrySetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdatePayoutsAwaitingForPayment(NewOnChainTransactionEvent newTransaction)
|
||||
{
|
||||
try
|
||||
{
|
||||
var outputs = newTransaction.
|
||||
NewTransactionEvent.
|
||||
TransactionData.
|
||||
Transaction.
|
||||
Outputs;
|
||||
var destinations = outputs.Select(o => GetDestination(o.ScriptPubKey)).ToHashSet();
|
||||
|
||||
using var ctx = _dbContextFactory.CreateContext();
|
||||
var payouts = await ctx.Payouts
|
||||
.Include(o => o.PullPaymentData)
|
||||
.Where(p => p.State == PayoutState.AwaitingPayment)
|
||||
.Where(p => destinations.Contains(p.Destination))
|
||||
.ToListAsync();
|
||||
var payoutByDestination = payouts.ToDictionary(p => p.Destination);
|
||||
foreach (var output in outputs)
|
||||
{
|
||||
if (!payoutByDestination.TryGetValue(GetDestination(output.ScriptPubKey), out var payout))
|
||||
continue;
|
||||
var payoutBlob = payout.GetBlob(this._jsonSerializerSettings);
|
||||
if (output.Value.ToDecimal(MoneyUnit.BTC) != payoutBlob.CryptoAmount)
|
||||
continue;
|
||||
var proof = payout.GetProofBlob(this._jsonSerializerSettings);
|
||||
var txId = newTransaction.NewTransactionEvent.TransactionData.TransactionHash;
|
||||
if (proof.Candidates.Add(txId))
|
||||
{
|
||||
payout.State = PayoutState.InProgress;
|
||||
if (proof.TransactionId is null)
|
||||
proof.TransactionId = txId;
|
||||
payout.SetProofBlob(proof, _jsonSerializerSettings);
|
||||
var walletId = new WalletId(payout.PullPaymentData.StoreId, newTransaction.CryptoCode);
|
||||
_eventAggregator.Publish(new UpdateTransactionLabel(walletId,
|
||||
newTransaction.NewTransactionEvent.TransactionData.TransactionHash,
|
||||
UpdateTransactionLabel.PayoutTemplate(payout.Id,payout.PullPaymentDataId, walletId.ToString())));
|
||||
}
|
||||
}
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logs.PayServer.LogWarning(ex, "Error while processing a transaction in the pull payment hosted service");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleCancel(CancelRequest cancel)
|
||||
{
|
||||
try
|
||||
@ -419,6 +457,95 @@ namespace BTCPayServer.HostedServices
|
||||
cancel.Completion.TrySetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdatePayoutsInProgress()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var ctx = _dbContextFactory.CreateContext();
|
||||
var payouts = await ctx.Payouts
|
||||
.Include(p => p.PullPaymentData)
|
||||
.Where(p => p.State == PayoutState.InProgress)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var payout in payouts)
|
||||
{
|
||||
var proof = payout.GetProofBlob(this._jsonSerializerSettings);
|
||||
var payoutBlob = payout.GetBlob(this._jsonSerializerSettings);
|
||||
foreach (var txid in proof.Candidates.ToList())
|
||||
{
|
||||
var explorer = _explorerClientProvider.GetExplorerClient(payout.GetPaymentMethodId().CryptoCode);
|
||||
var tx = await explorer.GetTransactionAsync(txid);
|
||||
if (tx is null)
|
||||
{
|
||||
proof.Candidates.Remove(txid);
|
||||
}
|
||||
else if (tx.Confirmations >= payoutBlob.MinimumConfirmation)
|
||||
{
|
||||
payout.State = PayoutState.Completed;
|
||||
proof.TransactionId = tx.TransactionHash;
|
||||
payout.Destination = null;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
var rebroadcasted = await explorer.BroadcastAsync(tx.Transaction);
|
||||
if (rebroadcasted.RPCCode == RPCErrorCode.RPC_TRANSACTION_ERROR ||
|
||||
rebroadcasted.RPCCode == RPCErrorCode.RPC_TRANSACTION_REJECTED)
|
||||
{
|
||||
proof.Candidates.Remove(txid);
|
||||
}
|
||||
else
|
||||
{
|
||||
payout.State = PayoutState.InProgress;
|
||||
proof.TransactionId = tx.TransactionHash;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (proof.TransactionId is null && !proof.Candidates.Contains(proof.TransactionId))
|
||||
{
|
||||
proof.TransactionId = null;
|
||||
}
|
||||
if (proof.Candidates.Count == 0)
|
||||
{
|
||||
payout.State = PayoutState.AwaitingPayment;
|
||||
}
|
||||
else if (proof.TransactionId is null)
|
||||
{
|
||||
proof.TransactionId = proof.Candidates.First();
|
||||
}
|
||||
if (payout.State == PayoutState.Completed)
|
||||
proof.Candidates = null;
|
||||
payout.SetProofBlob(proof, this._jsonSerializerSettings);
|
||||
}
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logs.PayServer.LogWarning(ex, "Error while processing an update in the pull payment hosted service");
|
||||
}
|
||||
}
|
||||
|
||||
private Money GetMinimumCryptoAmount(PaymentMethodId paymentMethodId, Script scriptPubKey)
|
||||
{
|
||||
Money mininumAmount = Money.Zero;
|
||||
if (_networkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode)?
|
||||
.NBitcoinNetwork?
|
||||
.Consensus?
|
||||
.ConsensusFactory?
|
||||
.CreateTxOut() is TxOut txout)
|
||||
{
|
||||
txout.ScriptPubKey = scriptPubKey;
|
||||
mininumAmount = txout.GetDustThreshold(new FeeRate(1.0m));
|
||||
}
|
||||
return mininumAmount;
|
||||
}
|
||||
|
||||
private static string GetDestination(Script scriptPubKey)
|
||||
{
|
||||
return Encoders.Base64.EncodeData(scriptPubKey.ToBytes(true));
|
||||
}
|
||||
public Task Cancel(CancelRequest cancelRequest)
|
||||
{
|
||||
CancellationToken.ThrowIfCancellationRequested();
|
||||
@ -441,7 +568,6 @@ namespace BTCPayServer.HostedServices
|
||||
public override Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_Channel?.Writer.Complete();
|
||||
_subscriptions.Dispose();
|
||||
return base.StopAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
30
BTCPayServer/HostedServices/TorServicesHostedService.cs
Normal file
30
BTCPayServer/HostedServices/TorServicesHostedService.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Services;
|
||||
|
||||
namespace BTCPayServer.HostedServices
|
||||
{
|
||||
public class TorServicesHostedService : BaseAsyncService
|
||||
{
|
||||
private readonly BTCPayServerOptions _options;
|
||||
private readonly TorServices _torServices;
|
||||
|
||||
public TorServicesHostedService(BTCPayServerOptions options, TorServices torServices)
|
||||
{
|
||||
_options = options;
|
||||
_torServices = torServices;
|
||||
}
|
||||
|
||||
internal override Task[] InitializeTasks()
|
||||
{
|
||||
return new Task[] { CreateLoopTask(RefreshTorServices) };
|
||||
}
|
||||
|
||||
async Task RefreshTorServices()
|
||||
{
|
||||
await _torServices.Refresh();
|
||||
await Task.Delay(TimeSpan.FromSeconds(120), Cancellation);
|
||||
}
|
||||
}
|
||||
}
|
@ -46,7 +46,7 @@ namespace BTCPayServer.HostedServices
|
||||
UpdateTransactionLabel.InvoiceLabelTemplate(invoiceEvent.Invoice.Id)
|
||||
};
|
||||
|
||||
if (invoiceEvent.Invoice.GetPayments(invoiceEvent.Payment.GetCryptoCode(), false).Any(entity =>
|
||||
if (invoiceEvent.Invoice.GetPayments(invoiceEvent.Payment.GetCryptoCode()).Any(entity =>
|
||||
entity.GetCryptoPaymentData() is BitcoinLikePaymentData pData &&
|
||||
pData.PayjoinInformation?.CoinjoinTransactionHash == transactionId))
|
||||
{
|
||||
|
@ -101,7 +101,7 @@ namespace BTCPayServer.HostedServices
|
||||
webhookEvent.DeliveryId = newDelivery.Id;
|
||||
webhookEvent.WebhookId = webhookDelivery.Webhook.Id;
|
||||
// if we redelivered a redelivery, we still want the initial delivery here
|
||||
webhookEvent.OriginalDeliveryId ??= deliveryId;
|
||||
webhookEvent.OrignalDeliveryId ??= deliveryId;
|
||||
webhookEvent.IsRedelivery = true;
|
||||
newDeliveryBlob.Request = ToBytes(webhookEvent);
|
||||
newDelivery.SetBlob(newDeliveryBlob);
|
||||
@ -125,7 +125,7 @@ namespace BTCPayServer.HostedServices
|
||||
webhookEvent.StoreId = invoiceEvent.Invoice.StoreId;
|
||||
webhookEvent.DeliveryId = delivery.Id;
|
||||
webhookEvent.WebhookId = webhook.Id;
|
||||
webhookEvent.OriginalDeliveryId = delivery.Id;
|
||||
webhookEvent.OrignalDeliveryId = delivery.Id;
|
||||
webhookEvent.IsRedelivery = false;
|
||||
webhookEvent.Timestamp = delivery.Timestamp;
|
||||
var context = new WebhookDeliveryRequest(webhook.Id, webhookEvent, delivery, webhookBlob);
|
||||
|
@ -35,6 +35,7 @@ using BTCPayServer.Services.PaymentRequests;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.U2F;
|
||||
using BundlerMinifier.TagHelpers;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
@ -82,6 +83,7 @@ namespace BTCPayServer.Hosting
|
||||
});
|
||||
|
||||
services.AddSingleton<BTCPayNetworkJsonSerializerSettings>();
|
||||
services.RegisterJsonConverter(n => new ClaimDestinationJsonConverter(n));
|
||||
|
||||
services.AddPayJoinServices();
|
||||
#if ALTCOINS
|
||||
@ -92,7 +94,6 @@ namespace BTCPayServer.Hosting
|
||||
services.TryAddSingleton<ISettingsRepository>(provider => provider.GetService<SettingsRepository>());
|
||||
services.TryAddSingleton<LabelFactory>();
|
||||
services.TryAddSingleton<TorServices>();
|
||||
services.AddSingleton<IHostedService>(provider => provider.GetRequiredService<TorServices>());
|
||||
services.TryAddSingleton<SocketFactory>();
|
||||
services.TryAddSingleton<LightningClientFactoryService>();
|
||||
services.TryAddSingleton<InvoicePaymentNotification>();
|
||||
@ -112,6 +113,7 @@ namespace BTCPayServer.Hosting
|
||||
services.TryAddSingleton<WalletRepository>();
|
||||
services.TryAddSingleton<EventAggregator>();
|
||||
services.TryAddSingleton<PaymentRequestService>();
|
||||
services.TryAddSingleton<U2FService>();
|
||||
services.AddSingleton<ApplicationDbContextFactory>();
|
||||
services.AddOptions<BTCPayServerOptions>().Configure(
|
||||
(options) =>
|
||||
@ -316,8 +318,6 @@ namespace BTCPayServer.Hosting
|
||||
.ConfigureHttpClient(h => h.DefaultRequestHeaders.ConnectionClose = true)
|
||||
.ConfigurePrimaryHttpMessageHandler<Socks5HttpClientHandler>();
|
||||
|
||||
|
||||
services.AddSingleton<IPayoutHandler, BitcoinLikePayoutHandler>();
|
||||
services.AddSingleton<HostedServices.PullPaymentHostedService>();
|
||||
services.AddSingleton<IHostedService, HostedServices.PullPaymentHostedService>(o => o.GetRequiredService<PullPaymentHostedService>());
|
||||
|
||||
@ -345,6 +345,7 @@ namespace BTCPayServer.Hosting
|
||||
services.AddSingleton<IHostedService, TransactionLabelMarkerHostedService>();
|
||||
services.AddSingleton<IHostedService, UserEventHostedService>();
|
||||
services.AddSingleton<IHostedService, DynamicDnsHostedService>();
|
||||
services.AddSingleton<IHostedService, TorServicesHostedService>();
|
||||
services.AddSingleton<IHostedService, PaymentRequestStreamer>();
|
||||
services.AddSingleton<IBackgroundJobClient, BackgroundJobClient>();
|
||||
services.AddScoped<IAuthorizationHandler, CookieAuthorizationHandler>();
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading.Tasks;
|
||||
@ -33,8 +32,6 @@ namespace BTCPayServer.Hosting
|
||||
|
||||
public async Task Invoke(HttpContext httpContext)
|
||||
{
|
||||
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
|
||||
CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
|
||||
try
|
||||
{
|
||||
var bitpayAuth = GetBitpayAuth(httpContext, out bool isBitpayAuth);
|
||||
|
@ -1,27 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Fido2;
|
||||
using BTCPayServer.Fido2.Models;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Fido2NetLib.Objects;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBXplorer;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PeterO.Cbor;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
{
|
||||
@ -121,13 +121,6 @@ namespace BTCPayServer.Hosting
|
||||
settings.TransitionInternalNodeConnectionString = true;
|
||||
await _Settings.UpdateSetting(settings);
|
||||
}
|
||||
|
||||
if (!settings.MigrateU2FToFIDO2)
|
||||
{
|
||||
await MigrateU2FToFIDO2();
|
||||
settings.MigrateU2FToFIDO2 = true;
|
||||
await _Settings.UpdateSetting(settings);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -136,59 +129,6 @@ namespace BTCPayServer.Hosting
|
||||
}
|
||||
}
|
||||
|
||||
private async Task MigrateU2FToFIDO2()
|
||||
{
|
||||
await using var ctx = _DBContextFactory.CreateContext();
|
||||
var u2fDevices = await ctx.U2FDevices.ToListAsync();
|
||||
foreach (U2FDevice u2FDevice in u2fDevices)
|
||||
{
|
||||
var fido2 = new Fido2Credential()
|
||||
{
|
||||
ApplicationUserId = u2FDevice.ApplicationUserId,
|
||||
Name = u2FDevice.Name,
|
||||
Type = Fido2Credential.CredentialType.FIDO2
|
||||
};
|
||||
fido2.SetBlob(new Fido2CredentialBlob()
|
||||
{
|
||||
SignatureCounter = (uint)u2FDevice.Counter,
|
||||
PublicKey = CreatePublicKeyFromU2fRegistrationData( u2FDevice.PublicKey).EncodeToBytes() ,
|
||||
UserHandle = u2FDevice.KeyHandle,
|
||||
Descriptor = new PublicKeyCredentialDescriptor(u2FDevice.KeyHandle),
|
||||
CredType = "u2f"
|
||||
});
|
||||
|
||||
await ctx.AddAsync(fido2);
|
||||
|
||||
ctx.Remove(u2FDevice);
|
||||
}
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
//from https://github.com/abergs/fido2-net-lib/blob/0fa7bb4b4a1f33f46c5f7ca4ee489b47680d579b/Test/ExistingU2fRegistrationDataTests.cs#L70
|
||||
private static CBORObject CreatePublicKeyFromU2fRegistrationData(byte[] publicKeyData)
|
||||
{
|
||||
if (publicKeyData.Length != 65)
|
||||
{
|
||||
throw new ArgumentException("u2f public key must be 65 bytes", nameof(publicKeyData));
|
||||
}
|
||||
var x = new byte[32];
|
||||
var y = new byte[32];
|
||||
Buffer.BlockCopy(publicKeyData, 1, x, 0, 32);
|
||||
Buffer.BlockCopy(publicKeyData, 33, y, 0, 32);
|
||||
|
||||
|
||||
var coseKey = CBORObject.NewMap();
|
||||
|
||||
coseKey.Add(COSE.KeyCommonParameter.KeyType, COSE.KeyType.EC2);
|
||||
coseKey.Add(COSE.KeyCommonParameter.Alg, -7);
|
||||
|
||||
coseKey.Add(COSE.KeyTypeParameter.Crv, COSE.EllipticCurve.P256);
|
||||
|
||||
coseKey.Add(COSE.KeyTypeParameter.X, x);
|
||||
coseKey.Add(COSE.KeyTypeParameter.Y, y);
|
||||
|
||||
return coseKey;
|
||||
}
|
||||
|
||||
private async Task TransitionInternalNodeConnectionString()
|
||||
{
|
||||
var nodes = LightningOptions.Value.InternalLightningByCryptoCode.Values.Select(c => c.ToString()).ToHashSet();
|
||||
|
@ -1,10 +1,8 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Fido2;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.PaymentRequest;
|
||||
@ -14,11 +12,9 @@ using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Storage;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Fido2NetLib;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
@ -30,9 +26,6 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using NBitcoin;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using BTCPayServer.Controllers.GreenField;
|
||||
using System.Globalization;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
{
|
||||
@ -85,36 +78,12 @@ namespace BTCPayServer.Hosting
|
||||
services.AddProviderStorage();
|
||||
services.AddSession();
|
||||
services.AddSignalR();
|
||||
services.AddFido2(options =>
|
||||
{
|
||||
options.ServerName = "BTCPay Server";
|
||||
})
|
||||
.AddCachedMetadataService(config =>
|
||||
{
|
||||
//They'll be used in a "first match wins" way in the order registered
|
||||
config.AddStaticMetadataRepository();
|
||||
});
|
||||
var descriptor =services.Single(descriptor => descriptor.ServiceType == typeof(Fido2Configuration));
|
||||
services.Remove(descriptor);
|
||||
services.AddScoped(provider =>
|
||||
{
|
||||
var httpContext = provider.GetService<IHttpContextAccessor>();
|
||||
return new Fido2Configuration()
|
||||
{
|
||||
ServerName = "BTCPay Server",
|
||||
Origin = $"{httpContext.HttpContext.Request.Scheme}://{httpContext.HttpContext.Request.Host}",
|
||||
ServerDomain = httpContext.HttpContext.Request.Host.Host
|
||||
};
|
||||
});
|
||||
services.AddScoped<Fido2Service>();
|
||||
|
||||
var mvcBuilder= services.AddMvc(o =>
|
||||
{
|
||||
o.Filters.Add(new XFrameOptionsAttribute("DENY"));
|
||||
o.Filters.Add(new XContentTypeOptionsAttribute("nosniff"));
|
||||
o.Filters.Add(new XXSSProtectionAttribute());
|
||||
o.Filters.Add(new ReferrerPolicyAttribute("same-origin"));
|
||||
o.ModelBinderProviders.Insert(0, new ModelBinders.DefaultModelBinderProvider());
|
||||
//o.Filters.Add(new ContentSecurityPolicyAttribute()
|
||||
//{
|
||||
// FontSrc = "'self' https://fonts.gstatic.com/",
|
||||
@ -123,12 +92,14 @@ namespace BTCPayServer.Hosting
|
||||
// StyleSrc = "'self' 'unsafe-inline'",
|
||||
// ScriptSrc = "'self' 'unsafe-inline'"
|
||||
//});
|
||||
})
|
||||
})
|
||||
.ConfigureApiBehaviorOptions(options =>
|
||||
{
|
||||
var builtInFactory = options.InvalidModelStateResponseFactory;
|
||||
options.InvalidModelStateResponseFactory = context =>
|
||||
{
|
||||
return new UnprocessableEntityObjectResult(context.ModelState.ToGreenfieldValidationError());
|
||||
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.UnprocessableEntity;
|
||||
return builtInFactory(context);
|
||||
};
|
||||
})
|
||||
.AddRazorOptions(o =>
|
||||
@ -144,6 +115,8 @@ namespace BTCPayServer.Hosting
|
||||
.AddPlugins(services, Configuration, LoggerFactory)
|
||||
.AddControllersAsServices();
|
||||
|
||||
|
||||
|
||||
services.TryAddScoped<ContentSecurityPolicies>();
|
||||
services.Configure<IdentityOptions>(options =>
|
||||
{
|
||||
|
@ -1,46 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.VisualBasic.CompilerServices;
|
||||
|
||||
namespace BTCPayServer.ModelBinders
|
||||
{
|
||||
public class DateTimeOffsetModelBinder : IModelBinder
|
||||
{
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
if (!typeof(DateTimeOffset).GetTypeInfo().IsAssignableFrom(bindingContext.ModelType) &&
|
||||
!typeof(DateTimeOffset?).GetTypeInfo().IsAssignableFrom(bindingContext.ModelType))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
ValueProviderResult val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
||||
if (val == null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
string v = val.FirstValue as string;
|
||||
if (v == null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var sec = long.Parse(v, CultureInfo.InvariantCulture);
|
||||
bindingContext.Result = ModelBindingResult.Success(NBitcoin.Utils.UnixTimeToDateTime(sec));
|
||||
}
|
||||
catch
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Failed();
|
||||
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Invalid unix timestamp");
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace BTCPayServer.ModelBinders
|
||||
{
|
||||
public class DefaultModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context.Metadata.ModelType == typeof(decimal) || context.Metadata.ModelType == typeof(decimal?))
|
||||
return new InvariantDecimalModelBinder();
|
||||
if (context.Metadata.ModelType == typeof(PaymentMethodId))
|
||||
return new PaymentMethodIdModelBinder();
|
||||
if (context.Metadata.ModelType == typeof(WalletIdModelBinder))
|
||||
return new ModelBinders.WalletIdModelBinder();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -43,17 +43,11 @@ namespace BTCPayServer.ModelBinders
|
||||
var data = network.NBXplorerNetwork.DerivationStrategyFactory.Parse(key);
|
||||
if (!bindingContext.ModelType.IsInstanceOfType(data))
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Failed();
|
||||
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Invalid derivation scheme");
|
||||
return Task.CompletedTask;
|
||||
throw new FormatException("Invalid destination type");
|
||||
}
|
||||
bindingContext.Result = ModelBindingResult.Success(data);
|
||||
}
|
||||
catch
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Failed();
|
||||
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Invalid derivation scheme");
|
||||
}
|
||||
catch { throw new FormatException("Invalid derivation scheme"); }
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
@ -31,11 +31,6 @@ namespace BTCPayServer.ModelBinders
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Success(paymentId);
|
||||
}
|
||||
else
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Failed();
|
||||
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Invalid payment id");
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
@ -30,11 +30,6 @@ namespace BTCPayServer.ModelBinders
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Success(walletId);
|
||||
}
|
||||
else
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Failed();
|
||||
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Invalid wallet id");
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
using BTCPayServer.Fido2.Models;
|
||||
using BTCPayServer.U2F.Models;
|
||||
|
||||
namespace BTCPayServer.Models.AccountViewModels
|
||||
{
|
||||
public class SecondaryLoginViewModel
|
||||
{
|
||||
public LoginWithFido2ViewModel LoginWithFido2ViewModel { get; set; }
|
||||
public LoginWith2faViewModel LoginWith2FaViewModel { get; set; }
|
||||
public LoginWithU2FViewModel LoginWithU2FViewModel { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,6 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
public class UpdateCrowdfundViewModel
|
||||
{
|
||||
public string StoreId { get; set; }
|
||||
public string StoreName { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(30)]
|
||||
public string Title { get; set; }
|
||||
@ -97,7 +95,12 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
[Display(Name = "Colors to rotate between with animation when a payment is made. First color is the default background. One color per line. Can be any valid css color value.")]
|
||||
public string AnimationColors { get; set; }
|
||||
|
||||
|
||||
// NOTE: Improve validation if needed
|
||||
public bool ModelWithMinimumData => Description != null && Title != null && TargetCurrency != null;
|
||||
public bool ModelWithMinimumData
|
||||
{
|
||||
get { return Description != null && Title != null && TargetCurrency != null; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,6 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
public class UpdatePointOfSaleViewModel
|
||||
{
|
||||
public string StoreId { get; set; }
|
||||
public string StoreName { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(30)]
|
||||
public string Title { get; set; }
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user