Compare commits

..

66 Commits

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

fixes #4890

* Email provided to pos form was not forwarded to form

fixes #4810

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

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

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

* fix pay request email copy

* fix payouts nav link

fixes #4788

* Update BTCPayServer/Views/UIPaymentRequest/EditPaymentRequest.cshtml

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

---------

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

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

In addition to f05a7f9f14. Fixes #4904.

* Revert "Prevent an NRE in LNURL"

This reverts commit 0b241d61ab45b79297211e04da0e05c2cb10dada.

* Fix NRE

---------

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

* typo

* project vs product

* Suggestion Update - Docker Deployment

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

* Suggestion - Email highlight

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

---------

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

* Abstract PaymentProof

* fix bug

* Make the selenium container resolves the btcpay host name
2023-04-10 16:38:49 +09:00
nicolas.dorier
a4d72d5bbc Fix tests 2023-04-10 15:06:59 +09:00
nicolas.dorier
24b8ec16f1 Fix tests 2023-04-10 13:37:09 +09:00
nicolas.dorier
ac25fef555 Remove warning 2023-04-10 12:01:11 +09:00
nicolas.dorier
8302f082a2 Do not render the TruncateCenter if text null or empty 2023-04-10 11:17:29 +09:00
nicolas.dorier
7546ef7a8e Dotnet reformat 2023-04-10 11:07:03 +09:00
nicolas.dorier
f598c70a4f Add comment TODO for improving automatic translations efficiency. 2023-04-10 11:03:36 +09:00
nicolas.dorier
422da21de5 Update Changelog 2023-04-07 17:52:48 +09:00
222 changed files with 1754 additions and 1741 deletions

View File

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

View File

@@ -10,7 +10,7 @@ public class CustodianApiException : Exception
HttpStatus = httpStatus;
Code = code;
}
public CustodianApiException(int httpStatus, string code, string message) : this(httpStatus, code, message, null)
{
}

View File

@@ -52,10 +52,10 @@ public class Field
public string HelpText;
[JsonExtensionData] public IDictionary<string, JToken> AdditionalData { get; set; }
public List<Field> Fields { get; set; } = new ();
public List<Field> Fields { get; set; } = new();
// The field is considered "valid" if there are no validation errors
public List<string> ValidationErrors = new ();
public List<string> ValidationErrors = new();
public virtual bool IsValid()
{

View File

@@ -23,12 +23,12 @@ public class SVGUse : UrlResolutionTagHelper2
{
_fileVersionProvider = fileVersionProvider;
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var attr = output.Attributes["href"].Value.ToString();
var symbolIndex = attr!.IndexOf("#", StringComparison.InvariantCulture);
var start = attr.IndexOf("~", StringComparison.InvariantCulture) + 1;
var start = attr.IndexOf("~", StringComparison.InvariantCulture) + 1;
var length = (symbolIndex != -1 ? symbolIndex : attr.Length) - start;
var filePath = attr.Substring(start, length);
if (!string.IsNullOrEmpty(filePath))

View File

@@ -51,7 +51,7 @@ namespace BTCPayServer.Client
method: HttpMethod.Get), token);
return await HandleResponse<AppDataBase>(response);
}
public virtual async Task<AppDataBase[]> GetAllApps(string storeId, CancellationToken token = default)
{
if (storeId == null)

View File

@@ -80,13 +80,13 @@ namespace BTCPayServer.Client
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/custodian-accounts/{accountId}/trades/quote", queryPayload), token);
return await HandleResponse<TradeQuoteResponseData>(response);
}
public virtual async Task<WithdrawalResponseData> CreateCustodianAccountWithdrawal(string storeId, string accountId, WithdrawRequestData request, CancellationToken token = default)
public virtual async Task<WithdrawalResponseData> CreateCustodianAccountWithdrawal(string storeId, string accountId, WithdrawRequestData request, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/custodian-accounts/{accountId}/withdrawals", bodyPayload: request, method: HttpMethod.Post), token);
return await HandleResponse<WithdrawalResponseData>(response);
}
public virtual async Task<WithdrawalSimulationResponseData> SimulateCustodianAccountWithdrawal(string storeId, string accountId, WithdrawRequestData request, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/custodian-accounts/{accountId}/withdrawals/simulation", bodyPayload: request, method: HttpMethod.Post), token);

View File

@@ -113,7 +113,7 @@ namespace BTCPayServer.Client
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/invoices", queryPayload), token);
return await HandleResponse<LightningInvoiceData[]>(response);
}
public virtual async Task<LightningPaymentData[]> GetLightningPayments(string cryptoCode,
bool? includePending = null, long? offsetIndex = null, CancellationToken token = default)
{

View File

@@ -115,7 +115,7 @@ namespace BTCPayServer.Client
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices", queryPayload), token);
return await HandleResponse<LightningInvoiceData[]>(response);
}
public virtual async Task<LightningPaymentData[]> GetLightningPayments(string storeId, string cryptoCode,
bool? includePending = null, long? offsetIndex = null, CancellationToken token = default)
{

View File

@@ -54,7 +54,7 @@ namespace BTCPayServer.Client
CancellationToken token = default)
{
using var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/rates",
CreateHttpRequest($"api/v1/stores/{storeId}/rates",
queryPayload: new Dictionary<string, object>() { { "currencyPair", currencyPair } },
method: HttpMethod.Get),
token);

View File

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

View File

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

View File

@@ -16,12 +16,11 @@ namespace BTCPayServer.Client.Models
{
}
public LNURLPayPaymentMethodData(string cryptoCode, bool enabled, bool useBech32Scheme, bool enableForStandardInvoices)
public LNURLPayPaymentMethodData(string cryptoCode, bool enabled, bool useBech32Scheme)
{
Enabled = enabled;
CryptoCode = cryptoCode;
UseBech32Scheme = useBech32Scheme;
EnableForStandardInvoices = enableForStandardInvoices;
}
}
}

View File

@@ -6,5 +6,5 @@ public class LightningAddressData
public string CurrencyCode { get; set; }
public decimal? Min { get; set; }
public decimal? Max { get; set; }
}

View File

@@ -16,7 +16,7 @@ namespace BTCPayServer.Client.Models
[JsonProperty("BOLT11")]
public string BOLT11 { get; set; }
public string PaymentHash { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
@@ -24,7 +24,7 @@ namespace BTCPayServer.Client.Models
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset? PaidAt { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset ExpiresAt { get; set; }

View File

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

View File

@@ -16,13 +16,12 @@ namespace BTCPayServer.Client.Models
{
}
public LightningNetworkPaymentMethodData(string cryptoCode, string connectionString, bool enabled, string paymentMethod, bool disableBOLT11PaymentOption)
public LightningNetworkPaymentMethodData(string cryptoCode, string connectionString, bool enabled, string paymentMethod)
{
Enabled = enabled;
CryptoCode = cryptoCode;
ConnectionString = connectionString;
PaymentMethod = paymentMethod;
DisableBOLT11PaymentOption = disableBOLT11PaymentOption;
}
public string PaymentMethod { get; set; }

View File

@@ -1,8 +1,8 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System;
using Newtonsoft.Json;
using System.Net.Http.Headers;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models;
@@ -14,9 +14,9 @@ public class WithdrawRequestData
public WithdrawRequestData()
{
}
public WithdrawRequestData(string paymentMethod, TradeQuantity qty)
{
PaymentMethod = paymentMethod;

View File

@@ -7,7 +7,7 @@ namespace BTCPayServer.Client.Models;
public class WithdrawalResponseData : WithdrawalBaseResponseData
{
[JsonConverter(typeof(StringEnumConverter))]
public WithdrawalStatus Status { get; }

View File

@@ -124,14 +124,14 @@ namespace BTCPayServer.Data
#pragma warning disable CS0612 // Type or member is obsolete
WalletTransactionData.OnModelCreating(builder);
#pragma warning restore CS0612 // Type or member is obsolete
WebhookDeliveryData.OnModelCreating(builder, Database);
LightningAddressData.OnModelCreating(builder, Database);
PayoutProcessorData.OnModelCreating(builder, Database);
WebhookData.OnModelCreating(builder, Database);
FormData.OnModelCreating(builder, Database);
WebhookDeliveryData.OnModelCreating(builder, Database);
LightningAddressData.OnModelCreating(builder, Database);
PayoutProcessorData.OnModelCreating(builder, Database);
WebhookData.OnModelCreating(builder, Database);
FormData.OnModelCreating(builder, Database);
if (Database.IsSqlite() && !_designTime)
if (Database.IsSqlite() && !_designTime)
{
// SQLite does not have proper support for DateTimeOffset via Entity Framework Core, see the limitations
// here: https://docs.microsoft.com/en-us/ef/core/providers/sqlite/limitations#query-limitations

View File

@@ -13,14 +13,14 @@ public class FormData
public StoreData Store { get; set; }
public string Config { get; set; }
public bool Public { get; set; }
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
builder.Entity<FormData>()
.HasOne(o => o.Store)
.WithMany(o => o.Forms).OnDelete(DeleteBehavior.Cascade);
builder.Entity<FormData>().HasIndex(o => o.StoreId);
if (databaseFacade.IsNpgsql())
{
builder.Entity<FormData>()

View File

@@ -39,6 +39,6 @@ public class LightningAddressDataBlob
public string CurrencyCode { get; set; }
public decimal? Min { get; set; }
public decimal? Max { get; set; }
public JObject InvoiceMetadata { get; set; }
}

View File

@@ -24,8 +24,8 @@ namespace BTCPayServer.Data
public string Blob2 { get; set; }
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
builder.Entity<NotificationData>()
.HasOne(o => o.ApplicationUser)
.WithMany(n => n.Notifications)

View File

@@ -1,4 +1,4 @@
using System;
using System;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;

File diff suppressed because one or more lines are too long

View File

@@ -20,7 +20,7 @@ namespace BTCPayServer.Services.Rates
}
}
public RateSourceInfo RateSourceInfo => new RateSourceInfo("NULL","NULL", "https://NULL.NULL");
public RateSourceInfo RateSourceInfo => new RateSourceInfo("NULL", "NULL", "https://NULL.NULL");
public Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{

View File

@@ -89,7 +89,7 @@ namespace BTCPayServer.Services.Rates
AvailableRateProviders.Add(new(rsi.Id, rsi.DisplayName, rsi.Url, RateSource.Coingecko));
}
}
AvailableRateProviders.Sort((a,b) => StringComparer.Ordinal.Compare(a.DisplayName, b.DisplayName));
AvailableRateProviders.Sort((a, b) => StringComparer.Ordinal.Compare(a.DisplayName, b.DisplayName));
}
public List<AvailableRateProvider> AvailableRateProviders { get; } = new List<AvailableRateProvider>();

View File

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

View File

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

View File

@@ -164,7 +164,7 @@ namespace BTCPayServer.Tests
var invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC_LightningLike");
s.GoToInvoiceCheckout(invoiceId);
Assert.Equal("Bitcoin (Lightning) (BTC)", s.Driver.FindElement(By.ClassName("payment__currencies")).Text);
Assert.Equal("Bitcoin (Lightning)", s.Driver.FindElement(By.ClassName("payment__currencies")).Text);
s.Driver.Quit();
}
@@ -187,7 +187,7 @@ namespace BTCPayServer.Tests
var invoiceId = s.CreateInvoice(10, "USD", "a@g.com");
s.GoToInvoiceCheckout(invoiceId);
Assert.Contains("sats", s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Text);
Assert.Contains("sats", s.Driver.FindElement(By.ClassName("buyerTotalLine")).Text);
}
[Fact(Timeout = TestTimeout)]

View File

@@ -44,14 +44,6 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("StoreWebsite")).SendKeys(storeUrl);
s.Driver.FindElement(By.Id("Save")).Click();
Assert.Contains("Store successfully updated", s.FindAlertMessage().Text);
// Enable LNURL, which we will need for (non-)presence checks throughout this test
s.GoToHome();
s.GoToLightningSettings();
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), true);
s.Driver.FindElement(By.Id("save")).Click();
Assert.Contains("BTC Lightning settings successfully updated", s.FindAlertMessage().Text);
s.GoToStore(StoreNavPages.CheckoutAppearance);
s.Driver.WaitForAndClick(By.Id("Presets"));
@@ -79,7 +71,7 @@ namespace BTCPayServer.Tests
Assert.Equal(address, copyAddress);
Assert.Equal($"bitcoin:{address.ToUpperInvariant()}", qrValue);
s.Driver.ElementDoesNotExist(By.Id("Lightning_BTC"));
// Details should show exchange rate
s.Driver.ToggleCollapse("PaymentDetails");
s.Driver.ElementDoesNotExist(By.Id("PaymentDetails-TotalPrice"));
@@ -87,7 +79,7 @@ namespace BTCPayServer.Tests
s.Driver.ElementDoesNotExist(By.Id("PaymentDetails-AmountDue"));
Assert.Contains("$", s.Driver.FindElement(By.Id("PaymentDetails-ExchangeRate")).Text);
Assert.Contains("sat/byte", s.Driver.FindElement(By.Id("PaymentDetails-RecommendedFee")).Text);
// Switch to LNURL
s.Driver.FindElement(By.CssSelector(".payment-method:nth-child(2)")).Click();
TestUtils.Eventually(() =>
@@ -125,7 +117,7 @@ namespace BTCPayServer.Tests
s.GoToInvoiceCheckout(invoiceId);
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
Assert.Contains("sats", s.Driver.FindElement(By.Id("AmountDue")).Text);
// Details should not show exchange rate
s.Driver.ToggleCollapse("PaymentDetails");
s.Driver.ElementDoesNotExist(By.Id("PaymentDetails-ExchangeRate"));
@@ -188,8 +180,10 @@ namespace BTCPayServer.Tests
});
s.Driver.Navigate().Refresh();
// Pay full amount
s.PayInvoice();
// Processing
TestUtils.Eventually(() =>
{
@@ -197,8 +191,9 @@ namespace BTCPayServer.Tests
Assert.True(processingSection.Displayed);
Assert.Contains("Payment Received", processingSection.Text);
Assert.Contains("Your payment has been received and is now processing", processingSection.Text);
Assert.True(s.Driver.ElementDoesNotExist(By.Id("confetti")));
});
s.Driver.FindElement(By.Id("confetti"));
// Mine
s.MineBlockOnInvoiceCheckout();
TestUtils.Eventually(() =>
@@ -206,7 +201,7 @@ namespace BTCPayServer.Tests
Assert.Contains("Mined 1 block",
s.Driver.WaitForElement(By.Id("CheatSuccessMessage")).Text);
});
// Settled
TestUtils.Eventually(() =>
{
@@ -244,7 +239,7 @@ namespace BTCPayServer.Tests
Assert.StartsWith("lnbcrt", copyAddressLightning);
Assert.StartsWith($"bitcoin:{address.ToUpperInvariant()}?amount=", qrValue);
Assert.Contains("&lightning=LNBCRT", qrValue);
// Check details
s.Driver.ToggleCollapse("PaymentDetails");
Assert.Contains("1 BTC = ", s.Driver.FindElement(By.Id("PaymentDetails-ExchangeRate")).Text);
@@ -252,7 +247,7 @@ namespace BTCPayServer.Tests
Assert.Contains("$", s.Driver.FindElement(By.Id("PaymentDetails-TotalFiat")).Text);
Assert.Contains("BTC", s.Driver.FindElement(By.Id("PaymentDetails-AmountDue")).Text);
Assert.Contains("BTC", s.Driver.FindElement(By.Id("PaymentDetails-TotalPrice")).Text);
// Switch to amount displayed in sats
s.GoToHome();
s.GoToStore(StoreNavPages.CheckoutAppearance);
@@ -262,7 +257,7 @@ namespace BTCPayServer.Tests
s.GoToInvoiceCheckout(invoiceId);
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
Assert.Contains("sats", s.Driver.FindElement(By.Id("AmountDue")).Text);
// Check details
s.Driver.ToggleCollapse("PaymentDetails");
Assert.Contains("1 sat = ", s.Driver.FindElement(By.Id("PaymentDetails-ExchangeRate")).Text);
@@ -270,7 +265,7 @@ namespace BTCPayServer.Tests
Assert.Contains("$", s.Driver.FindElement(By.Id("PaymentDetails-TotalFiat")).Text);
Assert.Contains("sats", s.Driver.FindElement(By.Id("PaymentDetails-AmountDue")).Text);
Assert.Contains("sats", s.Driver.FindElement(By.Id("PaymentDetails-TotalPrice")).Text);
// BIP21 with LN as default payment method
s.GoToHome();
invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC_LightningLike");
@@ -280,7 +275,7 @@ namespace BTCPayServer.Tests
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
Assert.StartsWith("bitcoin:", payUrl);
Assert.Contains("&lightning=lnbcrt", payUrl);
// Check details
s.Driver.ToggleCollapse("PaymentDetails");
Assert.Contains("1 sat = ", s.Driver.FindElement(By.Id("PaymentDetails-ExchangeRate")).Text);
@@ -293,8 +288,7 @@ namespace BTCPayServer.Tests
s.GoToHome();
s.GoToLightningSettings();
Assert.True(s.Driver.FindElement(By.Id("LNURLEnabled")).Selected);
Assert.True(s.Driver.FindElement(By.Id("LNURLStandardInvoiceEnabled")).Selected);
// BIP21 with top-up invoice
invoiceId = s.CreateInvoice(amount: null);
s.GoToInvoiceCheckout(invoiceId);
@@ -312,7 +306,7 @@ namespace BTCPayServer.Tests
Assert.Equal(address, copyAddressOnchain);
Assert.StartsWith("lnurl", copyAddressLightning);
Assert.StartsWith($"bitcoin:{address.ToUpperInvariant()}?lightning=LNURL", qrValue);
// Check details
s.Driver.ToggleCollapse("PaymentDetails");
Assert.Contains("1 sat = ", s.Driver.FindElement(By.Id("PaymentDetails-ExchangeRate")).Text);
@@ -331,7 +325,7 @@ namespace BTCPayServer.Tests
Assert.Contains("This invoice will expire in", paymentInfo.Text);
Assert.Contains("00:0", paymentInfo.Text);
Assert.DoesNotContain("Please send", paymentInfo.Text);
// Configure countdown timer
s.GoToHome();
invoiceId = s.CreateInvoice();
@@ -343,13 +337,13 @@ namespace BTCPayServer.Tests
displayExpirationTimer.SendKeys("10");
s.Driver.FindElement(By.Id("Save")).Click();
Assert.Contains("Store successfully updated", s.FindAlertMessage().Text);
s.GoToInvoiceCheckout(invoiceId);
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
paymentInfo = s.Driver.FindElement(By.Id("PaymentInfo"));
Assert.False(paymentInfo.Displayed);
Assert.DoesNotContain("This invoice will expire in", paymentInfo.Text);
expirySeconds = s.Driver.FindElement(By.Id("ExpirySeconds"));
expirySeconds.Clear();
expirySeconds.SendKeys("599");
@@ -359,7 +353,7 @@ namespace BTCPayServer.Tests
Assert.True(paymentInfo.Displayed);
Assert.Contains("This invoice will expire in", paymentInfo.Text);
Assert.Contains("09:5", paymentInfo.Text);
// Disable LNURL again
s.GoToHome();
s.GoToLightningSettings();
@@ -378,7 +372,7 @@ namespace BTCPayServer.Tests
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
Assert.StartsWith("bitcoin:", payUrl);
Assert.Contains("&lightning=lnbcrt", payUrl);
// Language Switch
var languageSelect = new SelectElement(s.Driver.FindElement(By.Id("DefaultLang")));
Assert.Equal("English", languageSelect.SelectedOption.Text);
@@ -387,9 +381,9 @@ namespace BTCPayServer.Tests
languageSelect.SelectByText("Deutsch");
Assert.Equal("Details anzeigen", s.Driver.FindElement(By.Id("DetailsToggle")).Text);
Assert.Contains("lang=de", s.Driver.Url);
s.Driver.Navigate().Refresh();
languageSelect = new SelectElement(s.Driver.FindElement(By.Id("DefaultLang")));
languageSelect = new SelectElement(s.Driver.WaitForElement(By.Id("DefaultLang")));
Assert.Equal("Deutsch", languageSelect.SelectedOption.Text);
Assert.Equal("Details anzeigen", s.Driver.FindElement(By.Id("DetailsToggle")).Text);
languageSelect.SelectByText("English");

View File

@@ -626,7 +626,7 @@ namespace BTCPayServer.Tests
[Fact]
public void RoundupCurrenciesCorrectly()
{
DisplayFormatter displayFormatter = new (CurrencyNameTable.Instance);
DisplayFormatter displayFormatter = new(CurrencyNameTable.Instance);
foreach (var test in new[]
{
(0.0005m, "0.0005 USD", "USD"), (0.001m, "0.001 USD", "USD"), (0.01m, "0.01 USD", "USD"),
@@ -766,7 +766,7 @@ namespace BTCPayServer.Tests
var root = new Mnemonic(
"usage fever hen zero slide mammal silent heavy donate budget pulse say brain thank sausage brand craft about save attract muffin advance illegal cabbage")
.DeriveExtKey();
// xpub
var tpub = "tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS";
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(tpub, testnet, out var settings, out var error));

View File

@@ -116,7 +116,7 @@ public class FormTests : UnitTestBase
break;
}
}
form = new Form()
{
Fields = new List<Field>
@@ -143,7 +143,7 @@ public class FormTests : UnitTestBase
{"invoice_item3", new StringValues("updated")},
{"invoice_test", new StringValues("updated")}
}));
foreach (var f in form.GetAllFields())
{
var field = f.Field;
@@ -185,7 +185,7 @@ public class FormTests : UnitTestBase
form.SetValues(obj);
obj = service.GetValues(form);
Assert.Null(obj["test"].Value<string>());
form.SetValues(new JObject{ ["test"] = "hello" });
form.SetValues(new JObject { ["test"] = "hello" });
obj = service.GetValues(form);
Assert.Equal("hello", obj["test"].Value<string>());
}

