Compare commits
2 Commits
193-bump
...
lnurl-hook
Author | SHA1 | Date | |
---|---|---|---|
72e66aa576 | |||
6388057806 |
.run
BTCPayServer.Abstractions
Custodians
Form
TagHelpers
BTCPayServer.Client
BTCPayServerClient.Apps.csBTCPayServerClient.CustodianAccounts.csBTCPayServerClient.Lightning.Internal.csBTCPayServerClient.Lightning.Store.csBTCPayServerClient.StoreRatesConfiguration.cs
JsonConverters
Models
BTCPayServer.Data
BTCPayServer.Rating
BTCPayServer.Tests
AltcoinTests
BTCPayServer.Tests.csprojCheckoutUITests.csCheckoutv2Tests.csExtensions.csFastTests.csFormTes.csGreenfieldAPITests.csMockCustodian
PayJoinTests.csSeleniumTester.csSeleniumTests.csServerTester.csTestAccount.csTestUtils.csUnitTest1.csUtilitiesTests.csdocker-compose.altcoins.ymldocker-compose.ymlBTCPayServer
BTCPayServer.csprojBufferizedFormFile.csColorPalette.cs
Components
AppSales
Icon
LabelManager
MainNav
Pager
QRCode
StoreRecentTransactions
TruncateCenter
Configuration
Controllers
GreenField
GreenfieldAppsController.csGreenfieldCustodianAccountController.csGreenfieldInvoiceController.csGreenfieldPaymentRequestsController.csGreenfieldPayoutProcessorsController.csGreenfieldPullPaymentController.csGreenfieldStoreAutomatedLightningPayoutProcessorsController.csGreenfieldStoreAutomatedOnChainPayoutProcessorsController.csGreenfieldStoreLNURLPayPaymentMethodsController.csGreenfieldStoreLightningAddressesController.csGreenfieldStoreLightningNetworkPaymentMethodsController.csGreenfieldStoreRatesConfigurationController.csGreenfieldStoreRatesController.csGreenfieldStoresController.csLocalBTCPayServerClient.cs
LightningAddressService.csUIAccountController.csUIAppsController.csUICustodianAccountsController.csUIInvoiceController.UI.csUIInvoiceController.csUILNURLController.csUIPaymentRequestController.csUIPullPaymentController.csUIServerController.csUIStorePullPaymentsController.PullPayments.csUIStoresController.Email.csUIStoresController.LightningLike.csUIStoresController.csUIUserStoresController.csUIWalletsController.PSBT.csUIWalletsController.csData
DerivationSchemeSettings.csExtensions
FileTypeDetector.csFilters
Forms
FormComponentProviders.csFormDataExtensions.csFormDataService.csHtmlFieldsetFormProvider.csHtmlInputFormProvider.csHtmlSelectFormProvider.csIFormComponentProvider.csModifyForm.csUIFormsController.cs
HostedServices
BaseAsyncService.csBitpayIPNSender.csDelayedTransactionBroadcasterHostedService.csDynamicDnsHostedService.csNewVersionCheckerHostedService.csPullPaymentHostedService.csRatesHostedService.csStoreEmailRuleProcessorSender.csTransactionLabelMarkerHostedService.csWebhookSender.cs
Hosting
Models
AppViewModels
CustodianAccountViewModels
AssetBalanceInfo.csCreateCustodianAccountViewModel.csEditCustodianAccountViewModel.csTradePrepareViewModel.cs
InvoicingModels
PostRedictViewModel.csStoreViewModels
CheckoutAppearanceViewModel.csGeneralSettingsViewModel.csLightningSettingsViewModel.csPaymentViewModel.cs
WalletViewModels
Payments
Bitcoin
IPaymentMethodHandler.csLNURLPay
LNURLPayPaymentHandler.csLNURLPayPaymentMethodDetails.csLNURLPaySupportedPaymentMethod.csPaymentTypes.LNURL.cs
Lightning
LightningLikePaymentData.csLightningLikePaymentHandler.csLightningListener.csLightningPendingPayoutListener.csLightningSupportedPaymentMethod.cs
PayJoin
PaymentMethodId.csPaymentTypes.Bitcoin.csPaymentTypes.Lightning.csPaymentTypes.csPayoutProcessors
Plugins
Properties
Roles.csServices
Altcoins
Monero/Payments
Zcash/Payments
Apps
BTCPayServerEnvironment.csDisplayFormatter.csInvoices
Labels
LanguageService.csMigrationSettings.csNotifications
PoliciesSettings.csStores
TorServices.csWalletRepository.csWallets/Export
TagHelpers
Views
Shared
Bitcoin
BitcoinLikeMethodCheckout-v2.cshtmlBitcoinLikeMethodCheckout.cshtmlViewBitcoinLikePaymentData.cshtml
ConfirmModal.cshtmlCrowdfund
Forms
Lightning
LightningLikeMethodCheckout-v2.cshtmlLightningLikeMethodCheckout.cshtmlViewLightningLikePaymentData.cshtml
NFC
PointOfSale
PosData.cshtmlShopify
_Form.cshtml_StoreFooterLogo.cshtmlUIApps
UICustodianAccounts
UIForms
UIHome
UIInvoice
Checkout-Body.cshtmlCheckout-Cheating.cshtmlCheckoutNoScript.cshtmlCheckoutV2.cshtmlInvoice.cshtmlInvoiceReceipt.cshtmlListInvoicesPaymentsPartial.cshtml
UILNURL
UILightningAutomatedPayoutProcessors
UIOnChainAutomatedPayoutProcessors
UIPaymentRequest
UIStorePullPayments
UIStores
UIUserStores
UIWallets
wwwroot
checkout-v2
img/readme
js
locales/checkout
am-ET.jsonar.jsonaz.jsonbg-BG.jsonbs-BA.jsonca-ES.jsoncs-CZ.jsonda-DK.jsonde-DE.jsonel-GR.jsonen.jsones-ES.jsonfa.jsonfi-FI.jsonfr-FR.jsonhe.jsonhi.jsonhr-HR.jsonhu-HU.jsonhy.jsonid.jsonis-IS.jsonit-IT.jsonja-JP.jsonka.jsonkk-KZ.jsonko.jsonlv.jsonnl-NL.jsonno.jsonnp-NP.jsonpl.jsonpt-BR.jsonpt-PT.jsonro.jsonru-RU.jsonsk-SK.jsonsl-SI.jsonsr.jsonsv.jsonth-TH.jsontr.jsonuk-UA.jsonvi-VN.jsonzh-SG.jsonzh-SP.jsonzh-TW.jsonzu.json
main
swagger/v1
Build
Changelog.mdPlugins
BTCPayServer.Plugins.Custodians.FakeCustodian
BTCPayServer.Plugins.Test
BTCPayServer.Plugins.Test.csproj
Controllers
Data
Migrations
Resources/img
Services
TestExtension.csTestPluginMigrationRunner.csViews
21
.run/Pack Test Extension.run.xml
Normal file
21
.run/Pack Test Extension.run.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Pack Test Plugin" type="DotNetProject" factoryName=".NET Project" singleton="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/BTCPayServer.PluginPacker/bin/Debug/netcoreapp3.1/BTCPayServer.PluginPacker.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="../../../../BTCPayServer.Plugins.Test\bin\Debug\netcoreapp3.1 BTCPayServer.Plugins.Test "../../../../Packed Plugins"" />
|
||||
<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>
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -21,6 +20,6 @@ public interface ICustodian
|
||||
*/
|
||||
Task<Dictionary<string, decimal>> GetAssetBalancesAsync(JObject config, CancellationToken cancellationToken);
|
||||
|
||||
public Task<Form.Form> GetConfigForm(JObject config, CancellationToken cancellationToken = default);
|
||||
public Task<Form.Form> GetConfigForm(CancellationToken cancellationToken = default);
|
||||
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -32,8 +32,6 @@ public class Form
|
||||
// Are all the fields valid in the form?
|
||||
public bool IsValid()
|
||||
{
|
||||
if (TopMessages?.Any(t => t.Type == AlertMessage.AlertMessageType.Danger) is true)
|
||||
return false;
|
||||
return Fields.Select(f => f.IsValid()).All(o => o);
|
||||
}
|
||||
|
||||
@ -137,5 +135,25 @@ public class Form
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public JObject GetValues()
|
||||
{
|
||||
var r = new JObject();
|
||||
foreach (var f in GetAllFields())
|
||||
{
|
||||
var node = r;
|
||||
for (int i = 0; i < f.Path.Count - 1; i++)
|
||||
{
|
||||
var p = f.Path[i];
|
||||
var child = node[p] as JObject;
|
||||
if (child is null)
|
||||
{
|
||||
child = new JObject();
|
||||
node[p] = child;
|
||||
}
|
||||
node = child;
|
||||
}
|
||||
node[f.Field.Name] = f.Field.Value;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -30,9 +30,9 @@ namespace BTCPayServer.JsonConverters
|
||||
case JTokenType.Integer:
|
||||
case JTokenType.String:
|
||||
if (objectType == typeof(decimal) || objectType == typeof(decimal?))
|
||||
return decimal.Parse(token.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture);
|
||||
return decimal.Parse(token.ToString(), CultureInfo.InvariantCulture);
|
||||
if (objectType == typeof(double) || objectType == typeof(double?))
|
||||
return double.Parse(token.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture);
|
||||
return double.Parse(token.ToString(), CultureInfo.InvariantCulture);
|
||||
throw new JsonSerializationException("Unexpected object type: " + objectType);
|
||||
case JTokenType.Null when objectType == typeof(decimal?) || objectType == typeof(double?):
|
||||
return null;
|
||||
|
@ -4,7 +4,6 @@ using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Lightning;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.JsonConverters
|
||||
{
|
||||
@ -12,19 +11,13 @@ namespace BTCPayServer.Client.JsonConverters
|
||||
{
|
||||
public override TradeQuantity ReadJson(JsonReader reader, Type objectType, TradeQuantity existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
JToken token = JToken.Load(reader);
|
||||
switch (token.Type)
|
||||
{
|
||||
case JTokenType.Float:
|
||||
case JTokenType.Integer:
|
||||
case JTokenType.String:
|
||||
if (TradeQuantity.TryParse(token.ToString(), out var q))
|
||||
return q;
|
||||
break;
|
||||
case JTokenType.Null:
|
||||
return null;
|
||||
}
|
||||
throw new JsonObjectException("Invalid TradeQuantity, expected string. Expected: \"1.50\" or \"50%\"", reader);
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
return null;
|
||||
if (reader.TokenType != JsonToken.String)
|
||||
throw new JsonObjectException("Invalid TradeQuantity, expected string. Expected: \"1.50\" or \"50%\"", reader);
|
||||
if (TradeQuantity.TryParse((string)reader.Value, out var q))
|
||||
return q;
|
||||
throw new JsonObjectException("Invalid format for TradeQuantity. Expected: \"1.50\" or \"50%\"", reader);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, TradeQuantity value, JsonSerializer serializer)
|
||||
|
@ -86,7 +86,6 @@ 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
|
||||
|
@ -3,6 +3,7 @@ namespace BTCPayServer.Client.Models
|
||||
public class LNURLPayPaymentMethodBaseData
|
||||
{
|
||||
public bool UseBech32Scheme { get; set; }
|
||||
public bool EnableForStandardInvoices { get; set; }
|
||||
public bool LUD12Enabled { get; set; }
|
||||
|
||||
public LNURLPayPaymentMethodBaseData()
|
||||
|
@ -16,11 +16,12 @@ namespace BTCPayServer.Client.Models
|
||||
{
|
||||
}
|
||||
|
||||
public LNURLPayPaymentMethodData(string cryptoCode, bool enabled, bool useBech32Scheme)
|
||||
public LNURLPayPaymentMethodData(string cryptoCode, bool enabled, bool useBech32Scheme, bool enableForStandardInvoices)
|
||||
{
|
||||
Enabled = enabled;
|
||||
CryptoCode = cryptoCode;
|
||||
UseBech32Scheme = useBech32Scheme;
|
||||
EnableForStandardInvoices = enableForStandardInvoices;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,5 +6,5 @@ public class LightningAddressData
|
||||
public string CurrencyCode { get; set; }
|
||||
public decimal? Min { get; set; }
|
||||
public decimal? Max { get; set; }
|
||||
|
||||
|
||||
}
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -4,6 +4,7 @@ namespace BTCPayServer.Client.Models
|
||||
{
|
||||
|
||||
public string ConnectionString { get; set; }
|
||||
public bool DisableBOLT11PaymentOption { get; set; }
|
||||
public LightningNetworkPaymentMethodBaseData()
|
||||
{
|
||||
|
||||
|
@ -16,12 +16,13 @@ namespace BTCPayServer.Client.Models
|
||||
{
|
||||
}
|
||||
|
||||
public LightningNetworkPaymentMethodData(string cryptoCode, string connectionString, bool enabled, string paymentMethod)
|
||||
public LightningNetworkPaymentMethodData(string cryptoCode, string connectionString, bool enabled, string paymentMethod, bool disableBOLT11PaymentOption)
|
||||
{
|
||||
Enabled = enabled;
|
||||
CryptoCode = cryptoCode;
|
||||
ConnectionString = connectionString;
|
||||
PaymentMethod = paymentMethod;
|
||||
DisableBOLT11PaymentOption = disableBOLT11PaymentOption;
|
||||
}
|
||||
|
||||
public string PaymentMethod { get; set; }
|
||||
|
@ -1,11 +1,8 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class TradeRequestData
|
||||
{
|
||||
public string FromAsset { set; get; }
|
||||
public string ToAsset { set; get; }
|
||||
[JsonConverter(typeof(JsonConverters.TradeQuantityJsonConverter))]
|
||||
public TradeQuantity Qty { set; get; }
|
||||
public string Qty { set; get; }
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Net.Http.Headers;
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
@ -14,9 +14,9 @@ public class WithdrawRequestData
|
||||
|
||||
public WithdrawRequestData()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public WithdrawRequestData(string paymentMethod, TradeQuantity qty)
|
||||
{
|
||||
PaymentMethod = paymentMethod;
|
||||
|
@ -7,7 +7,7 @@ namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class WithdrawalResponseData : WithdrawalBaseResponseData
|
||||
{
|
||||
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public WithdrawalStatus Status { get; }
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>()
|
||||
|
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Data;
|
||||
|
||||
@ -39,6 +38,4 @@ public class LightningAddressDataBlob
|
||||
public string CurrencyCode { get; set; }
|
||||
public decimal? Min { get; set; }
|
||||
public decimal? Max { get; set; }
|
||||
|
||||
public JObject InvoiceMetadata { get; set; }
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
@ -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)
|
||||
{
|
||||
|
@ -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>();
|
||||
|
@ -51,7 +51,6 @@ 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
|
||||
|
@ -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="112.0.5615.4900" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="110.0.5481.7700" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Views.Stores;
|
||||
@ -28,7 +27,6 @@ namespace BTCPayServer.Tests
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser();
|
||||
s.CreateNewStore();
|
||||
s.EnableCheckout(CheckoutType.V1);
|
||||
s.AddDerivationScheme();
|
||||
s.GoToStore(StoreNavPages.CheckoutAppearance);
|
||||
s.Driver.FindElement(By.Id("RequiresRefundEmail")).Click();
|
||||
@ -74,7 +72,6 @@ namespace BTCPayServer.Tests
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser();
|
||||
s.CreateNewStore();
|
||||
s.EnableCheckout(CheckoutType.V1);
|
||||
s.AddDerivationScheme();
|
||||
|
||||
// Now create an invoice that requires a refund email
|
||||
@ -127,7 +124,6 @@ namespace BTCPayServer.Tests
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser();
|
||||
s.CreateNewStore();
|
||||
s.EnableCheckout(CheckoutType.V1);
|
||||
s.AddDerivationScheme();
|
||||
|
||||
var invoiceId = s.CreateInvoice();
|
||||
@ -158,13 +154,13 @@ namespace BTCPayServer.Tests
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser(true);
|
||||
s.CreateNewStore();
|
||||
s.EnableCheckout(CheckoutType.V1);
|
||||
s.AddLightningNode();
|
||||
s.AddDerivationScheme();
|
||||
|
||||
var invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC_LightningLike");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
Assert.Equal("Bitcoin (Lightning)", s.Driver.FindElement(By.ClassName("payment__currencies")).Text);
|
||||
|
||||
Assert.Equal("Bitcoin (Lightning) (BTC)", s.Driver.FindElement(By.ClassName("payment__currencies")).Text);
|
||||
s.Driver.Quit();
|
||||
}
|
||||
|
||||
@ -178,7 +174,6 @@ namespace BTCPayServer.Tests
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser(true);
|
||||
s.CreateNewStore();
|
||||
s.EnableCheckout(CheckoutType.V1);
|
||||
s.AddLightningNode();
|
||||
s.GoToLightningSettings();
|
||||
s.Driver.SetCheckbox(By.Id("LightningAmountInSatoshi"), true);
|
||||
@ -187,7 +182,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var invoiceId = s.CreateInvoice(10, "USD", "a@g.com");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
Assert.Contains("sats", s.Driver.FindElement(By.ClassName("buyerTotalLine")).Text);
|
||||
Assert.Contains("sats", s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Text);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
@ -198,7 +193,6 @@ namespace BTCPayServer.Tests
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser();
|
||||
s.CreateNewStore();
|
||||
s.EnableCheckout(CheckoutType.V1);
|
||||
s.GoToStore();
|
||||
s.AddDerivationScheme();
|
||||
var invoiceId = s.CreateInvoice(0.001m, "BTC", "a@x.com");
|
||||
|
@ -1,13 +1,11 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Views.Stores;
|
||||
using NBitcoin;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Support.Extensions;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
@ -34,6 +32,7 @@ namespace BTCPayServer.Tests
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser(true);
|
||||
s.CreateNewStore();
|
||||
s.EnableCheckoutV2();
|
||||
s.AddLightningNode();
|
||||
// Use non-legacy derivation scheme
|
||||
s.AddDerivationScheme("BTC", "tpubDD79XF4pzhmPSJ9AyUay9YbXAeD1c6nkUqC32pnKARJH6Ja5hGUfGc76V82ahXpsKqN6UcSGXMkzR34aZq4W23C6DAdZFaVrzWqzj24F8BC");
|
||||
@ -44,14 +43,15 @@ 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);
|
||||
|
||||
s.GoToStore(StoreNavPages.CheckoutAppearance);
|
||||
s.Driver.WaitForAndClick(By.Id("Presets"));
|
||||
s.Driver.WaitForAndClick(By.Id("Presets_InStore"));
|
||||
Assert.True(s.Driver.SetCheckbox(By.Id("ShowPayInWalletButton"), true));
|
||||
s.Driver.FindElement(By.Id("Save")).SendKeys(Keys.Enter);
|
||||
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);
|
||||
|
||||
// Top up/zero amount invoices
|
||||
var invoiceId = s.CreateInvoice(amount: null);
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
@ -71,7 +71,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(address, copyAddress);
|
||||
Assert.Equal($"bitcoin:{address.ToUpperInvariant()}", qrValue);
|
||||
s.Driver.ElementDoesNotExist(By.Id("Lightning_BTC"));
|
||||
|
||||
s.Driver.ElementDoesNotExist(By.Id("PayByLNURL"));
|
||||
|
||||
// Details should show exchange rate
|
||||
s.Driver.ToggleCollapse("PaymentDetails");
|
||||
s.Driver.ElementDoesNotExist(By.Id("PaymentDetails-TotalPrice"));
|
||||
@ -79,7 +80,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(() =>
|
||||
@ -88,6 +89,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.StartsWith("lightning:lnurl", payUrl);
|
||||
Assert.StartsWith("lnurl", s.Driver.WaitForElement(By.Id("Lightning_BTC")).GetAttribute("value"));
|
||||
s.Driver.ElementDoesNotExist(By.Id("Address_BTC"));
|
||||
s.Driver.FindElement(By.Id("PayByLNURL"));
|
||||
});
|
||||
|
||||
// Default payment method
|
||||
@ -106,6 +108,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(address, copyAddress);
|
||||
Assert.Equal($"lightning:{address.ToUpperInvariant()}", qrValue);
|
||||
s.Driver.ElementDoesNotExist(By.Id("Address_BTC"));
|
||||
s.Driver.FindElement(By.Id("PayByLNURL"));
|
||||
|
||||
// Lightning amount in sats
|
||||
Assert.Contains("BTC", s.Driver.FindElement(By.Id("AmountDue")).Text);
|
||||
@ -117,7 +120,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"));
|
||||
@ -141,7 +144,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(expiredSection.Displayed);
|
||||
Assert.Contains("Invoice Expired", expiredSection.Text);
|
||||
});
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("receipt-btn")));
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ReceiptLink")));
|
||||
Assert.Equal(storeUrl, s.Driver.FindElement(By.Id("StoreLink")).GetAttribute("href"));
|
||||
|
||||
// Test payment
|
||||
@ -172,36 +175,41 @@ namespace BTCPayServer.Tests
|
||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||
|
||||
// Fake Pay
|
||||
s.Driver.FindElement(By.Id("FakePayAmount")).FillIn(amountFraction);
|
||||
s.Driver.FindElement(By.Id("FakePay")).Click();
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
Assert.Contains("Created transaction",
|
||||
s.Driver.WaitForElement(By.Id("CheatSuccessMessage")).Text);
|
||||
s.Server.ExplorerNode.Generate(2);
|
||||
paymentInfo = s.Driver.WaitForElement(By.Id("PaymentInfo"));
|
||||
Assert.Contains("The invoice hasn't been paid in full", paymentInfo.Text);
|
||||
Assert.Contains("Please send", paymentInfo.Text);
|
||||
});
|
||||
|
||||
s.Driver.Navigate().Refresh();
|
||||
|
||||
// Pay full amount
|
||||
s.PayInvoice();
|
||||
|
||||
var amountDue = s.Driver.FindElement(By.Id("AmountDue")).GetAttribute("data-amount-due");
|
||||
s.Driver.FindElement(By.Id("FakePayAmount")).FillIn(amountDue);
|
||||
s.Driver.FindElement(By.Id("FakePay")).Click();
|
||||
|
||||
// Processing
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
var processingSection = s.Driver.WaitForElement(By.Id("processing"));
|
||||
Assert.True(processingSection.Displayed);
|
||||
Assert.Contains("Payment Received", processingSection.Text);
|
||||
Assert.Contains("Payment Sent", 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();
|
||||
s.Driver.FindElement(By.Id("Mine")).Click();
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
Assert.Contains("Mined 1 block",
|
||||
s.Driver.WaitForElement(By.Id("CheatSuccessMessage")).Text);
|
||||
});
|
||||
|
||||
|
||||
// Settled
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
@ -210,7 +218,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("Invoice Paid", settledSection.Text);
|
||||
});
|
||||
s.Driver.FindElement(By.Id("confetti"));
|
||||
s.Driver.FindElement(By.Id("receipt-btn"));
|
||||
s.Driver.FindElement(By.Id("ReceiptLink"));
|
||||
Assert.Equal(storeUrl, s.Driver.FindElement(By.Id("StoreLink")).GetAttribute("href"));
|
||||
|
||||
// BIP21
|
||||
@ -239,7 +247,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.StartsWith("lnbcrt", copyAddressLightning);
|
||||
Assert.StartsWith($"bitcoin:{address.ToUpperInvariant()}?amount=", qrValue);
|
||||
Assert.Contains("&lightning=LNBCRT", qrValue);
|
||||
|
||||
s.Driver.FindElement(By.Id("PayByLNURL"));
|
||||
|
||||
// Check details
|
||||
s.Driver.ToggleCollapse("PaymentDetails");
|
||||
Assert.Contains("1 BTC = ", s.Driver.FindElement(By.Id("PaymentDetails-ExchangeRate")).Text);
|
||||
@ -247,7 +256,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);
|
||||
@ -257,7 +266,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);
|
||||
@ -265,7 +274,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");
|
||||
@ -275,7 +284,8 @@ namespace BTCPayServer.Tests
|
||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||
Assert.StartsWith("bitcoin:", payUrl);
|
||||
Assert.Contains("&lightning=lnbcrt", payUrl);
|
||||
|
||||
s.Driver.FindElement(By.Id("PayByLNURL"));
|
||||
|
||||
// Check details
|
||||
s.Driver.ToggleCollapse("PaymentDetails");
|
||||
Assert.Contains("1 sat = ", s.Driver.FindElement(By.Id("PaymentDetails-ExchangeRate")).Text);
|
||||
@ -288,7 +298,8 @@ 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);
|
||||
@ -306,7 +317,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(address, copyAddressOnchain);
|
||||
Assert.StartsWith("lnurl", copyAddressLightning);
|
||||
Assert.StartsWith($"bitcoin:{address.ToUpperInvariant()}?lightning=LNURL", qrValue);
|
||||
|
||||
s.Driver.FindElement(By.Id("PayByLNURL"));
|
||||
|
||||
// Check details
|
||||
s.Driver.ToggleCollapse("PaymentDetails");
|
||||
Assert.Contains("1 sat = ", s.Driver.FindElement(By.Id("PaymentDetails-ExchangeRate")).Text);
|
||||
@ -325,7 +337,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();
|
||||
@ -337,13 +349,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");
|
||||
@ -353,7 +365,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();
|
||||
@ -372,7 +384,8 @@ namespace BTCPayServer.Tests
|
||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||
Assert.StartsWith("bitcoin:", payUrl);
|
||||
Assert.Contains("&lightning=lnbcrt", payUrl);
|
||||
|
||||
s.Driver.FindElement(By.Id("PayByLNURL"));
|
||||
|
||||
// Language Switch
|
||||
var languageSelect = new SelectElement(s.Driver.FindElement(By.Id("DefaultLang")));
|
||||
Assert.Equal("English", languageSelect.SelectedOption.Text);
|
||||
@ -381,9 +394,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.WaitForElement(By.Id("DefaultLang")));
|
||||
languageSelect = new SelectElement(s.Driver.FindElement(By.Id("DefaultLang")));
|
||||
Assert.Equal("Deutsch", languageSelect.SelectedOption.Text);
|
||||
Assert.Equal("Details anzeigen", s.Driver.FindElement(By.Id("DetailsToggle")).Text);
|
||||
languageSelect.SelectByText("English");
|
||||
@ -399,6 +412,7 @@ namespace BTCPayServer.Tests
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser();
|
||||
s.CreateNewStore();
|
||||
s.EnableCheckoutV2();
|
||||
s.GoToStore();
|
||||
s.AddDerivationScheme();
|
||||
var invoiceId = s.CreateInvoice(0.001m, "BTC", "a@x.com");
|
||||
|
@ -199,15 +199,11 @@ retry:
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool SetCheckbox(this IWebDriver driver, By selector, bool value)
|
||||
public static void SetCheckbox(this IWebDriver driver, By selector, bool value)
|
||||
{
|
||||
var element = driver.FindElement(selector);
|
||||
if (value != element.Selected)
|
||||
{
|
||||
driver.WaitForAndClick(selector);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -134,33 +134,6 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void CanParseDecimals()
|
||||
{
|
||||
CanParseDecimalsCore("{\"qty\": 1}", 1.0m);
|
||||
CanParseDecimalsCore("{\"qty\": \"1\"}", 1.0m);
|
||||
CanParseDecimalsCore("{\"qty\": 1.0}", 1.0m);
|
||||
CanParseDecimalsCore("{\"qty\": \"1.0\"}", 1.0m);
|
||||
CanParseDecimalsCore("{\"qty\": 6.1e-7}", 6.1e-7m);
|
||||
CanParseDecimalsCore("{\"qty\": \"6.1e-7\"}", 6.1e-7m);
|
||||
|
||||
var data = JsonConvert.DeserializeObject<TradeRequestData>("{\"qty\": \"6.1e-7\", \"fromAsset\":\"Test\"}");
|
||||
Assert.Equal(6.1e-7m, data.Qty.Value);
|
||||
Assert.Equal("Test", data.FromAsset);
|
||||
data = JsonConvert.DeserializeObject<TradeRequestData>("{\"fromAsset\":\"Test\", \"qty\": \"6.1e-7\"}");
|
||||
Assert.Equal(6.1e-7m, data.Qty.Value);
|
||||
Assert.Equal("Test", data.FromAsset);
|
||||
}
|
||||
|
||||
private void CanParseDecimalsCore(string str, decimal expected)
|
||||
{
|
||||
var d = JsonConvert.DeserializeObject<LedgerEntryData>(str);
|
||||
Assert.Equal(expected, d.Qty);
|
||||
var d2 = JsonConvert.DeserializeObject<TradeRequestData>(str);
|
||||
Assert.Equal(new TradeQuantity(expected, TradeQuantity.ValueType.Exact), d2.Qty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanMergeReceiptOptions()
|
||||
{
|
||||
@ -626,7 +599,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 +739,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));
|
||||
|
@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
using BTCPayServer.Forms;
|
||||
@ -40,10 +42,9 @@ public class FormTests : UnitTestBase
|
||||
}
|
||||
}
|
||||
};
|
||||
var providers = new FormComponentProviders(new List<IFormComponentProvider>());
|
||||
var service = new FormDataService(null, providers);
|
||||
var service = new FormDataService(null, null);
|
||||
Assert.False(service.IsFormSchemaValid(form.ToString(), out _, out _));
|
||||
form = new Form
|
||||
form = new Form()
|
||||
{
|
||||
Fields = new List<Field>
|
||||
{
|
||||
@ -116,7 +117,7 @@ public class FormTests : UnitTestBase
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
form = new Form()
|
||||
{
|
||||
Fields = new List<Field>
|
||||
@ -143,7 +144,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;
|
||||
@ -160,12 +161,12 @@ public class FormTests : UnitTestBase
|
||||
}
|
||||
}
|
||||
|
||||
var obj = service.GetValues(form);
|
||||
var obj = form.GetValues();
|
||||
Assert.Equal("original", obj["invoice"]["test"].Value<string>());
|
||||
Assert.Equal("updated", obj["invoice_item3"].Value<string>());
|
||||
Clear(form);
|
||||
form.SetValues(obj);
|
||||
obj = service.GetValues(form);
|
||||
obj = form.GetValues();
|
||||
Assert.Equal("original", obj["invoice"]["test"].Value<string>());
|
||||
Assert.Equal("updated", obj["invoice_item3"].Value<string>());
|
||||
|
||||
@ -183,10 +184,10 @@ public class FormTests : UnitTestBase
|
||||
}
|
||||
};
|
||||
form.SetValues(obj);
|
||||
obj = service.GetValues(form);
|
||||
obj = form.GetValues();
|
||||
Assert.Null(obj["test"].Value<string>());
|
||||
form.SetValues(new JObject { ["test"] = "hello" });
|
||||
obj = service.GetValues(form);
|
||||
form.SetValues(new JObject{ ["test"] = "hello" });
|
||||
obj = form.GetValues();
|
||||
Assert.Equal("hello", obj["test"].Value<string>());
|
||||
}
|
||||
|
@ -15,15 +15,12 @@ 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;
|
||||
@ -305,8 +302,7 @@ 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");
|
||||
});
|
||||
|
||||
@ -455,7 +451,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"
|
||||
@ -466,12 +462,10 @@ 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");
|
||||
});
|
||||
|
||||
@ -494,8 +488,7 @@ 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);
|
||||
});
|
||||
}
|
||||
@ -519,8 +512,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" });
|
||||
|
||||
@ -551,7 +544,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);
|
||||
@ -1073,7 +1066,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);
|
||||
@ -1098,7 +1091,7 @@ namespace BTCPayServer.Tests
|
||||
Destination = new Key().GetAddress(ScriptPubKeyType.TaprootBIP86, Network.RegTest).ToString()
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
var pullPayment = await approved.CreatePullPayment(acc.StoreId, new CreatePullPaymentRequest()
|
||||
{
|
||||
Amount = 100,
|
||||
@ -1107,7 +1100,7 @@ namespace BTCPayServer.Tests
|
||||
PaymentMethods = new[] { "BTC" },
|
||||
AutoApproveClaims = true
|
||||
});
|
||||
|
||||
|
||||
var p = await approved.CreatePayout(acc.StoreId, new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
Amount = 100,
|
||||
@ -1263,10 +1256,7 @@ 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()
|
||||
{
|
||||
@ -1275,8 +1265,7 @@ namespace BTCPayServer.Tests
|
||||
PaymentMethod = "BTC",
|
||||
CurrencyCode = "USD"
|
||||
}
|
||||
}
|
||||
});
|
||||
}});
|
||||
Assert.Equal("B", updatedStore.Name);
|
||||
var s = (await client.GetStore(newStore.Id));
|
||||
Assert.Equal("B", s.Name);
|
||||
@ -1286,9 +1275,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);
|
||||
@ -1477,7 +1466,6 @@ 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
|
||||
@ -2343,10 +2331,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);
|
||||
@ -2415,7 +2403,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);
|
||||
@ -2454,7 +2442,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);
|
||||
@ -2500,7 +2488,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
|
||||
@ -2515,12 +2503,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"));
|
||||
@ -3205,7 +3193,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Fact(Timeout =TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task StoreLightningAddressesAPITests()
|
||||
{
|
||||
@ -3217,14 +3205,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
|
||||
@ -3233,8 +3221,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());
|
||||
|
||||
@ -3245,8 +3233,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)]
|
||||
@ -3412,8 +3400,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(600) });
|
||||
Assert.Equal(600, Assert.Single(await adminClient.GetStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork")).IntervalSeconds.TotalSeconds);
|
||||
new LightningAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(2) });
|
||||
Assert.Equal(2, Assert.Single(await adminClient.GetStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork")).IntervalSeconds.TotalSeconds);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var payoutC =
|
||||
@ -3498,8 +3486,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.Empty(await adminClient.GetPayoutProcessors(admin.StoreId));
|
||||
|
||||
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC",
|
||||
new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(3600) });
|
||||
Assert.Equal(3600, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
|
||||
new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(100000) });
|
||||
Assert.Equal(100000, 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));
|
||||
@ -3512,10 +3500,8 @@ 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.00001m) + fee);
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.000012m));
|
||||
await tester.ExplorerNode.GenerateAsync(1);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
@ -3525,9 +3511,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(3, payouts.Length);
|
||||
});
|
||||
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC",
|
||||
new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(600), FeeBlockTarget = 1000 });
|
||||
Assert.Equal(600, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
|
||||
|
||||
new OnChainAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(5) });
|
||||
Assert.Equal(5, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
Assert.Equal(2, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count());
|
||||
@ -3535,10 +3520,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.Single(payouts.Where(data => data.State == PayoutState.InProgress));
|
||||
});
|
||||
|
||||
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 tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.01m));
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
Assert.Equal(4, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count());
|
||||
@ -3723,7 +3706,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);
|
||||
@ -3957,7 +3940,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;
|
||||
|
||||
@ -3998,19 +3981,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);
|
||||
@ -4018,7 +4001,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 = MockCustodian.TradeQtyBought.ToString(CultureInfo.InvariantCulture) };
|
||||
await AssertHttpError(401, async () => await unauthClient.MarketTradeCustodianAccountAsset(storeId, accountId, tradeRequest));
|
||||
|
||||
// Test: Trade, auth, but wrong permission
|
||||
@ -4045,13 +4028,17 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
|
||||
Assert.Equal(LedgerEntryData.LedgerEntryType.Fee, newTradeResult.LedgerEntries[2].Type);
|
||||
|
||||
// Test: GetTradeQuote, SATS
|
||||
var satsTradeRequest = new TradeRequestData { FromAsset = MockCustodian.TradeFromAsset, ToAsset = "SATS", Qty = new TradeQuantity(MockCustodian.TradeQtyBought, TradeQuantity.ValueType.Exact) };
|
||||
var satsTradeRequest = new TradeRequestData { FromAsset = MockCustodian.TradeFromAsset, ToAsset = "SATS", Qty = MockCustodian.TradeQtyBought.ToString(CultureInfo.InvariantCulture) };
|
||||
await AssertApiError(400, "use-asset-synonym", async () => await tradeClient.MarketTradeCustodianAccountAsset(storeId, accountId, satsTradeRequest));
|
||||
|
||||
// TODO Test: Trade with percentage qty
|
||||
|
||||
// Test: Trade with wrong decimal format (example: JavaScript scientific format)
|
||||
var wrongQtyTradeRequest = new TradeRequestData { FromAsset = MockCustodian.TradeFromAsset, ToAsset = MockCustodian.TradeToAsset, Qty = "6.1e-7" };
|
||||
await AssertApiError(400, "bad-qty-format", async () => await tradeClient.MarketTradeCustodianAccountAsset(storeId, accountId, wrongQtyTradeRequest));
|
||||
|
||||
// Test: Trade, wrong assets method
|
||||
var wrongAssetsTradeRequest = new TradeRequestData { FromAsset = "WRONG", ToAsset = MockCustodian.TradeToAsset, Qty = new TradeQuantity(MockCustodian.TradeQtyBought, TradeQuantity.ValueType.Exact) };
|
||||
var wrongAssetsTradeRequest = new TradeRequestData { FromAsset = "WRONG", ToAsset = MockCustodian.TradeToAsset, Qty = MockCustodian.TradeQtyBought.ToString(CultureInfo.InvariantCulture) };
|
||||
await AssertHttpError(WrongTradingPairException.HttpCode, async () => await tradeClient.MarketTradeCustodianAccountAsset(storeId, accountId, wrongAssetsTradeRequest));
|
||||
|
||||
// Test: wrong account ID
|
||||
@ -4061,16 +4048,16 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
|
||||
await AssertHttpError(403, async () => await tradeClient.MarketTradeCustodianAccountAsset("WRONG-STORE-ID", accountId, tradeRequest));
|
||||
|
||||
// Test: Trade, correct assets, wrong amount
|
||||
var insufficientFundsTradeRequest = new TradeRequestData { FromAsset = MockCustodian.TradeFromAsset, ToAsset = MockCustodian.TradeToAsset, Qty = new TradeQuantity(0.01m, TradeQuantity.ValueType.Exact) };
|
||||
var insufficientFundsTradeRequest = new TradeRequestData { FromAsset = MockCustodian.TradeFromAsset, ToAsset = MockCustodian.TradeToAsset, Qty = "0.01" };
|
||||
await AssertApiError(400, "insufficient-funds", async () => await tradeClient.MarketTradeCustodianAccountAsset(storeId, accountId, insufficientFundsTradeRequest));
|
||||
|
||||
|
||||
// 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);
|
||||
@ -4080,28 +4067,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);
|
||||
@ -4124,36 +4111,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));
|
||||
@ -4162,53 +4149,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
|
||||
|
@ -56,7 +56,7 @@ public class MockCustodian : ICustodian, ICanDeposit, ICanTrade, ICanWithdraw
|
||||
return Task.FromResult(r);
|
||||
}
|
||||
|
||||
public Task<Form> GetConfigForm(JObject config, CancellationToken cancellationToken = default)
|
||||
public Task<Form> GetConfigForm(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
|
@ -249,7 +249,6 @@ namespace BTCPayServer.Tests
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser(true);
|
||||
var receiver = s.CreateNewStore();
|
||||
s.EnableCheckout(CheckoutType.V1);
|
||||
var receiverSeed = s.GenerateWallet("BTC", "", true, true, ScriptPubKeyType.Segwit);
|
||||
var receiverWalletId = new WalletId(receiver.storeId, "BTC");
|
||||
|
||||
@ -304,7 +303,6 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
var cryptoCode = "BTC";
|
||||
var receiver = s.CreateNewStore();
|
||||
s.EnableCheckout(CheckoutType.V1);
|
||||
var receiverSeed = s.GenerateWallet(cryptoCode, "", true, true, format);
|
||||
var receiverWalletId = new WalletId(receiver.storeId, cryptoCode);
|
||||
|
||||
|
@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Views.Manage;
|
||||
@ -65,6 +63,7 @@ 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 +75,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 +86,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)
|
||||
@ -95,7 +94,6 @@ namespace BTCPayServer.Tests
|
||||
Driver.FindElement(By.Id("test-payment-amount")).Clear();
|
||||
Driver.FindElement(By.Id("test-payment-amount")).SendKeys(amount.ToString());
|
||||
}
|
||||
Driver.WaitUntilAvailable(By.Id("FakePayment"));
|
||||
Driver.FindElement(By.Id("FakePayment")).Click();
|
||||
if (mine)
|
||||
{
|
||||
@ -195,22 +193,16 @@ namespace BTCPayServer.Tests
|
||||
StoreId = storeId;
|
||||
return (name, storeId);
|
||||
}
|
||||
public void EnableCheckout(CheckoutType checkoutType, bool bip21 = false)
|
||||
|
||||
public void EnableCheckoutV2(bool bip21 = false)
|
||||
{
|
||||
GoToStore(StoreNavPages.CheckoutAppearance);
|
||||
if (checkoutType == CheckoutType.V2)
|
||||
{
|
||||
Driver.SetCheckbox(By.Id("UseClassicCheckout"), false);
|
||||
Driver.WaitForElement(By.Id("OnChainWithLnInvoiceFallback"));
|
||||
Driver.SetCheckbox(By.Id("OnChainWithLnInvoiceFallback"), bip21);
|
||||
}
|
||||
else
|
||||
{
|
||||
Driver.SetCheckbox(By.Id("UseClassicCheckout"), true);
|
||||
}
|
||||
Driver.SetCheckbox(By.Id("UseNewCheckout"), true);
|
||||
Driver.WaitForElement(By.Id("OnChainWithLnInvoiceFallback"));
|
||||
Driver.SetCheckbox(By.Id("OnChainWithLnInvoiceFallback"), bip21);
|
||||
Driver.FindElement(By.Id("Save")).SendKeys(Keys.Enter);
|
||||
Assert.Contains("Store successfully updated", FindAlertMessage().Text);
|
||||
Assert.True(Driver.FindElement(By.Id("UseClassicCheckout")).Selected);
|
||||
Assert.True(Driver.FindElement(By.Id("UseNewCheckout")).Selected);
|
||||
}
|
||||
|
||||
public Mnemonic GenerateWallet(string cryptoCode = "BTC", string seed = "", bool? importkeys = null, bool isHotWallet = false, ScriptPubKeyType format = ScriptPubKeyType.Segwit)
|
||||
|
@ -4,6 +4,7 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
@ -11,13 +12,12 @@ 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;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Views.Manage;
|
||||
using BTCPayServer.Views.Server;
|
||||
@ -29,7 +29,6 @@ using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitcoin.Payment;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Support.Extensions;
|
||||
@ -78,7 +77,8 @@ namespace BTCPayServer.Tests
|
||||
s.GenerateWallet(isHotWallet: true);
|
||||
|
||||
// Point Of Sale
|
||||
s.Driver.FindElement(By.Id("StoreNav-CreatePointOfSale")).Click();
|
||||
s.Driver.FindElement(By.Id("StoreNav-CreateApp")).Click();
|
||||
new SelectElement(s.Driver.FindElement(By.Id("SelectedAppType"))).SelectByValue("PointOfSale");
|
||||
s.Driver.FindElement(By.Id("AppName")).SendKeys(Guid.NewGuid().ToString());
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
Assert.Contains("App successfully created", s.FindAlertMessage().Text);
|
||||
@ -118,11 +118,8 @@ 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);
|
||||
@ -153,7 +150,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");
|
||||
@ -168,23 +165,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)]
|
||||
@ -471,7 +468,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Rules_0__To")).SendKeys("test@gmail.com");
|
||||
s.Driver.FindElement(By.Id("Rules_0__CustomerEmail")).Click();
|
||||
s.Driver.FindElement(By.Id("Rules_0__Subject")).SendKeys("Thanks!");
|
||||
s.Driver.FindElement(By.ClassName("note-editable")).SendKeys("Your invoice is settled");
|
||||
s.Driver.FindElement(By.Id("Rules_0__Body")).SendKeys("Your invoice is settled");
|
||||
s.Driver.FindElement(By.Id("SaveEmailRules")).Click();
|
||||
Assert.Contains("Store email rules saved", s.FindAlertMessage().Text);
|
||||
}
|
||||
@ -596,7 +593,7 @@ namespace BTCPayServer.Tests
|
||||
s.GoToInvoices(s.StoreId);
|
||||
s.GoToInvoiceCheckout(i);
|
||||
var checkouturi = s.Driver.Url;
|
||||
s.PayInvoice(mine: true);
|
||||
s.PayInvoice();
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
@ -606,7 +603,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
Assert.DoesNotContain("invoice-unsettled", s.Driver.PageSource);
|
||||
Assert.Contains("\"PaymentDetails\"", s.Driver.PageSource);
|
||||
Assert.Contains("invoice-processing", s.Driver.PageSource);
|
||||
});
|
||||
s.GoToUrl(checkouturi);
|
||||
|
||||
@ -942,8 +939,9 @@ namespace BTCPayServer.Tests
|
||||
await s.StartAsync();
|
||||
var userId = s.RegisterNewUser(true);
|
||||
s.CreateNewStore();
|
||||
s.Driver.FindElement(By.Id("StoreNav-CreatePointOfSale")).Click();
|
||||
s.Driver.FindElement(By.Id("StoreNav-CreateApp")).Click();
|
||||
s.Driver.FindElement(By.Name("AppName")).SendKeys("PoS" + Guid.NewGuid());
|
||||
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("Point of Sale");
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
Assert.Contains("App successfully created", s.FindAlertMessage().Text);
|
||||
|
||||
@ -1029,8 +1027,9 @@ namespace BTCPayServer.Tests
|
||||
s.CreateNewStore();
|
||||
s.AddDerivationScheme();
|
||||
|
||||
s.Driver.FindElement(By.Id("StoreNav-CreateCrowdfund")).Click();
|
||||
s.Driver.FindElement(By.Id("StoreNav-CreateApp")).Click();
|
||||
s.Driver.FindElement(By.Name("AppName")).SendKeys("CF" + Guid.NewGuid());
|
||||
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("Crowdfund");
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
Assert.Contains("App successfully created", s.FindAlertMessage().Text);
|
||||
|
||||
@ -1071,7 +1070,6 @@ namespace BTCPayServer.Tests
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser();
|
||||
s.CreateNewStore();
|
||||
s.EnableCheckout(CheckoutType.V1);
|
||||
s.AddDerivationScheme();
|
||||
|
||||
s.Driver.FindElement(By.Id("StoreNav-PaymentRequests")).Click();
|
||||
@ -1382,9 +1380,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']")));
|
||||
});
|
||||
@ -1406,10 +1404,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);
|
||||
});
|
||||
@ -2008,7 +2006,9 @@ namespace BTCPayServer.Tests
|
||||
s.AddLightningNode(LightningConnectionType.CLightning, false);
|
||||
s.GoToLightningSettings();
|
||||
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
|
||||
s.Driver.FindElement(By.Id("StoreNav-CreatePointOfSale")).Click();
|
||||
s.Driver.FindElement(By.Id("StoreNav-CreateApp")).Click();
|
||||
s.Driver.FindElement(By.Id("SelectedAppType")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("option[value='PointOfSale']")).Click();
|
||||
s.Driver.FindElement(By.Id("AppName")).SendKeys(Guid.NewGuid().ToString());
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
TestUtils.Eventually(() => Assert.Contains("App successfully created", s.FindAlertMessage().Text));
|
||||
@ -2043,7 +2043,6 @@ namespace BTCPayServer.Tests
|
||||
new[] { s.Server.MerchantLnd.Client });
|
||||
s.RegisterNewUser(true);
|
||||
(_, string storeId) = s.CreateNewStore();
|
||||
s.EnableCheckout(CheckoutType.V1);
|
||||
var network = s.Server.NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode).NBitcoinNetwork;
|
||||
s.AddLightningNode(LightningConnectionType.CLightning, false);
|
||||
s.GoToLightningSettings();
|
||||
@ -2075,8 +2074,7 @@ namespace BTCPayServer.Tests
|
||||
var res = await s.Server.CustomerLightningD.Pay(lnurlResponse.Pr);
|
||||
Assert.Equal(PayResult.Error, res.Result);
|
||||
|
||||
res = await s.Server.CustomerLightningD.Pay(lnurlResponse2.Pr);
|
||||
Assert.Equal(PayResult.Ok, res.Result);
|
||||
await s.Server.CustomerLightningD.Pay(lnurlResponse2.Pr);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var inv = await s.Server.PayTester.InvoiceRepository.GetInvoice(i);
|
||||
@ -2090,23 +2088,22 @@ 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_noborder")).Click();
|
||||
// BOLT11 is also displayed for standard invoice (not LNURL, even if it is available)
|
||||
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.Id("copy-tab")).Click();
|
||||
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());
|
||||
}
|
||||
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()));
|
||||
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),
|
||||
@ -2128,6 +2125,13 @@ 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,12 +2145,23 @@ 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);
|
||||
@ -2158,17 +2173,18 @@ namespace BTCPayServer.Tests
|
||||
|
||||
s.GoToHome();
|
||||
s.CreateNewStore(false);
|
||||
s.EnableCheckout(CheckoutType.V1);
|
||||
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(null, cryptoCode);
|
||||
var invForPP = s.CreateInvoice(0.0000001m, cryptoCode);
|
||||
s.GoToInvoiceCheckout(invForPP);
|
||||
s.Driver.FindElement(By.Id("copy-tab")).Click();
|
||||
lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
|
||||
LNURL.LNURL.Parse(lnurl, out tag);
|
||||
parsed = LNURL.LNURL.Parse(lnurl, out tag);
|
||||
|
||||
// Check that pull payment has lightning option
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
@ -2242,16 +2258,13 @@ namespace BTCPayServer.Tests
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
|
||||
s.Driver.ToggleCollapse("AddAddress");
|
||||
|
||||
var lnaddress2 = "EUR" + Guid.NewGuid().ToString();
|
||||
s.Driver.FindElement(By.Id("Add_Username")).SendKeys(lnaddress2);
|
||||
lnaddress2 = lnaddress2.ToLowerInvariant();
|
||||
|
||||
s.Driver.ToggleCollapse("AdvancedSettings");
|
||||
s.Driver.FindElement(By.Id("Add_CurrencyCode")).SendKeys("EUR");
|
||||
s.Driver.FindElement(By.Id("Add_Min")).SendKeys("2");
|
||||
s.Driver.FindElement(By.Id("Add_Max")).SendKeys("10");
|
||||
s.Driver.FindElement(By.Id("Add_InvoiceMetadata")).SendKeys("{\"test\":\"lol\"}");
|
||||
s.Driver.FindElement(By.CssSelector("button[value='add']")).Click();
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
|
||||
@ -2267,101 +2280,20 @@ namespace BTCPayServer.Tests
|
||||
var lnurl = new Uri(LNURL.LNURL.ExtractUriFromInternetIdentifier(value).ToString()
|
||||
.Replace("https", "http"));
|
||||
var request = (LNURL.LNURLPayRequest)await LNURL.LNURL.FetchInformation(lnurl, new HttpClient());
|
||||
var m = request.ParsedMetadata.ToDictionary(o => o.Key, o => o.Value);
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case { } v when v.StartsWith(lnaddress2):
|
||||
Assert.StartsWith(lnaddress2 + "@", m["text/identifier"]);
|
||||
lnaddress2 = m["text/identifier"];
|
||||
Assert.Equal(2, request.MinSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
||||
Assert.Equal(10, request.MaxSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
||||
break;
|
||||
|
||||
case { } v when v.StartsWith(lnaddress1):
|
||||
Assert.StartsWith(lnaddress1 + "@", m["text/identifier"]);
|
||||
lnaddress1 = m["text/identifier"];
|
||||
Assert.Equal(1, request.MinSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
||||
Assert.Equal(6.12m, request.MaxSendable.ToDecimal(LightMoneyUnit.BTC));
|
||||
break;
|
||||
default:
|
||||
Assert.False(true, "Should have matched");
|
||||
break;
|
||||
}
|
||||
}
|
||||
var repo = s.Server.PayTester.GetService<InvoiceRepository>();
|
||||
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));
|
||||
var paymentMethodDetails =
|
||||
lightningPaymentMethod.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails;
|
||||
Assert.Contains(
|
||||
paymentMethodDetails.ConsumedLightningAddress,
|
||||
new[] { lnaddress1, lnaddress2 });
|
||||
|
||||
if (paymentMethodDetails.ConsumedLightningAddress == lnaddress2)
|
||||
{
|
||||
Assert.Equal("lol", i.Metadata.AdditionalData["test"].Value<string>());
|
||||
}
|
||||
}
|
||||
|
||||
var lnUsername = lnaddress1.Split('@')[0];
|
||||
|
||||
|
||||
LNURLPayRequest req;
|
||||
using (var resp = await s.Server.PayTester.HttpClient.GetAsync($"/.well-known/lnurlp/{lnUsername}"))
|
||||
{
|
||||
var str = await resp.Content.ReadAsStringAsync();
|
||||
req = JsonConvert.DeserializeObject<LNURLPayRequest>(str);
|
||||
Assert.Contains(req.ParsedMetadata, m => m.Key == "text/identifier" && m.Value == lnaddress1);
|
||||
Assert.Contains(req.ParsedMetadata, m => m.Key == "text/plain" && m.Value.StartsWith("Paid to"));
|
||||
Assert.NotNull(req.Callback);
|
||||
Assert.Equal(new LightMoney(1000), req.MinSendable);
|
||||
Assert.Equal(LightMoney.FromUnit(6.12m, LightMoneyUnit.BTC), req.MaxSendable);
|
||||
}
|
||||
lnUsername = lnaddress2.Split('@')[0];
|
||||
using (var resp = await s.Server.PayTester.HttpClient.GetAsync($"/.well-known/lnurlp/{lnUsername}"))
|
||||
{
|
||||
var str = await resp.Content.ReadAsStringAsync();
|
||||
req = JsonConvert.DeserializeObject<LNURLPayRequest>(str);
|
||||
Assert.Equal(new LightMoney(2000), req.MinSendable);
|
||||
Assert.Equal(new LightMoney(10_000), req.MaxSendable);
|
||||
}
|
||||
// Check if we can get the same payrequest through the callback
|
||||
using (var resp = await s.Server.PayTester.HttpClient.GetAsync(req.Callback))
|
||||
{
|
||||
var str = await resp.Content.ReadAsStringAsync();
|
||||
req = JsonConvert.DeserializeObject<LNURLPayRequest>(str);
|
||||
Assert.Equal(new LightMoney(2000), req.MinSendable);
|
||||
Assert.Equal(new LightMoney(10_000), req.MaxSendable);
|
||||
}
|
||||
|
||||
// Can we ask for invoice? (Should fail, below minSpendable)
|
||||
using (var resp = await s.Server.PayTester.HttpClient.GetAsync(req.Callback + "?amount=1999"))
|
||||
{
|
||||
var str = await resp.Content.ReadAsStringAsync();
|
||||
var err = JsonConvert.DeserializeObject<LNUrlStatusResponse>(str);
|
||||
Assert.Equal("Amount is out of bounds.", err.Reason);
|
||||
}
|
||||
// Can we ask for invoice?
|
||||
using (var resp = await s.Server.PayTester.HttpClient.GetAsync(req.Callback + "?amount=2000"))
|
||||
{
|
||||
var str = await resp.Content.ReadAsStringAsync();
|
||||
var succ = JsonConvert.DeserializeObject<LNURLPayRequest.LNURLPayRequestCallbackResponse>(str);
|
||||
Assert.NotNull(succ.Pr);
|
||||
Assert.Equal(new LightMoney(2000), BOLT11PaymentRequest.Parse(succ.Pr, Network.RegTest).MinimumAmount);
|
||||
}
|
||||
|
||||
// Can we change comment?
|
||||
using (var resp = await s.Server.PayTester.HttpClient.GetAsync(req.Callback + "?amount=2001"))
|
||||
{
|
||||
var str = await resp.Content.ReadAsStringAsync();
|
||||
var succ = JsonConvert.DeserializeObject<LNURLPayRequest.LNURLPayRequestCallbackResponse>(str);
|
||||
Assert.NotNull(succ.Pr);
|
||||
Assert.Equal(new LightMoney(2001), BOLT11PaymentRequest.Parse(succ.Pr, Network.RegTest).MinimumAmount);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -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,8 +194,7 @@ namespace BTCPayServer.Tests
|
||||
tcs.TrySetResult(evt);
|
||||
}
|
||||
});
|
||||
if (action != null)
|
||||
await action.Invoke();
|
||||
await action.Invoke();
|
||||
var result = await tcs.Task;
|
||||
sub.Dispose();
|
||||
return result;
|
||||
@ -248,8 +247,6 @@ 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)
|
||||
|
@ -214,13 +214,6 @@ 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>();
|
||||
|
@ -17,7 +17,7 @@ namespace BTCPayServer.Tests
|
||||
#if DEBUG && !SHORT_TIMEOUT
|
||||
public const int TestTimeout = 600_000;
|
||||
#else
|
||||
public const int TestTimeout = 90_000;
|
||||
public const int TestTimeout = 60_000;
|
||||
#endif
|
||||
public static DirectoryInfo TryGetSolutionDirectoryInfo(string currentPath = null)
|
||||
{
|
||||
@ -112,14 +112,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
catch (XunitException) when (!cts.Token.IsCancellationRequested)
|
||||
{
|
||||
bool timeout =false;
|
||||
try
|
||||
{
|
||||
await Task.Delay(500, cts.Token);
|
||||
}
|
||||
catch { timeout = true; }
|
||||
if (timeout)
|
||||
throw;
|
||||
await Task.Delay(500, cts.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,7 +1635,6 @@ 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);
|
||||
@ -1650,12 +1649,16 @@ 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);
|
||||
user.SetLNUrl(cryptoCode, true);
|
||||
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);
|
||||
vm = user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModel<CheckoutAppearanceViewModel>();
|
||||
criteria = Assert.Single(vm.PaymentMethodCriteria);
|
||||
Assert.Equal(new PaymentMethodId(cryptoCode, LightningPaymentType.Instance).ToString(), criteria.PaymentMethod);
|
||||
@ -2442,31 +2445,6 @@ 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()
|
||||
|
@ -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
|
||||
|
||||
@ -131,6 +131,7 @@ namespace BTCPayServer.Tests
|
||||
// return name.Replace("_", "").ToLowerInvariant();
|
||||
//}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This utility will use selenium to pilot your browser to
|
||||
/// automatically translate a language.
|
||||
@ -146,8 +147,8 @@ namespace BTCPayServer.Tests
|
||||
[FactWithSecret("TransifexAPIToken")]
|
||||
public async Task AutoTranslateChatGPT()
|
||||
{
|
||||
var file = TranslationFolder.CheckoutV2;
|
||||
|
||||
var file = TranslationFolder.CheckoutV1;
|
||||
|
||||
using var driver = new ChromeDriver(new ChromeOptions()
|
||||
{
|
||||
DebuggerAddress = "127.0.0.1:9222"
|
||||
@ -185,19 +186,6 @@ 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();
|
||||
@ -547,7 +535,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()
|
||||
@ -577,7 +565,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)
|
||||
|
@ -38,10 +38,6 @@ 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"
|
||||
@ -93,8 +89,6 @@ 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
|
||||
@ -243,7 +237,7 @@ services:
|
||||
- "5432"
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.16.2-beta
|
||||
image: btcpayserver/lnd:v0.15.4-beta-1
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -278,7 +272,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.16.2-beta
|
||||
image: btcpayserver/lnd:v0.15.4-beta-1
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -410,12 +404,3 @@ volumes:
|
||||
torrcdir:
|
||||
tor_servicesdir:
|
||||
monero_data:
|
||||
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
custom:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.23.0.0/16
|
||||
|
@ -36,10 +36,6 @@ 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"
|
||||
@ -88,8 +84,6 @@ services:
|
||||
- merchant_lnd
|
||||
selenium:
|
||||
image: selenium/standalone-chrome:101.0
|
||||
extra_hosts:
|
||||
- "tests:172.18.0.18"
|
||||
expose:
|
||||
- "4444"
|
||||
nbxplorer:
|
||||
@ -116,6 +110,7 @@ services:
|
||||
depends_on:
|
||||
- bitcoind
|
||||
|
||||
|
||||
bitcoind:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/bitcoin:24.0
|
||||
@ -230,7 +225,7 @@ services:
|
||||
- "5432"
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.16.2-beta
|
||||
image: btcpayserver/lnd:v0.15.4-beta-1
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -267,7 +262,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.16.2-beta
|
||||
image: btcpayserver/lnd:v0.15.4-beta-1
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -328,12 +323,3 @@ volumes:
|
||||
tor_datadir:
|
||||
torrcdir:
|
||||
tor_servicesdir:
|
||||
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
custom:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.23.0.0/16
|
||||
|
@ -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.23" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.4.21" />
|
||||
<PackageReference Include="CsvHelper" Version="15.0.5" />
|
||||
<PackageReference Include="Dapper" Version="2.0.123" />
|
||||
<PackageReference Include="Fido2" Version="2.0.2" />
|
||||
@ -134,6 +134,7 @@
|
||||
<ProjectReference Include="..\BTCPayServer.Data\BTCPayServer.Data.csproj" />
|
||||
<ProjectReference Include="..\BTCPayServer.Rating\BTCPayServer.Rating.csproj" />
|
||||
<ProjectReference Include="..\BTCPayServer.Common\BTCPayServer.Common.csproj" />
|
||||
<ProjectReference Include="..\Plugins\BTCPayServer.Plugins.Custodians.FakeCustodian\BTCPayServer.Plugins.Custodians.FakeCustodian.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,7 +1,7 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -1,5 +1,3 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Components.Icon
|
||||
{
|
||||
public class IconViewModel
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a asp-area="" asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(new [] {StoreNavPages.Rates, StoreNavPages.CheckoutAppearance, StoreNavPages.General, StoreNavPages.Tokens, StoreNavPages.Users, StoreNavPages.Webhooks, StoreNavPages.PayoutProcessors, StoreNavPages.Emails})" id="StoreNav-StoreSettings">
|
||||
<a asp-area="" asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(new [] {StoreNavPages.Rates, StoreNavPages.CheckoutAppearance, StoreNavPages.General, StoreNavPages.Tokens, StoreNavPages.Users, StoreNavPages.Plugins, StoreNavPages.Webhooks, StoreNavPages.PayoutProcessors, StoreNavPages.Emails})" id="StoreNav-StoreSettings">
|
||||
<vc:icon symbol="settings"/>
|
||||
<span>Settings</span>
|
||||
</a>
|
||||
@ -145,10 +145,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
|
||||
<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">
|
||||
<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">
|
||||
<vc:icon symbol="payouts"/>
|
||||
<span>Payouts</span>
|
||||
</a>
|
||||
@ -158,6 +155,30 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item" permission="@Policies.CanModifyStoreSettings">
|
||||
<header class="accordion-header" id="Nav-Apps-Header">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#Nav-Apps" aria-expanded="true" aria-controls="Nav-Apps">
|
||||
Apps
|
||||
<vc:icon symbol="caret-down"/>
|
||||
</button>
|
||||
</header>
|
||||
<div id="Nav-Apps" class="accordion-collapse collapse show" aria-labelledby="Nav-Apps-Header">
|
||||
<div class="accordion-body">
|
||||
<ul class="navbar-nav">
|
||||
@foreach (var app in Model.Apps)
|
||||
{
|
||||
<vc:ui-extension-point location="apps-nav" model="@app"/>
|
||||
}
|
||||
<li class="nav-item">
|
||||
<a asp-area="" asp-controller="UIApps" asp-action="CreateApp" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(AppsNavPages.Create)" id="StoreNav-CreateApp">
|
||||
<vc:icon symbol="new"/>
|
||||
<span>New App</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="accordion-item">
|
||||
<header class="accordion-header" id="Nav-Plugins-Header">
|
||||
@ -174,11 +195,9 @@
|
||||
{
|
||||
<vc:ui-extension-point location="store-integrations-nav" model="@Model"/>
|
||||
}
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item" permission="@Policies.CanModifyServerSettings">
|
||||
<a asp-area="" asp-controller="UIServer" asp-action="ListPlugins" class="nav-link @ViewData.IsActivePage(ServerNavPages.Plugins)" id="Nav-ManagePlugins">
|
||||
<vc:icon symbol="plugin"/>
|
||||
<vc:icon symbol="new"/>
|
||||
<span>Manage Plugins</span>
|
||||
</a>
|
||||
</li>
|
||||
|
@ -6,53 +6,51 @@
|
||||
|
||||
@if (Model.Total is null || Model.Total.Value > pageSizeOptions.Min())
|
||||
{
|
||||
<nav aria-label="..." class="d-flex flex-wrap gap-3 justify-content-between">
|
||||
<nav aria-label="..." class="w-100 clearfix">
|
||||
@if (Model.Total is null || Model.Total.Value > Model.Count)
|
||||
{
|
||||
<ul class="pagination me-auto">
|
||||
@if (Model.Skip > 0)
|
||||
{
|
||||
<li class="page-item">
|
||||
<a class="page-link" tabindex="-1" href="@NavigatePages(-1, Model.Count)">Prev</a>
|
||||
</li>
|
||||
}
|
||||
<ul class="pagination float-start">
|
||||
<li class="page-item @(Model.Skip == 0 ? "disabled" : null)">
|
||||
<a class="page-link" tabindex="-1" href="@NavigatePages(-1, Model.Count)">«</a>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link @(Model.Total is null ? "px-0" : (Model.Skip == 0 ? "ps-0" : null))">
|
||||
@if (Model.Total is null)
|
||||
{
|
||||
<span class="page-link">
|
||||
@(Model.Skip + 1)–@(Model.Skip + Model.Count)
|
||||
</span>
|
||||
}
|
||||
else if (Model.Total.Value <= Model.Count)
|
||||
{
|
||||
@($"{Model.Skip + 1} – {Model.Skip + Model.Count}")
|
||||
}
|
||||
else if (Model.Total.Value <= Model.Count)
|
||||
{
|
||||
@($"1 – {Model.Total.Value}")
|
||||
<span class="page-link">
|
||||
1–@Model.Total.Value
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
@($"{Model.Skip + 1} – {Model.Skip + Model.Count}, Total: {Model.Total.Value}")
|
||||
<span class="page-link">
|
||||
@(Model.Skip + 1)–@(Model.Skip + Model.Count), Total: @Model.Total.Value
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
</li>
|
||||
@if ((Model.Total is null && Model.CurrentPageCount >= Model.Count) || (Model.Total is not null && Model.Total.Value > Model.Skip + Model.Count))
|
||||
{
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="@NavigatePages(1, Model.Count)">Next</a>
|
||||
</li>
|
||||
}
|
||||
<li class="page-item @(((Model.Total is null && Model.CurrentPageCount >= Model.Count) || (Model.Total is not null && Model.Total.Value > (Model.Skip + Model.Count))) ? null : "disabled")">
|
||||
<a class="page-link" href="@NavigatePages(1, Model.Count)">»</a>
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
|
||||
@if (Model.Total is null || Model.Total.Value >= pageSizeOptions.Min())
|
||||
{
|
||||
<ul class="pagination ms-auto">
|
||||
<ul class="pagination float-end">
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">Page Size</span>
|
||||
<span class="page-link">Page Size:</span>
|
||||
</li>
|
||||
@foreach (var pageSize in pageSizeOptions)
|
||||
@foreach (int pageSize in pageSizeOptions)
|
||||
{
|
||||
if (Model.Total is null || Model.Total.Value >= pageSize)
|
||||
{
|
||||
<li class="page-item @(Model.Count == pageSize ? "active" : null)">
|
||||
<a class="page-link @(Model.Count != pageSize && pageSize == pageSizeOptions.Max() ? "pe-0" : null)" href="@NavigatePages(0, pageSize)">@pageSize</a>
|
||||
<a class="page-link" href="@NavigatePages(0, pageSize)">@pageSize</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -36,7 +36,6 @@
|
||||
<tr>
|
||||
<th class="w-125px">Date</th>
|
||||
<th>Transaction</th>
|
||||
<th>Labels</th>
|
||||
<th class="text-end">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -46,29 +45,9 @@
|
||||
<tr>
|
||||
<td>@tx.Timestamp.ToTimeAgo()</td>
|
||||
<td>
|
||||
<vc:truncate-center text="@tx.Id" link="@tx.Link" classes="truncate-center-id" />
|
||||
</td>
|
||||
<td>
|
||||
@if (tx.Labels.Any())
|
||||
{
|
||||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||
@foreach (var label in tx.Labels)
|
||||
{
|
||||
<div class="transaction-label" style="--label-bg:@label.Color;--label-fg:@label.TextColor">
|
||||
<span>@label.Text</span>
|
||||
@if (!string.IsNullOrEmpty(label.Link))
|
||||
{
|
||||
<a class="transaction-label-info transaction-details-icon" href="@label.Link"
|
||||
target="_blank" rel="noreferrer noopener" title="@label.Tooltip" data-bs-html="true"
|
||||
data-bs-toggle="tooltip" data-bs-custom-class="transaction-label-tooltip">
|
||||
<vc:icon symbol="info" />
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<a href="@tx.Link" target="_blank" rel="noreferrer noopener" class="text-break">
|
||||
@tx.Id
|
||||
</a>
|
||||
</td>
|
||||
@if (tx.Positive)
|
||||
{
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
|
||||
namespace BTCPayServer.Components.StoreRecentTransactions;
|
||||
|
||||
@ -13,5 +11,4 @@ public class StoreRecentTransactionViewModel
|
||||
public bool IsConfirmed { get; set; }
|
||||
public string Link { get; set; }
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
public IEnumerable<TransactionTagModel> Labels { get; set; }
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Labels;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Dapper;
|
||||
@ -24,20 +23,14 @@ namespace BTCPayServer.Components.StoreRecentTransactions;
|
||||
public class StoreRecentTransactions : ViewComponent
|
||||
{
|
||||
private readonly BTCPayWalletProvider _walletProvider;
|
||||
private readonly WalletRepository _walletRepository;
|
||||
private readonly LabelService _labelService;
|
||||
public BTCPayNetworkProvider NetworkProvider { get; }
|
||||
|
||||
public StoreRecentTransactions(
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
BTCPayWalletProvider walletProvider,
|
||||
WalletRepository walletRepository,
|
||||
LabelService labelService)
|
||||
BTCPayWalletProvider walletProvider)
|
||||
{
|
||||
NetworkProvider = networkProvider;
|
||||
_walletProvider = walletProvider;
|
||||
_walletRepository = walletRepository;
|
||||
_labelService = labelService;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(StoreRecentTransactionsViewModel vm)
|
||||
@ -59,25 +52,16 @@ 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());
|
||||
|
||||
transactions = allTransactions
|
||||
.Select(tx =>
|
||||
.Select(tx => new StoreRecentTransactionViewModel
|
||||
{
|
||||
walletTransactionsInfo.TryGetValue(tx.TransactionId.ToString(), out var transactionInfo);
|
||||
var labels = _labelService.CreateTransactionTagModels(transactionInfo, Request);
|
||||
return new StoreRecentTransactionViewModel
|
||||
{
|
||||
Id = tx.TransactionId.ToString(),
|
||||
Positive = tx.BalanceChange.GetValue(network) >= 0,
|
||||
Balance = tx.BalanceChange.ShowMoney(network),
|
||||
Currency = vm.CryptoCode,
|
||||
IsConfirmed = tx.Confirmations != 0,
|
||||
Link = string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink,
|
||||
tx.TransactionId.ToString()),
|
||||
Timestamp = tx.SeenAt,
|
||||
Labels = labels
|
||||
};
|
||||
Id = tx.TransactionId.ToString(),
|
||||
Positive = tx.BalanceChange.GetValue(network) >= 0,
|
||||
Balance = tx.BalanceChange.ShowMoney(network),
|
||||
Currency = vm.CryptoCode,
|
||||
IsConfirmed = tx.Confirmations != 0,
|
||||
Link = string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, tx.TransactionId.ToString()),
|
||||
Timestamp = tx.SeenAt
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
|
||||
namespace BTCPayServer.Components.TruncateCenter;
|
||||
|
||||
@ -17,8 +15,6 @@ 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,
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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()
|
||||
}
|
||||
)
|
||||
),
|
||||
|
@ -255,15 +255,26 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
|
||||
if (custodian is ICanTrade tradableCustodian)
|
||||
{
|
||||
decimal qty;
|
||||
try
|
||||
bool isPercentage = request.Qty.EndsWith("%", StringComparison.InvariantCultureIgnoreCase);
|
||||
string qtyString = isPercentage ? request.Qty.Substring(0, request.Qty.Length - 1) : request.Qty;
|
||||
bool canParseQty = Decimal.TryParse(qtyString, out decimal qty);
|
||||
if (!canParseQty)
|
||||
{
|
||||
qty = await ParseQty(request.Qty, request.FromAsset, custodianAccount, custodian, cancellationToken);
|
||||
return this.CreateAPIError(400, "bad-qty-format",
|
||||
$"Quantity should be a number or a number ending with '%' for percentages.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
if (isPercentage)
|
||||
{
|
||||
return UnsupportedAsset(request.FromAsset, ex.Message);
|
||||
// Percentage of current holdings => calculate the amount
|
||||
var config = custodianAccount.GetBlob();
|
||||
var balances = custodian.GetAssetBalancesAsync(config, cancellationToken).Result;
|
||||
var fromAssetBalance = balances[request.FromAsset];
|
||||
var priceQuote =
|
||||
await tradableCustodian.GetQuoteForAssetAsync(request.FromAsset, request.ToAsset, config, cancellationToken);
|
||||
qty = fromAssetBalance / priceQuote.Ask * qty / 100;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await tradableCustodian.TradeMarketAsync(request.FromAsset, request.ToAsset, qty,
|
||||
@ -362,10 +373,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);
|
||||
}
|
||||
|
@ -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, entity, details, accounting.Due,
|
||||
method.GetId().PaymentType.GetPaymentLink(method.Network, details, accounting.Due,
|
||||
Request.GetAbsoluteRoot()),
|
||||
Payments = payments.Select(paymentEntity => ToPaymentModel(entity, paymentEntity)).ToList(),
|
||||
AdditionalData = details.GetAdditionalData()
|
||||
|
@ -124,8 +124,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
|
||||
try
|
||||
{
|
||||
var prData = await _paymentRequestRepository.FindPaymentRequest(pr.Id, null);
|
||||
var invoice = await _invoiceController.CreatePaymentRequestInvoice(prData, amount, pr.AmountDue, this.StoreData, Request, cancellationToken);
|
||||
var invoice = await _invoiceController.CreatePaymentRequestInvoice(pr, amount, this.StoreData, Request, cancellationToken);
|
||||
return Ok(GreenfieldInvoiceController.ToModel(invoice, _linkGenerator, Request));
|
||||
}
|
||||
catch (BitpayHttpException e)
|
||||
|
@ -11,8 +11,8 @@ using BTCPayServer.Security;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PayoutProcessorData = BTCPayServer.Client.Models.PayoutProcessorData;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
using PayoutProcessorData = BTCPayServer.Client.Models.PayoutProcessorData;
|
||||
|
||||
namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
|
@ -115,7 +115,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Period), $"The period should be positive");
|
||||
}
|
||||
if (request.BOLT11Expiration < TimeSpan.Zero)
|
||||
if (request.BOLT11Expiration <= TimeSpan.Zero)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.BOLT11Expiration), $"The BOLT11 expiration should be positive");
|
||||
}
|
||||
@ -142,7 +142,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
var ppId = await _pullPaymentService.CreatePullPayment(new CreatePullPayment()
|
||||
var ppId = await _pullPaymentService.CreatePullPayment(new HostedServices.CreatePullPayment()
|
||||
{
|
||||
StartsAt = request.StartsAt,
|
||||
ExpiresAt = request.ExpiresAt,
|
||||
@ -264,8 +264,7 @@ 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()
|
||||
});
|
||||
@ -360,7 +359,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");
|
||||
|
4
BTCPayServer/Controllers/GreenField/GreenfieldStoreAutomatedLightningPayoutProcessorsController.cs
4
BTCPayServer/Controllers/GreenField/GreenfieldStoreAutomatedLightningPayoutProcessorsController.cs
@ -2,7 +2,6 @@
|
||||
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;
|
||||
@ -70,9 +69,6 @@ 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(
|
||||
|
@ -1,9 +1,7 @@
|
||||
#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;
|
||||
@ -77,11 +75,6 @@ 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(
|
||||
|
@ -54,7 +54,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
new LNURLPayPaymentMethodData(
|
||||
paymentMethod.PaymentId.CryptoCode,
|
||||
!excludedPaymentMethods.Match(paymentMethod.PaymentId),
|
||||
paymentMethod.UseBech32Scheme
|
||||
paymentMethod.UseBech32Scheme, paymentMethod.EnableForStandardInvoices
|
||||
)
|
||||
)
|
||||
.Where((result) => enabled is null || enabled == result.Enabled)
|
||||
@ -124,7 +124,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
LNURLPaySupportedPaymentMethod? paymentMethod = new LNURLPaySupportedPaymentMethod()
|
||||
{
|
||||
CryptoCode = cryptoCode,
|
||||
UseBech32Scheme = paymentMethodData.UseBech32Scheme
|
||||
UseBech32Scheme = paymentMethodData.UseBech32Scheme,
|
||||
EnableForStandardInvoices = paymentMethodData.EnableForStandardInvoices
|
||||
};
|
||||
|
||||
var store = Store;
|
||||
@ -153,7 +154,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
: new LNURLPayPaymentMethodData(
|
||||
paymentMethod.PaymentId.CryptoCode,
|
||||
!excluded,
|
||||
paymentMethod.UseBech32Scheme
|
||||
paymentMethod.UseBech32Scheme, paymentMethod.EnableForStandardInvoices
|
||||
);
|
||||
}
|
||||
private void AssertCryptoCodeWallet(string cryptoCode, out BTCPayNetwork network)
|
||||
|
@ -33,10 +33,7 @@ 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
|
||||
};
|
||||
}
|
||||
|
||||
@ -44,7 +41,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());
|
||||
}
|
||||
|
||||
@ -67,8 +64,7 @@ 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.");
|
||||
}
|
||||
@ -83,17 +79,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);
|
||||
}
|
||||
|
@ -66,7 +66,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
paymentMethod.GetExternalLightningUrl()?.ToString() ??
|
||||
paymentMethod.GetDisplayableConnectionString(),
|
||||
!excludedPaymentMethods.Match(paymentMethod.PaymentId),
|
||||
paymentMethod.PaymentId.ToStringNormalized()
|
||||
paymentMethod.PaymentId.ToStringNormalized(),
|
||||
paymentMethod.DisableBOLT11PaymentOption
|
||||
)
|
||||
)
|
||||
.Where((result) => enabled is null || enabled == result.Enabled)
|
||||
@ -206,7 +207,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
? null
|
||||
: new LightningNetworkPaymentMethodData(paymentMethod.PaymentId.CryptoCode,
|
||||
paymentMethod.GetDisplayableConnectionString(), !excluded,
|
||||
paymentMethod.PaymentId.ToStringNormalized());
|
||||
paymentMethod.PaymentId.ToStringNormalized(), paymentMethod.DisableBOLT11PaymentOption);
|
||||
}
|
||||
|
||||
private BTCPayNetwork AssertSupportLightning(string cryptoCode)
|
||||
|
@ -111,7 +111,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
parsedCurrencyPairs = blob.DefaultCurrencyPairs.ToHashSet();
|
||||
}
|
||||
|
||||
|
||||
ValidateAndSanitizeConfiguration(configuration, blob);
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,8 +249,7 @@ 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);
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
@ -111,29 +111,22 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[HttpGet("/stores/{storeId}/apps/create/{appType?}")]
|
||||
[HttpGet("/stores/{storeId}/apps/create")]
|
||||
public IActionResult CreateApp(string storeId, string appType = null)
|
||||
{
|
||||
var vm = new CreateAppViewModel(_appService)
|
||||
{
|
||||
StoreId = storeId,
|
||||
AppType = appType,
|
||||
SelectedAppType = appType
|
||||
};
|
||||
var vm = new CreateAppViewModel (_appService){StoreId = GetCurrentStore().Id, SelectedAppType = appType};
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[HttpPost("/stores/{storeId}/apps/create/{appType?}")]
|
||||
[HttpPost("/stores/{storeId}/apps/create")]
|
||||
public async Task<IActionResult> CreateApp(string storeId, CreateAppViewModel vm)
|
||||
{
|
||||
var store = GetCurrentStore();
|
||||
vm.StoreId = store.Id;
|
||||
var type = _appService.GetAppType(vm.AppType ?? vm.SelectedAppType);
|
||||
var type = _appService.GetAppType(vm.SelectedAppType);
|
||||
if (type is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.SelectedAppType), "Invalid App Type");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
@ -144,17 +137,17 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
StoreDataId = store.Id,
|
||||
Name = vm.AppName,
|
||||
AppType = type!.Type
|
||||
AppType = vm.SelectedAppType
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Forms;
|
||||
using BTCPayServer.Models.CustodianAccountViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services;
|
||||
@ -39,7 +38,6 @@ namespace BTCPayServer.Controllers
|
||||
private readonly BTCPayServerClient _btcPayServerClient;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
private readonly FormDataService _formDataService;
|
||||
|
||||
public UICustodianAccountsController(
|
||||
DisplayFormatter displayFormatter,
|
||||
@ -48,8 +46,7 @@ namespace BTCPayServer.Controllers
|
||||
IEnumerable<ICustodian> custodianRegistry,
|
||||
BTCPayServerClient btcPayServerClient,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
LinkGenerator linkGenerator,
|
||||
FormDataService formDataService
|
||||
LinkGenerator linkGenerator
|
||||
)
|
||||
{
|
||||
_displayFormatter = displayFormatter;
|
||||
@ -58,7 +55,6 @@ namespace BTCPayServer.Controllers
|
||||
_btcPayServerClient = btcPayServerClient;
|
||||
_networkProvider = networkProvider;
|
||||
_linkGenerator = linkGenerator;
|
||||
_formDataService = formDataService;
|
||||
}
|
||||
|
||||
public string CreatedCustodianAccountId { get; set; }
|
||||
@ -221,14 +217,12 @@ namespace BTCPayServer.Controllers
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var blob = custodianAccount.GetBlob();
|
||||
var configForm = await custodian.GetConfigForm(blob, HttpContext.RequestAborted);
|
||||
configForm.SetValues(blob);
|
||||
var configForm = await custodian.GetConfigForm();
|
||||
configForm.SetValues(custodianAccount.GetBlob());
|
||||
|
||||
var vm = new EditCustodianAccountViewModel();
|
||||
vm.CustodianAccount = custodianAccount;
|
||||
vm.ConfigForm = configForm;
|
||||
vm.Config = _formDataService.GetValues(configForm).ToString();
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
@ -246,13 +240,15 @@ namespace BTCPayServer.Controllers
|
||||
// TODO The custodian account is broken. The custodian is no longer available. Maybe delete the custodian account?
|
||||
return NotFound();
|
||||
}
|
||||
var configForm = await GetNextForm(custodian, vm.Config);
|
||||
|
||||
var configForm = await custodian.GetConfigForm();
|
||||
configForm.ApplyValuesFromForm(Request.Form);
|
||||
|
||||
|
||||
if (configForm.IsValid())
|
||||
{
|
||||
var newData = _formDataService.GetValues(configForm);
|
||||
var newData = configForm.GetValues();
|
||||
custodianAccount.SetBlob(newData);
|
||||
custodianAccount.Name = vm.CustodianAccount.Name;
|
||||
custodianAccount = await _custodianAccountRepository.CreateOrUpdate(custodianAccount);
|
||||
return RedirectToAction(nameof(ViewCustodianAccount),
|
||||
new { storeId = custodianAccount.StoreId, accountId = custodianAccount.Id });
|
||||
@ -261,36 +257,9 @@ namespace BTCPayServer.Controllers
|
||||
// Form not valid: The user must fix the errors before we can save
|
||||
vm.CustodianAccount = custodianAccount;
|
||||
vm.ConfigForm = configForm;
|
||||
vm.Config = _formDataService.GetValues(configForm).ToString();
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
private async Task<Form> GetNextForm(ICustodian custodian, string config)
|
||||
{
|
||||
JObject b = null;
|
||||
try
|
||||
{
|
||||
if (config != null)
|
||||
b = JObject.Parse(config);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
b ??= new JObject();
|
||||
// First, we restore the previous form based on the previous blob that was
|
||||
// stored in config
|
||||
var form = await custodian.GetConfigForm(b, HttpContext.RequestAborted);
|
||||
form.SetValues(b);
|
||||
// Then we apply new values overriding the previous blob from the Form params
|
||||
form.ApplyValuesFromForm(Request.Form);
|
||||
// We extract the new resulting blob, and request what is the next form based on it
|
||||
b = _formDataService.GetValues(form);
|
||||
form = await custodian.GetConfigForm(_formDataService.GetValues(form), HttpContext.RequestAborted);
|
||||
// We set all the values to this blob, and validate the form
|
||||
form.SetValues(b);
|
||||
_formDataService.Validate(form, ModelState);
|
||||
return form;
|
||||
}
|
||||
|
||||
[HttpGet("/stores/{storeId}/custodian-accounts/create")]
|
||||
public IActionResult CreateCustodianAccount(string storeId)
|
||||
@ -328,12 +297,12 @@ namespace BTCPayServer.Controllers
|
||||
};
|
||||
|
||||
|
||||
var configForm = await GetNextForm(custodian, vm.Config);
|
||||
var configForm = await custodian.GetConfigForm();
|
||||
configForm.ApplyValuesFromForm(Request.Form);
|
||||
if (configForm.IsValid())
|
||||
{
|
||||
var configData = _formDataService.GetValues(configForm);
|
||||
var configData = configForm.GetValues();
|
||||
custodianAccountData.SetBlob(configData);
|
||||
custodianAccountData.Name = vm.Name;
|
||||
custodianAccountData = await _custodianAccountRepository.CreateOrUpdate(custodianAccountData);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Custodian account successfully created";
|
||||
CreatedCustodianAccountId = custodianAccountData.Id;
|
||||
@ -344,7 +313,6 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
// Ask for more data
|
||||
vm.ConfigForm = configForm;
|
||||
vm.Config = _formDataService.GetValues(configForm).ToString();
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
@ -432,8 +400,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
catch (WrongTradingPairException)
|
||||
{
|
||||
// Cannot trade this asset
|
||||
return BadRequest(vm);
|
||||
// Cannot trade this asset, just ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -515,17 +482,21 @@ namespace BTCPayServer.Controllers
|
||||
if (paymentMethodId != null)
|
||||
{
|
||||
var walletId = new WalletId(storeId, paymentMethodId.CryptoCode);
|
||||
var returnUrl = _linkGenerator.GetPathByAction(
|
||||
var returnUrl = _linkGenerator.GetUriByAction(
|
||||
nameof(ViewCustodianAccount),
|
||||
"UICustodianAccounts",
|
||||
new { storeId = custodianAccount.StoreId, accountId = custodianAccount.Id },
|
||||
Request.Scheme,
|
||||
Request.Host,
|
||||
Request.PathBase);
|
||||
|
||||
vm.CryptoImageUrl = GetImage(paymentMethodId, network);
|
||||
vm.CreateTransactionUrl = _linkGenerator.GetPathByAction(
|
||||
vm.CreateTransactionUrl = _linkGenerator.GetUriByAction(
|
||||
nameof(UIWalletsController.WalletSend),
|
||||
"UIWallets",
|
||||
new { walletId, defaultDestination = vm.Address, returnUrl },
|
||||
Request.Scheme,
|
||||
Request.Host,
|
||||
Request.PathBase);
|
||||
}
|
||||
}
|
||||
@ -599,7 +570,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
catch (BadConfigException e)
|
||||
{
|
||||
Form configForm = await custodian.GetConfigForm(config);
|
||||
Form configForm = await custodian.GetConfigForm();
|
||||
configForm.SetValues(config);
|
||||
string[] badConfigFields = new string[e.BadConfigKeys.Length];
|
||||
int i = 0;
|
||||
|
@ -20,7 +20,6 @@ using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Models.PaymentRequestViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Apps;
|
||||
@ -123,7 +122,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,14 +183,8 @@ 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
|
||||
{
|
||||
@ -207,12 +200,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);
|
||||
|
||||
@ -244,7 +237,7 @@ namespace BTCPayServer.Controllers
|
||||
Link = link,
|
||||
Id = txId,
|
||||
Destination = paymentData.GetDestination(),
|
||||
PaymentProof = paymentData.GetPaymentProof(),
|
||||
PaymentProof = GetPaymentProof(paymentData),
|
||||
PaymentType = paymentData.GetPaymentType()
|
||||
};
|
||||
})
|
||||
@ -258,6 +251,16 @@ namespace BTCPayServer.Controllers
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
private string? GetPaymentProof(CryptoPaymentData paymentData)
|
||||
{
|
||||
return paymentData switch
|
||||
{
|
||||
BitcoinLikePaymentData b => b.Outpoint.ToString(),
|
||||
LightningPaymentData l => l.Preimage,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
private string? GetTransactionLink(PaymentMethodId paymentMethodId, string txId)
|
||||
{
|
||||
var network = _NetworkProvider.GetNetwork(paymentMethodId.CryptoCode);
|
||||
@ -672,55 +675,48 @@ 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 (displayedPaymentMethods.Contains(invoicePaymentId))
|
||||
if (enabledPaymentIds.Contains(invoicePaymentId))
|
||||
paymentMethodId = invoicePaymentId;
|
||||
}
|
||||
if (paymentMethodId is null && storePaymentId is not null)
|
||||
{
|
||||
if (displayedPaymentMethods.Contains(storePaymentId))
|
||||
if (enabledPaymentIds.Contains(storePaymentId))
|
||||
paymentMethodId = storePaymentId;
|
||||
}
|
||||
if (paymentMethodId is null && invoicePaymentId is not null)
|
||||
{
|
||||
paymentMethodId = invoicePaymentId.FindNearest(displayedPaymentMethods);
|
||||
paymentMethodId = invoicePaymentId.FindNearest(enabledPaymentIds);
|
||||
}
|
||||
if (paymentMethodId is null && storePaymentId is not null)
|
||||
{
|
||||
paymentMethodId = storePaymentId.FindNearest(displayedPaymentMethods);
|
||||
paymentMethodId = storePaymentId.FindNearest(enabledPaymentIds);
|
||||
}
|
||||
if (paymentMethodId is null)
|
||||
{
|
||||
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();
|
||||
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();
|
||||
}
|
||||
isDefaultPaymentId = true;
|
||||
}
|
||||
@ -734,37 +730,30 @@ namespace BTCPayServer.Controllers
|
||||
return null;
|
||||
var paymentMethodTemp = invoice
|
||||
.GetPaymentMethods()
|
||||
.Where(p => displayedPaymentMethods.Contains(p.GetId()))
|
||||
.FirstOrDefault();
|
||||
.FirstOrDefault(pm =>
|
||||
{
|
||||
var pmId = pm.GetId();
|
||||
return paymentMethodId.CryptoCode == pmId.CryptoCode &&
|
||||
((invoice.IsUnsetTopUp() && !storeBlob.OnChainWithLnInvoiceFallback) || pmId != lnurlId);
|
||||
});
|
||||
if (paymentMethodTemp == null)
|
||||
paymentMethodTemp = invoice.GetPaymentMethods().FirstOrDefault();
|
||||
if (paymentMethodTemp is null)
|
||||
return null;
|
||||
network = paymentMethodTemp.Network;
|
||||
paymentMethodId = paymentMethodTemp.GetId();
|
||||
}
|
||||
|
||||
|
||||
// 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())
|
||||
{
|
||||
var pmi = pm.GetId();
|
||||
if (pmi != paymentMethodId || !displayedPaymentMethods.Contains(pmi))
|
||||
continue;
|
||||
var pmd = pm.GetPaymentMethodDetails();
|
||||
if (!pmd.Activated)
|
||||
{
|
||||
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();
|
||||
if (!paymentMethodDetails.Activated)
|
||||
{
|
||||
if (await _invoiceActivator.ActivateInvoicePaymentMethod(paymentMethod.GetId(), invoice, store))
|
||||
{
|
||||
return await GetInvoiceModel(invoiceId, paymentMethodId, lang);
|
||||
}
|
||||
}
|
||||
|
||||
var dto = invoice.EntityToDTO();
|
||||
var accounting = paymentMethod.Calculate();
|
||||
var paymentMethodHandler = _paymentMethodHandlerDictionary[paymentMethodId];
|
||||
@ -774,7 +763,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):
|
||||
{
|
||||
@ -793,21 +782,17 @@ 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(),
|
||||
OrderId = invoice.Metadata.OrderId,
|
||||
InvoiceId = invoice.Id,
|
||||
DefaultLang = lang ?? invoice.DefaultLanguage ?? storeBlob.DefaultLang ?? "en",
|
||||
ShowPayInWalletButton = storeBlob.ShowPayInWalletButton,
|
||||
ShowStoreHeader = storeBlob.ShowStoreHeader,
|
||||
CustomCSSLink = storeBlob.CustomCSS,
|
||||
CustomLogoLink = storeBlob.CustomLogo,
|
||||
LogoFileId = storeBlob.LogoFileId,
|
||||
@ -864,15 +849,11 @@ 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 = isAltcoinsBuild
|
||||
? pmName
|
||||
: pmName.Replace("Bitcoin (", "").Replace(")", "").Replace("Lightning ", ""),
|
||||
PaymentMethodName = availableCryptoHandler.GetPaymentMethodName(availableCryptoPaymentMethodId),
|
||||
IsLightning =
|
||||
kv.GetId().PaymentType == PaymentTypes.LightningLike,
|
||||
CryptoImage = Request.GetRelativePathOrAbsolute(availableCryptoHandler.GetCryptoImage(availableCryptoPaymentMethodId)),
|
||||
@ -888,6 +869,17 @@ 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();
|
||||
@ -902,22 +894,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);
|
||||
}
|
||||
|
||||
@ -1162,7 +1154,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
StoreId = model.StoreId,
|
||||
Currency = storeBlob?.DefaultCurrency,
|
||||
CheckoutType = storeBlob?.CheckoutType ?? CheckoutType.V2,
|
||||
UseNewCheckout = storeBlob?.CheckoutType is CheckoutType.V2,
|
||||
AvailablePaymentMethods = GetPaymentMethodsSelectList()
|
||||
};
|
||||
|
||||
@ -1177,7 +1169,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
model.CheckoutType = storeBlob.CheckoutType;
|
||||
model.UseNewCheckout = storeBlob.CheckoutType == CheckoutType.V2;
|
||||
model.AvailablePaymentMethods = GetPaymentMethodsSelectList();
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
|
@ -187,36 +187,33 @@ namespace BTCPayServer.Controllers
|
||||
return await CreateInvoiceCoreRaw(entity, store, excludeFilter, null, cancellationToken, entityManipulator);
|
||||
}
|
||||
|
||||
internal async Task<InvoiceEntity> CreatePaymentRequestInvoice(Data.PaymentRequestData prData, decimal? amount, decimal amountDue, StoreData storeData, HttpRequest request, CancellationToken cancellationToken)
|
||||
internal async Task<InvoiceEntity> CreatePaymentRequestInvoice(ViewPaymentRequestViewModel pr, decimal? amount, StoreData storeData, HttpRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var id = prData.Id;
|
||||
var prBlob = prData.GetBlob();
|
||||
if (prBlob.AllowCustomPaymentAmounts && amount != null)
|
||||
amount = Math.Min(amountDue, amount.Value);
|
||||
if (pr.AllowCustomPaymentAmounts && amount != null)
|
||||
amount = Math.Min(pr.AmountDue, amount.Value);
|
||||
else
|
||||
amount = amountDue;
|
||||
var redirectUrl = _linkGenerator.PaymentRequestLink(id, request.Scheme, request.Host, request.PathBase);
|
||||
amount = pr.AmountDue;
|
||||
var redirectUrl = _linkGenerator.PaymentRequestLink(pr.Id, request.Scheme, request.Host, request.PathBase);
|
||||
|
||||
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 invoiceMetadata =
|
||||
new InvoiceMetadata
|
||||
{
|
||||
OrderId = PaymentRequestRepository.GetOrderIdForPaymentRequest(pr.Id),
|
||||
PaymentRequestId = pr.Id,
|
||||
BuyerEmail = pr.Email
|
||||
};
|
||||
|
||||
var invoiceRequest =
|
||||
new CreateInvoiceRequest
|
||||
{
|
||||
Metadata = invoiceMetadata,
|
||||
Currency = prBlob.Currency,
|
||||
Metadata = invoiceMetadata.ToJObject(),
|
||||
Currency = pr.Currency,
|
||||
Amount = amount,
|
||||
Checkout = { RedirectURL = redirectUrl },
|
||||
Receipt = new InvoiceDataBase.ReceiptOptions { Enabled = false }
|
||||
};
|
||||
|
||||
var additionalTags = new List<string> { PaymentRequestRepository.GetInternalTag(id) };
|
||||
var additionalTags = new List<string> { PaymentRequestRepository.GetInternalTag(pr.Id) };
|
||||
return await CreateInvoiceCoreRaw(invoiceRequest, storeData, request.GetAbsoluteRoot(), additionalTags, cancellationToken);
|
||||
}
|
||||
|
||||
@ -249,7 +246,6 @@ 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)
|
||||
{
|
||||
@ -462,7 +458,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 = entity.LazyPaymentMethods && !storeBlob.OnChainWithLnInvoiceFallback
|
||||
var preparePayment = storeBlob.LazyPaymentMethods && !storeBlob.OnChainWithLnInvoiceFallback
|
||||
? null
|
||||
: handler.PreparePayment(supportedPaymentMethod, store, network);
|
||||
var rate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, entity.Currency)];
|
||||
|
@ -17,7 +17,6 @@ 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;
|
||||
@ -36,7 +35,6 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using LightningAddressData = BTCPayServer.Data.LightningAddressData;
|
||||
using MarkPayoutRequest = BTCPayServer.HostedServices.MarkPayoutRequest;
|
||||
|
||||
@ -59,7 +57,6 @@ namespace BTCPayServer
|
||||
private readonly PullPaymentHostedService _pullPaymentHostedService;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
|
||||
private readonly IPluginHookService _pluginHookService;
|
||||
private readonly InvoiceActivator _invoiceActivator;
|
||||
|
||||
public UILNURLController(InvoiceRepository invoiceRepository,
|
||||
EventAggregator eventAggregator,
|
||||
@ -73,8 +70,7 @@ namespace BTCPayServer
|
||||
LightningLikePayoutHandler lightningLikePayoutHandler,
|
||||
PullPaymentHostedService pullPaymentHostedService,
|
||||
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
|
||||
IPluginHookService pluginHookService,
|
||||
InvoiceActivator invoiceActivator)
|
||||
IPluginHookService pluginHookService)
|
||||
{
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_eventAggregator = eventAggregator;
|
||||
@ -89,7 +85,6 @@ namespace BTCPayServer
|
||||
_pullPaymentHostedService = pullPaymentHostedService;
|
||||
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
|
||||
_pluginHookService = pluginHookService;
|
||||
_invoiceActivator = invoiceActivator;
|
||||
}
|
||||
|
||||
[HttpGet("withdraw/pp/{pullPaymentId}")]
|
||||
@ -196,7 +191,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
|
||||
@ -249,6 +244,17 @@ namespace BTCPayServer
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
||||
var lnpmi = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
var methods = store.GetSupportedPaymentMethods(_btcPayNetworkProvider);
|
||||
var lnUrlMethod =
|
||||
methods.FirstOrDefault(method => method.PaymentId == pmi) as LNURLPaySupportedPaymentMethod;
|
||||
var lnMethod = methods.FirstOrDefault(method => method.PaymentId == lnpmi);
|
||||
if (lnUrlMethod is null || lnMethod is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
ViewPointOfSaleViewModel.Item[] items;
|
||||
string currencyCode;
|
||||
PointOfSaleSettings posS = null;
|
||||
@ -272,9 +278,6 @@ namespace BTCPayServer
|
||||
ViewPointOfSaleViewModel.Item item = null;
|
||||
if (!string.IsNullOrEmpty(itemCode))
|
||||
{
|
||||
var pmi = GetLNUrlPaymentMethodId(cryptoCode, store, out _);
|
||||
if (pmi is null)
|
||||
return NotFound("LNUrl or LN is disabled");
|
||||
var escapedItemId = Extensions.UnescapeBackSlashUriString(itemCode);
|
||||
item = items.FirstOrDefault(item1 =>
|
||||
item1.Id.Equals(itemCode, StringComparison.InvariantCultureIgnoreCase) ||
|
||||
@ -292,39 +295,9 @@ namespace BTCPayServer
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var createInvoice = new CreateInvoiceRequest()
|
||||
{
|
||||
Amount = item?.Price.Value,
|
||||
Currency = currencyCode,
|
||||
Checkout = new InvoiceDataBase.CheckoutOptions()
|
||||
{
|
||||
RedirectURL = app.AppType switch
|
||||
{
|
||||
PointOfSaleAppType.AppType => app.GetSettings<PointOfSaleSettings>().RedirectUrl ??
|
||||
HttpContext.Request.GetAbsoluteUri($"/apps/{app.Id}/pos"),
|
||||
_ => null
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var invoiceMetadata = new InvoiceMetadata();
|
||||
invoiceMetadata.OrderId = AppService.GetAppOrderId(app);
|
||||
if (item != null)
|
||||
{
|
||||
invoiceMetadata.ItemCode = item.Id;
|
||||
invoiceMetadata.ItemDesc = item.Description;
|
||||
}
|
||||
createInvoice.Metadata = invoiceMetadata.ToJObject();
|
||||
|
||||
|
||||
return await GetLNURLRequest(
|
||||
cryptoCode,
|
||||
store,
|
||||
store.GetStoreBlob(),
|
||||
createInvoice,
|
||||
additionalTags: new List<string> { AppService.GetAppInternalTag(appId) },
|
||||
allowOverpay: false);
|
||||
|
||||
return await GetLNURL(cryptoCode, app.StoreDataId, currencyCode, null, null,
|
||||
() => (null, app, item, new List<string> { AppService.GetAppInternalTag(appId) }, item?.Price.Value, true));
|
||||
}
|
||||
|
||||
public class EditLightningAddressVM
|
||||
@ -354,13 +327,10 @@ namespace BTCPayServer
|
||||
[Display(Name = "Max sats")]
|
||||
[Range(1, double.PositiveInfinity)]
|
||||
public decimal? Max { get; set; }
|
||||
|
||||
[Display(Name = "Invoice metadata")]
|
||||
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()
|
||||
{
|
||||
@ -374,167 +344,36 @@ namespace BTCPayServer
|
||||
public async Task<IActionResult> ResolveLightningAddress(string username)
|
||||
{
|
||||
var lightningAddressSettings = await _lightningAddressService.ResolveByAddress(username);
|
||||
if (lightningAddressSettings is null || username is null)
|
||||
return NotFound("Unknown username");
|
||||
|
||||
var store = await _storeRepository.FindStore(lightningAddressSettings.StoreDataId);
|
||||
if (store is null)
|
||||
if (lightningAddressSettings is null)
|
||||
{
|
||||
return NotFound("Unknown username");
|
||||
}
|
||||
|
||||
var blob = lightningAddressSettings.GetBlob();
|
||||
|
||||
return await GetLNURLRequest(
|
||||
"BTC",
|
||||
store,
|
||||
store.GetStoreBlob(),
|
||||
new CreateInvoiceRequest()
|
||||
{
|
||||
Currency = blob?.CurrencyCode,
|
||||
Metadata = blob?.InvoiceMetadata
|
||||
},
|
||||
new LNURLPayRequest()
|
||||
{
|
||||
MinSendable = blob?.Min is decimal min ? new LightMoney(min, LightMoneyUnit.Satoshi) : null,
|
||||
MaxSendable = blob?.Max is decimal max ? new LightMoney(max, LightMoneyUnit.Satoshi) : null,
|
||||
},
|
||||
new Dictionary<string, string>()
|
||||
{
|
||||
{ "text/identifier", $"{username}@{Request.Host}" }
|
||||
});
|
||||
return await GetLNURL("BTC", lightningAddressSettings.StoreDataId, blob.CurrencyCode, blob.Min, blob.Max,
|
||||
() => (username, null, null, null, null, true));
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("pay")]
|
||||
[EnableCors(CorsPolicies.All)]
|
||||
[IgnoreAntiforgeryToken]
|
||||
public async Task<IActionResult> GetLNUrlForStore(
|
||||
string cryptoCode,
|
||||
string storeId,
|
||||
string currencyCode = null)
|
||||
public async Task<IActionResult> GetLNURL(string cryptoCode, string storeId, string currencyCode = null,
|
||||
decimal? min = null, decimal? max = null,
|
||||
Func<(string username, AppData app, ViewPointOfSaleViewModel.Item item, List<string> additionalTags, decimal? invoiceAmount, bool? anyoneCanInvoice)>
|
||||
internalDetails = null)
|
||||
{
|
||||
var store = this.HttpContext.GetStoreData();
|
||||
if (store is null)
|
||||
return NotFound();
|
||||
|
||||
var blob = store.GetStoreBlob();
|
||||
if (!blob.AnyoneCanInvoice)
|
||||
return NotFound("'Anyone can invoice' is turned off");
|
||||
return await GetLNURLRequest(
|
||||
cryptoCode,
|
||||
store,
|
||||
blob,
|
||||
new CreateInvoiceRequest
|
||||
{
|
||||
Currency = currencyCode
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<IActionResult> GetLNURLRequest(
|
||||
string cryptoCode,
|
||||
Data.StoreData store,
|
||||
Data.StoreBlob blob,
|
||||
CreateInvoiceRequest createInvoice,
|
||||
LNURLPayRequest lnurlRequest = null,
|
||||
Dictionary<string, string> lnUrlMetadata = null,
|
||||
List<string> additionalTags = null,
|
||||
bool allowOverpay = true)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return this.CreateAPIError(null, e.Message);
|
||||
}
|
||||
lnurlRequest = await CreateLNUrlRequestFromInvoice(cryptoCode, i, store, blob, lnurlRequest, lnUrlMetadata, allowOverpay);
|
||||
return lnurlRequest is null ? NotFound() : Ok(lnurlRequest);
|
||||
}
|
||||
|
||||
private async Task<LNURLPayRequest> CreateLNUrlRequestFromInvoice(
|
||||
string cryptoCode,
|
||||
InvoiceEntity i,
|
||||
Data.StoreData store,
|
||||
StoreBlob blob,
|
||||
LNURLPayRequest lnurlRequest = null,
|
||||
Dictionary<string, string> lnUrlMetadata = null,
|
||||
bool allowOverpay = true)
|
||||
{
|
||||
var pmi = GetLNUrlPaymentMethodId(cryptoCode, store, out var lnUrlMethod);
|
||||
if (pmi is null)
|
||||
return null;
|
||||
lnurlRequest ??= new LNURLPayRequest();
|
||||
lnUrlMetadata ??= new Dictionary<string, 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)
|
||||
{
|
||||
paymentMethodDetails.ConsumedLightningAddress = lnAddress;
|
||||
updatePaymentMethodDetails = true;
|
||||
}
|
||||
|
||||
if (!lnUrlMetadata.ContainsKey("text/plain"))
|
||||
{
|
||||
var invoiceDescription = blob.LightningDescriptionTemplate
|
||||
.Replace("{StoreName}", store.StoreName ?? "", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{ItemDescription}", i.Metadata.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{OrderId}", i.Metadata.OrderId ?? "", StringComparison.OrdinalIgnoreCase);
|
||||
lnUrlMetadata.Add("text/plain", invoiceDescription);
|
||||
}
|
||||
|
||||
lnurlRequest.Tag = "payRequest";
|
||||
lnurlRequest.CommentAllowed = lnUrlMethod.LUD12Enabled ? 2000 : 0;
|
||||
lnurlRequest.Callback = new Uri(_linkGenerator.GetUriByAction(
|
||||
action: nameof(GetLNURLForInvoice),
|
||||
controller: "UILNURL",
|
||||
values: new { pmi.CryptoCode, invoiceId = i.Id }, Request.Scheme, Request.Host, Request.PathBase));
|
||||
lnurlRequest.Metadata = JsonConvert.SerializeObject(lnUrlMetadata.Select(kv => new[] { kv.Key, kv.Value }));
|
||||
if (i.Type != InvoiceType.TopUp)
|
||||
{
|
||||
lnurlRequest.MinSendable = new LightMoney(pm.Calculate().Due.ToDecimal(MoneyUnit.Satoshi), LightMoneyUnit.Satoshi);
|
||||
if (!allowOverpay)
|
||||
lnurlRequest.MaxSendable = lnurlRequest.MinSendable;
|
||||
}
|
||||
|
||||
// We don't think BTCPay handle well 0 sats payments, just in case make it minimum one sat.
|
||||
if (lnurlRequest.MinSendable is null || lnurlRequest.MinSendable < LightMoney.Satoshis(1.0m))
|
||||
lnurlRequest.MinSendable = LightMoney.Satoshis(1.0m);
|
||||
|
||||
if (lnurlRequest.MaxSendable is null)
|
||||
lnurlRequest.MaxSendable = LightMoney.FromUnit(6.12m, LightMoneyUnit.BTC);
|
||||
|
||||
lnurlRequest = await _pluginHookService.ApplyFilter("modify-lnurlp-request", lnurlRequest) as LNURLPayRequest;
|
||||
if (paymentMethodDetails.PayRequest is null)
|
||||
{
|
||||
paymentMethodDetails.PayRequest = lnurlRequest;
|
||||
updatePaymentMethodDetails = true;
|
||||
}
|
||||
if (updatePaymentMethodDetails)
|
||||
{
|
||||
pm.SetPaymentMethodDetails(paymentMethodDetails);
|
||||
await _invoiceRepository.UpdateInvoicePaymentMethod(i.Id, pm);
|
||||
}
|
||||
return lnurlRequest;
|
||||
}
|
||||
|
||||
PaymentMethodId GetLNUrlPaymentMethodId(string cryptoCode, Data.StoreData store, out LNURLPaySupportedPaymentMethod lnUrlSettings)
|
||||
{
|
||||
lnUrlSettings = null;
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
if (network is null || !network.SupportLightning)
|
||||
return null;
|
||||
{
|
||||
return NotFound("This network does not support Lightning");
|
||||
}
|
||||
|
||||
var store = await _storeRepository.FindStore(storeId);
|
||||
if (store is null)
|
||||
{
|
||||
return NotFound("Store not found");
|
||||
}
|
||||
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
currencyCode ??= storeBlob.DefaultCurrency ?? cryptoCode;
|
||||
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
||||
var lnpmi = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
var methods = store.GetSupportedPaymentMethods(_btcPayNetworkProvider);
|
||||
@ -542,12 +381,112 @@ namespace BTCPayServer
|
||||
methods.FirstOrDefault(method => method.PaymentId == pmi) as LNURLPaySupportedPaymentMethod;
|
||||
var lnMethod = methods.FirstOrDefault(method => method.PaymentId == lnpmi);
|
||||
if (lnUrlMethod is null || lnMethod is null)
|
||||
return null;
|
||||
{
|
||||
return NotFound("LNURL or Lightning payment method not found");
|
||||
}
|
||||
|
||||
var blob = store.GetStoreBlob();
|
||||
if (blob.GetExcludedPaymentMethods().Match(pmi) || blob.GetExcludedPaymentMethods().Match(lnpmi))
|
||||
return null;
|
||||
lnUrlSettings = lnUrlMethod;
|
||||
return pmi;
|
||||
{
|
||||
return NotFound("LNURL or Lightning payment method disabled");
|
||||
}
|
||||
|
||||
(string username, AppData app, ViewPointOfSaleViewModel.Item item, List<string> additionalTags, decimal? invoiceAmount, bool? anyoneCanInvoice) =
|
||||
(internalDetails ?? (() => (null, null, null, null, null, null)))();
|
||||
|
||||
if ((anyoneCanInvoice ?? blob.AnyoneCanInvoice) is false)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var lnAddress = username is null ? null : $"{username}@{Request.Host}";
|
||||
List<string[]> lnurlMetadata = new();
|
||||
|
||||
var redirectUrl = app?.AppType switch
|
||||
{
|
||||
PointOfSaleAppType.AppType => app.GetSettings<PointOfSaleSettings>().RedirectUrl ??
|
||||
HttpContext.Request.GetAbsoluteUri($"/apps/{app.Id}/pos"),
|
||||
_ => null
|
||||
};
|
||||
var invoiceRequest = new CreateInvoiceRequest
|
||||
{
|
||||
Amount = invoiceAmount,
|
||||
Checkout = new InvoiceDataBase.CheckoutOptions
|
||||
{
|
||||
PaymentMethods = new[] { pmi.ToStringNormalized() },
|
||||
Expiration = blob.InvoiceExpiration < TimeSpan.FromMinutes(2)
|
||||
? blob.InvoiceExpiration
|
||||
: TimeSpan.FromMinutes(2),
|
||||
RedirectURL = redirectUrl
|
||||
},
|
||||
Currency = currencyCode,
|
||||
Type = invoiceAmount is null ? InvoiceType.TopUp : InvoiceType.Standard,
|
||||
};
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
invoiceRequest.Metadata =
|
||||
new InvoiceMetadata
|
||||
{
|
||||
ItemCode = item.Id,
|
||||
ItemDesc = item.Description,
|
||||
OrderId = AppService.GetAppOrderId(app)
|
||||
}.ToJObject();
|
||||
}
|
||||
InvoiceEntity i;
|
||||
try
|
||||
{
|
||||
i = await _invoiceController.CreateInvoiceCoreRaw(invoiceRequest, store, Request.GetAbsoluteRoot(), additionalTags);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return this.CreateAPIError(null, e.Message);
|
||||
}
|
||||
if (i.Type != InvoiceType.TopUp)
|
||||
{
|
||||
min = i.GetPaymentMethod(pmi).Calculate().Due.ToDecimal(MoneyUnit.Satoshi);
|
||||
max = item?.Price?.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Minimum ? null : min;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(username))
|
||||
{
|
||||
var pm = i.GetPaymentMethod(pmi);
|
||||
var paymentMethodDetails = (LNURLPayPaymentMethodDetails)pm.GetPaymentMethodDetails();
|
||||
paymentMethodDetails.ConsumedLightningAddress = lnAddress;
|
||||
pm.SetPaymentMethodDetails(paymentMethodDetails);
|
||||
await _invoiceRepository.UpdateInvoicePaymentMethod(i.Id, pm);
|
||||
}
|
||||
|
||||
var invoiceDescription = blob.LightningDescriptionTemplate
|
||||
.Replace("{StoreName}", store.StoreName ?? "", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{ItemDescription}", i.Metadata.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{OrderId}", i.Metadata.OrderId ?? "", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
lnurlMetadata.Add(new[] { "text/plain", invoiceDescription });
|
||||
if (!string.IsNullOrEmpty(username))
|
||||
{
|
||||
lnurlMetadata.Add(new[] { "text/identifier", lnAddress });
|
||||
}
|
||||
|
||||
if (await _pluginHookService.ApplyFilter("modify-lnurlp-request", new LNURLPayRequest
|
||||
{
|
||||
Tag = "payRequest",
|
||||
MinSendable = new LightMoney(min ?? 1m, LightMoneyUnit.Satoshi),
|
||||
MaxSendable =
|
||||
max is null
|
||||
? LightMoney.FromUnit(6.12m, LightMoneyUnit.BTC)
|
||||
: new LightMoney(max.Value, LightMoneyUnit.Satoshi),
|
||||
CommentAllowed = lnUrlMethod.LUD12Enabled ? 2000 : 0,
|
||||
Metadata = JsonConvert.SerializeObject(lnurlMetadata),
|
||||
Callback = new Uri(_linkGenerator.GetUriByAction(
|
||||
action: nameof(GetLNURLForInvoice),
|
||||
controller: "UILNURL",
|
||||
values: new {cryptoCode, invoiceId = i.Id}, Request.Scheme, Request.Host, Request.PathBase))
|
||||
}) is not LNURLPayRequest lnurlp)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
return Ok(lnurlp);
|
||||
}
|
||||
|
||||
[HttpGet("pay/i/{invoiceId}")]
|
||||
@ -562,51 +501,61 @@ namespace BTCPayServer
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (comment is not null)
|
||||
comment = comment.Truncate(2000);
|
||||
|
||||
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
||||
var i = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||
if (i is null)
|
||||
return NotFound();
|
||||
|
||||
var store = await _storeRepository.FindStore(i.StoreId);
|
||||
if (store is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (i.Status == InvoiceStatusLegacy.New)
|
||||
{
|
||||
var pmi = GetLNUrlPaymentMethodId(cryptoCode, store, out var lnurlSupportedPaymentMethod);
|
||||
if (pmi is null)
|
||||
var isTopup = i.IsUnsetTopUp();
|
||||
var lnurlSupportedPaymentMethod =
|
||||
i.GetSupportedPaymentMethod<LNURLPaySupportedPaymentMethod>(pmi).FirstOrDefault();
|
||||
if (lnurlSupportedPaymentMethod is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var lightningPaymentMethod = i.GetPaymentMethod(pmi);
|
||||
var accounting = lightningPaymentMethod.Calculate();
|
||||
var paymentMethodDetails =
|
||||
lightningPaymentMethod?.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails;
|
||||
if (paymentMethodDetails is not null && !paymentMethodDetails.Activated)
|
||||
lightningPaymentMethod.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails;
|
||||
if (paymentMethodDetails.LightningSupportedPaymentMethod is null)
|
||||
{
|
||||
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 = paymentMethodDetails.PayRequest;
|
||||
var blob = store.GetStoreBlob();
|
||||
if (paymentMethodDetails.PayRequest is null)
|
||||
{
|
||||
lnurlPayRequest = await CreateLNUrlRequestFromInvoice(cryptoCode, i, store, blob, allowOverpay: false);
|
||||
if (lnurlPayRequest is null)
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (amount is null)
|
||||
return Ok(lnurlPayRequest);
|
||||
var amt = amount.HasValue ? new LightMoney(amount.Value) : null;
|
||||
var min = new LightMoney(isTopup ? 1m : accounting.Due.ToUnit(MoneyUnit.Satoshi), LightMoneyUnit.Satoshi);
|
||||
var max = isTopup ? LightMoney.FromUnit(6.12m, LightMoneyUnit.BTC) : min;
|
||||
|
||||
var amt = new LightMoney(amount.Value);
|
||||
if (amt < lnurlPayRequest.MinSendable || amount > lnurlPayRequest.MaxSendable)
|
||||
List<string[]> lnurlMetadata = new();
|
||||
|
||||
var blob = store.GetStoreBlob();
|
||||
var invoiceDescription = blob.LightningDescriptionTemplate
|
||||
.Replace("{StoreName}", store.StoreName ?? "", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{ItemDescription}", i.Metadata.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{OrderId}", i.Metadata.OrderId ?? "", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
lnurlMetadata.Add(new[] { "text/plain", invoiceDescription });
|
||||
if (!string.IsNullOrEmpty(paymentMethodDetails.ConsumedLightningAddress))
|
||||
{
|
||||
lnurlMetadata.Add(new[] { "text/identifier", paymentMethodDetails.ConsumedLightningAddress });
|
||||
}
|
||||
|
||||
var metadata = JsonConvert.SerializeObject(lnurlMetadata);
|
||||
if (amt != null && (amt < min || amount > max))
|
||||
{
|
||||
return BadRequest(new LNUrlStatusResponse { Status = "ERROR", Reason = "Amount is out of bounds." });
|
||||
|
||||
}
|
||||
|
||||
LNURLPayRequest.LNURLPayRequestCallbackResponse.ILNURLPayRequestSuccessAction successAction = null;
|
||||
if ((i.ReceiptOptions?.Enabled ?? blob.ReceiptOptions.Enabled) is true)
|
||||
{
|
||||
@ -616,7 +565,7 @@ namespace BTCPayServer
|
||||
Tag = "url",
|
||||
Description = "Thank you for your purchase. Here is your receipt",
|
||||
Url = _linkGenerator.GetUriByAction(
|
||||
nameof(UIInvoiceController.InvoiceReceipt),
|
||||
nameof(UIInvoiceController.InvoiceReceipt),
|
||||
"UIInvoice",
|
||||
new { invoiceId },
|
||||
Request.Scheme,
|
||||
@ -625,15 +574,22 @@ namespace BTCPayServer
|
||||
};
|
||||
}
|
||||
|
||||
bool updatePaymentMethod = false;
|
||||
if (lnurlSupportedPaymentMethod.LUD12Enabled)
|
||||
if (amt is null)
|
||||
{
|
||||
comment = comment?.Truncate(2000);
|
||||
if (paymentMethodDetails.ProvidedComment != comment)
|
||||
if (await _pluginHookService.ApplyFilter("modify-lnurlp-request", new LNURLPayRequest
|
||||
{
|
||||
Tag = "payRequest",
|
||||
MinSendable = min,
|
||||
MaxSendable = max,
|
||||
CommentAllowed = lnurlSupportedPaymentMethod.LUD12Enabled ? 2000 : 0,
|
||||
Metadata = metadata,
|
||||
Callback = new Uri(Request.GetCurrentUrl())
|
||||
}) is not LNURLPayRequest lnurlp)
|
||||
{
|
||||
paymentMethodDetails.ProvidedComment = comment;
|
||||
updatePaymentMethod = true;
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(lnurlp);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(paymentMethodDetails.BOLT11) || paymentMethodDetails.GeneratedBoltAmount != amt)
|
||||
@ -657,10 +613,11 @@ namespace BTCPayServer
|
||||
try
|
||||
{
|
||||
var expiry = i.ExpirationTime.ToUniversalTime() - DateTimeOffset.UtcNow;
|
||||
var description = (await _pluginHookService.ApplyFilter("modify-lnurlp-description", lnurlPayRequest.Metadata)) as string;
|
||||
var description = (await _pluginHookService.ApplyFilter("modify-lnurlp-description", metadata)) as string;
|
||||
if (description is null)
|
||||
{
|
||||
return NotFound();
|
||||
|
||||
}
|
||||
var param = new CreateInvoiceParams(amt, description, expiry)
|
||||
{
|
||||
PrivateRouteHints = blob.LightningPrivateRouteHints,
|
||||
@ -692,23 +649,42 @@ namespace BTCPayServer
|
||||
paymentMethodDetails.Preimage = string.IsNullOrEmpty(invoice.Preimage) ? null : uint256.Parse(invoice.Preimage);
|
||||
paymentMethodDetails.InvoiceId = invoice.Id;
|
||||
paymentMethodDetails.GeneratedBoltAmount = amt;
|
||||
updatePaymentMethod = true;
|
||||
}
|
||||
if (lnurlSupportedPaymentMethod.LUD12Enabled)
|
||||
{
|
||||
paymentMethodDetails.ProvidedComment = comment;
|
||||
}
|
||||
|
||||
if (updatePaymentMethod)
|
||||
{
|
||||
lightningPaymentMethod.SetPaymentMethodDetails(paymentMethodDetails);
|
||||
await _invoiceRepository.UpdateInvoicePaymentMethod(invoiceId, lightningPaymentMethod);
|
||||
_eventAggregator.Publish(new InvoiceNewPaymentDetailsEvent(invoiceId, paymentMethodDetails, pmi));
|
||||
|
||||
_eventAggregator.Publish(new InvoiceNewPaymentDetailsEvent(invoiceId,
|
||||
paymentMethodDetails, pmi));
|
||||
return Ok(new LNURLPayRequest.LNURLPayRequestCallbackResponse
|
||||
{
|
||||
Disposable = true,
|
||||
Routes = Array.Empty<string>(),
|
||||
Pr = paymentMethodDetails.BOLT11,
|
||||
SuccessAction = successAction
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(new LNURLPayRequest.LNURLPayRequestCallbackResponse
|
||||
if (paymentMethodDetails.GeneratedBoltAmount == amt)
|
||||
{
|
||||
Disposable = true,
|
||||
Routes = Array.Empty<string>(),
|
||||
Pr = paymentMethodDetails.BOLT11,
|
||||
SuccessAction = successAction
|
||||
});
|
||||
if (lnurlSupportedPaymentMethod.LUD12Enabled && paymentMethodDetails.ProvidedComment != comment)
|
||||
{
|
||||
paymentMethodDetails.ProvidedComment = comment;
|
||||
lightningPaymentMethod.SetPaymentMethodDetails(paymentMethodDetails);
|
||||
await _invoiceRepository.UpdateInvoicePaymentMethod(invoiceId, lightningPaymentMethod);
|
||||
}
|
||||
|
||||
return Ok(new LNURLPayRequest.LNURLPayRequestCallbackResponse
|
||||
{
|
||||
Disposable = true,
|
||||
Routes = Array.Empty<string>(),
|
||||
Pr = paymentMethodDetails.BOLT11,
|
||||
SuccessAction = successAction
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return BadRequest(new LNUrlStatusResponse
|
||||
@ -717,7 +693,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")]
|
||||
@ -749,7 +725,6 @@ namespace BTCPayServer
|
||||
CurrencyCode = blob.CurrencyCode,
|
||||
StoreId = storeId,
|
||||
Username = s.Username,
|
||||
InvoiceMetadata = blob.InvoiceMetadata?.ToString(Formatting.Indented)
|
||||
};
|
||||
}
|
||||
).ToList()
|
||||
@ -771,18 +746,6 @@ namespace BTCPayServer
|
||||
vm.AddModelError(addressVm => addressVm.Add.CurrencyCode, "Currency is invalid", this);
|
||||
}
|
||||
|
||||
JObject metadata = null;
|
||||
if (!string.IsNullOrEmpty(vm.Add.InvoiceMetadata))
|
||||
{
|
||||
try
|
||||
{
|
||||
metadata = JObject.Parse(vm.Add.InvoiceMetadata);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
vm.AddModelError(addressVm => addressVm.Add.InvoiceMetadata, "Metadata must be a valid json object", this);
|
||||
}
|
||||
}
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(vm);
|
||||
@ -797,8 +760,7 @@ namespace BTCPayServer
|
||||
{
|
||||
Max = vm.Add.Max,
|
||||
Min = vm.Add.Min,
|
||||
CurrencyCode = vm.Add.CurrencyCode,
|
||||
InvoiceMetadata = metadata
|
||||
CurrencyCode = vm.Add.CurrencyCode
|
||||
})))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
|
@ -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,34 +224,26 @@ 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);
|
||||
{
|
||||
prBlob.FormResponse = form.GetValues();
|
||||
result.SetBlob(prBlob);
|
||||
await _PaymentRequestRepository.CreateOrUpdatePaymentRequest(result);
|
||||
return RedirectToAction("PayPaymentRequest", new { payReqId });
|
||||
return RedirectToAction("PayPaymentRequest", new {payReqId});
|
||||
}
|
||||
}
|
||||
viewModel.FormName = formData.Name;
|
||||
@ -285,12 +277,13 @@ 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});
|
||||
}
|
||||
}
|
||||
|
||||
@ -329,8 +322,7 @@ namespace BTCPayServer.Controllers
|
||||
try
|
||||
{
|
||||
var store = await _storeRepository.FindStore(result.StoreId);
|
||||
var prData = await _PaymentRequestRepository.FindPaymentRequest(result.Id, null);
|
||||
var newInvoice = await _InvoiceController.CreatePaymentRequestInvoice(prData, amount, result.AmountDue, store, Request, cancellationToken);
|
||||
var newInvoice = await _InvoiceController.CreatePaymentRequestInvoice(result, amount, store, Request, cancellationToken);
|
||||
if (redirectToInvoice)
|
||||
{
|
||||
return RedirectToAction("Checkout", "UIInvoice", new { invoiceId = newInvoice.Id });
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -43,16 +43,10 @@ namespace BTCPayServer.Controllers
|
||||
public async Task<IActionResult> StoreEmails(string storeId, StoreEmailRuleViewModel vm, string command)
|
||||
{
|
||||
vm.Rules ??= new List<StoreEmailRule>();
|
||||
int index = 0;
|
||||
var indSep = command.IndexOf(":", StringComparison.InvariantCultureIgnoreCase);
|
||||
if (indSep > 0)
|
||||
{
|
||||
var item = command[(indSep + 1)..];
|
||||
index = int.Parse(item, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (command.StartsWith("remove", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var item = command[(command.IndexOf(":", StringComparison.InvariantCultureIgnoreCase) + 1)..];
|
||||
var index = int.Parse(item, CultureInfo.InvariantCulture);
|
||||
vm.Rules.RemoveAt(index);
|
||||
}
|
||||
else if (command == "add")
|
||||
@ -67,55 +61,17 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
var store = HttpContext.GetStoreData();
|
||||
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
var blob = store.GetStoreBlob();
|
||||
|
||||
if (command.StartsWith("test", StringComparison.InvariantCultureIgnoreCase))
|
||||
blob.EmailRules = vm.Rules;
|
||||
store.SetStoreBlob(blob);
|
||||
await _Repo.UpdateStore(store);
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
var rule = vm.Rules[index];
|
||||
if (string.IsNullOrEmpty(rule.Subject) || string.IsNullOrEmpty(rule.Body) || string.IsNullOrEmpty(rule.To))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Warning,
|
||||
Message = "Please fill all required fields before testing"
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var emailSettings = blob.EmailSettings;
|
||||
using var client = await emailSettings.CreateSmtpClient();
|
||||
var message = emailSettings.CreateMailMessage(MailboxAddress.Parse(rule.To), "(test) " + rule.Subject, rule.Body, true);
|
||||
await client.SendAsync(message);
|
||||
await client.DisconnectAsync(true);
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Rule email saved and sent to {rule.To}. Please verify you received it.";
|
||||
|
||||
blob.EmailRules = vm.Rules;
|
||||
store.SetStoreBlob(blob);
|
||||
await _Repo.UpdateStore(store);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Error: " + ex.Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// UPDATE
|
||||
blob.EmailRules = vm.Rules;
|
||||
store.SetStoreBlob(blob);
|
||||
await _Repo.UpdateStore(store);
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
Message = "Store email rules saved"
|
||||
});
|
||||
}
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
Message = "Store email rules saved"
|
||||
});
|
||||
return RedirectToAction("StoreEmails", new { storeId });
|
||||
}
|
||||
|
||||
|
@ -181,6 +181,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
CryptoCode = vm.CryptoCode,
|
||||
UseBech32Scheme = true,
|
||||
EnableForStandardInvoices = false,
|
||||
LUD12Enabled = false
|
||||
});
|
||||
|
||||
@ -244,12 +245,26 @@ 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);
|
||||
@ -275,11 +290,22 @@ 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))
|
||||
{
|
||||
@ -289,6 +315,7 @@ namespace BTCPayServer.Controllers
|
||||
store.SetSupportedPaymentMethod(new LNURLPaySupportedPaymentMethod
|
||||
{
|
||||
CryptoCode = vm.CryptoCode,
|
||||
EnableForStandardInvoices = vm.LNURLStandardInvoiceEnabled,
|
||||
UseBech32Scheme = vm.LNURLBech32Mode,
|
||||
LUD12Enabled = vm.LUD12Enabled
|
||||
});
|
||||
|
@ -385,11 +385,9 @@ namespace BTCPayServer.Controllers
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
vm.UseClassicCheckout = storeBlob.CheckoutType == Client.Models.CheckoutType.V1;
|
||||
vm.UseNewCheckout = storeBlob.CheckoutType == Client.Models.CheckoutType.V2;
|
||||
vm.CelebratePayment = storeBlob.CelebratePayment;
|
||||
vm.OnChainWithLnInvoiceFallback = storeBlob.OnChainWithLnInvoiceFallback;
|
||||
vm.ShowPayInWalletButton = storeBlob.ShowPayInWalletButton;
|
||||
vm.ShowStoreHeader = storeBlob.ShowStoreHeader;
|
||||
vm.LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi;
|
||||
vm.RequiresRefundEmail = storeBlob.RequiresRefundEmail;
|
||||
vm.LazyPaymentMethods = storeBlob.LazyPaymentMethods;
|
||||
@ -507,9 +505,7 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
blob.ShowPayInWalletButton = model.ShowPayInWalletButton;
|
||||
blob.ShowStoreHeader = model.ShowStoreHeader;
|
||||
blob.CheckoutType = model.UseClassicCheckout ? Client.Models.CheckoutType.V1 : Client.Models.CheckoutType.V2;
|
||||
blob.CheckoutType = model.UseNewCheckout ? Client.Models.CheckoutType.V2 : Client.Models.CheckoutType.V1;
|
||||
blob.CelebratePayment = model.CelebratePayment;
|
||||
blob.OnChainWithLnInvoiceFallback = model.OnChainWithLnInvoiceFallback;
|
||||
blob.LightningAmountInSatoshi = model.LightningAmountInSatoshi;
|
||||
@ -730,7 +726,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
await _fileService.RemoveFile(blob.CssFileId, userId);
|
||||
}
|
||||
|
||||
|
||||
// add new CSS file
|
||||
try
|
||||
{
|
||||
|
@ -60,13 +60,9 @@ namespace BTCPayServer.Controllers
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
var store = new StoreData { StoreName = vm.Name };
|
||||
var blob = store.GetStoreBlob();
|
||||
blob.DefaultCurrency = vm.DefaultCurrency;
|
||||
blob.PreferredExchange = vm.PreferredExchange;
|
||||
store.SetStoreBlob(blob);
|
||||
await _repo.CreateStore(GetUserId(), store);
|
||||
var store = await _repo.CreateStore(GetUserId(), vm.Name, vm.DefaultCurrency, vm.PreferredExchange);
|
||||
CreatedStoreId = store.Id;
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Store successfully created";
|
||||
return RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new
|
||||
{
|
||||
|
@ -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,14 +442,13 @@ 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;
|
||||
@ -459,22 +458,17 @@ namespace BTCPayServer.Controllers
|
||||
ix.Merge(i);
|
||||
}
|
||||
}
|
||||
if (ix is null)
|
||||
continue;
|
||||
|
||||
var labels = _labelService.CreateTransactionTagModels(ix, Request);
|
||||
if (ix is null) continue;
|
||||
var input = vm.Inputs.First(model => model.Index == inputToObject.Key);
|
||||
input.Labels = labels;
|
||||
input.Labels = ix.LabelColors;
|
||||
}
|
||||
foreach (var outputToObject in outputToObjects)
|
||||
{
|
||||
if (!labelInfo.TryGetValue(outputToObject.Value.Id, out var ix))
|
||||
continue;
|
||||
var labels = _labelService.CreateTransactionTagModels(ix, Request);
|
||||
if (!labelInfo.TryGetValue(outputToObject.Value.Id, out var ix)) continue;
|
||||
var destination = vm.Destinations.First(model => model.Destination == outputToObject.Key);
|
||||
destination.Labels = labels;
|
||||
destination.Labels = ix.LabelColors;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
[HttpPost("{walletId}/psbt/ready")]
|
||||
@ -494,7 +488,7 @@ namespace BTCPayServer.Controllers
|
||||
if (derivationSchemeSettings == null)
|
||||
return NotFound();
|
||||
|
||||
await FetchTransactionDetails(walletId, derivationSchemeSettings, vm, network);
|
||||
await FetchTransactionDetails(walletId,derivationSchemeSettings, vm, network);
|
||||
|
||||
switch (command)
|
||||
{
|
||||
@ -625,7 +619,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");
|
||||
|
@ -65,8 +65,9 @@ namespace BTCPayServer.Controllers
|
||||
private readonly SettingsRepository _settingsRepository;
|
||||
private readonly DelayedTransactionBroadcaster _broadcaster;
|
||||
private readonly PayjoinClient _payjoinClient;
|
||||
private readonly LabelService _labelService;
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
private readonly PullPaymentHostedService _pullPaymentHostedService;
|
||||
private readonly UTXOLocker _utxoLocker;
|
||||
private readonly WalletHistogramService _walletHistogramService;
|
||||
|
||||
readonly CurrencyNameTable _currencyTable;
|
||||
@ -89,10 +90,11 @@ namespace BTCPayServer.Controllers
|
||||
PayjoinClient payjoinClient,
|
||||
IServiceProvider serviceProvider,
|
||||
PullPaymentHostedService pullPaymentHostedService,
|
||||
LabelService labelService)
|
||||
UTXOLocker utxoLocker,
|
||||
LinkGenerator linkGenerator)
|
||||
{
|
||||
_currencyTable = currencyTable;
|
||||
_labelService = labelService;
|
||||
_linkGenerator = linkGenerator;
|
||||
Repository = repo;
|
||||
WalletRepository = walletRepository;
|
||||
RateFetcher = rateProvider;
|
||||
@ -108,6 +110,7 @@ namespace BTCPayServer.Controllers
|
||||
_broadcaster = broadcaster;
|
||||
_payjoinClient = payjoinClient;
|
||||
_pullPaymentHostedService = pullPaymentHostedService;
|
||||
_utxoLocker = utxoLocker;
|
||||
ServiceProvider = serviceProvider;
|
||||
_walletHistogramService = walletHistogramService;
|
||||
}
|
||||
@ -263,7 +266,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
if (walletTransactionsInfo.TryGetValue(tx.TransactionId.ToString(), out var transactionInfo))
|
||||
{
|
||||
var labels = _labelService.CreateTransactionTagModels(transactionInfo, Request);
|
||||
var labels = CreateTransactionTagModels(transactionInfo);
|
||||
vm.Tags.AddRange(labels);
|
||||
vm.Comment = transactionInfo.Comment;
|
||||
}
|
||||
@ -340,7 +343,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>()
|
||||
});
|
||||
}
|
||||
|
||||
@ -453,7 +456,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (!string.IsNullOrEmpty(link))
|
||||
{
|
||||
await LoadFromBIP21(walletId, model, link, network);
|
||||
LoadFromBIP21(model, link, network);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -568,7 +571,7 @@ namespace BTCPayServer.Controllers
|
||||
if (!string.IsNullOrEmpty(bip21))
|
||||
{
|
||||
vm.Outputs?.Clear();
|
||||
await LoadFromBIP21(walletId, vm, bip21, network);
|
||||
LoadFromBIP21(vm, bip21, network);
|
||||
}
|
||||
|
||||
decimal transactionAmountSum = 0;
|
||||
@ -586,7 +589,7 @@ namespace BTCPayServer.Controllers
|
||||
.GetUnspentCoins(schemeSettings.AccountDerivation, false, cancellation);
|
||||
|
||||
var walletTransactionsInfoAsync = await this.WalletRepository.GetWalletTransactionsInfo(walletId,
|
||||
utxos.SelectMany(GetWalletObjectsQuery.Get).Distinct().ToArray());
|
||||
utxos.SelectMany(u => GetWalletObjectsQuery.Get(u)).Distinct().ToArray());
|
||||
vm.InputsAvailable = utxos.Select(coin =>
|
||||
{
|
||||
walletTransactionsInfoAsync.TryGetValue(coin.OutPoint.Hash.ToString(), out var info1);
|
||||
@ -598,7 +601,7 @@ namespace BTCPayServer.Controllers
|
||||
Outpoint = coin.OutPoint.ToString(),
|
||||
Amount = coin.Value.GetValue(network),
|
||||
Comment = info?.Comment,
|
||||
Labels = _labelService.CreateTransactionTagModels(info, Request),
|
||||
Labels = CreateTransactionTagModels(info),
|
||||
Link = string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink,
|
||||
coin.OutPoint.Hash.ToString()),
|
||||
Confirmations = coin.Confirmations
|
||||
@ -733,14 +736,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 +790,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;
|
||||
}
|
||||
}
|
||||
@ -863,10 +866,8 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
|
||||
private async Task LoadFromBIP21(WalletId walletId, WalletSendModel vm, string bip21,
|
||||
BTCPayNetwork network)
|
||||
private void LoadFromBIP21(WalletSendModel vm, string bip21, BTCPayNetwork network)
|
||||
{
|
||||
BitcoinAddress? address = null;
|
||||
vm.Outputs ??= new();
|
||||
try
|
||||
{
|
||||
@ -881,7 +882,6 @@ namespace BTCPayServer.Controllers
|
||||
? uriBuilder.UnknownParameters["payout"]
|
||||
: null
|
||||
});
|
||||
address = uriBuilder.Address;
|
||||
if (!string.IsNullOrEmpty(uriBuilder.Label) || !string.IsNullOrEmpty(uriBuilder.Message))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
@ -899,10 +899,9 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
try
|
||||
{
|
||||
address = BitcoinAddress.Create(bip21, network.NBitcoinNetwork);
|
||||
vm.Outputs.Add(new WalletSendModel.TransactionOutput()
|
||||
{
|
||||
DestinationAddress = address.ToString()
|
||||
DestinationAddress = BitcoinAddress.Create(bip21, network.NBitcoinNetwork).ToString()
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -917,11 +916,6 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
private IActionResult ViewVault(WalletId walletId, WalletPSBTViewModel vm)
|
||||
@ -1350,14 +1344,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 +1360,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 +1393,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)
|
||||
}));
|
||||
}
|
||||
|
||||
@ -1418,6 +1412,124 @@ namespace BTCPayServer.Controllers
|
||||
private string GetUserId() => _userManager.GetUserId(User);
|
||||
|
||||
private StoreData GetCurrentStore() => HttpContext.GetStoreData();
|
||||
|
||||
public IEnumerable<TransactionTagModel> CreateTransactionTagModels(WalletTransactionInfo? transactionInfo)
|
||||
{
|
||||
if (transactionInfo is null)
|
||||
return Array.Empty<TransactionTagModel>();
|
||||
|
||||
string PayoutTooltip(IGrouping<string, string>? payoutsByPullPaymentId = null)
|
||||
{
|
||||
if (payoutsByPullPaymentId is null)
|
||||
{
|
||||
return "Paid a payout";
|
||||
}
|
||||
else if (payoutsByPullPaymentId.Count() == 1)
|
||||
{
|
||||
var pp = payoutsByPullPaymentId.Key;
|
||||
var payout = payoutsByPullPaymentId.First();
|
||||
if (!string.IsNullOrEmpty(pp))
|
||||
return $"Paid a payout ({payout}) of a pull payment ({pp})";
|
||||
else
|
||||
return $"Paid a payout {payout}";
|
||||
}
|
||||
else
|
||||
{
|
||||
var pp = payoutsByPullPaymentId.Key;
|
||||
if (!string.IsNullOrEmpty(pp))
|
||||
return $"Paid {payoutsByPullPaymentId.Count()} payouts of a pull payment ({pp})";
|
||||
else
|
||||
return $"Paid {payoutsByPullPaymentId.Count()} payouts";
|
||||
}
|
||||
}
|
||||
|
||||
var models = new Dictionary<string, TransactionTagModel>();
|
||||
foreach (var tag in transactionInfo.Attachments)
|
||||
{
|
||||
if (models.ContainsKey(tag.Type))
|
||||
continue;
|
||||
if (!transactionInfo.LabelColors.TryGetValue(tag.Type, out var color))
|
||||
continue;
|
||||
var model = new TransactionTagModel
|
||||
{
|
||||
Text = tag.Type,
|
||||
Color = color,
|
||||
TextColor = ColorPalette.Default.TextColor(color)
|
||||
};
|
||||
models.Add(tag.Type, model);
|
||||
if (tag.Type == WalletObjectData.Types.Payout)
|
||||
{
|
||||
var payoutsByPullPaymentId =
|
||||
transactionInfo.Attachments.Where(t => t.Type == "payout")
|
||||
.GroupBy(t => t.Data?["pullPaymentId"]?.Value<string>() ?? "",
|
||||
k => k.Id).ToList();
|
||||
|
||||
model.Tooltip = payoutsByPullPaymentId.Count switch
|
||||
{
|
||||
0 => PayoutTooltip(),
|
||||
1 => PayoutTooltip(payoutsByPullPaymentId.First()),
|
||||
_ => string.Join(", ", payoutsByPullPaymentId.Select(PayoutTooltip))
|
||||
};
|
||||
|
||||
model.Link = _linkGenerator.PayoutLink(transactionInfo.WalletId.ToString(), null, PayoutState.Completed, Request.Scheme, Request.Host,
|
||||
Request.PathBase);
|
||||
}
|
||||
else if (tag.Type == WalletObjectData.Types.Payjoin)
|
||||
{
|
||||
model.Tooltip = "This UTXO was part of a PayJoin transaction.";
|
||||
}
|
||||
else if (tag.Type == WalletObjectData.Types.Invoice)
|
||||
{
|
||||
model.Tooltip = $"Received through an invoice {tag.Id}";
|
||||
model.Link = string.IsNullOrEmpty(tag.Id)
|
||||
? null
|
||||
: _linkGenerator.InvoiceLink(tag.Id, Request.Scheme, Request.Host, Request.PathBase);
|
||||
}
|
||||
else if (tag.Type == WalletObjectData.Types.PaymentRequest)
|
||||
{
|
||||
model.Tooltip = $"Received through a payment request {tag.Id}";
|
||||
model.Link = _linkGenerator.PaymentRequestLink(tag.Id, Request.Scheme, Request.Host, Request.PathBase);
|
||||
}
|
||||
else if (tag.Type == WalletObjectData.Types.App)
|
||||
{
|
||||
model.Tooltip = $"Received through an app {tag.Id}";
|
||||
model.Link = _linkGenerator.AppLink(tag.Id, Request.Scheme, Request.Host, Request.PathBase);
|
||||
}
|
||||
else if (tag.Type == WalletObjectData.Types.PayjoinExposed)
|
||||
{
|
||||
|
||||
if (tag.Id.Length != 0)
|
||||
{
|
||||
model.Tooltip = $"This UTXO was exposed through a PayJoin proposal for an invoice ({tag.Id})";
|
||||
model.Link = _linkGenerator.InvoiceLink(tag.Id, Request.Scheme, Request.Host, Request.PathBase);
|
||||
}
|
||||
else
|
||||
{
|
||||
model.Tooltip = $"This UTXO was exposed through a PayJoin proposal";
|
||||
}
|
||||
}
|
||||
else if (tag.Type == WalletObjectData.Types.Payjoin)
|
||||
{
|
||||
model.Tooltip = $"This UTXO was part of a PayJoin transaction.";
|
||||
}
|
||||
else
|
||||
{
|
||||
model.Tooltip = tag.Data?.TryGetValue("tooltip", StringComparison.InvariantCultureIgnoreCase, out var tooltip) is true ? tooltip.ToString() : tag.Id;
|
||||
if (tag.Data?.TryGetValue("link", StringComparison.InvariantCultureIgnoreCase, out var link) is true)
|
||||
{
|
||||
model.Link = link.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var label in transactionInfo.LabelColors)
|
||||
models.TryAdd(label.Key, new TransactionTagModel
|
||||
{
|
||||
Text = label.Key,
|
||||
Color = label.Value,
|
||||
TextColor = ColorPalette.Default.TextColor(label.Value)
|
||||
});
|
||||
return models.Values.OrderBy(v => v.Text);
|
||||
}
|
||||
}
|
||||
|
||||
public class WalletReceiveViewModel
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -64,19 +64,12 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
_btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethod.CryptoCode)?.ReadonlyWallet is false;
|
||||
}
|
||||
|
||||
public async Task TrackClaim(ClaimRequest claimRequest, PayoutData payoutData)
|
||||
public async Task TrackClaim(PaymentMethodId paymentMethodId, IClaimDestination claimDestination)
|
||||
{
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(claimRequest.PaymentMethodId.CryptoCode);
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
var explorerClient = _explorerClientProvider.GetExplorerClient(network);
|
||||
if (claimRequest.Destination is IBitcoinLikeClaimDestination bitcoinLikeClaimDestination)
|
||||
{
|
||||
|
||||
if (claimDestination is IBitcoinLikeClaimDestination bitcoinLikeClaimDestination)
|
||||
await explorerClient.TrackAsync(TrackedSource.Create(bitcoinLikeClaimDestination.Address));
|
||||
await WalletRepository.AddWalletTransactionAttachment(
|
||||
new WalletId(claimRequest.StoreId, claimRequest.PaymentMethodId.CryptoCode),
|
||||
bitcoinLikeClaimDestination.Address.ToString(),
|
||||
Attachment.Payout(payoutData.PullPaymentDataId, payoutData.Id), WalletObjectData.Types.Address);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination, CancellationToken cancellationToken)
|
||||
@ -210,13 +203,11 @@ 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)
|
||||
{
|
||||
@ -232,16 +223,14 @@ 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)
|
||||
{
|
||||
|
@ -6,7 +6,6 @@ using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PayoutData = BTCPayServer.Data.PayoutData;
|
||||
@ -15,7 +14,7 @@ using StoreData = BTCPayServer.Data.StoreData;
|
||||
public interface IPayoutHandler
|
||||
{
|
||||
public bool CanHandle(PaymentMethodId paymentMethod);
|
||||
public Task TrackClaim(ClaimRequest claimRequest, PayoutData payoutData);
|
||||
public Task TrackClaim(PaymentMethodId paymentMethodId, IClaimDestination claimDestination);
|
||||
//Allows payout handler to parse payout destinations on its own
|
||||
public Task<(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination, CancellationToken cancellationToken);
|
||||
public (bool valid, string? error) ValidateClaimDestination(IClaimDestination claimDestination, PullPaymentBlob? pullPaymentBlob);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user