View File

@@ -15,12 +15,15 @@ using BTCPayServer.Lightning;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.PayoutProcessors;
using BTCPayServer.PayoutProcessors.OnChain;
using BTCPayServer.Services;
using BTCPayServer.Services.Custodian.Client.MockCustodian;
using BTCPayServer.Services.Notifications;
using BTCPayServer.Services.Notifications.Blobs;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using NBitcoin;
using NBitpayClient;
using Newtonsoft.Json;
@@ -302,7 +305,8 @@ namespace BTCPayServer.Tests
{
await client.GetApp("some random ID lol");
});
await AssertHttpError(404, async () => {
await AssertHttpError(404, async () =>
{
await client.GetPosApp("some random ID lol");
});
@@ -451,7 +455,7 @@ namespace BTCPayServer.Tests
// Test creating a crowdfund app
var app = await client.CreateCrowdfundApp(
user.StoreId,
new CreateCrowdfundAppRequest()
new CreateCrowdfundAppRequest()
{
AppName = "test app from API",
Title = "test app title"
@@ -462,10 +466,12 @@ namespace BTCPayServer.Tests
Assert.Equal("Crowdfund", app.AppType);
// Make sure we return a 404 if we try to get an app that doesn't exist
await AssertHttpError(404, async () => {
await AssertHttpError(404, async () =>
{
await client.GetApp("some random ID lol");
});
await AssertHttpError(404, async () => {
await AssertHttpError(404, async () =>
{
await client.GetCrowdfundApp("some random ID lol");
});
@@ -488,7 +494,8 @@ namespace BTCPayServer.Tests
// Test deleting the newly created app
await client.DeleteApp(retrievedApp.Id);
await AssertHttpError(404, async () => {
await AssertHttpError(404, async () =>
{
await client.GetApp(retrievedApp.Id);
});
}
@@ -512,8 +519,8 @@ namespace BTCPayServer.Tests
}
);
var crowdfundApp = await client.CreateCrowdfundApp(user.StoreId, new CreateCrowdfundAppRequest() { AppName = "test app from API" });
// Create another store and one app on it so we can get all apps from all stores for the user below
// Create another store and one app on it so we can get all apps from all stores for the user below
var newStore = await client.CreateStore(new CreateStoreRequest() { Name = "A" });
var newApp = await client.CreateCrowdfundApp(newStore.Id, new CreateCrowdfundAppRequest() { AppName = "new app" });
@@ -544,7 +551,7 @@ namespace BTCPayServer.Tests
Assert.Equal(crowdfundApp.Name, apps[1].Name);
Assert.Equal(crowdfundApp.StoreId, apps[1].StoreId);
Assert.Equal(crowdfundApp.AppType, apps[1].AppType);
Assert.Equal(newApp.Name, apps[2].Name);
Assert.Equal(newApp.StoreId, apps[2].StoreId);
Assert.Equal(newApp.AppType, apps[2].AppType);
@@ -1066,7 +1073,7 @@ namespace BTCPayServer.Tests
var lnrURLs = await unauthenticated.GetPullPaymentLNURL(test4.Id);
Assert.IsType<string>(lnrURLs.LNURLBech32);
Assert.IsType<string>(lnrURLs.LNURLUri);
//permission test around auto approved pps and payouts
var nonApproved = await acc.CreateClient(Policies.CanCreateNonApprovedPullPayments);
var approved = await acc.CreateClient(Policies.CanCreatePullPayments);
@@ -1091,7 +1098,7 @@ namespace BTCPayServer.Tests
Destination = new Key().GetAddress(ScriptPubKeyType.TaprootBIP86, Network.RegTest).ToString()
});
});
var pullPayment = await approved.CreatePullPayment(acc.StoreId, new CreatePullPaymentRequest()
{
Amount = 100,
@@ -1100,7 +1107,7 @@ namespace BTCPayServer.Tests
PaymentMethods = new[] { "BTC" },
AutoApproveClaims = true
});
var p = await approved.CreatePayout(acc.StoreId, new CreatePayoutThroughStoreRequest()
{
Amount = 100,
@@ -1256,7 +1263,10 @@ namespace BTCPayServer.Tests
//update store
Assert.Empty(newStore.PaymentMethodCriteria);
await client.GenerateOnChainWallet(newStore.Id, "BTC", new GenerateOnChainWalletRequest());
var updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest() { Name = "B", PaymentMethodCriteria = new List<PaymentMethodCriteriaData>()
var updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest()
{
Name = "B",
PaymentMethodCriteria = new List<PaymentMethodCriteriaData>()
{
new()
{
@@ -1265,7 +1275,8 @@ namespace BTCPayServer.Tests
PaymentMethod = "BTC",
CurrencyCode = "USD"
}
}});
}
});
Assert.Equal("B", updatedStore.Name);
var s = (await client.GetStore(newStore.Id));
Assert.Equal("B", s.Name);
@@ -1275,9 +1286,9 @@ namespace BTCPayServer.Tests
Assert.True(pmc.Above);
Assert.Equal("BTC", pmc.PaymentMethod);
Assert.Equal("USD", pmc.CurrencyCode);
updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest() { Name = "B"});
updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest() { Name = "B" });
Assert.Empty(newStore.PaymentMethodCriteria);
//list stores
var stores = await client.GetStores();
var storeIds = stores.Select(data => data.Id);
@@ -1466,6 +1477,7 @@ namespace BTCPayServer.Tests
var newDeliveryId = await clientProfile.RedeliverWebhook(user.StoreId, hook.Id, delivery.Id);
req = await fakeServer.GetNextRequest();
req.Response.StatusCode = 404;
Assert.StartsWith("BTCPayServer", Assert.Single(req.Request.Headers.UserAgent));
await TestUtils.EventuallyAsync(async () =>
{
// Releasing semaphore several times may help making this test less flaky
@@ -2331,10 +2343,10 @@ namespace BTCPayServer.Tests
Assert.NotNull(merchantInvoice.Id);
Assert.NotNull(merchantInvoice.PaymentHash);
Assert.Equal(merchantInvoice.Id, merchantInvoice.PaymentHash);
// The default client is using charge, so we should not be able to query channels
var chargeClient = await user.CreateClient(Policies.CanUseInternalLightningNode);
var info = await chargeClient.GetLightningNodeInfo("BTC");
Assert.Single(info.NodeURIs);
Assert.NotEqual(0, info.BlockHeight);
@@ -2403,7 +2415,7 @@ namespace BTCPayServer.Tests
Assert.NotNull(payResponse.FeeAmount);
Assert.NotNull(payResponse.TotalAmount);
Assert.NotNull(payResponse.PaymentHash);
// check the get invoice response
var merchInvoice = await merchantClient.GetLightningInvoice(merchant.StoreId, "BTC", merchantInvoice.Id);
Assert.NotNull(merchInvoice);
@@ -2442,7 +2454,7 @@ namespace BTCPayServer.Tests
// Amount received might be bigger because of internal implementation shit from lightning
Assert.True(LightMoney.Satoshis(1000) <= invoice.AmountReceived);
// check payments list for store node
var payments = await client.GetLightningPayments(user.StoreId, "BTC");
Assert.NotEmpty(payments);
@@ -2488,7 +2500,7 @@ namespace BTCPayServer.Tests
var user = tester.NewAccount();
await user.GrantAccessAsync(true);
user.RegisterLightningNode("BTC", LightningConnectionType.CLightning);
var client = await user.CreateClient(Policies.Unrestricted);
var invoice = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest
@@ -2503,12 +2515,12 @@ namespace BTCPayServer.Tests
});
var pm = Assert.Single(await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id));
Assert.False(pm.AdditionalData.HasValues);
var resp = await tester.CustomerLightningD.Pay(pm.Destination);
Assert.Equal(PayResult.Ok, resp.Result);
Assert.NotNull(resp.Details.PaymentHash);
Assert.NotNull(resp.Details.Preimage);
pm = Assert.Single(await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id));
Assert.True(pm.AdditionalData.HasValues);
Assert.Equal(resp.Details.PaymentHash.ToString(), pm.AdditionalData.GetValue("paymentHash"));
@@ -3193,7 +3205,7 @@ namespace BTCPayServer.Tests
}
[Fact(Timeout =TestTimeout)]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task StoreLightningAddressesAPITests()
{
@@ -3205,14 +3217,14 @@ namespace BTCPayServer.Tests
var store = await adminClient.GetStore(admin.StoreId);
Assert.Empty(await adminClient.GetStorePaymentMethods(store.Id));
var store2 = (await adminClient.CreateStore(new CreateStoreRequest() {Name = "test2"})).Id;
var store2 = (await adminClient.CreateStore(new CreateStoreRequest() { Name = "test2" })).Id;
var address1 = Guid.NewGuid().ToString("n").Substring(0, 8);
var address2 = Guid.NewGuid().ToString("n").Substring(0, 8);
Assert.Empty(await adminClient.GetStoreLightningAddresses(store.Id));
Assert.Empty(await adminClient.GetStoreLightningAddresses(store2));
Assert.Empty(await adminClient.GetStoreLightningAddresses(store.Id));
Assert.Empty(await adminClient.GetStoreLightningAddresses(store2));
await adminClient.AddOrUpdateStoreLightningAddress(store.Id, address1, new LightningAddressData());
await adminClient.AddOrUpdateStoreLightningAddress(store.Id, address1, new LightningAddressData()
{
Max = 1
@@ -3221,8 +3233,8 @@ namespace BTCPayServer.Tests
{
await adminClient.AddOrUpdateStoreLightningAddress(store2, address1, new LightningAddressData());
});
Assert.Equal(1,Assert.Single(await adminClient.GetStoreLightningAddresses(store.Id)).Max);
Assert.Empty(await adminClient.GetStoreLightningAddresses(store2));
Assert.Equal(1, Assert.Single(await adminClient.GetStoreLightningAddresses(store.Id)).Max);
Assert.Empty(await adminClient.GetStoreLightningAddresses(store2));
await adminClient.AddOrUpdateStoreLightningAddress(store2, address2, new LightningAddressData());
@@ -3233,8 +3245,8 @@ namespace BTCPayServer.Tests
await adminClient.RemoveStoreLightningAddress(store2, address1);
});
await adminClient.RemoveStoreLightningAddress(store2, address2);
Assert.Empty(await adminClient.GetStoreLightningAddresses(store2));
Assert.Empty(await adminClient.GetStoreLightningAddresses(store2));
}
[Fact(Timeout = 60 * 2 * 1000)]
@@ -3400,8 +3412,8 @@ namespace BTCPayServer.Tests
});
Assert.Empty(await adminClient.GetStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork"));
await adminClient.UpdateStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork",
new LightningAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(2) });
Assert.Equal(2, Assert.Single(await adminClient.GetStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork")).IntervalSeconds.TotalSeconds);
new LightningAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(600) });
Assert.Equal(600, Assert.Single(await adminClient.GetStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork")).IntervalSeconds.TotalSeconds);
await TestUtils.EventuallyAsync(async () =>
{
var payoutC =
@@ -3486,8 +3498,8 @@ namespace BTCPayServer.Tests
Assert.Empty(await adminClient.GetPayoutProcessors(admin.StoreId));
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC",
new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(100000) });
Assert.Equal(100000, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(3600) });
Assert.Equal(3600, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
var tpGen = Assert.Single(await adminClient.GetPayoutProcessors(admin.StoreId));
Assert.Equal("BTC", Assert.Single(tpGen.PaymentMethods));
@@ -3500,8 +3512,10 @@ namespace BTCPayServer.Tests
Assert.Empty(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC"));
Assert.Empty(await adminClient.GetPayoutProcessors(admin.StoreId));
// Send just enough money to cover the smallest of the payouts.
var fee = (await tester.PayTester.GetService<IFeeProviderFactory>().CreateFeeProvider(tester.DefaultNetwork).GetFeeRateAsync(100)).GetFee(150);
await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.000012m));
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.00001m) + fee);
await tester.ExplorerNode.GenerateAsync(1);
await TestUtils.EventuallyAsync(async () =>
{
@@ -3511,8 +3525,9 @@ namespace BTCPayServer.Tests
Assert.Equal(3, payouts.Length);
});
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC",
new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(5) });
Assert.Equal(5, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(600), FeeBlockTarget = 1000 });
Assert.Equal(600, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
await TestUtils.EventuallyAsync(async () =>
{
Assert.Equal(2, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count());
@@ -3520,8 +3535,10 @@ namespace BTCPayServer.Tests
Assert.Single(payouts.Where(data => data.State == PayoutState.InProgress));
});
await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.01m));
var txid = await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.01m) + fee);
await tester.WaitForEvent<NewOnChainTransactionEvent>(null, correctEvent: ev => ev.NewTransactionEvent.TransactionData.TransactionHash == txid);
await tester.PayTester.GetService<PayoutProcessorService>().Restart(new PayoutProcessorService.PayoutProcessorQuery(admin.StoreId, "BTC"));
await TestUtils.EventuallyAsync(async () =>
{
Assert.Equal(4, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count());
@@ -3706,7 +3723,7 @@ namespace BTCPayServer.Tests
Assert.Equal(0.9m,
Assert.Single(await clientBasic.GetStoreRates(user.StoreId, new[] { "BTC_XYZ" })).Rate);
config = await clientBasic.GetStoreRateConfiguration(user.StoreId);
Assert.NotNull(config);
Assert.NotNull(config.EffectiveScript);
@@ -3940,7 +3957,7 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
var withdrawalClient = await admin.CreateClient(Policies.CanWithdrawFromCustodianAccounts);
var depositClient = await admin.CreateClient(Policies.CanDepositToCustodianAccounts);
var tradeClient = await admin.CreateClient(Policies.CanTradeCustodianAccount);
var store = await adminClient.GetStore(admin.StoreId);
var storeId = store.Id;
@@ -3981,19 +3998,19 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
// Test: GetDepositAddress, unauth
await AssertHttpError(401, async () => await unauthClient.GetCustodianAccountDepositAddress(storeId, accountId, MockCustodian.DepositPaymentMethod));
// Test: GetDepositAddress, auth, but wrong permission
await AssertHttpError(403, async () => await managerClient.GetCustodianAccountDepositAddress(storeId, accountId, MockCustodian.DepositPaymentMethod));
// Test: GetDepositAddress, wrong payment method
await AssertApiError( 400, "unsupported-payment-method", async () => await depositClient.GetCustodianAccountDepositAddress(storeId, accountId, "WRONG-PaymentMethod"));
await AssertApiError(400, "unsupported-payment-method", async () => await depositClient.GetCustodianAccountDepositAddress(storeId, accountId, "WRONG-PaymentMethod"));
// Test: GetDepositAddress, wrong store ID
await AssertHttpError(403, async () => await depositClient.GetCustodianAccountDepositAddress("WRONG-STORE", accountId, MockCustodian.DepositPaymentMethod));
// Test: GetDepositAddress, wrong account ID
await AssertHttpError(404, async () => await depositClient.GetCustodianAccountDepositAddress(storeId, "WRONG-ACCOUNT-ID", MockCustodian.DepositPaymentMethod));
// Test: GetDepositAddress, correct payment method
var depositAddress = await depositClient.GetCustodianAccountDepositAddress(storeId, accountId, MockCustodian.DepositPaymentMethod);
Assert.NotNull(depositAddress);
@@ -4001,7 +4018,7 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
// Test: Trade, unauth
var tradeRequest = new TradeRequestData { FromAsset = MockCustodian.TradeFromAsset, ToAsset = MockCustodian.TradeToAsset, Qty = new TradeQuantity(MockCustodian.TradeQtyBought, TradeQuantity.ValueType.Exact)};
var tradeRequest = new TradeRequestData { FromAsset = MockCustodian.TradeFromAsset, ToAsset = MockCustodian.TradeToAsset, Qty = new TradeQuantity(MockCustodian.TradeQtyBought, TradeQuantity.ValueType.Exact) };
await AssertHttpError(401, async () => await unauthClient.MarketTradeCustodianAccountAsset(storeId, accountId, tradeRequest));
// Test: Trade, auth, but wrong permission
@@ -4049,11 +4066,11 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
// Test: GetTradeQuote, unauth
await AssertHttpError(401, async () => await unauthClient.GetCustodianAccountTradeQuote(storeId, accountId, MockCustodian.TradeFromAsset, MockCustodian.TradeToAsset));
await AssertHttpError(401, async () => await unauthClient.GetCustodianAccountTradeQuote(storeId, accountId, MockCustodian.TradeFromAsset, MockCustodian.TradeToAsset));
// Test: GetTradeQuote, auth, but wrong permission
await AssertHttpError(403, async () => await managerClient.GetCustodianAccountTradeQuote(storeId, accountId, MockCustodian.TradeFromAsset, MockCustodian.TradeToAsset));
await AssertHttpError(403, async () => await managerClient.GetCustodianAccountTradeQuote(storeId, accountId, MockCustodian.TradeFromAsset, MockCustodian.TradeToAsset));
// Test: GetTradeQuote, auth, correct permission
var tradeQuote = await tradeClient.GetCustodianAccountTradeQuote(storeId, accountId, MockCustodian.TradeFromAsset, MockCustodian.TradeToAsset);
Assert.NotNull(tradeQuote);
@@ -4063,28 +4080,28 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
Assert.Equal(MockCustodian.BtcPriceInEuro, tradeQuote.Ask);
// Test: GetTradeQuote, SATS
await AssertApiError(400, "use-asset-synonym", async () => await tradeClient.GetCustodianAccountTradeQuote(storeId, accountId, MockCustodian.TradeFromAsset, "SATS"));
await AssertApiError(400, "use-asset-synonym", async () => await tradeClient.GetCustodianAccountTradeQuote(storeId, accountId, MockCustodian.TradeFromAsset, "SATS"));
// Test: GetTradeQuote, wrong asset
await AssertHttpError(404, async () => await tradeClient.GetCustodianAccountTradeQuote(storeId, accountId, "WRONG-ASSET", MockCustodian.TradeToAsset));
await AssertHttpError(404, async () => await tradeClient.GetCustodianAccountTradeQuote(storeId, accountId, MockCustodian.TradeFromAsset , "WRONG-ASSET"));
await AssertHttpError(404, async () => await tradeClient.GetCustodianAccountTradeQuote(storeId, accountId, "WRONG-ASSET", MockCustodian.TradeToAsset));
await AssertHttpError(404, async () => await tradeClient.GetCustodianAccountTradeQuote(storeId, accountId, MockCustodian.TradeFromAsset, "WRONG-ASSET"));
// Test: wrong account ID
await AssertHttpError(404, async () => await tradeClient.GetCustodianAccountTradeQuote(storeId, "WRONG-ACCOUNT-ID", MockCustodian.TradeFromAsset, MockCustodian.TradeToAsset));
await AssertHttpError(404, async () => await tradeClient.GetCustodianAccountTradeQuote(storeId, "WRONG-ACCOUNT-ID", MockCustodian.TradeFromAsset, MockCustodian.TradeToAsset));
// Test: wrong store ID
await AssertHttpError(403, async () => await tradeClient.GetCustodianAccountTradeQuote("WRONG-STORE-ID", accountId, MockCustodian.TradeFromAsset, MockCustodian.TradeToAsset));
await AssertHttpError(403, async () => await tradeClient.GetCustodianAccountTradeQuote("WRONG-STORE-ID", accountId, MockCustodian.TradeFromAsset, MockCustodian.TradeToAsset));
// Test: GetTradeInfo, unauth
await AssertHttpError(401, async () => await unauthClient.GetCustodianAccountTradeInfo(storeId, accountId, MockCustodian.TradeId));
await AssertHttpError(401, async () => await unauthClient.GetCustodianAccountTradeInfo(storeId, accountId, MockCustodian.TradeId));
// Test: GetTradeInfo, auth, but wrong permission
await AssertHttpError(403, async () => await managerClient.GetCustodianAccountTradeInfo(storeId, accountId, MockCustodian.TradeId));
await AssertHttpError(403, async () => await managerClient.GetCustodianAccountTradeInfo(storeId, accountId, MockCustodian.TradeId));
// Test: GetTradeInfo, auth, correct permission
var tradeResult = await tradeClient.GetCustodianAccountTradeInfo(storeId, accountId, MockCustodian.TradeId);
Assert.NotNull(tradeResult);
@@ -4107,36 +4124,36 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
// Test: GetTradeInfo, wrong trade ID
await AssertHttpError(404, async () => await tradeClient.GetCustodianAccountTradeInfo(storeId, accountId, "WRONG-TRADE-ID"));
// Test: wrong account ID
await AssertHttpError(404, async () => await tradeClient.GetCustodianAccountTradeInfo(storeId, "WRONG-ACCOUNT-ID", MockCustodian.TradeId));
// Test: wrong store ID
await AssertHttpError(403, async () => await tradeClient.GetCustodianAccountTradeInfo("WRONG-STORE-ID", accountId, MockCustodian.TradeId));
var qty = new TradeQuantity(MockCustodian.WithdrawalAmount, TradeQuantity.ValueType.Exact);
// Test: SimulateWithdrawal, unauth
// Test: SimulateWithdrawal, unauth
var simulateWithdrawalRequest = new WithdrawRequestData(MockCustodian.WithdrawalPaymentMethod, qty);
await AssertHttpError(401, async () => await unauthClient.SimulateCustodianAccountWithdrawal(storeId, accountId, simulateWithdrawalRequest));
// Test: SimulateWithdrawal, auth, but wrong permission
await AssertHttpError(403, async () => await managerClient.SimulateCustodianAccountWithdrawal(storeId, accountId, simulateWithdrawalRequest));
// Test: SimulateWithdrawal, correct payment method, correct amount
var simulateWithdrawResponse = await withdrawalClient.SimulateCustodianAccountWithdrawal(storeId, accountId, simulateWithdrawalRequest);
AssertMockWithdrawal(simulateWithdrawResponse, custodianAccountData);
// Test: SimulateWithdrawal, wrong payment method
var wrongPaymentMethodSimulateWithdrawalRequest = new WithdrawRequestData("WRONG-PAYMENT-METHOD", qty);
await AssertApiError( 400, "unsupported-payment-method", async () => await withdrawalClient.SimulateCustodianAccountWithdrawal(storeId, accountId, wrongPaymentMethodSimulateWithdrawalRequest));
await AssertApiError(400, "unsupported-payment-method", async () => await withdrawalClient.SimulateCustodianAccountWithdrawal(storeId, accountId, wrongPaymentMethodSimulateWithdrawalRequest));
// Test: SimulateWithdrawal, wrong account ID
await AssertHttpError(404, async () => await withdrawalClient.SimulateCustodianAccountWithdrawal(storeId, "WRONG-ACCOUNT-ID", simulateWithdrawalRequest));
// Test: SimulateWithdrawal, wrong store ID
// TODO it is wierd that 403 is considered normal, but it is like this for all calls where the store is wrong... I'd have preferred a 404 error, because the store cannot be found.
await AssertHttpError(403, async () => await withdrawalClient.SimulateCustodianAccountWithdrawal( "WRONG-STORE-ID",accountId, simulateWithdrawalRequest));
await AssertHttpError(403, async () => await withdrawalClient.SimulateCustodianAccountWithdrawal("WRONG-STORE-ID", accountId, simulateWithdrawalRequest));
// Test: SimulateWithdrawal, correct payment method, wrong amount
var wrongAmountSimulateWithdrawalRequest = new WithdrawRequestData(MockCustodian.WithdrawalPaymentMethod, TradeQuantity.Parse("0.666"));
await AssertHttpError(400, async () => await withdrawalClient.SimulateCustodianAccountWithdrawal(storeId, accountId, wrongAmountSimulateWithdrawalRequest));
@@ -4145,53 +4162,53 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
var createWithdrawalRequest = new WithdrawRequestData(MockCustodian.WithdrawalPaymentMethod, qty);
var createWithdrawalRequestPercentage = new WithdrawRequestData(MockCustodian.WithdrawalPaymentMethod, qty);
await AssertHttpError(401, async () => await unauthClient.CreateCustodianAccountWithdrawal(storeId, accountId, createWithdrawalRequest));
// Test: CreateWithdrawal, auth, but wrong permission
await AssertHttpError(403, async () => await managerClient.CreateCustodianAccountWithdrawal(storeId, accountId, createWithdrawalRequest));
// Test: CreateWithdrawal, correct payment method, correct amount
var withdrawResponse = await withdrawalClient.CreateCustodianAccountWithdrawal(storeId, accountId, createWithdrawalRequest);
AssertMockWithdrawal(withdrawResponse, custodianAccountData);
// Test: CreateWithdrawal, correct payment method, correct amount, but as a percentage
var withdrawWithPercentageResponse = await withdrawalClient.CreateCustodianAccountWithdrawal(storeId, accountId, createWithdrawalRequestPercentage);
AssertMockWithdrawal(withdrawWithPercentageResponse, custodianAccountData);
// Test: CreateWithdrawal, wrong payment method
var wrongPaymentMethodCreateWithdrawalRequest = new WithdrawRequestData("WRONG-PAYMENT-METHOD", qty);
await AssertApiError( 400, "unsupported-payment-method", async () => await withdrawalClient.CreateCustodianAccountWithdrawal(storeId, accountId, wrongPaymentMethodCreateWithdrawalRequest));
await AssertApiError(400, "unsupported-payment-method", async () => await withdrawalClient.CreateCustodianAccountWithdrawal(storeId, accountId, wrongPaymentMethodCreateWithdrawalRequest));
// Test: CreateWithdrawal, wrong account ID
await AssertHttpError(404, async () => await withdrawalClient.CreateCustodianAccountWithdrawal(storeId, "WRONG-ACCOUNT-ID", createWithdrawalRequest));
// Test: CreateWithdrawal, wrong store ID
// TODO it is wierd that 403 is considered normal, but it is like this for all calls where the store is wrong... I'd have preferred a 404 error, because the store cannot be found.
await AssertHttpError(403, async () => await withdrawalClient.CreateCustodianAccountWithdrawal( "WRONG-STORE-ID",accountId, createWithdrawalRequest));
await AssertHttpError(403, async () => await withdrawalClient.CreateCustodianAccountWithdrawal("WRONG-STORE-ID", accountId, createWithdrawalRequest));
// Test: CreateWithdrawal, correct payment method, wrong amount
var wrongAmountCreateWithdrawalRequest = new WithdrawRequestData(MockCustodian.WithdrawalPaymentMethod, TradeQuantity.Parse("0.666"));
await AssertHttpError(400, async () => await withdrawalClient.CreateCustodianAccountWithdrawal(storeId, accountId, wrongAmountCreateWithdrawalRequest));
// Test: GetWithdrawalInfo, unauth
await AssertHttpError(401, async () => await unauthClient.GetCustodianAccountWithdrawalInfo(storeId, accountId, MockCustodian.WithdrawalPaymentMethod, MockCustodian.WithdrawalId));
// Test: GetWithdrawalInfo, auth, but wrong permission
await AssertHttpError(403, async () => await managerClient.GetCustodianAccountWithdrawalInfo(storeId, accountId, MockCustodian.WithdrawalPaymentMethod, MockCustodian.WithdrawalId));
// Test: GetWithdrawalInfo, auth, correct permission
var withdrawalInfo = await withdrawalClient.GetCustodianAccountWithdrawalInfo(storeId, accountId, MockCustodian.WithdrawalPaymentMethod, MockCustodian.WithdrawalId);
AssertMockWithdrawal(withdrawalInfo, custodianAccountData);
// Test: GetWithdrawalInfo, wrong withdrawal ID
await AssertHttpError(404, async () => await withdrawalClient.GetCustodianAccountWithdrawalInfo(storeId, accountId, MockCustodian.WithdrawalPaymentMethod, "WRONG-WITHDRAWAL-ID"));
// Test: wrong account ID
await AssertHttpError(404, async () => await withdrawalClient.GetCustodianAccountWithdrawalInfo(storeId, "WRONG-ACCOUNT-ID", MockCustodian.WithdrawalPaymentMethod, MockCustodian.WithdrawalId));
// Test: wrong store ID
// TODO shouldn't this be 404? I cannot change this without bigger impact, as it would affect all API endpoints that are store centered
await AssertHttpError(403, async () => await withdrawalClient.GetCustodianAccountWithdrawalInfo("WRONG-STORE-ID", accountId, MockCustodian.WithdrawalPaymentMethod, MockCustodian.WithdrawalId));
// TODO assert API error codes, not just status codes by using AssertCustodianApiError()
// TODO also test withdrawals for the various "Status" (Queued, Complete, Failed)
// TODO create a mock custodian with only ICustodian

View File

@@ -139,7 +139,7 @@ public class MockCustodian : ICustodian, ICanDeposit, ICanTrade, ICanWithdraw
var r = new WithdrawResult(WithdrawalPaymentMethod, WithdrawalAsset, ledgerEntries, WithdrawalId, WithdrawalStatus, createdTime, WithdrawalTargetAddress, WithdrawalTransactionId);
return r;
}
private SimulateWithdrawalResult CreateWithdrawSimulationResult()
{
var ledgerEntries = new List<LedgerEntryData>();
@@ -153,7 +153,7 @@ public class MockCustodian : ICustodian, ICanDeposit, ICanTrade, ICanWithdraw
{
if (paymentMethod == WithdrawalPaymentMethod)
{
if (amount.ToString(CultureInfo.InvariantCulture).Equals(""+WithdrawalAmount, StringComparison.InvariantCulture) || WithdrawalAmountPercentage.Equals(amount))
if (amount.ToString(CultureInfo.InvariantCulture).Equals("" + WithdrawalAmount, StringComparison.InvariantCulture) || WithdrawalAmountPercentage.Equals(amount))
{
return Task.FromResult(CreateWithdrawResult());
}

View File

@@ -1,4 +1,5 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -64,7 +65,6 @@ namespace BTCPayServer.Tests
var containerIp = File.ReadAllText("/etc/hosts").Split('\n', StringSplitOptions.RemoveEmptyEntries).Last()
.Split('\t', StringSplitOptions.RemoveEmptyEntries)[0].Trim();
TestLogs.LogInformation($"Selenium: Container's IP {containerIp}");
ServerUri = new Uri(Server.PayTester.ServerUri.AbsoluteUri.Replace($"http://{Server.PayTester.HostName}", $"http://{containerIp}", StringComparison.OrdinalIgnoreCase), UriKind.Absolute);
}
else
{
@@ -76,8 +76,8 @@ namespace BTCPayServer.Tests
Driver = new ChromeDriver(cds, options,
// A bit less than test timeout
TimeSpan.FromSeconds(50));
ServerUri = Server.PayTester.ServerUri;
}
ServerUri = Server.PayTester.ServerUri;
Driver.Manage().Window.Maximize();
TestLogs.LogInformation($"Selenium: Using {Driver.GetType()}");
@@ -87,7 +87,7 @@ namespace BTCPayServer.Tests
Driver.AssertNoError();
}
public void PayInvoice(bool mine = false, decimal? amount= null)
public void PayInvoice(bool mine = false, decimal? amount = null)
{
if (amount is not null)

View File

@@ -11,8 +11,10 @@ using System.Threading.Tasks;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Lightning;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Payments;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
@@ -116,8 +118,11 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Name("buyerEmail")).SendKeys("aa@aa.com");
s.Driver.FindElement(By.CssSelector("input[type='submit']")).Click();
invoiceId = s.Driver.Url.Split('/').Last();
s.Driver.Navigate().GoToUrl(editUrl);
Assert.Contains("aa@aa.com", s.Driver.PageSource);
var invoice = await s.Server.PayTester.GetService<InvoiceRepository>().GetInvoice(invoiceId);
Assert.Equal("aa@aa.com", invoice.Metadata.BuyerEmail);
//Custom Forms
s.GoToStore(StoreNavPages.Forms);
@@ -148,7 +153,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.LinkText("Remove")).Click();
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
Assert.DoesNotContain("Custom Form 1", s.Driver.PageSource);
s.Driver.FindElement(By.Id("CreateForm")).Click();
s.Driver.FindElement(By.Name("Name")).SendKeys("Custom Form 2");
@@ -163,23 +168,23 @@ namespace BTCPayServer.Tests
formurl = s.Driver.Url;
result = await s.Server.PayTester.HttpClient.GetAsync(formurl);
Assert.NotEqual(HttpStatusCode.NotFound, result.StatusCode);
s.GoToHome();
s.GoToStore(StoreNavPages.Forms);
Assert.Contains("Custom Form 2", s.Driver.PageSource);
s.Driver.FindElement(By.LinkText("Custom Form 2")).Click();
s.Driver.FindElement(By.Name("Name")).Clear();
s.Driver.FindElement(By.Name("Name")).SendKeys("Custom Form 3");
s.Driver.FindElement(By.Id("SaveButton")).Click();
s.GoToStore(StoreNavPages.Forms);
Assert.Contains("Custom Form 3", s.Driver.PageSource);
s.Driver.FindElement(By.Id("StoreNav-PaymentRequests")).Click();
s.Driver.FindElement(By.Id("CreatePaymentRequest")).Click();
Assert.Equal(4, new SelectElement(s.Driver.FindElement(By.Id("FormId"))).Options.Count);
Assert.Equal(4, new SelectElement(s.Driver.FindElement(By.Id("FormId"))).Options.Count);
}
[Fact(Timeout = TestTimeout)]
@@ -1377,9 +1382,9 @@ namespace BTCPayServer.Tests
await Task.Delay(500);
s.Driver.WaitForElement(By.CssSelector("div.label-manager input")).SendKeys("label2" + Keys.Enter);
});
TestUtils.Eventually(() =>
{
{
s.Driver.Navigate().Refresh();
Assert.NotNull(s.Driver.FindElement(By.CssSelector("[data-value='test-label']")));
});
@@ -1401,10 +1406,10 @@ namespace BTCPayServer.Tests
s.Driver.WaitForElement(By.CssSelector("[data-value='test-label']")).Click();
await Task.Delay(500);
s.Driver.ExecuteJavaScript("document.querySelector('[data-value=\"test-label\"]').nextSibling.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Delete', keyCode: 46}));");
});
TestUtils.Eventually(() =>
{
{
s.Driver.Navigate().Refresh();
Assert.DoesNotContain("test-label", s.Driver.PageSource);
});
@@ -2070,7 +2075,8 @@ namespace BTCPayServer.Tests
var res = await s.Server.CustomerLightningD.Pay(lnurlResponse.Pr);
Assert.Equal(PayResult.Error, res.Result);
await s.Server.CustomerLightningD.Pay(lnurlResponse2.Pr);
res = await s.Server.CustomerLightningD.Pay(lnurlResponse2.Pr);
Assert.Equal(PayResult.Ok, res.Result);
await TestUtils.EventuallyAsync(async () =>
{
var inv = await s.Server.PayTester.InvoiceRepository.GetInvoice(i);
@@ -2084,22 +2090,23 @@ namespace BTCPayServer.Tests
});
// Standard invoice test
s.GoToStore(storeId);
s.GoToLightningSettings();
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), true);
SudoForceSaveLightningSettingsRightNowAndFast(s, cryptoCode);
i = s.CreateInvoice(storeId, 0.0000001m, cryptoCode);
s.GoToInvoiceCheckout(i);
s.Driver.FindElement(By.ClassName("payment__currencies")).Click();
// BOLT11 is also available for standard invoices
Assert.Equal(2, s.Driver.FindElements(By.CssSelector(".vex.vex-theme-btcpay .vex-content .vexmenu li.vexmenuitem")).Count);
s.Driver.FindElement(By.CssSelector(".vex.vex-theme-btcpay .vex-content .vexmenu li.vexmenuitem")).Click();
s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Click();
// BOLT11 is also displayed for standard invoice (not LNURL, even if it is available)
s.Driver.FindElement(By.Id("copy-tab")).Click();
lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
parsed = LNURL.LNURL.Parse(lnurl, out tag);
fetchedReuqest = Assert.IsType<LNURLPayRequest>(await LNURL.LNURL.FetchInformation(parsed, new HttpClient()));
var bolt11 = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
var bolt11Parsed = Lightning.BOLT11PaymentRequest.Parse(bolt11, s.Server.ExplorerNode.Network);
var invoiceId = s.Driver.Url.Split('/').Last();
using (var resp = await s.Server.PayTester.HttpClient.GetAsync("BTC/lnurl/pay/i/" + invoiceId))
{
resp.EnsureSuccessStatusCode();
fetchedReuqest = JsonConvert.DeserializeObject<LNURLPayRequest>(await resp.Content.ReadAsStringAsync());
}
Assert.Equal(0.0000001m, fetchedReuqest.MaxSendable.ToDecimal(LightMoneyUnit.BTC));
Assert.Equal(0.0000001m, fetchedReuqest.MinSendable.ToDecimal(LightMoneyUnit.BTC));
await Assert.ThrowsAsync<LNUrlException>(async () =>
{
await fetchedReuqest.SendRequest(new LightMoney(0.0000002m, LightMoneyUnit.BTC),
@@ -2121,13 +2128,6 @@ namespace BTCPayServer.Tests
Assert.Equal(new LightMoney(0.0000001m, LightMoneyUnit.BTC),
lnurlResponse2.GetPaymentRequest(network).MinimumAmount);
s.GoToHome();
s.GoToLightningSettings();
// LNURL is enabled and settings are expanded
Assert.True(s.Driver.FindElement(By.Id("LNURLEnabled")).Selected);
Assert.Contains("show", s.Driver.FindElement(By.Id("LNURLSettings")).GetAttribute("class"));
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), false);
s.Driver.FindElement(By.Id("save")).Click();
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
i = s.CreateInvoice(storeId, 0.000001m, cryptoCode);
s.GoToInvoiceCheckout(i);
@@ -2141,23 +2141,12 @@ namespace BTCPayServer.Tests
s.GoToHome();
s.GoToLightningSettings();
s.Driver.SetCheckbox(By.Id("LNURLBech32Mode"), false);
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), false);
s.Driver.SetCheckbox(By.Id("DisableBolt11PaymentMethod"), true);
s.Driver.FindElement(By.Id("save")).Click();
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
// Ensure the toggles are set correctly
s.GoToLightningSettings();
//TODO: DisableBolt11PaymentMethod is actually disabled because LNURLStandardInvoiceEnabled is disabled
// checkboxes is not good choice here, in next release we should have multi choice instead
Assert.False(s.Driver.FindElement(By.Id("LNURLBech32Mode")).Selected);
Assert.False(s.Driver.FindElement(By.Id("LNURLStandardInvoiceEnabled")).Selected);
//even though we set DisableBolt11PaymentMethod to true, logic when saving it turns it back off as otherwise no lightning option is available at all!
Assert.False(s.Driver.FindElement(By.Id("DisableBolt11PaymentMethod")).Selected);
// Invoice creation should fail, because it is a standard invoice with amount, but DisableBolt11PaymentMethod = true and LNURLStandardInvoiceEnabled = false
s.CreateInvoice(storeId, 0.0000001m, cryptoCode, "", null, expectedSeverity: StatusMessageModel.StatusSeverity.Success);
i = s.CreateInvoice(storeId, null, cryptoCode);
s.GoToInvoiceCheckout(i);
@@ -2173,15 +2162,13 @@ namespace BTCPayServer.Tests
s.AddLightningNode(LightningConnectionType.LndREST, false);
s.GoToLightningSettings();
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
s.Driver.SetCheckbox(By.Id("DisableBolt11PaymentMethod"), true);
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), true);
s.Driver.FindElement(By.Id("save")).Click();
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
var invForPP = s.CreateInvoice(0.0000001m, cryptoCode);
var invForPP = s.CreateInvoice(null, cryptoCode);
s.GoToInvoiceCheckout(invForPP);
s.Driver.FindElement(By.Id("copy-tab")).Click();
lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
parsed = LNURL.LNURL.Parse(lnurl, out tag);
LNURL.LNURL.Parse(lnurl, out tag);
// Check that pull payment has lightning option
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
@@ -2305,7 +2292,6 @@ namespace BTCPayServer.Tests
var invoices = await repo.GetInvoices(new InvoiceQuery() { StoreId = new[] { s.StoreId } });
Assert.Equal(2, invoices.Length);
var emailSuffix = $"@{s.Server.PayTester.HostName}:{s.Server.PayTester.Port}";
foreach (var i in invoices)
{
var lightningPaymentMethod = i.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.LNURLPay));
@@ -2322,6 +2308,8 @@ namespace BTCPayServer.Tests
}
var lnUsername = lnaddress1.Split('@')[0];
LNURLPayRequest req;
using (var resp = await s.Server.PayTester.HttpClient.GetAsync($"/.well-known/lnurlp/{lnUsername}"))
{

View File

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

View File

@@ -214,6 +214,13 @@ namespace BTCPayServer.Tests
get => GenerateWalletResponseV.DerivationScheme;
}
public void SetLNUrl(string cryptoCode, bool activated)
{
var lnSettingsVm = GetController<UIStoresController>().LightningSettings(StoreId, cryptoCode).AssertViewModel<LightningSettingsViewModel>();
lnSettingsVm.LNURLEnabled = activated;
Assert.IsType<RedirectToActionResult>(GetController<UIStoresController>().LightningSettings(lnSettingsVm).Result);
}
private async Task RegisterAsync(bool isAdmin = false)
{
var account = parent.PayTester.GetController<UIAccountController>();

View File

@@ -112,7 +112,14 @@ namespace BTCPayServer.Tests
}
catch (XunitException) when (!cts.Token.IsCancellationRequested)
{
await Task.Delay(500, cts.Token);
bool timeout =false;
try
{
await Task.Delay(500, cts.Token);
}
catch { timeout = true; }
if (timeout)
throw;
}
}
}

View File

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

View File

@@ -61,34 +61,34 @@ namespace BTCPayServer.Tests
return description;
}
// /// <summary>
// /// This will take the translations from v1 or v2
// /// and upload them to transifex if not found
// /// </summary>
// [FactWithSecret("TransifexAPIToken")]
// [Trait("Utilities", "Utilities")]
//#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
// public async Task UpdateTransifex()
// {
// // DO NOT RUN IT, THIS WILL ERASE THE CURRENT TRANSIFEX TRANSLATIONS
// /// <summary>
// /// This will take the translations from v1 or v2
// /// and upload them to transifex if not found
// /// </summary>
// [FactWithSecret("TransifexAPIToken")]
// [Trait("Utilities", "Utilities")]
//#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
// public async Task UpdateTransifex()
// {
// // DO NOT RUN IT, THIS WILL ERASE THE CURRENT TRANSIFEX TRANSLATIONS
// var client = GetTransifexClient();
// var translations = JsonTranslation.GetTranslations(TranslationFolder.CheckoutV2);
// var enTranslations = translations["en"];
// translations.Remove("en");
// var client = GetTransifexClient();
// var translations = JsonTranslation.GetTranslations(TranslationFolder.CheckoutV2);
// var enTranslations = translations["en"];
// translations.Remove("en");
// foreach (var t in translations)
// {
// foreach (var w in t.Value.Words.ToArray())
// {
// if (t.Value.Words[w.Key] == null)
// t.Value.Words[w.Key] = enTranslations.Words[w.Key];
// }
// t.Value.Words.Remove("code");
// t.Value.Words.Remove("NOTICE_WARN");
// }
// await client.UpdateTranslations(translations);
// }
// foreach (var t in translations)
// {
// foreach (var w in t.Value.Words.ToArray())
// {
// if (t.Value.Words[w.Key] == null)
// t.Value.Words[w.Key] = enTranslations.Words[w.Key];
// }
// t.Value.Words.Remove("code");
// t.Value.Words.Remove("NOTICE_WARN");
// }
// await client.UpdateTranslations(translations);
// }
//#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
@@ -147,7 +147,7 @@ namespace BTCPayServer.Tests
public async Task AutoTranslateChatGPT()
{
var file = TranslationFolder.CheckoutV2;
using var driver = new ChromeDriver(new ChromeOptions()
{
DebuggerAddress = "127.0.0.1:9222"
@@ -185,6 +185,19 @@ namespace BTCPayServer.Tests
var english = englishTranslations.Words[translation.Key];
if (translation.Value != null)
continue; // Already translated
//TODO: A better way to avoid rate limits is to use this format:
//I am translating a checkout crypto payment page, and I want you to translate it from English (en-US) to French (fr-FR).
//##
//English: This invoice will expire in
//French:
//##
//English: Scan the QR code, or tap to copy the address.
//French:
//##
//English: Your payment has been received and is now processing.
//French:
if (!askedPrompt)
{
driver.FindElement(By.XPath("//a[contains(text(), \"New chat\")]")).Click();
@@ -534,7 +547,7 @@ retry:
public string FullPath { get; set; }
public string TransifexProject { get; set; }
public string TransifexProject { get; set; }
public string TransifexResource { get; private set; }
public void Save()
@@ -564,7 +577,7 @@ retry:
}
}
public void Translate(Dictionary<string,string> sourceTranslations)
public void Translate(Dictionary<string, string> sourceTranslations)
{
foreach (var o in sourceTranslations)
if (o.Value != null)

View File

@@ -38,6 +38,10 @@ services:
- selenium
extra_hosts:
- "tests:127.0.0.1"
networks:
default:
custom:
ipv4_address: 172.23.0.18
volumes:
- "sshd_datadir:/root/.ssh"
- "customer_lightningd_datadir:/etc/customer_lightningd_datadir"
@@ -89,6 +93,8 @@ services:
image: selenium/standalone-chrome:101.0
expose:
- "4444"
extra_hosts:
- "tests:172.18.0.18"
nbxplorer:
image: nicolasdorier/nbxplorer:2.3.58
restart: unless-stopped
@@ -237,7 +243,7 @@ services:
- "5432"
merchant_lnd:
image: btcpayserver/lnd:v0.15.4-beta-1
image: btcpayserver/lnd:v0.16.2-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
@@ -272,7 +278,7 @@ services:
- bitcoind
customer_lnd:
image: btcpayserver/lnd:v0.15.4-beta-1
image: btcpayserver/lnd:v0.16.2-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
@@ -404,3 +410,12 @@ volumes:
torrcdir:
tor_servicesdir:
monero_data:
networks:
default:
driver: bridge
custom:
driver: bridge
ipam:
config:
- subnet: 172.23.0.0/16

View File

@@ -36,6 +36,10 @@ services:
- selenium
extra_hosts:
- "tests:127.0.0.1"
networks:
default:
custom:
ipv4_address: 172.23.0.18
volumes:
- "sshd_datadir:/root/.ssh"
- "customer_lightningd_datadir:/etc/customer_lightningd_datadir"
@@ -84,6 +88,8 @@ services:
- merchant_lnd
selenium:
image: selenium/standalone-chrome:101.0
extra_hosts:
- "tests:172.18.0.18"
expose:
- "4444"
nbxplorer:
@@ -110,7 +116,6 @@ services:
depends_on:
- bitcoind
bitcoind:
restart: unless-stopped
image: btcpayserver/bitcoin:24.0
@@ -225,7 +230,7 @@ services:
- "5432"
merchant_lnd:
image: btcpayserver/lnd:v0.15.4-beta-1
image: btcpayserver/lnd:v0.16.2-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
@@ -262,7 +267,7 @@ services:
- bitcoind
customer_lnd:
image: btcpayserver/lnd:v0.15.4-beta-1
image: btcpayserver/lnd:v0.16.2-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
@@ -323,3 +328,12 @@ volumes:
tor_datadir:
torrcdir:
tor_servicesdir:
networks:
default:
driver: bridge
custom:
driver: bridge
ipam:
config:
- subnet: 172.23.0.0/16

View File

@@ -47,7 +47,7 @@
<ItemGroup>
<PackageReference Include="BIP78.Sender" Version="0.2.2" />
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.2" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.4.22" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.4.23" />
<PackageReference Include="CsvHelper" Version="15.0.5" />
<PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="Fido2" Version="2.0.2" />

View File

@@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Http;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace BTCPayServer
{

View File

@@ -59,7 +59,7 @@ namespace BTCPayServer
return Labels[num % Labels.Length];
}
}
/// https://gist.github.com/zihotki/09fc41d52981fb6f93a81ebf20b35cd5
/// <summary>
/// Creates color with corrected brightness.
@@ -92,7 +92,7 @@ namespace BTCPayServer
return Color.FromArgb(color.A, (int)red, (int)green, (int)blue);
}
public string AdjustBrightness(string html, float correctionFactor)
{
var color = AdjustBrightness(ColorTranslator.FromHtml(html), correctionFactor);

View File

@@ -41,7 +41,7 @@ public class AppSales : ViewComponent
};
if (vm.InitialRendering)
return View(vm);
var app = HttpContext.GetAppData();
var stats = await _appService.GetSalesStats(app);
vm.SalesCount = stats.SalesCount;

View File

@@ -13,7 +13,7 @@ namespace BTCPayServer.Components.LabelManager
{
ExcludeTypes = excludeTypes,
WalletObjectId = walletObjectId,
SelectedLabels = selectedLabels?? Array.Empty<string>(),
SelectedLabels = selectedLabels ?? Array.Empty<string>(),
DisplayInline = displayInline,
RichLabelInfo = richLabelInfo,
AutoUpdate = autoUpdate,
@@ -25,7 +25,7 @@ namespace BTCPayServer.Components.LabelManager
public class RichLabelInfo
{
public string Link { get; set; }
public string Tooltip { get; set; }
public string Link { get; set; }
public string Tooltip { get; set; }
}
}

View File

@@ -145,7 +145,10 @@
</a>
</li>
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UIStorePullPayments" asp-action="Payouts" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.Payouts)" id="StoreNav-Payouts">
<a asp-area=""
asp-controller="UIStorePullPayments" asp-action="Payouts"
asp-route-pullPaymentId=""
asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.Payouts)" id="StoreNav-Payouts">
<vc:icon symbol="payouts"/>
<span>Payouts</span>
</a>

View File

@@ -9,8 +9,8 @@ namespace BTCPayServer.Components.QRCode
{
public class QRCode : ViewComponent
{
private static QRCodeGenerator _qrGenerator = new ();
private static QRCodeGenerator _qrGenerator = new();
public IViewComponentResult Invoke(string data)
{
var qrCodeData = _qrGenerator.CreateQrCode(data, QRCodeGenerator.ECCLevel.Q);

View File

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

View File

@@ -1,4 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace BTCPayServer.Components.TruncateCenter;
@@ -15,6 +17,8 @@ public class TruncateCenter : ViewComponent
{
public IViewComponentResult Invoke(string text, string link = null, string classes = null, int padding = 7, bool copy = true)
{
if (string.IsNullOrEmpty(text))
return new HtmlContentViewComponentResult(new StringHtmlContent(string.Empty));
var vm = new TruncateCenterViewModel
{
Classes = classes,

View File

@@ -137,7 +137,7 @@ namespace BTCPayServer.Configuration
foreach (var n in new BTCPayNetworkProvider(networkType).GetAll().OfType<BTCPayNetwork>())
{
builder.AppendLine(CultureInfo.InvariantCulture, $"#{n.CryptoCode}.explorer.url={n.NBXplorerNetwork.DefaultSettings.DefaultUrl}");
builder.AppendLine(CultureInfo.InvariantCulture, $"#{n.CryptoCode}.explorer.cookiefile={ n.NBXplorerNetwork.DefaultSettings.DefaultCookieFile}");
builder.AppendLine(CultureInfo.InvariantCulture, $"#{n.CryptoCode}.explorer.cookiefile={n.NBXplorerNetwork.DefaultSettings.DefaultCookieFile}");
builder.AppendLine(CultureInfo.InvariantCulture, $"#{n.CryptoCode}.blockexplorerlink=https://mempool.space/tx/{{0}}");
if (n.SupportLightning)
{

View File

@@ -189,7 +189,7 @@ namespace BTCPayServer.Controllers.Greenfield
{
return AppNotFound();
}
return Ok(ToPointOfSaleModel(app));
}
@@ -202,7 +202,7 @@ namespace BTCPayServer.Controllers.Greenfield
{
return AppNotFound();
}
return Ok(ToCrowdfundModel(app));
}
@@ -267,7 +267,7 @@ namespace BTCPayServer.Controllers.Greenfield
return new PointOfSaleSettings()
{
Title = request.Title,
DefaultView = (PosViewType) request.DefaultView,
DefaultView = (PosViewType)request.DefaultView,
ShowCustomAmount = request.ShowCustomAmount,
ShowDiscount = request.ShowDiscount,
EnableTips = request.EnableTips,
@@ -331,10 +331,10 @@ namespace BTCPayServer.Controllers.Greenfield
Currency = settings.Currency,
Items = JsonConvert.DeserializeObject(
JsonConvert.SerializeObject(
_appService.Parse(settings.Template, settings.Currency),
_appService.Parse(settings.Template, settings.Currency),
new JsonSerializerSettings
{
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
{
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
}
)
),
@@ -406,10 +406,10 @@ namespace BTCPayServer.Controllers.Greenfield
Tagline = settings.Tagline,
Perks = JsonConvert.DeserializeObject(
JsonConvert.SerializeObject(
_appService.Parse(settings.PerksTemplate, settings.TargetCurrency),
_appService.Parse(settings.PerksTemplate, settings.TargetCurrency),
new JsonSerializerSettings
{
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
{
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
}
)
),

View File

@@ -362,10 +362,10 @@ namespace BTCPayServer.Controllers.Greenfield
{
return UnsupportedAsset(asset, ex.Message);
}
var simulateWithdrawResult =
await withdrawableCustodian.SimulateWithdrawalAsync(request.PaymentMethod, qty, custodianAccount.GetBlob(), cancellationToken);
var result = new WithdrawalSimulationResponseData(simulateWithdrawResult.PaymentMethod, simulateWithdrawResult.Asset,
var result = new WithdrawalSimulationResponseData(simulateWithdrawResult.PaymentMethod, simulateWithdrawResult.Asset,
accountId, custodian.Code, simulateWithdrawResult.LedgerEntries, simulateWithdrawResult.MinQty, simulateWithdrawResult.MaxQty);
return Ok(result);
}

View File

@@ -547,7 +547,7 @@ namespace BTCPayServer.Controllers.Greenfield
Amount = accounting.TotalDue.ToDecimal(MoneyUnit.BTC),
NetworkFee = accounting.NetworkFee.ToDecimal(MoneyUnit.BTC),
PaymentLink =
method.GetId().PaymentType.GetPaymentLink(method.Network, details, accounting.Due,
method.GetId().PaymentType.GetPaymentLink(method.Network, entity, details, accounting.Due,
Request.GetAbsoluteRoot()),
Payments = payments.Select(paymentEntity => ToPaymentModel(entity, paymentEntity)).ToList(),
AdditionalData = details.GetAdditionalData()

View File

@@ -124,7 +124,8 @@ namespace BTCPayServer.Controllers.Greenfield
try
{
var invoice = await _invoiceController.CreatePaymentRequestInvoice(pr, amount, this.StoreData, Request, cancellationToken);
var prData = await _paymentRequestRepository.FindPaymentRequest(pr.Id, null);
var invoice = await _invoiceController.CreatePaymentRequestInvoice(prData, amount, pr.AmountDue, this.StoreData, Request, cancellationToken);
return Ok(GreenfieldInvoiceController.ToModel(invoice, _linkGenerator, Request));
}
catch (BitpayHttpException e)

View File

@@ -11,8 +11,8 @@ using BTCPayServer.Security;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using StoreData = BTCPayServer.Data.StoreData;
using PayoutProcessorData = BTCPayServer.Client.Models.PayoutProcessorData;
using StoreData = BTCPayServer.Data.StoreData;
namespace BTCPayServer.Controllers.Greenfield
{

View File

@@ -264,7 +264,8 @@ namespace BTCPayServer.Controllers.Greenfield
pullPaymentId = pullPaymentId
}, Request.Scheme, Request.Host.ToString())!);
return base.Ok(new PullPaymentLNURL() {
return base.Ok(new PullPaymentLNURL()
{
LNURLBech32 = LNURL.LNURL.EncodeUri(lnurlEndpoint, "withdrawRequest", true).ToString(),
LNURLUri = LNURL.LNURL.EncodeUri(lnurlEndpoint, "withdrawRequest", false).ToString()
});
@@ -359,7 +360,7 @@ namespace BTCPayServer.Controllers.Greenfield
return this.CreateAPIPermissionError(Policies.CanCreatePullPayments);
}
}
if (request is null || !PaymentMethodId.TryParse(request?.PaymentMethod, out var paymentMethodId))
{
ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method");

View File

@@ -2,6 +2,7 @@
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Data;
@@ -69,6 +70,9 @@ namespace BTCPayServer.Controllers.Greenfield
public async Task<IActionResult> UpdateStoreLightningAutomatedPayoutProcessor(
string storeId, string paymentMethod, LightningAutomatedPayoutSettings request)
{
AutomatedPayoutConstants.ValidateInterval(ModelState, request.IntervalSeconds, nameof(request.IntervalSeconds));
if (!ModelState.IsValid)
return this.CreateValidationError(ModelState);
paymentMethod = PaymentMethodId.Parse(paymentMethod).ToString();
var activeProcessor =
(await _payoutProcessorService.GetProcessors(

View File

@@ -1,7 +1,9 @@
#nullable enable
using System;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Data;
@@ -75,6 +77,11 @@ namespace BTCPayServer.Controllers.Greenfield
public async Task<IActionResult> UpdateStoreOnchainAutomatedPayoutProcessor(
string storeId, string paymentMethod, OnChainAutomatedPayoutSettings request)
{
AutomatedPayoutConstants.ValidateInterval(ModelState, request.IntervalSeconds, nameof(request.IntervalSeconds));
if (request.FeeBlockTarget is int t && (t < 1 || t > 1000))
ModelState.AddModelError(nameof(request.FeeBlockTarget), "The feeBlockTarget should be between 1 and 1000");
if (!ModelState.IsValid)
return this.CreateValidationError(ModelState);
paymentMethod = PaymentMethodId.Parse(paymentMethod).ToString();
var activeProcessor =
(await _payoutProcessorService.GetProcessors(

View File

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

View File

@@ -33,7 +33,10 @@ namespace BTCPayServer.Controllers.Greenfield
return new LightningAddressData();
return new LightningAddressData()
{
Username = data.Username, Max = blob.Max, Min = blob.Min, CurrencyCode = blob.CurrencyCode
Username = data.Username,
Max = blob.Max,
Min = blob.Min,
CurrencyCode = blob.CurrencyCode
};
}
@@ -41,7 +44,7 @@ namespace BTCPayServer.Controllers.Greenfield
[HttpGet("~/api/v1/stores/{storeId}/lightning-addresses")]
public async Task<IActionResult> GetStoreLightningAddresses(string storeId)
{
return Ok((await _lightningAddressService.Get(new LightningAddressQuery() {StoreIds = new[] {storeId}}))
return Ok((await _lightningAddressService.Get(new LightningAddressQuery() { StoreIds = new[] { storeId } }))
.Select(ToModel).ToArray());
}
@@ -64,7 +67,8 @@ namespace BTCPayServer.Controllers.Greenfield
{
var res = await _lightningAddressService.Get(new LightningAddressQuery()
{
Usernames = new[] {username}, StoreIds = new[] {storeId},
Usernames = new[] { username },
StoreIds = new[] { storeId },
});
return res?.Any() is true ? Ok(ToModel(res.First())) : this.CreateAPIError(404, "lightning-address-not-found", "The lightning address was not present.");
}
@@ -79,17 +83,17 @@ namespace BTCPayServer.Controllers.Greenfield
ModelState.AddModelError(nameof(data.Min), "Minimum must be greater than 0 if provided.");
return this.CreateValidationError(ModelState);
}
if (await _lightningAddressService.Set(new Data.LightningAddressData()
{
StoreDataId = storeId,
Username = username
}.SetBlob(new LightningAddressDataBlob()
{
Max = data.Max,
Min = data.Min,
CurrencyCode = data.CurrencyCode
})))
{
StoreDataId = storeId,
Username = username
}.SetBlob(new LightningAddressDataBlob()
{
Max = data.Max,
Min = data.Min,
CurrencyCode = data.CurrencyCode
})))
{
return await GetStoreLightningAddress(storeId, username);
}

View File

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

View File

@@ -111,7 +111,7 @@ namespace BTCPayServer.Controllers.GreenField
{
parsedCurrencyPairs = blob.DefaultCurrencyPairs.ToHashSet();
}
ValidateAndSanitizeConfiguration(configuration, blob);
if (!ModelState.IsValid)
return this.CreateValidationError(ModelState);

View File

@@ -32,7 +32,7 @@ namespace BTCPayServer.Controllers.GreenField
_rateProviderFactory = rateProviderFactory;
_btcPayNetworkProvider = btcPayNetworkProvider;
}
[HttpGet("")]
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> GetStoreRates([FromQuery] string[]? currencyPair)
@@ -59,7 +59,7 @@ namespace BTCPayServer.Controllers.GreenField
{
parsedCurrencyPairs = blob.DefaultCurrencyPairs.ToHashSet();
}
var rules = blob.GetRateRules(_btcPayNetworkProvider);

View File

@@ -149,13 +149,13 @@ namespace BTCPayServer.Controllers.Greenfield
LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate,
PaymentTolerance = storeBlob.PaymentTolerance,
PayJoinEnabled = storeBlob.PayJoinEnabled,
PaymentMethodCriteria = storeBlob.PaymentMethodCriteria?.Where(criteria => criteria.Value is not null)?.Select(criteria => new PaymentMethodCriteriaData()
PaymentMethodCriteria = storeBlob.PaymentMethodCriteria?.Where(criteria => criteria.Value is not null)?.Select(criteria => new PaymentMethodCriteriaData()
{
Above = criteria.Above,
Amount = criteria.Value.Value,
CurrencyCode = criteria.Value.Currency,
PaymentMethod = criteria.PaymentMethod.ToStringNormalized()
})?.ToList()?? new List<PaymentMethodCriteriaData>()
})?.ToList() ?? new List<PaymentMethodCriteriaData>()
};
}
@@ -249,7 +249,8 @@ namespace BTCPayServer.Controllers.Greenfield
if (string.IsNullOrEmpty(pmc.CurrencyCode))
{
request.AddModelError(data => data.PaymentMethodCriteria[index].CurrencyCode, "CurrencyCode is required", this);
}else if (CurrencyNameTable.Instance.GetCurrencyData(pmc.CurrencyCode, false) is null)
}
else if (CurrencyNameTable.Instance.GetCurrencyData(pmc.CurrencyCode, false) is null)
{
request.AddModelError(data => data.PaymentMethodCriteria[index].CurrencyCode, "CurrencyCode is invalid", this);
}

View File

@@ -30,11 +30,11 @@ using LightningAddressData = BTCPayServer.Client.Models.LightningAddressData;
using NotificationData = BTCPayServer.Client.Models.NotificationData;
using PaymentRequestData = BTCPayServer.Client.Models.PaymentRequestData;
using PayoutData = BTCPayServer.Client.Models.PayoutData;
using PayoutProcessorData = BTCPayServer.Client.Models.PayoutProcessorData;
using PullPaymentData = BTCPayServer.Client.Models.PullPaymentData;
using StoreData = BTCPayServer.Client.Models.StoreData;
using StoreWebhookData = BTCPayServer.Client.Models.StoreWebhookData;
using WebhookDeliveryData = BTCPayServer.Client.Models.WebhookDeliveryData;
using PayoutProcessorData = BTCPayServer.Client.Models.PayoutProcessorData;
namespace BTCPayServer.Controllers.Greenfield
{
@@ -223,14 +223,14 @@ namespace BTCPayServer.Controllers.Greenfield
return GetFromActionResult<MarketTradeResponseData>(
await GetController<GreenfieldCustodianAccountController>().MarketTradeCustodianAccountAsset(storeId, accountId, request, cancellationToken));
}
public override async Task<WithdrawalSimulationResponseData> SimulateCustodianAccountWithdrawal(string storeId, string accountId,
WithdrawRequestData request, CancellationToken cancellationToken = default)
{
return GetFromActionResult<WithdrawalSimulationResponseData>(
await GetController<GreenfieldCustodianAccountController>().SimulateWithdrawal(storeId, accountId, request, cancellationToken));
}
public override async Task<WithdrawalResponseData> CreateCustodianAccountWithdrawal(string storeId, string accountId,
WithdrawRequestData request, CancellationToken cancellationToken = default)
{
@@ -1238,7 +1238,7 @@ namespace BTCPayServer.Controllers.Greenfield
return Task.FromResult(GetFromActionResult<StoreRateConfiguration>(GetController<GreenfieldStoreRateConfigurationController>().GetStoreRateConfiguration()));
}
public override async Task<List<StoreRateResult>> GetStoreRates (string storeId,
public override async Task<List<StoreRateResult>> GetStoreRates(string storeId,
string[] currencyPair, CancellationToken token = default)
{
return GetFromActionResult<List<StoreRateResult>>(await GetController<GreenfieldStoreRatesController>().GetStoreRates(currencyPair));

View File

@@ -62,7 +62,7 @@ public class LightningAddressService
{
data.Username = NormalizeUsername(data.Username);
await using var context = _applicationDbContextFactory.CreateContext();
var result = (await GetCore(context, new LightningAddressQuery() { Usernames = new[] { data.Username} }))
var result = (await GetCore(context, new LightningAddressQuery() { Usernames = new[] { data.Username } }))
.FirstOrDefault();
if (result is not null)
{

View File

@@ -88,7 +88,7 @@ namespace BTCPayServer.Controllers
[HttpGet("/cheat/permissions")]
[HttpGet("/cheat/permissions/stores/{storeId}")]
[CheatModeRoute]
public async Task<IActionResult> CheatPermissions([FromServices]IAuthorizationService authorizationService, string storeId = null)
public async Task<IActionResult> CheatPermissions([FromServices] IAuthorizationService authorizationService, string storeId = null)
{
var vm = new CheatPermissionsViewModel();
vm.StoreId = storeId;
@@ -790,7 +790,7 @@ namespace BTCPayServer.Controllers
if (matchedDomainMapping is not null)
return RedirectToAction(nameof(UIHomeController.Home), "UIHome");
}
return RedirectToAction(nameof(UIHomeController.Index), "UIHome");
}

View File

@@ -54,7 +54,7 @@ namespace BTCPayServer.Controllers
var app = await _appService.GetApp(appId, null);
if (app is null)
return NotFound();
var res = await _appService.ViewLink(app);
if (res is null)
{
@@ -150,11 +150,11 @@ namespace BTCPayServer.Controllers
var defaultCurrency = await GetStoreDefaultCurrentIfEmpty(appData.StoreDataId, null);
await _appService.SetDefaultSettings(appData, defaultCurrency);
await _appService.UpdateOrCreateApp(appData);
TempData[WellKnownTempData.SuccessMessage] = "App successfully created";
CreatedAppId = appData.Id;
var url = await type.ConfigureLink(appData);
return Redirect(url);
}

View File

@@ -123,7 +123,7 @@ namespace BTCPayServer.Controllers
var metaData = PosDataParser.ParsePosData(invoice.Metadata.ToJObject());
var additionalData = metaData
.Where(dict => !InvoiceAdditionalDataExclude.Contains(dict.Key))
.ToDictionary(dict=> dict.Key, dict=> dict.Value);
.ToDictionary(dict => dict.Key, dict => dict.Value);
var model = new InvoiceDetailsModel
{
StoreId = store.Id,
@@ -184,8 +184,14 @@ namespace BTCPayServer.Controllers
var receipt = InvoiceDataBase.ReceiptOptions.Merge(store.GetStoreBlob().ReceiptOptions, i.ReceiptOptions);
if (receipt.Enabled is not true)
{
if (i.RedirectURL is not null)
{
return Redirect(i.RedirectURL.ToString());
}
return NotFound();
}
var storeBlob = store.GetStoreBlob();
var vm = new InvoiceReceiptViewModel
{
@@ -201,12 +207,12 @@ namespace BTCPayServer.Controllers
CssFileId = storeBlob.CssFileId,
ReceiptOptions = receipt
};
if (i.Status.ToModernStatus() != InvoiceStatus.Settled)
{
return View(vm);
}
JToken? receiptData = null;
i.Metadata?.AdditionalData?.TryGetValue("receiptData", out receiptData);
@@ -238,7 +244,7 @@ namespace BTCPayServer.Controllers
Link = link,
Id = txId,
Destination = paymentData.GetDestination(),
PaymentProof = GetPaymentProof(paymentData),
PaymentProof = paymentData.GetPaymentProof(),
PaymentType = paymentData.GetPaymentType()
};
})
@@ -252,16 +258,6 @@ namespace BTCPayServer.Controllers
return View(vm);
}
private string? GetPaymentProof(CryptoPaymentData paymentData)
{
return paymentData switch
{
BitcoinLikePaymentData b => b.Outpoint.ToString(),
LightningLikePaymentData l => l.Preimage?.ToString(),
_ => null
};
}
private string? GetTransactionLink(PaymentMethodId paymentMethodId, string txId)
{
var network = _NetworkProvider.GetNetwork(paymentMethodId.CryptoCode);
@@ -676,48 +672,55 @@ namespace BTCPayServer.Controllers
var btcId = PaymentMethodId.Parse("BTC");
var lnId = PaymentMethodId.Parse("BTC_LightningLike");
var lnurlId = PaymentMethodId.Parse("BTC_LNURLPAY");
var displayedPaymentMethods = invoice.GetPaymentMethods().Select(p => p.GetId()).ToList();
// Exclude Lightning if OnChainWithLnInvoiceFallback is active and we have both payment methods
if (storeBlob is { OnChainWithLnInvoiceFallback: true } &&
displayedPaymentMethods.Contains(btcId))
{
displayedPaymentMethods.Remove(lnId);
displayedPaymentMethods.Remove(lnurlId);
}
// BOLT11 doesn't really support payment without amount
if (invoice.IsUnsetTopUp())
displayedPaymentMethods.Remove(lnId);
// Exclude lnurl if bolt11 is available
if (displayedPaymentMethods.Contains(lnId) && displayedPaymentMethods.Contains(lnurlId))
displayedPaymentMethods.Remove(lnurlId);
if (paymentMethodId is not null && !displayedPaymentMethods.Contains(paymentMethodId))
paymentMethodId = null;
if (paymentMethodId is null)
{
var enabledPaymentIds = store.GetEnabledPaymentIds(_NetworkProvider).ToArray();
// Exclude Lightning if OnChainWithLnInvoiceFallback is active and we have both payment methods
if (storeBlob is { CheckoutType: CheckoutType.V2, OnChainWithLnInvoiceFallback: true })
{
if (enabledPaymentIds.Contains(btcId) && enabledPaymentIds.Contains(lnId))
{
enabledPaymentIds = enabledPaymentIds.Where(pmId => pmId != lnId).ToArray();
}
if (enabledPaymentIds.Contains(btcId) && enabledPaymentIds.Contains(lnurlId))
{
enabledPaymentIds = enabledPaymentIds.Where(pmId => pmId != lnurlId).ToArray();
}
}
PaymentMethodId? invoicePaymentId = invoice.GetDefaultPaymentMethod();
PaymentMethodId? storePaymentId = store.GetDefaultPaymentId();
if (invoicePaymentId is not null)
{
if (enabledPaymentIds.Contains(invoicePaymentId))
if (displayedPaymentMethods.Contains(invoicePaymentId))
paymentMethodId = invoicePaymentId;
}
if (paymentMethodId is null && storePaymentId is not null)
{
if (enabledPaymentIds.Contains(storePaymentId))
if (displayedPaymentMethods.Contains(storePaymentId))
paymentMethodId = storePaymentId;
}
if (paymentMethodId is null && invoicePaymentId is not null)
{
paymentMethodId = invoicePaymentId.FindNearest(enabledPaymentIds);
paymentMethodId = invoicePaymentId.FindNearest(displayedPaymentMethods);
}
if (paymentMethodId is null && storePaymentId is not null)
{
paymentMethodId = storePaymentId.FindNearest(enabledPaymentIds);
paymentMethodId = storePaymentId.FindNearest(displayedPaymentMethods);
}
if (paymentMethodId is null)
{
paymentMethodId = enabledPaymentIds.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == PaymentTypes.BTCLike) ??
enabledPaymentIds.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType != PaymentTypes.LNURLPay) ??
enabledPaymentIds.FirstOrDefault();
paymentMethodId = displayedPaymentMethods.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == PaymentTypes.BTCLike) ??
displayedPaymentMethods.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType != PaymentTypes.LNURLPay) ??
displayedPaymentMethods.FirstOrDefault();
}
isDefaultPaymentId = true;
}
@@ -731,30 +734,37 @@ namespace BTCPayServer.Controllers
return null;
var paymentMethodTemp = invoice
.GetPaymentMethods()
.FirstOrDefault(pm =>
{
var pmId = pm.GetId();
return paymentMethodId.CryptoCode == pmId.CryptoCode &&
((invoice.IsUnsetTopUp() && !storeBlob.OnChainWithLnInvoiceFallback) || pmId != lnurlId);
});
if (paymentMethodTemp == null)
paymentMethodTemp = invoice.GetPaymentMethods().FirstOrDefault();
.Where(p => displayedPaymentMethods.Contains(p.GetId()))
.FirstOrDefault();
if (paymentMethodTemp is null)
return null;
network = paymentMethodTemp.Network;
paymentMethodId = paymentMethodTemp.GetId();
}
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
var paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
if (!paymentMethodDetails.Activated)
// We activate the default payment method, and also those which aren't displayed (as they can't be set as default)
bool activated = false;
foreach (var pm in invoice.GetPaymentMethods())
{
if (await _invoiceActivator.ActivateInvoicePaymentMethod(paymentMethod.GetId(), invoice, store))
var pmi = pm.GetId();
if (pmi != paymentMethodId || !displayedPaymentMethods.Contains(pmi))
continue;
var pmd = pm.GetPaymentMethodDetails();
if (!pmd.Activated)
{
return await GetInvoiceModel(invoiceId, paymentMethodId, lang);
if (await _invoiceActivator.ActivateInvoicePaymentMethod(pmi, invoice, store))
{
activated = true;
}
}
}
if (activated)
return await GetInvoiceModel(invoiceId, paymentMethodId, lang);
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
var paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
var dto = invoice.EntityToDTO();
var accounting = paymentMethod.Calculate();
var paymentMethodHandler = _paymentMethodHandlerDictionary[paymentMethodId];
@@ -764,7 +774,7 @@ namespace BTCPayServer.Controllers
{
case "auto":
case null when storeBlob.AutoDetectLanguage:
lang = _languageService.AutoDetectLanguageUsingHeader(HttpContext.Request.Headers, null).Code;
lang = _languageService.AutoDetectLanguageUsingHeader(HttpContext.Request.Headers, null)?.Code;
break;
case { } langs when !string.IsNullOrEmpty(langs):
{
@@ -783,11 +793,13 @@ namespace BTCPayServer.Controllers
Request.Host,
Request.PathBase) : null;
var isAltcoinsBuild = false;
#if ALTCOINS
isAltcoinsBuild = true;
#endif
var model = new PaymentModel
{
#if ALTCOINS
AltcoinsBuild = true,
#endif
Activated = paymentMethodDetails.Activated,
CryptoCode = network.CryptoCode,
RootPath = Request.PathBase.Value.WithTrailingSlash(),
@@ -852,11 +864,15 @@ namespace BTCPayServer.Controllers
{
var availableCryptoPaymentMethodId = kv.GetId();
var availableCryptoHandler = _paymentMethodHandlerDictionary[availableCryptoPaymentMethodId];
var pmName = availableCryptoHandler.GetPaymentMethodName(availableCryptoPaymentMethodId);
return new PaymentModel.AvailableCrypto
{
Displayed = displayedPaymentMethods.Contains(kv.GetId()),
PaymentMethodId = kv.GetId().ToString(),
CryptoCode = kv.Network?.CryptoCode ?? kv.GetId().CryptoCode,
PaymentMethodName = availableCryptoHandler.GetPaymentMethodName(availableCryptoPaymentMethodId),
PaymentMethodName = isAltcoinsBuild
? pmName
: pmName.Replace("Bitcoin (", "").Replace(")", "").Replace("Lightning ", ""),
IsLightning =
kv.GetId().PaymentType == PaymentTypes.LightningLike,
CryptoImage = Request.GetRelativePathOrAbsolute(availableCryptoHandler.GetCryptoImage(availableCryptoPaymentMethodId)),
@@ -872,17 +888,6 @@ namespace BTCPayServer.Controllers
.ToList()
};
// Exclude Lightning if OnChainWithLnInvoiceFallback is active and we have both payment methods
if (storeBlob is { CheckoutType: CheckoutType.V2, OnChainWithLnInvoiceFallback: true })
{
var onchainPM = model.AvailableCryptos.Find(c => c.PaymentMethodId == btcId.ToString());
var lightningPM = model.AvailableCryptos.Find(c => c.PaymentMethodId == lnId.ToString());
if (onchainPM != null && lightningPM != null)
{
model.AvailableCryptos.Remove(lightningPM);
}
}
paymentMethodHandler.PreparePaymentModel(model, dto, storeBlob, paymentMethod);
model.UISettings = paymentMethodHandler.GetCheckoutUISettings();
model.PaymentMethodId = paymentMethodId.ToString();
@@ -897,22 +902,22 @@ namespace BTCPayServer.Controllers
{
var currency = invoiceEntity.Currency;
var crypto = cryptoCode.ToUpperInvariant(); // uppercase to make comparison easier, might be "sats"
// if invoice source currency is the same as currently display currency, no need for "order amount from invoice"
if (crypto == currency || (crypto == "SATS" && currency == "BTC") || (crypto == "BTC" && currency == "SATS"))
return null;
return _displayFormatter.Currency(invoiceEntity.Price, currency, format);
}
private string? ExchangeRate(string cryptoCode, PaymentMethod paymentMethod, DisplayFormatter.CurrencyFormat format = DisplayFormatter.CurrencyFormat.Code)
{
var currency = paymentMethod.ParentEntity.Currency;
var crypto = cryptoCode.ToUpperInvariant(); // uppercase to make comparison easier, might be "sats"
if (crypto == currency || (crypto == "SATS" && currency == "BTC") || (crypto == "BTC" && currency == "SATS"))
return null;
return _displayFormatter.Currency(paymentMethod.Rate, currency, format);
}
@@ -1296,10 +1301,20 @@ namespace BTCPayServer.Controllers
{
case JTokenType.Array:
var items = item.Value.AsEnumerable().ToList();
var arrayResult = new List<object>();
for (var i = 0; i < items.Count; i++)
{
result.TryAdd($"{item.Key}[{i}]", ParsePosData(items[i]));
if (items[i] is JObject)
{
arrayResult.Add( ParsePosData(items[i]));
}
else
{
arrayResult.Add( items[i].ToString());
}
}
result.TryAdd(item.Key, arrayResult);
break;
case JTokenType.Object:

View File

@@ -187,33 +187,36 @@ namespace BTCPayServer.Controllers
return await CreateInvoiceCoreRaw(entity, store, excludeFilter, null, cancellationToken, entityManipulator);
}
internal async Task<InvoiceEntity> CreatePaymentRequestInvoice(ViewPaymentRequestViewModel pr, decimal? amount, StoreData storeData, HttpRequest request, CancellationToken cancellationToken)
internal async Task<InvoiceEntity> CreatePaymentRequestInvoice(Data.PaymentRequestData prData, decimal? amount, decimal amountDue, StoreData storeData, HttpRequest request, CancellationToken cancellationToken)
{
if (pr.AllowCustomPaymentAmounts && amount != null)
amount = Math.Min(pr.AmountDue, amount.Value);
var id = prData.Id;
var prBlob = prData.GetBlob();
if (prBlob.AllowCustomPaymentAmounts && amount != null)
amount = Math.Min(amountDue, amount.Value);
else
amount = pr.AmountDue;
var redirectUrl = _linkGenerator.PaymentRequestLink(pr.Id, request.Scheme, request.Host, request.PathBase);
amount = amountDue;
var redirectUrl = _linkGenerator.PaymentRequestLink(id, request.Scheme, request.Host, request.PathBase);
var invoiceMetadata =
new InvoiceMetadata
{
OrderId = PaymentRequestRepository.GetOrderIdForPaymentRequest(pr.Id),
PaymentRequestId = pr.Id,
BuyerEmail = pr.Email
};
JObject invoiceMetadata = prData.GetBlob()?.FormResponse ?? new JObject();
invoiceMetadata.Merge(new InvoiceMetadata
{
OrderId = PaymentRequestRepository.GetOrderIdForPaymentRequest(id),
PaymentRequestId = id,
BuyerEmail = invoiceMetadata.TryGetValue("buyerEmail", out var formEmail) && formEmail.Type == JTokenType.String ? formEmail.Value<string>():
string.IsNullOrEmpty(prBlob.Email) ? null : prBlob.Email
}.ToJObject(), new JsonMergeSettings() { MergeNullValueHandling = MergeNullValueHandling.Ignore });
var invoiceRequest =
new CreateInvoiceRequest
{
Metadata = invoiceMetadata.ToJObject(),
Currency = pr.Currency,
Metadata = invoiceMetadata,
Currency = prBlob.Currency,
Amount = amount,
Checkout = { RedirectURL = redirectUrl },
Receipt = new InvoiceDataBase.ReceiptOptions { Enabled = false }
};
var additionalTags = new List<string> { PaymentRequestRepository.GetInternalTag(pr.Id) };
var additionalTags = new List<string> { PaymentRequestRepository.GetInternalTag(id) };
return await CreateInvoiceCoreRaw(invoiceRequest, storeData, request.GetAbsoluteRoot(), additionalTags, cancellationToken);
}
@@ -246,6 +249,7 @@ namespace BTCPayServer.Controllers
entity.RedirectAutomatically = invoice.Checkout.RedirectAutomatically ?? storeBlob.RedirectAutomatically;
entity.CheckoutType = invoice.Checkout.CheckoutType;
entity.RequiresRefundEmail = invoice.Checkout.RequiresRefundEmail;
entity.LazyPaymentMethods = invoice.Checkout.LazyPaymentMethods ?? storeBlob.LazyPaymentMethods;
IPaymentFilter? excludeFilter = null;
if (invoice.Checkout.PaymentMethods != null)
{
@@ -458,7 +462,7 @@ namespace BTCPayServer.Controllers
var storeBlob = store.GetStoreBlob();
// Checkout v2 does not show a payment method switch for Bitcoin-only + BIP21, so exclude that case
var preparePayment = storeBlob.LazyPaymentMethods && !storeBlob.OnChainWithLnInvoiceFallback
var preparePayment = entity.LazyPaymentMethods && !storeBlob.OnChainWithLnInvoiceFallback
? null
: handler.PreparePayment(supportedPaymentMethod, store, network);
var rate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, entity.Currency)];

View File

@@ -17,6 +17,7 @@ using BTCPayServer.Data.Payouts.LightningLike;
using BTCPayServer.Events;
using BTCPayServer.HostedServices;
using BTCPayServer.Lightning;
using BTCPayServer.Logging;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Plugins.Crowdfund;
@@ -58,6 +59,7 @@ namespace BTCPayServer
private readonly PullPaymentHostedService _pullPaymentHostedService;
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
private readonly IPluginHookService _pluginHookService;
private readonly InvoiceActivator _invoiceActivator;
public UILNURLController(InvoiceRepository invoiceRepository,
EventAggregator eventAggregator,
@@ -71,7 +73,8 @@ namespace BTCPayServer
LightningLikePayoutHandler lightningLikePayoutHandler,
PullPaymentHostedService pullPaymentHostedService,
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
IPluginHookService pluginHookService)
IPluginHookService pluginHookService,
InvoiceActivator invoiceActivator)
{
_invoiceRepository = invoiceRepository;
_eventAggregator = eventAggregator;
@@ -86,6 +89,7 @@ namespace BTCPayServer
_pullPaymentHostedService = pullPaymentHostedService;
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
_pluginHookService = pluginHookService;
_invoiceActivator = invoiceActivator;
}
[HttpGet("withdraw/pp/{pullPaymentId}")]
@@ -192,7 +196,7 @@ namespace BTCPayServer
case PayResult.Error:
default:
await _pullPaymentHostedService.Cancel(
new PullPaymentHostedService.CancelRequest(new []
new PullPaymentHostedService.CancelRequest(new[]
{ claimResponse.PayoutData.Id }, null));
return BadRequest(new LNUrlStatusResponse
@@ -305,7 +309,7 @@ namespace BTCPayServer
};
var invoiceMetadata = new InvoiceMetadata();
invoiceMetadata.OrderId =AppService.GetAppOrderId(app);
invoiceMetadata.OrderId = AppService.GetAppOrderId(app);
if (item != null)
{
invoiceMetadata.ItemCode = item.Id;
@@ -355,8 +359,8 @@ namespace BTCPayServer
public string InvoiceMetadata { get; set; }
}
public ConcurrentDictionary<string, LightningAddressItem> Items { get; } = new ();
public ConcurrentDictionary<string, string[]> StoreToItemMap { get; } = new ();
public ConcurrentDictionary<string, LightningAddressItem> Items { get; } = new();
public ConcurrentDictionary<string, string[]> StoreToItemMap { get; } = new();
public override string ToString()
{
@@ -435,12 +439,16 @@ namespace BTCPayServer
List<string> additionalTags = null,
bool allowOverpay = true)
{
if (GetLNUrlPaymentMethodId(cryptoCode, store, out _) is null)
var pmi = GetLNUrlPaymentMethodId(cryptoCode, store, out _);
if (pmi is null)
return NotFound("LNUrl or LN is disabled");
InvoiceEntity i;
try
{
createInvoice.Checkout ??= new InvoiceDataBase.CheckoutOptions();
createInvoice.Checkout.LazyPaymentMethods = false;
createInvoice.Checkout.PaymentMethods = new[] { pmi.ToStringNormalized() };
i = await _invoiceController.CreateInvoiceCoreRaw(createInvoice, store, Request.GetAbsoluteRoot(), additionalTags);
}
catch (Exception e)
@@ -466,13 +474,15 @@ namespace BTCPayServer
lnurlRequest ??= new LNURLPayRequest();
lnUrlMetadata ??= new Dictionary<string, string>();
if (lnUrlMetadata?.TryGetValue("text/identifier", out var lnAddress) is true && lnAddress is string)
var pm = i.GetPaymentMethod(pmi);
if (pm is null)
return null;
var paymentMethodDetails = (LNURLPayPaymentMethodDetails)pm.GetPaymentMethodDetails();
bool updatePaymentMethodDetails = false;
if (lnUrlMetadata?.TryGetValue("text/identifier", out var lnAddress) is true && lnAddress is not null)
{
var pm = i.GetPaymentMethod(pmi);
var paymentMethodDetails = (LNURLPayPaymentMethodDetails)pm.GetPaymentMethodDetails();
paymentMethodDetails.ConsumedLightningAddress = lnAddress;
pm.SetPaymentMethodDetails(paymentMethodDetails);
await _invoiceRepository.UpdateInvoicePaymentMethod(i.Id, pm);
updatePaymentMethodDetails = true;
}
if (!lnUrlMetadata.ContainsKey("text/plain"))
@@ -493,7 +503,7 @@ namespace BTCPayServer
lnurlRequest.Metadata = JsonConvert.SerializeObject(lnUrlMetadata.Select(kv => new[] { kv.Key, kv.Value }));
if (i.Type != InvoiceType.TopUp)
{
lnurlRequest.MinSendable = new LightMoney(i.GetPaymentMethod(pmi).Calculate().Due.ToDecimal(MoneyUnit.Satoshi), LightMoneyUnit.Satoshi);
lnurlRequest.MinSendable = new LightMoney(pm.Calculate().Due.ToDecimal(MoneyUnit.Satoshi), LightMoneyUnit.Satoshi);
if (!allowOverpay)
lnurlRequest.MaxSendable = lnurlRequest.MinSendable;
}
@@ -506,15 +516,16 @@ namespace BTCPayServer
lnurlRequest.MaxSendable = LightMoney.FromUnit(6.12m, LightMoneyUnit.BTC);
lnurlRequest = await _pluginHookService.ApplyFilter("modify-lnurlp-request", lnurlRequest) as LNURLPayRequest;
i.Metadata ??= new InvoiceMetadata();
var metadata = i.Metadata.ToJObject();
if (metadata.Property("payRequest") is null)
if (paymentMethodDetails.PayRequest is null)
{
metadata.Add("payRequest", JToken.FromObject(lnurlRequest));
await _invoiceRepository.UpdateInvoiceMetadata(i.Id, i.StoreId, metadata);
paymentMethodDetails.PayRequest = lnurlRequest;
updatePaymentMethodDetails = true;
}
if (updatePaymentMethodDetails)
{
pm.SetPaymentMethodDetails(paymentMethodDetails);
await _invoiceRepository.UpdateInvoicePaymentMethod(i.Id, pm);
}
return lnurlRequest;
}
@@ -567,17 +578,22 @@ namespace BTCPayServer
var lightningPaymentMethod = i.GetPaymentMethod(pmi);
var paymentMethodDetails =
lightningPaymentMethod.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails;
if (paymentMethodDetails.LightningSupportedPaymentMethod is null)
lightningPaymentMethod?.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails;
if (paymentMethodDetails is not null && !paymentMethodDetails.Activated)
{
if (!await _invoiceActivator.ActivateInvoicePaymentMethod(pmi, i, store))
return NotFound();
i = await _invoiceRepository.GetInvoice(invoiceId, true);
lightningPaymentMethod = i.GetPaymentMethod(pmi);
paymentMethodDetails = lightningPaymentMethod.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails;
}
if (paymentMethodDetails?.LightningSupportedPaymentMethod is null)
return NotFound();
LNURLPayRequest lnurlPayRequest;
LNURLPayRequest lnurlPayRequest = paymentMethodDetails.PayRequest;
var blob = store.GetStoreBlob();
if (i.Metadata.AdditionalData.TryGetValue("payRequest", out var t) && t is JObject jo)
{
lnurlPayRequest = jo.ToObject<LNURLPayRequest>();
}
else
if (paymentMethodDetails.PayRequest is null)
{
lnurlPayRequest = await CreateLNUrlRequestFromInvoice(cryptoCode, i, store, blob, allowOverpay: false);
if (lnurlPayRequest is null)
@@ -641,8 +657,7 @@ namespace BTCPayServer
try
{
var expiry = i.ExpirationTime.ToUniversalTime() - DateTimeOffset.UtcNow;
var metadata = JsonConvert.SerializeObject(lnurlPayRequest.Metadata);
var description = (await _pluginHookService.ApplyFilter("modify-lnurlp-description", metadata)) as string;
var description = (await _pluginHookService.ApplyFilter("modify-lnurlp-description", lnurlPayRequest.Metadata)) as string;
if (description is null)
return NotFound();
@@ -678,15 +693,13 @@ namespace BTCPayServer
paymentMethodDetails.InvoiceId = invoice.Id;
paymentMethodDetails.GeneratedBoltAmount = amt;
updatePaymentMethod = true;
_eventAggregator.Publish(new InvoiceNewPaymentDetailsEvent(invoiceId,
paymentMethodDetails, pmi));
}
if (updatePaymentMethod)
{
lightningPaymentMethod.SetPaymentMethodDetails(paymentMethodDetails);
await _invoiceRepository.UpdateInvoicePaymentMethod(invoiceId, lightningPaymentMethod);
_eventAggregator.Publish(new InvoiceNewPaymentDetailsEvent(invoiceId, paymentMethodDetails, pmi));
}
return Ok(new LNURLPayRequest.LNURLPayRequestCallbackResponse
@@ -704,7 +717,7 @@ namespace BTCPayServer
Reason = "Invoice not in a valid payable state"
});
}
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[HttpGet("~/stores/{storeId}/plugins/lightning-address")]
@@ -759,13 +772,13 @@ namespace BTCPayServer
}
JObject metadata = null;
if (!string.IsNullOrEmpty(vm.Add.InvoiceMetadata) )
if (!string.IsNullOrEmpty(vm.Add.InvoiceMetadata))
{
try
{
metadata = JObject.Parse(vm.Add.InvoiceMetadata);
metadata = JObject.Parse(vm.Add.InvoiceMetadata);
}
catch (Exception e)
catch (Exception)
{
vm.AddModelError(addressVm => addressVm.Add.InvoiceMetadata, "Metadata must be a valid json object", this);
}

View File

@@ -198,14 +198,14 @@ namespace BTCPayServer.Controllers
{
return NotFound();
}
var storeBlob = store.GetStoreBlob();
vm.StoreName = store.StoreName;
vm.BrandColor = storeBlob.BrandColor;
vm.LogoFileId = storeBlob.LogoFileId;
vm.CssFileId = storeBlob.CssFileId;
vm.HubPath = PaymentRequestHub.GetHubPath(Request);
return View(vm);
}
@@ -224,26 +224,34 @@ namespace BTCPayServer.Controllers
var prBlob = result.GetBlob();
if (prBlob.FormResponse is not null)
{
return RedirectToAction("PayPaymentRequest", new {payReqId});
return RedirectToAction("PayPaymentRequest", new { payReqId });
}
var prFormId = prBlob.FormId;
var formData = await FormDataService.GetForm(prFormId);
if (formData is null)
{
return RedirectToAction("PayPaymentRequest", new {payReqId});
return RedirectToAction("PayPaymentRequest", new { payReqId });
}
var form = Form.Parse(formData.Config);
if (!string.IsNullOrEmpty(prBlob.Email))
{
var emailField = form.GetFieldByFullName("buyerEmail");
if (emailField is not null)
{
emailField.Value = prBlob.Email;
}
}
if (Request.Method == "POST" && Request.HasFormContentType)
{
form.ApplyValuesFromForm(Request.Form);
if (FormDataService.Validate(form, ModelState))
{
{
prBlob.FormResponse = FormDataService.GetValues(form);
result.SetBlob(prBlob);
await _PaymentRequestRepository.CreateOrUpdatePaymentRequest(result);
return RedirectToAction("PayPaymentRequest", new {payReqId});
return RedirectToAction("PayPaymentRequest", new { payReqId });
}
}
viewModel.FormName = formData.Name;
@@ -277,13 +285,12 @@ namespace BTCPayServer.Controllers
return BadRequest("Payment Request cannot be paid as it has been archived");
}
if (!result.FormSubmitted && !string.IsNullOrEmpty(result.FormId))
{
var formData = await FormDataService.GetForm(result.FormId);
if (formData is not null)
{
return RedirectToAction("ViewPaymentRequestForm", new {payReqId});
return RedirectToAction("ViewPaymentRequestForm", new { payReqId });
}
}
@@ -322,7 +329,8 @@ namespace BTCPayServer.Controllers
try
{
var store = await _storeRepository.FindStore(result.StoreId);
var newInvoice = await _InvoiceController.CreatePaymentRequestInvoice(result, amount, store, Request, cancellationToken);
var prData = await _PaymentRequestRepository.FindPaymentRequest(result.Id, null);
var newInvoice = await _InvoiceController.CreatePaymentRequestInvoice(prData, amount, result.AmountDue, store, Request, cancellationToken);
if (redirectToInvoice)
{
return RedirectToAction("Checkout", "UIInvoice", new { invoiceId = newInvoice.Id });

View File

@@ -63,7 +63,7 @@ namespace BTCPayServer.Controllers
var store = await _storeRepository.FindStore(pp.StoreId);
if (store is null)
return NotFound();
var storeBlob = store.GetStoreBlob();
var payouts = (await ctx.Payouts.GetPayoutInPeriod(pp)
.OrderByDescending(o => o.Date)

View File

@@ -424,7 +424,7 @@ namespace BTCPayServer.Controllers
{
var types = _AppService.GetAvailableAppTypes();
var apps = (await _AppService.GetAllApps(null, true))
.Select(a =>
.Select(a =>
new SelectListItem($"{types[a.AppType]} - {a.AppName} - {a.StoreName}", a.Id)).ToList();
apps.Insert(0, new SelectListItem("(None)", null));
return apps;

View File

@@ -455,9 +455,9 @@ namespace BTCPayServer.Controllers
{
IncludeArchived = false,
IncludeStoreData = true,
Stores = new[] {storeId},
Stores = new[] { storeId },
PayoutIds = payoutIds,
PaymentMethods = new[] {paymentMethodId.ToString()}
PaymentMethods = new[] { paymentMethodId.ToString() }
}, ctx, cancellationToken);
}

View File

@@ -181,7 +181,6 @@ namespace BTCPayServer.Controllers
{
CryptoCode = vm.CryptoCode,
UseBech32Scheme = true,
EnableForStandardInvoices = false,
LUD12Enabled = false
});
@@ -245,26 +244,12 @@ namespace BTCPayServer.Controllers
};
SetExistingValues(store, vm);
if (lightning != null)
{
vm.DisableBolt11PaymentMethod = lightning.DisableBOLT11PaymentOption;
}
var lnurl = GetExistingLNURLSupportedPaymentMethod(vm.CryptoCode, store);
if (lnurl != null)
{
vm.LNURLEnabled = !store.GetStoreBlob().GetExcludedPaymentMethods().Match(lnurl.PaymentId);
vm.LNURLBech32Mode = lnurl.UseBech32Scheme;
vm.LNURLStandardInvoiceEnabled = lnurl.EnableForStandardInvoices;
vm.LUD12Enabled = lnurl.LUD12Enabled;
vm.DisableBolt11PaymentMethod =
vm.LNURLEnabled && vm.LNURLStandardInvoiceEnabled && vm.DisableBolt11PaymentMethod;
}
else
{
//disable by default for now
//vm.LNURLEnabled = !lnSet;
vm.DisableBolt11PaymentMethod = false;
}
return View(vm);
@@ -290,22 +275,11 @@ namespace BTCPayServer.Controllers
blob.LightningAmountInSatoshi = vm.LightningAmountInSatoshi;
blob.LightningPrivateRouteHints = vm.LightningPrivateRouteHints;
blob.OnChainWithLnInvoiceFallback = vm.OnChainWithLnInvoiceFallback;
var disableBolt11PaymentMethod =
vm.LNURLEnabled && vm.LNURLStandardInvoiceEnabled && vm.DisableBolt11PaymentMethod;
var lnurlId = new PaymentMethodId(vm.CryptoCode, PaymentTypes.LNURLPay);
blob.SetExcluded(lnurlId, !vm.LNURLEnabled);
var lightning = GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store);
// Going to mark "lightning" as non-null here assuming that if we are POSTing here it's because we have a Lightning Node set-up
if (lightning!.DisableBOLT11PaymentOption != disableBolt11PaymentMethod)
{
needUpdate = true;
lightning.DisableBOLT11PaymentOption = disableBolt11PaymentMethod;
store.SetSupportedPaymentMethod(lightning);
}
var lnurl = GetExistingLNURLSupportedPaymentMethod(vm.CryptoCode, store);
if (lnurl is null || (
lnurl.EnableForStandardInvoices != vm.LNURLStandardInvoiceEnabled ||
lnurl.UseBech32Scheme != vm.LNURLBech32Mode ||
lnurl.LUD12Enabled != vm.LUD12Enabled))
{
@@ -315,7 +289,6 @@ namespace BTCPayServer.Controllers
store.SetSupportedPaymentMethod(new LNURLPaySupportedPaymentMethod
{
CryptoCode = vm.CryptoCode,
EnableForStandardInvoices = vm.LNURLStandardInvoiceEnabled,
UseBech32Scheme = vm.LNURLBech32Mode,
LUD12Enabled = vm.LUD12Enabled
});

View File

@@ -730,7 +730,7 @@ namespace BTCPayServer.Controllers
{
await _fileService.RemoveFile(blob.CssFileId, userId);
}
// add new CSS file
try
{

View File

@@ -267,7 +267,7 @@ namespace BTCPayServer.Controllers
ModelState.Remove(nameof(vm.PSBT));
ModelState.Remove(nameof(vm.FileName));
ModelState.Remove(nameof(vm.UploadedPSBTFile));
await FetchTransactionDetails(walletId,derivationSchemeSettings, vm, network);
await FetchTransactionDetails(walletId, derivationSchemeSettings, vm, network);
return View("WalletPSBTDecoded", vm);
case "save-psbt":
@@ -386,15 +386,15 @@ namespace BTCPayServer.Controllers
inputVm.BalanceChange = ValueToString(balanceChange2, network);
inputVm.Positive = balanceChange2 >= Money.Zero;
inputVm.Index = (int)input.Index;
var walletObjectIds = new List<ObjectTypeId>();
walletObjectIds.Add(new ObjectTypeId(WalletObjectData.Types.Utxo, input.PrevOut.ToString()));
walletObjectIds.Add(new ObjectTypeId(WalletObjectData.Types.Tx, input.PrevOut.Hash.ToString()));
walletObjectIds.Add(new ObjectTypeId(WalletObjectData.Types.Utxo, input.PrevOut.ToString()));
walletObjectIds.Add(new ObjectTypeId(WalletObjectData.Types.Tx, input.PrevOut.Hash.ToString()));
var address = txOut?.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork)?.ToString();
if(address != null)
if (address != null)
walletObjectIds.Add(new ObjectTypeId(WalletObjectData.Types.Address, address));
inputToObjects.Add(input.Index, walletObjectIds.ToArray());
}
vm.Destinations = new List<WalletPSBTReadyViewModel.DestinationViewModel>();
foreach (var output in psbtObject.Outputs)
@@ -409,9 +409,9 @@ namespace BTCPayServer.Controllers
dest.Positive = balanceChange2 >= Money.Zero;
dest.Destination = output.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork)?.ToString() ?? output.ScriptPubKey.ToString();
var address = output.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork)?.ToString();
if(address != null)
if (address != null)
outputToObjects.Add(dest.Destination, new ObjectTypeId(WalletObjectData.Types.Address, address));
}
if (psbtObject.TryGetFee(out var fee))
@@ -442,13 +442,14 @@ namespace BTCPayServer.Controllers
.DistinctBy(id => $"{id.Type}:{id.Id}").ToArray();
var labelInfo = await WalletRepository.GetWalletTransactionsInfo(walletId, combinedTypeIds);
foreach (KeyValuePair<uint,ObjectTypeId[]> inputToObject in inputToObjects)
foreach (KeyValuePair<uint, ObjectTypeId[]> inputToObject in inputToObjects)
{
var keys = inputToObject.Value.Select(id => id.Id).ToArray();
WalletTransactionInfo ix = null;
foreach (var key in keys)
{
if (!labelInfo.TryGetValue(key, out var i)) continue;
if (!labelInfo.TryGetValue(key, out var i))
continue;
if (ix is null)
{
ix = i;
@@ -458,20 +459,22 @@ namespace BTCPayServer.Controllers
ix.Merge(i);
}
}
if (ix is null) continue;
if (ix is null)
continue;
var labels = _labelService.CreateTransactionTagModels(ix, Request);
var input = vm.Inputs.First(model => model.Index == inputToObject.Key);
input.Labels = labels;
}
foreach (var outputToObject in outputToObjects)
{
if (!labelInfo.TryGetValue(outputToObject.Value.Id, out var ix)) continue;
if (!labelInfo.TryGetValue(outputToObject.Value.Id, out var ix))
continue;
var labels = _labelService.CreateTransactionTagModels(ix, Request);
var destination = vm.Destinations.First(model => model.Destination == outputToObject.Key);
destination.Labels = labels;
}
}
[HttpPost("{walletId}/psbt/ready")]
@@ -491,7 +494,7 @@ namespace BTCPayServer.Controllers
if (derivationSchemeSettings == null)
return NotFound();
await FetchTransactionDetails(walletId,derivationSchemeSettings, vm, network);
await FetchTransactionDetails(walletId, derivationSchemeSettings, vm, network);
switch (command)
{
@@ -622,7 +625,7 @@ namespace BTCPayServer.Controllers
BackUrl = vm.BackUrl
});
case "decode":
await FetchTransactionDetails(walletId,derivationSchemeSettings, vm, network);
await FetchTransactionDetails(walletId, derivationSchemeSettings, vm, network);
return View("WalletPSBTDecoded", vm);
default:
vm.Errors.Add("Unknown command");

View File

@@ -340,7 +340,7 @@ namespace BTCPayServer.Controllers
CryptoImage = GetImage(paymentMethod.PaymentId, network),
PaymentLink = bip21.ToString(),
ReturnUrl = returnUrl ?? HttpContext.Request.GetTypedHeaders().Referer?.AbsolutePath,
SelectedLabels = labels?? Array.Empty<string>()
SelectedLabels = labels ?? Array.Empty<string>()
});
}
@@ -733,14 +733,14 @@ namespace BTCPayServer.Controllers
{
var labels = transactionOutput.Labels.Where(s => !string.IsNullOrWhiteSpace(s)).ToArray();
var walletObjectAddress = new WalletObjectId(walletId, WalletObjectData.Types.Address, transactionOutput.DestinationAddress.ToLowerInvariant());
var obj = await WalletRepository.GetWalletObject(walletObjectAddress);
if (obj is null)
var obj = await WalletRepository.GetWalletObject(walletObjectAddress);
if (obj is null)
{
await WalletRepository.EnsureWalletObject(walletObjectAddress);
await WalletRepository.EnsureWalletObject(walletObjectAddress);
}
await WalletRepository.AddWalletObjectLabels(walletObjectAddress, labels);
}
var derivationScheme = GetDerivationSchemeSettings(walletId);
if (derivationScheme is null)
return NotFound();
@@ -787,10 +787,10 @@ namespace BTCPayServer.Controllers
switch (response.Result)
{
case ClaimRequest.ClaimResult.Duplicate:
errorMessage += $"{claimRequest.Value} to {claimRequest.Destination.ToString() } - address reuse<br/>";
errorMessage += $"{claimRequest.Value} to {claimRequest.Destination.ToString()} - address reuse<br/>";
break;
case ClaimRequest.ClaimResult.AmountTooLow:
errorMessage += $"{claimRequest.Value} to {claimRequest.Destination.ToString() } - amount too low<br/>";
errorMessage += $"{claimRequest.Value} to {claimRequest.Destination.ToString()} - amount too low<br/>";
break;
}
}
@@ -919,8 +919,8 @@ namespace BTCPayServer.Controllers
ModelState.Clear();
if (address is not null)
{
var addressLabels = await WalletRepository.GetWalletLabels(new WalletObjectId(walletId, WalletObjectData.Types.Address, address.ToString()));
vm.Outputs.Last().Labels = addressLabels.Select(tuple => tuple.Label).ToArray();
var addressLabels = await WalletRepository.GetWalletLabels(new WalletObjectId(walletId, WalletObjectData.Types.Address, address.ToString()));
vm.Outputs.Last().Labels = addressLabels.Select(tuple => tuple.Label).ToArray();
}
}
@@ -1350,14 +1350,14 @@ namespace BTCPayServer.Controllers
Response.Headers.Add("X-Content-Type-Options", "nosniff");
return Content(res, mimeType);
}
public class UpdateLabelsRequest
{
public string? Id { get; set; }
public string? Type { get; set; }
public string[]? Labels { get; set; }
}
[HttpPost("{walletId}/update-labels")]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> UpdateLabels(
@@ -1366,23 +1366,23 @@ namespace BTCPayServer.Controllers
{
if (string.IsNullOrEmpty(request.Type) || string.IsNullOrEmpty(request.Id) || request.Labels is null)
return BadRequest();
var objid = new WalletObjectId(walletId, request.Type, request.Id);
var obj = await WalletRepository.GetWalletObject(objid);
if (obj is null)
var obj = await WalletRepository.GetWalletObject(objid);
if (obj is null)
{
await WalletRepository.EnsureWalletObject(objid);
await WalletRepository.EnsureWalletObject(objid);
}
else
{
var currentLabels = obj.GetNeighbours().Where(data => data.Type == WalletObjectData.Types.Label).ToArray();
var toRemove = currentLabels.Where(data => !request.Labels.Contains(data.Id)).Select(data => data.Id).ToArray();
await WalletRepository.RemoveWalletObjectLabels(objid, toRemove);
var currentLabels = obj.GetNeighbours().Where(data => data.Type == WalletObjectData.Types.Label).ToArray();
var toRemove = currentLabels.Where(data => !request.Labels.Contains(data.Id)).Select(data => data.Id).ToArray();
await WalletRepository.RemoveWalletObjectLabels(objid, toRemove);
}
await WalletRepository.AddWalletObjectLabels(objid, request.Labels);
return Ok();
}
[HttpGet("{walletId}/labels")]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> GetLabels(
@@ -1399,11 +1399,11 @@ namespace BTCPayServer.Controllers
: await WalletRepository.GetWalletLabels(walletObjectId);
return Ok(labels
.Where(l => !excludeTypes || !WalletObjectData.Types.AllTypes.Contains(l.Label))
.Select(tuple => new
.Select(tuple => new
{
label = tuple.Label,
color = tuple.Color,
textColor = ColorPalette.Default.TextColor(tuple.Color)
textColor = ColorPalette.Default.TextColor(tuple.Color)
}));
}

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer.Data
{
public static class IHasBlobExtensions
{
static readonly JsonSerializerSettings DefaultSerializer;
static readonly JsonSerializerSettings DefaultSerializer;
static IHasBlobExtensions()
{
DefaultSerializer = new JsonSerializerSettings()
@@ -30,7 +30,7 @@ namespace BTCPayServer.Data
this.data = data;
}
[Obsolete("Use Blob2 instead")]
public byte[] Blob { get { return data.Blob; } set { data.Blob = value; } }
public byte[] Blob { get { return data.Blob; } set { data.Blob = value; } }
public string Blob2 { get { return data.Blob2; } set { data.Blob2 = value; } }
}
class HasBlobWrapper : IHasBlob

View File

@@ -34,7 +34,7 @@ namespace BTCPayServer.Data
}
else
{
var entity = invoiceData.HasTypedBlob<InvoiceEntity>().GetBlob();
var entity = invoiceData.HasTypedBlob<InvoiceEntity>().GetBlob();
entity.Networks = networks;
return entity;
}

View File

@@ -70,7 +70,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
var explorerClient = _explorerClientProvider.GetExplorerClient(network);
if (claimRequest.Destination is IBitcoinLikeClaimDestination bitcoinLikeClaimDestination)
{
await explorerClient.TrackAsync(TrackedSource.Create(bitcoinLikeClaimDestination.Address));
await WalletRepository.AddWalletTransactionAttachment(
new WalletId(claimRequest.StoreId, claimRequest.PaymentMethodId.CryptoCode),
@@ -210,11 +210,13 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
await using (var context = _dbContextFactory.CreateContext())
{
var payouts = (await PullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery()
{
States = new[] {PayoutState.AwaitingPayment}, Stores = new[] {storeId}, PayoutIds = payoutIds
}, context)).Where(data =>
PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId) &&
CanHandle(paymentMethodId))
{
States = new[] { PayoutState.AwaitingPayment },
Stores = new[] { storeId },
PayoutIds = payoutIds
}, context)).Where(data =>
PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId) &&
CanHandle(paymentMethodId))
.Select(data => (data, ParseProof(data) as PayoutTransactionOnChainBlob)).Where(tuple => tuple.Item2 != null && tuple.Item2.TransactionId != null && tuple.Item2.Accounted == false);
foreach (var valueTuple in payouts)
{
@@ -230,14 +232,16 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
Severity = StatusMessageModel.StatusSeverity.Success
};
case "reject-payment":
await using (var context = _dbContextFactory.CreateContext())
await using (var context = _dbContextFactory.CreateContext())
{
var payouts = (await PullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery()
{
States = new[] {PayoutState.AwaitingPayment}, Stores = new[] {storeId}, PayoutIds = payoutIds
}, context)).Where(data =>
PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId) &&
CanHandle(paymentMethodId))
{
States = new[] { PayoutState.AwaitingPayment },
Stores = new[] { storeId },
PayoutIds = payoutIds
}, context)).Where(data =>
PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId) &&
CanHandle(paymentMethodId))
.Select(data => (data, ParseProof(data) as PayoutTransactionOnChainBlob)).Where(tuple => tuple.Item2 != null && tuple.Item2.TransactionId != null && tuple.Item2.Accounted == true);
foreach (var valueTuple in payouts)
{

View File

@@ -225,11 +225,11 @@ namespace BTCPayServer.Data
[DefaultValue(true)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public bool ShowPayInWalletButton { get; set; } = true;
[DefaultValue(true)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public bool ShowStoreHeader { get; set; } = true;
[DefaultValue(true)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public bool CelebratePayment { get; set; } = true;

View File

@@ -186,12 +186,12 @@ namespace BTCPayServer.Data
{
return IsPaymentTypeEnabled(storeData, networks, cryptoCode, LNURLPayPaymentType.Instance);
}
private static bool IsPaymentTypeEnabled(this StoreData storeData, BTCPayNetworkProvider networks, string cryptoCode, PaymentType paymentType)
{
var paymentMethods = storeData.GetSupportedPaymentMethods(networks);
var excludeFilters = storeData.GetStoreBlob().GetExcludedPaymentMethods();
return paymentMethods.Any(method =>
return paymentMethods.Any(method =>
method.PaymentId.CryptoCode == cryptoCode &&
method.PaymentId.PaymentType == paymentType &&
!excludeFilters.Match(method.PaymentId));

View File

@@ -49,7 +49,7 @@ namespace BTCPayServer
{
return AccountDerivation is null ? null : DBUtils.nbxv1_get_wallet_id(Network.CryptoCode, AccountDerivation.ToString());
}
private static bool TryParseXpub(string xpub, DerivationSchemeParser derivationSchemeParser, ref DerivationSchemeSettings derivationSchemeSettings, ref string error, bool electrum = true)
{
if (!electrum)
@@ -87,9 +87,12 @@ namespace BTCPayServer
var match = derivationRegex.Match(xpub.Trim());
if (match.Success)
{
if (!string.IsNullOrEmpty(match.Groups[1].Value)) rootFingerprint = HDFingerprint.Parse(match.Groups[1].Value);
if (!string.IsNullOrEmpty(match.Groups[2].Value)) accountKeyPath = KeyPath.Parse(match.Groups[2].Value);
if (!string.IsNullOrEmpty(match.Groups[3].Value)) xpub = match.Groups[3].Value;
if (!string.IsNullOrEmpty(match.Groups[1].Value))
rootFingerprint = HDFingerprint.Parse(match.Groups[1].Value);
if (!string.IsNullOrEmpty(match.Groups[2].Value))
accountKeyPath = KeyPath.Parse(match.Groups[2].Value);
if (!string.IsNullOrEmpty(match.Groups[3].Value))
xpub = match.Groups[3].Value;
}
derivationSchemeSettings.AccountOriginal = xpub.Trim();
derivationSchemeSettings.AccountDerivation = electrum ? derivationSchemeParser.ParseElectrum(derivationSchemeSettings.AccountOriginal) : derivationSchemeParser.Parse(derivationSchemeSettings.AccountOriginal);

View File

@@ -41,13 +41,13 @@ namespace BTCPayServer
{
pattern = pattern.Replace(" ", "");
int[] res = new int[pattern.Length / 2];
for (int i = 0; i < pattern.Length; i+=2)
for (int i = 0; i < pattern.Length; i += 2)
{
var b = pattern[i..(i + 2)];
if (b == "XX")
res[i/2] = -1;
res[i / 2] = -1;
else
res[i/2] = byte.Parse(b, System.Globalization.NumberStyles.HexNumber);
res[i / 2] = byte.Parse(b, System.Globalization.NumberStyles.HexNumber);
}
return res;
}

View File

@@ -13,12 +13,12 @@ namespace BTCPayServer.Filters
public DomainMappingConstraintAttribute()
{
}
public DomainMappingConstraintAttribute(string appType)
{
AppType = appType;
}
public int Order => 100;
private string AppType { get; }
@@ -33,21 +33,22 @@ namespace BTCPayServer.Filters
{
var appId = (string)context.RouteContext.RouteData.Values["appId"];
var matchedDomainMapping = mapping.FirstOrDefault(item => item.AppId == appId);
// App is accessed via path, redirect to canonical domain
var req = context.RouteContext.HttpContext.Request;
if (matchedDomainMapping != null && req.Method != "POST" && !req.HasFormContentType)
{
var uri = new UriBuilder(req.Scheme, matchedDomainMapping.Domain);
if (req.Host.Port.HasValue) uri.Port = req.Host.Port.Value;
if (req.Host.Port.HasValue)
uri.Port = req.Host.Port.Value;
context.RouteContext.HttpContext.Response.Redirect(uri.ToString());
return true;
}
}
if (hasDomainMapping)
{
var matchedDomainMapping = mapping.FirstOrDefault(item =>
var matchedDomainMapping = mapping.FirstOrDefault(item =>
item.Domain.Equals(context.RouteContext.HttpContext.Request.Host.Host,
StringComparison.InvariantCultureIgnoreCase));
if (matchedDomainMapping != null)

View File

@@ -16,8 +16,8 @@ public static class FormDataExtensions
serviceCollection.AddSingleton<IFormComponentProvider, HtmlSelectFormProvider>();
serviceCollection.AddSingleton<IFormComponentProvider, FieldValueMirror>();
}
public static JObject Deserialize(this FormData form)
public static JObject Deserialize(this FormData form)
{
return JsonConvert.DeserializeObject<JObject>(form.Config);
}

View File

@@ -22,7 +22,7 @@ public class FormDataService
private readonly FormComponentProviders _formProviders;
public FormDataService(
ApplicationDbContextFactory applicationDbContextFactory,
ApplicationDbContextFactory applicationDbContextFactory,
FormComponentProviders formProviders)
{
_applicationDbContextFactory = applicationDbContextFactory;
@@ -47,16 +47,16 @@ public class FormDataService
Field.Create("State", "buyerState", null, false, null),
new SelectField()
{
Name = "buyerCountry",
Name = "buyerCountry",
Label = "Country",
Required = true,
Type = "select",
Options = "Afghanistan, Albania, Algeria, Andorra, Angola, Antigua and Barbuda, Argentina, Armenia, Australia, Austria, Azerbaijan, The Bahamas, Bahrain, Bangladesh, Barbados, Belarus, Belgium, Belize, Benin, Bhutan, Bolivia, Bosnia and Herzegovina, Botswana, Brazil, Brunei, Bulgaria, Burkina Faso, Burundi, Cabo Verde, Cambodia, Cameroon, Canada, Central African Republic (CAR), Chad, Chile, China, Colombia, Comoros, Democratic Republic of the Congo, Republic of the Congo, Costa Rica, Cote d'Ivoire, Croatia, Cuba, Cyprus, Czech Republic, Denmark, Djibouti, Dominica, Dominican Republic, Ecuador, Egypt, El Salvador, Equatorial Guinea, Eritrea, Estonia, Eswatini (formerly Swaziland), Ethiopia, Fiji, Finland, France, Gabon, The Gambia, Georgia, Germany, Ghana, Greece, Grenada, Guatemala, Guinea, Guinea-Bissau, Guyana, Haiti, Honduras, Hungary, Iceland, India, Indonesia, Iran, Iraq, Ireland, Israel, Italy, Jamaica, Japan, Jordan, Kazakhstan, Kenya, Kiribati, Kosovo, Kuwait, Kyrgyzstan, Laos, Latvia, Lebanon, Lesotho, Liberia, Libya, Liechtenstein, Lithuania, Luxembourg, Madagascar, Malawi, Malaysia, Maldives, Mali, Malta, Marshall Islands, Mauritania, Mauritius, Mexico, Micronesia, Moldova, Monaco, Mongolia, Montenegro, Morocco, Mozambique, Myanmar (formerly Burma), Namibia, Nauru, Nepal, Netherlands, New Zealand, Nicaragua, Niger, Nigeria, North Korea, North Macedonia (formerly Macedonia), Norway, Oman, Pakistan, Palau, Palestine, Panama, Papua New Guinea, Paraguay, Peru, Philippines, Poland, Portugal, Qatar, Romania, Russia, Rwanda, Saint Kitts and Nevis, Saint Lucia, Saint Vincent and the Grenadines, Samoa, San Marino, Sao Tome and Principe, Saudi Arabia, Senegal, Serbia, Seychelles, Sierra Leone, Singapore, Slovakia, Slovenia, Solomon Islands, Somalia, South Africa, South Korea, South Sudan, Spain, Sri Lanka, Sudan, Suriname, Sweden, Switzerland, Syria, Taiwan, Tajikistan, Tanzania, Thailand, Timor-Leste (formerly East Timor), Togo, Tonga, Trinidad and Tobago, Tunisia, Turkey, Turkmenistan, Tuvalu, Uganda, Ukraine, United Arab Emirates (UAE), United Kingdom (UK), United States of America (USA), Uruguay, Uzbekistan, Vanuatu, Vatican City (Holy See), Venezuela, Vietnam, Yemen, Zambia, Zimbabwe.".Split(',').Select(s => new SelectListItem(s,s)).ToList()
}
}
};
private static readonly Dictionary<string, (string selectText, string name, Form form)> _hardcodedOptions = new()
{
{"", ("Do not request any information", null, null)!},
@@ -64,13 +64,13 @@ public class FormDataService
{"Address", ("Request shipping address", "Provide your address", StaticFormAddress)},
};
public async Task<SelectList> GetSelect(string storeId ,string selectedFormId)
public async Task<SelectList> GetSelect(string storeId, string selectedFormId)
{
var forms = await GetForms(storeId);
return new SelectList(_hardcodedOptions.Select(pair => new SelectListItem(pair.Value.selectText, pair.Key, selectedFormId == pair.Key)).Concat(forms.Select(data => new SelectListItem(data.Name, data.Id, data.Id == selectedFormId))),
nameof(SelectListItem.Value), nameof(SelectListItem.Text));
}
public async Task<List<FormData>> GetForms(string storeId)
{
ArgumentNullException.ThrowIfNull(storeId);
@@ -129,7 +129,7 @@ public class FormDataService
{
return _formProviders.Validate(form, modelState);
}
public bool IsFormSchemaValid(string schema, [MaybeNullWhen(false)] out Form form, [MaybeNullWhen(false)] out string error)
{
error = null;
@@ -144,7 +144,7 @@ public class FormDataService
}
catch (Exception ex)
{
error = $"Form config was invalid: {ex.Message}";
error = $"Form config was invalid: {ex.Message}";
}
return error is null && form is not null;
}
@@ -177,7 +177,7 @@ public class FormDataService
public JObject GetValues(Form form)
{
var r = new JObject();
foreach (var f in form.GetAllFields())
{
var node = r;

View File

@@ -12,7 +12,7 @@ public class FieldValueMirror : IFormComponentProvider
{
if (form.GetFieldByFullName(field.Value) is null)
{
field.ValidationErrors = new List<string>() {$"{field.Name} requires {field.Value} to be present"};
field.ValidationErrors = new List<string>() { $"{field.Name} requires {field.Value} to be present" };
}
}

View File

@@ -24,7 +24,7 @@ public class HtmlSelectFormProvider : FormComponentProviderBase
}
}
public class SelectField: Field
public class SelectField : Field
{
public List<SelectListItem> Options { get; set; }
}

View File

@@ -8,7 +8,7 @@ public class ModifyForm
[DisplayName("Form configuration (JSON)")]
public string FormConfig { get; set; }
[DisplayName("Allow form for public use")]
public bool Public { get; set; }
}

View File

@@ -49,7 +49,7 @@ public class UIFormsController : Controller
[HttpGet("~/stores/{storeId}/forms/new")]
public IActionResult Create(string storeId)
{
var vm = new ModifyForm {FormConfig = new Form().ToString()};
var vm = new ModifyForm { FormConfig = new Form().ToString() };
return View("Modify", vm);
}
@@ -57,10 +57,11 @@ public class UIFormsController : Controller
public async Task<IActionResult> Modify(string storeId, string id)
{
var form = await _formDataService.GetForm(storeId, id);
if (form is null) return NotFound();
if (form is null)
return NotFound();
var config = Form.Parse(form.Config);
return View(new ModifyForm {Name = form.Name, FormConfig = config.ToString(), Public = form.Public});
return View(new ModifyForm { Name = form.Name, FormConfig = config.ToString(), Public = form.Public });
}
[HttpPost("~/stores/{storeId}/forms/modify/{id?}")]
@@ -76,7 +77,7 @@ public class UIFormsController : Controller
if (!_formDataService.IsFormSchemaValid(modifyForm.FormConfig, out var form, out var error))
{
ModelState.AddModelError(nameof(modifyForm.FormConfig),
$"Form config was invalid: {error})");
}
@@ -84,7 +85,7 @@ public class UIFormsController : Controller
{
modifyForm.FormConfig = form.ToString();
}
if (!ModelState.IsValid)
{
@@ -95,7 +96,11 @@ public class UIFormsController : Controller
{
var formData = new FormData
{
Id = id, StoreId = storeId, Name = modifyForm.Name, Config = modifyForm.FormConfig,Public = modifyForm.Public
Id = id,
StoreId = storeId,
Name = modifyForm.Name,
Config = modifyForm.FormConfig,
Public = modifyForm.Public
};
var isNew = id is null;
await _formDataService.AddOrUpdateForm(formData);
@@ -106,7 +111,7 @@ public class UIFormsController : Controller
});
if (isNew)
{
return RedirectToAction("Modify", new {storeId, id = formData.Id});
return RedirectToAction("Modify", new { storeId, id = formData.Id });
}
}
catch (Exception e)
@@ -123,9 +128,10 @@ public class UIFormsController : Controller
await _formDataService.RemoveForm(id, storeId);
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Success, Message = "Form removed"
Severity = StatusMessageModel.StatusSeverity.Success,
Message = "Form removed"
});
return RedirectToAction("FormsList", new {storeId});
return RedirectToAction("FormsList", new { storeId });
}
[AllowAnonymous]
@@ -154,7 +160,7 @@ public class UIFormsController : Controller
form.ApplyValuesFromForm(Request.Query);
var store = formData.Store ?? await _storeRepository.FindStore(formData.StoreId);
var storeBlob = store?.GetStoreBlob();
return View("View", new FormViewModel
{
FormName = formData.Name,
@@ -187,7 +193,7 @@ public class UIFormsController : Controller
if (!Request.HasFormContentType)
return await GetFormView(formData);
var form = Form.Parse(formData.Config);
form.ApplyValuesFromForm(Request.Form);
@@ -202,6 +208,6 @@ public class UIFormsController : Controller
var request = _formDataService.GenerateInvoiceParametersFromForm(form);
var inv = await invoiceController.CreateInvoiceCoreRaw(request, store, Request.GetAbsoluteRoot());
return RedirectToAction("Checkout", "UIInvoice", new {invoiceId = inv.Id});
return RedirectToAction("Checkout", "UIInvoice", new { invoiceId = inv.Id });
}
}

View File

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

View File

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

View File

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

View File

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

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