mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-01-18 05:12:51 +01:00
Dotnet reformat
This commit is contained in:
parent
f598c70a4f
commit
7546ef7a8e
@ -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)
|
||||
{
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System.Net.Http.Headers;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
@ -14,9 +14,9 @@ public class WithdrawRequestData
|
||||
|
||||
public WithdrawRequestData()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public WithdrawRequestData(string paymentMethod, TradeQuantity qty)
|
||||
{
|
||||
PaymentMethod = paymentMethod;
|
||||
|
@ -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>()
|
||||
|
@ -39,6 +39,6 @@ public class LightningAddressDataBlob
|
||||
public string CurrencyCode { get; set; }
|
||||
public decimal? Min { get; set; }
|
||||
public decimal? Max { get; set; }
|
||||
|
||||
|
||||
public JObject InvoiceMetadata { get; set; }
|
||||
}
|
||||
|
@ -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>();
|
||||
|
@ -44,7 +44,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("StoreWebsite")).SendKeys(storeUrl);
|
||||
s.Driver.FindElement(By.Id("Save")).Click();
|
||||
Assert.Contains("Store successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
|
||||
// Enable LNURL, which we will need for (non-)presence checks throughout this test
|
||||
s.GoToHome();
|
||||
s.GoToLightningSettings();
|
||||
@ -79,7 +79,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(address, copyAddress);
|
||||
Assert.Equal($"bitcoin:{address.ToUpperInvariant()}", qrValue);
|
||||
s.Driver.ElementDoesNotExist(By.Id("Lightning_BTC"));
|
||||
|
||||
|
||||
// Details should show exchange rate
|
||||
s.Driver.ToggleCollapse("PaymentDetails");
|
||||
s.Driver.ElementDoesNotExist(By.Id("PaymentDetails-TotalPrice"));
|
||||
@ -87,7 +87,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.ElementDoesNotExist(By.Id("PaymentDetails-AmountDue"));
|
||||
Assert.Contains("$", s.Driver.FindElement(By.Id("PaymentDetails-ExchangeRate")).Text);
|
||||
Assert.Contains("sat/byte", s.Driver.FindElement(By.Id("PaymentDetails-RecommendedFee")).Text);
|
||||
|
||||
|
||||
// Switch to LNURL
|
||||
s.Driver.FindElement(By.CssSelector(".payment-method:nth-child(2)")).Click();
|
||||
TestUtils.Eventually(() =>
|
||||
@ -125,7 +125,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"));
|
||||
@ -206,7 +206,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("Mined 1 block",
|
||||
s.Driver.WaitForElement(By.Id("CheatSuccessMessage")).Text);
|
||||
});
|
||||
|
||||
|
||||
// Settled
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
@ -244,7 +244,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.StartsWith("lnbcrt", copyAddressLightning);
|
||||
Assert.StartsWith($"bitcoin:{address.ToUpperInvariant()}?amount=", qrValue);
|
||||
Assert.Contains("&lightning=LNBCRT", qrValue);
|
||||
|
||||
|
||||
// Check details
|
||||
s.Driver.ToggleCollapse("PaymentDetails");
|
||||
Assert.Contains("1 BTC = ", s.Driver.FindElement(By.Id("PaymentDetails-ExchangeRate")).Text);
|
||||
@ -252,7 +252,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("$", s.Driver.FindElement(By.Id("PaymentDetails-TotalFiat")).Text);
|
||||
Assert.Contains("BTC", s.Driver.FindElement(By.Id("PaymentDetails-AmountDue")).Text);
|
||||
Assert.Contains("BTC", s.Driver.FindElement(By.Id("PaymentDetails-TotalPrice")).Text);
|
||||
|
||||
|
||||
// Switch to amount displayed in sats
|
||||
s.GoToHome();
|
||||
s.GoToStore(StoreNavPages.CheckoutAppearance);
|
||||
@ -262,7 +262,7 @@ namespace BTCPayServer.Tests
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||
Assert.Contains("sats", s.Driver.FindElement(By.Id("AmountDue")).Text);
|
||||
|
||||
|
||||
// Check details
|
||||
s.Driver.ToggleCollapse("PaymentDetails");
|
||||
Assert.Contains("1 sat = ", s.Driver.FindElement(By.Id("PaymentDetails-ExchangeRate")).Text);
|
||||
@ -270,7 +270,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("$", s.Driver.FindElement(By.Id("PaymentDetails-TotalFiat")).Text);
|
||||
Assert.Contains("sats", s.Driver.FindElement(By.Id("PaymentDetails-AmountDue")).Text);
|
||||
Assert.Contains("sats", s.Driver.FindElement(By.Id("PaymentDetails-TotalPrice")).Text);
|
||||
|
||||
|
||||
// BIP21 with LN as default payment method
|
||||
s.GoToHome();
|
||||
invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC_LightningLike");
|
||||
@ -280,7 +280,7 @@ namespace BTCPayServer.Tests
|
||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||
Assert.StartsWith("bitcoin:", payUrl);
|
||||
Assert.Contains("&lightning=lnbcrt", payUrl);
|
||||
|
||||
|
||||
// Check details
|
||||
s.Driver.ToggleCollapse("PaymentDetails");
|
||||
Assert.Contains("1 sat = ", s.Driver.FindElement(By.Id("PaymentDetails-ExchangeRate")).Text);
|
||||
@ -294,7 +294,7 @@ namespace BTCPayServer.Tests
|
||||
s.GoToLightningSettings();
|
||||
Assert.True(s.Driver.FindElement(By.Id("LNURLEnabled")).Selected);
|
||||
Assert.True(s.Driver.FindElement(By.Id("LNURLStandardInvoiceEnabled")).Selected);
|
||||
|
||||
|
||||
// BIP21 with top-up invoice
|
||||
invoiceId = s.CreateInvoice(amount: null);
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
@ -312,7 +312,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(address, copyAddressOnchain);
|
||||
Assert.StartsWith("lnurl", copyAddressLightning);
|
||||
Assert.StartsWith($"bitcoin:{address.ToUpperInvariant()}?lightning=LNURL", qrValue);
|
||||
|
||||
|
||||
// Check details
|
||||
s.Driver.ToggleCollapse("PaymentDetails");
|
||||
Assert.Contains("1 sat = ", s.Driver.FindElement(By.Id("PaymentDetails-ExchangeRate")).Text);
|
||||
@ -331,7 +331,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("This invoice will expire in", paymentInfo.Text);
|
||||
Assert.Contains("00:0", paymentInfo.Text);
|
||||
Assert.DoesNotContain("Please send", paymentInfo.Text);
|
||||
|
||||
|
||||
// Configure countdown timer
|
||||
s.GoToHome();
|
||||
invoiceId = s.CreateInvoice();
|
||||
@ -343,13 +343,13 @@ namespace BTCPayServer.Tests
|
||||
displayExpirationTimer.SendKeys("10");
|
||||
s.Driver.FindElement(By.Id("Save")).Click();
|
||||
Assert.Contains("Store successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||
paymentInfo = s.Driver.FindElement(By.Id("PaymentInfo"));
|
||||
Assert.False(paymentInfo.Displayed);
|
||||
Assert.DoesNotContain("This invoice will expire in", paymentInfo.Text);
|
||||
|
||||
|
||||
expirySeconds = s.Driver.FindElement(By.Id("ExpirySeconds"));
|
||||
expirySeconds.Clear();
|
||||
expirySeconds.SendKeys("599");
|
||||
@ -359,7 +359,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(paymentInfo.Displayed);
|
||||
Assert.Contains("This invoice will expire in", paymentInfo.Text);
|
||||
Assert.Contains("09:5", paymentInfo.Text);
|
||||
|
||||
|
||||
// Disable LNURL again
|
||||
s.GoToHome();
|
||||
s.GoToLightningSettings();
|
||||
@ -378,7 +378,7 @@ namespace BTCPayServer.Tests
|
||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||
Assert.StartsWith("bitcoin:", payUrl);
|
||||
Assert.Contains("&lightning=lnbcrt", payUrl);
|
||||
|
||||
|
||||
// Language Switch
|
||||
var languageSelect = new SelectElement(s.Driver.FindElement(By.Id("DefaultLang")));
|
||||
Assert.Equal("English", languageSelect.SelectedOption.Text);
|
||||
@ -387,7 +387,7 @@ namespace BTCPayServer.Tests
|
||||
languageSelect.SelectByText("Deutsch");
|
||||
Assert.Equal("Details anzeigen", s.Driver.FindElement(By.Id("DetailsToggle")).Text);
|
||||
Assert.Contains("lang=de", s.Driver.Url);
|
||||
|
||||
|
||||
s.Driver.Navigate().Refresh();
|
||||
languageSelect = new SelectElement(s.Driver.FindElement(By.Id("DefaultLang")));
|
||||
Assert.Equal("Deutsch", languageSelect.SelectedOption.Text);
|
||||
|
@ -626,7 +626,7 @@ namespace BTCPayServer.Tests
|
||||
[Fact]
|
||||
public void RoundupCurrenciesCorrectly()
|
||||
{
|
||||
DisplayFormatter displayFormatter = new (CurrencyNameTable.Instance);
|
||||
DisplayFormatter displayFormatter = new(CurrencyNameTable.Instance);
|
||||
foreach (var test in new[]
|
||||
{
|
||||
(0.0005m, "0.0005 USD", "USD"), (0.001m, "0.001 USD", "USD"), (0.01m, "0.01 USD", "USD"),
|
||||
@ -766,7 +766,7 @@ namespace BTCPayServer.Tests
|
||||
var root = new Mnemonic(
|
||||
"usage fever hen zero slide mammal silent heavy donate budget pulse say brain thank sausage brand craft about save attract muffin advance illegal cabbage")
|
||||
.DeriveExtKey();
|
||||
|
||||
|
||||
// xpub
|
||||
var tpub = "tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS";
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(tpub, testnet, out var settings, out var error));
|
||||
|
@ -116,7 +116,7 @@ public class FormTests : UnitTestBase
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
form = new Form()
|
||||
{
|
||||
Fields = new List<Field>
|
||||
@ -143,7 +143,7 @@ public class FormTests : UnitTestBase
|
||||
{"invoice_item3", new StringValues("updated")},
|
||||
{"invoice_test", new StringValues("updated")}
|
||||
}));
|
||||
|
||||
|
||||
foreach (var f in form.GetAllFields())
|
||||
{
|
||||
var field = f.Field;
|
||||
@ -185,7 +185,7 @@ public class FormTests : UnitTestBase
|
||||
form.SetValues(obj);
|
||||
obj = service.GetValues(form);
|
||||
Assert.Null(obj["test"].Value<string>());
|
||||
form.SetValues(new JObject{ ["test"] = "hello" });
|
||||
form.SetValues(new JObject { ["test"] = "hello" });
|
||||
obj = service.GetValues(form);
|
||||
Assert.Equal("hello", obj["test"].Value<string>());
|
||||
}
|
||||
|
@ -302,7 +302,8 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await client.GetApp("some random ID lol");
|
||||
});
|
||||
await AssertHttpError(404, async () => {
|
||||
await AssertHttpError(404, async () =>
|
||||
{
|
||||
await client.GetPosApp("some random ID lol");
|
||||
});
|
||||
|
||||
@ -451,7 +452,7 @@ namespace BTCPayServer.Tests
|
||||
// Test creating a crowdfund app
|
||||
var app = await client.CreateCrowdfundApp(
|
||||
user.StoreId,
|
||||
new CreateCrowdfundAppRequest()
|
||||
new CreateCrowdfundAppRequest()
|
||||
{
|
||||
AppName = "test app from API",
|
||||
Title = "test app title"
|
||||
@ -462,10 +463,12 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal("Crowdfund", app.AppType);
|
||||
|
||||
// Make sure we return a 404 if we try to get an app that doesn't exist
|
||||
await AssertHttpError(404, async () => {
|
||||
await AssertHttpError(404, async () =>
|
||||
{
|
||||
await client.GetApp("some random ID lol");
|
||||
});
|
||||
await AssertHttpError(404, async () => {
|
||||
await AssertHttpError(404, async () =>
|
||||
{
|
||||
await client.GetCrowdfundApp("some random ID lol");
|
||||
});
|
||||
|
||||
@ -488,7 +491,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// Test deleting the newly created app
|
||||
await client.DeleteApp(retrievedApp.Id);
|
||||
await AssertHttpError(404, async () => {
|
||||
await AssertHttpError(404, async () =>
|
||||
{
|
||||
await client.GetApp(retrievedApp.Id);
|
||||
});
|
||||
}
|
||||
@ -512,8 +516,8 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
);
|
||||
var crowdfundApp = await client.CreateCrowdfundApp(user.StoreId, new CreateCrowdfundAppRequest() { AppName = "test app from API" });
|
||||
|
||||
// Create another store and one app on it so we can get all apps from all stores for the user below
|
||||
|
||||
// Create another store and one app on it so we can get all apps from all stores for the user below
|
||||
var newStore = await client.CreateStore(new CreateStoreRequest() { Name = "A" });
|
||||
var newApp = await client.CreateCrowdfundApp(newStore.Id, new CreateCrowdfundAppRequest() { AppName = "new app" });
|
||||
|
||||
@ -544,7 +548,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(crowdfundApp.Name, apps[1].Name);
|
||||
Assert.Equal(crowdfundApp.StoreId, apps[1].StoreId);
|
||||
Assert.Equal(crowdfundApp.AppType, apps[1].AppType);
|
||||
|
||||
|
||||
Assert.Equal(newApp.Name, apps[2].Name);
|
||||
Assert.Equal(newApp.StoreId, apps[2].StoreId);
|
||||
Assert.Equal(newApp.AppType, apps[2].AppType);
|
||||
@ -1066,7 +1070,7 @@ namespace BTCPayServer.Tests
|
||||
var lnrURLs = await unauthenticated.GetPullPaymentLNURL(test4.Id);
|
||||
Assert.IsType<string>(lnrURLs.LNURLBech32);
|
||||
Assert.IsType<string>(lnrURLs.LNURLUri);
|
||||
|
||||
|
||||
//permission test around auto approved pps and payouts
|
||||
var nonApproved = await acc.CreateClient(Policies.CanCreateNonApprovedPullPayments);
|
||||
var approved = await acc.CreateClient(Policies.CanCreatePullPayments);
|
||||
@ -1091,7 +1095,7 @@ namespace BTCPayServer.Tests
|
||||
Destination = new Key().GetAddress(ScriptPubKeyType.TaprootBIP86, Network.RegTest).ToString()
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
var pullPayment = await approved.CreatePullPayment(acc.StoreId, new CreatePullPaymentRequest()
|
||||
{
|
||||
Amount = 100,
|
||||
@ -1100,7 +1104,7 @@ namespace BTCPayServer.Tests
|
||||
PaymentMethods = new[] { "BTC" },
|
||||
AutoApproveClaims = true
|
||||
});
|
||||
|
||||
|
||||
var p = await approved.CreatePayout(acc.StoreId, new CreatePayoutThroughStoreRequest()
|
||||
{
|
||||
Amount = 100,
|
||||
@ -1256,7 +1260,10 @@ namespace BTCPayServer.Tests
|
||||
//update store
|
||||
Assert.Empty(newStore.PaymentMethodCriteria);
|
||||
await client.GenerateOnChainWallet(newStore.Id, "BTC", new GenerateOnChainWalletRequest());
|
||||
var updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest() { Name = "B", PaymentMethodCriteria = new List<PaymentMethodCriteriaData>()
|
||||
var updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest()
|
||||
{
|
||||
Name = "B",
|
||||
PaymentMethodCriteria = new List<PaymentMethodCriteriaData>()
|
||||
{
|
||||
new()
|
||||
{
|
||||
@ -1265,7 +1272,8 @@ namespace BTCPayServer.Tests
|
||||
PaymentMethod = "BTC",
|
||||
CurrencyCode = "USD"
|
||||
}
|
||||
}});
|
||||
}
|
||||
});
|
||||
Assert.Equal("B", updatedStore.Name);
|
||||
var s = (await client.GetStore(newStore.Id));
|
||||
Assert.Equal("B", s.Name);
|
||||
@ -1275,9 +1283,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);
|
||||
@ -2331,10 +2339,10 @@ namespace BTCPayServer.Tests
|
||||
Assert.NotNull(merchantInvoice.Id);
|
||||
Assert.NotNull(merchantInvoice.PaymentHash);
|
||||
Assert.Equal(merchantInvoice.Id, merchantInvoice.PaymentHash);
|
||||
|
||||
|
||||
// The default client is using charge, so we should not be able to query channels
|
||||
var chargeClient = await user.CreateClient(Policies.CanUseInternalLightningNode);
|
||||
|
||||
|
||||
var info = await chargeClient.GetLightningNodeInfo("BTC");
|
||||
Assert.Single(info.NodeURIs);
|
||||
Assert.NotEqual(0, info.BlockHeight);
|
||||
@ -2403,7 +2411,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.NotNull(payResponse.FeeAmount);
|
||||
Assert.NotNull(payResponse.TotalAmount);
|
||||
Assert.NotNull(payResponse.PaymentHash);
|
||||
|
||||
|
||||
// check the get invoice response
|
||||
var merchInvoice = await merchantClient.GetLightningInvoice(merchant.StoreId, "BTC", merchantInvoice.Id);
|
||||
Assert.NotNull(merchInvoice);
|
||||
@ -2442,7 +2450,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// Amount received might be bigger because of internal implementation shit from lightning
|
||||
Assert.True(LightMoney.Satoshis(1000) <= invoice.AmountReceived);
|
||||
|
||||
|
||||
// check payments list for store node
|
||||
var payments = await client.GetLightningPayments(user.StoreId, "BTC");
|
||||
Assert.NotEmpty(payments);
|
||||
@ -2488,7 +2496,7 @@ namespace BTCPayServer.Tests
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync(true);
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.CLightning);
|
||||
|
||||
|
||||
var client = await user.CreateClient(Policies.Unrestricted);
|
||||
var invoice = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest
|
||||
@ -2503,12 +2511,12 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
var pm = Assert.Single(await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id));
|
||||
Assert.False(pm.AdditionalData.HasValues);
|
||||
|
||||
|
||||
var resp = await tester.CustomerLightningD.Pay(pm.Destination);
|
||||
Assert.Equal(PayResult.Ok, resp.Result);
|
||||
Assert.NotNull(resp.Details.PaymentHash);
|
||||
Assert.NotNull(resp.Details.Preimage);
|
||||
|
||||
|
||||
pm = Assert.Single(await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id));
|
||||
Assert.True(pm.AdditionalData.HasValues);
|
||||
Assert.Equal(resp.Details.PaymentHash.ToString(), pm.AdditionalData.GetValue("paymentHash"));
|
||||
@ -3193,7 +3201,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
}
|
||||
|
||||
[Fact(Timeout =TestTimeout)]
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task StoreLightningAddressesAPITests()
|
||||
{
|
||||
@ -3205,14 +3213,14 @@ namespace BTCPayServer.Tests
|
||||
var store = await adminClient.GetStore(admin.StoreId);
|
||||
|
||||
Assert.Empty(await adminClient.GetStorePaymentMethods(store.Id));
|
||||
var store2 = (await adminClient.CreateStore(new CreateStoreRequest() {Name = "test2"})).Id;
|
||||
var store2 = (await adminClient.CreateStore(new CreateStoreRequest() { Name = "test2" })).Id;
|
||||
var address1 = Guid.NewGuid().ToString("n").Substring(0, 8);
|
||||
var address2 = Guid.NewGuid().ToString("n").Substring(0, 8);
|
||||
|
||||
Assert.Empty(await adminClient.GetStoreLightningAddresses(store.Id));
|
||||
Assert.Empty(await adminClient.GetStoreLightningAddresses(store2));
|
||||
|
||||
Assert.Empty(await adminClient.GetStoreLightningAddresses(store.Id));
|
||||
Assert.Empty(await adminClient.GetStoreLightningAddresses(store2));
|
||||
await adminClient.AddOrUpdateStoreLightningAddress(store.Id, address1, new LightningAddressData());
|
||||
|
||||
|
||||
await adminClient.AddOrUpdateStoreLightningAddress(store.Id, address1, new LightningAddressData()
|
||||
{
|
||||
Max = 1
|
||||
@ -3221,8 +3229,8 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await adminClient.AddOrUpdateStoreLightningAddress(store2, address1, new LightningAddressData());
|
||||
});
|
||||
Assert.Equal(1,Assert.Single(await adminClient.GetStoreLightningAddresses(store.Id)).Max);
|
||||
Assert.Empty(await adminClient.GetStoreLightningAddresses(store2));
|
||||
Assert.Equal(1, Assert.Single(await adminClient.GetStoreLightningAddresses(store.Id)).Max);
|
||||
Assert.Empty(await adminClient.GetStoreLightningAddresses(store2));
|
||||
|
||||
await adminClient.AddOrUpdateStoreLightningAddress(store2, address2, new LightningAddressData());
|
||||
|
||||
@ -3233,8 +3241,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)]
|
||||
@ -3706,7 +3714,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
Assert.Equal(0.9m,
|
||||
Assert.Single(await clientBasic.GetStoreRates(user.StoreId, new[] { "BTC_XYZ" })).Rate);
|
||||
|
||||
|
||||
config = await clientBasic.GetStoreRateConfiguration(user.StoreId);
|
||||
Assert.NotNull(config);
|
||||
Assert.NotNull(config.EffectiveScript);
|
||||
@ -3940,7 +3948,7 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
|
||||
var withdrawalClient = await admin.CreateClient(Policies.CanWithdrawFromCustodianAccounts);
|
||||
var depositClient = await admin.CreateClient(Policies.CanDepositToCustodianAccounts);
|
||||
var tradeClient = await admin.CreateClient(Policies.CanTradeCustodianAccount);
|
||||
|
||||
|
||||
var store = await adminClient.GetStore(admin.StoreId);
|
||||
var storeId = store.Id;
|
||||
|
||||
@ -3981,19 +3989,19 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
|
||||
|
||||
// Test: GetDepositAddress, unauth
|
||||
await AssertHttpError(401, async () => await unauthClient.GetCustodianAccountDepositAddress(storeId, accountId, MockCustodian.DepositPaymentMethod));
|
||||
|
||||
|
||||
// Test: GetDepositAddress, auth, but wrong permission
|
||||
await AssertHttpError(403, async () => await managerClient.GetCustodianAccountDepositAddress(storeId, accountId, MockCustodian.DepositPaymentMethod));
|
||||
|
||||
|
||||
// Test: GetDepositAddress, wrong payment method
|
||||
await AssertApiError( 400, "unsupported-payment-method", async () => await depositClient.GetCustodianAccountDepositAddress(storeId, accountId, "WRONG-PaymentMethod"));
|
||||
|
||||
await AssertApiError(400, "unsupported-payment-method", async () => await depositClient.GetCustodianAccountDepositAddress(storeId, accountId, "WRONG-PaymentMethod"));
|
||||
|
||||
// Test: GetDepositAddress, wrong store ID
|
||||
await AssertHttpError(403, async () => await depositClient.GetCustodianAccountDepositAddress("WRONG-STORE", accountId, MockCustodian.DepositPaymentMethod));
|
||||
|
||||
|
||||
// Test: GetDepositAddress, wrong account ID
|
||||
await AssertHttpError(404, async () => await depositClient.GetCustodianAccountDepositAddress(storeId, "WRONG-ACCOUNT-ID", MockCustodian.DepositPaymentMethod));
|
||||
|
||||
|
||||
// Test: GetDepositAddress, correct payment method
|
||||
var depositAddress = await depositClient.GetCustodianAccountDepositAddress(storeId, accountId, MockCustodian.DepositPaymentMethod);
|
||||
Assert.NotNull(depositAddress);
|
||||
@ -4001,7 +4009,7 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
|
||||
|
||||
|
||||
// Test: Trade, unauth
|
||||
var tradeRequest = new TradeRequestData { FromAsset = MockCustodian.TradeFromAsset, ToAsset = MockCustodian.TradeToAsset, Qty = new TradeQuantity(MockCustodian.TradeQtyBought, TradeQuantity.ValueType.Exact)};
|
||||
var tradeRequest = new TradeRequestData { FromAsset = MockCustodian.TradeFromAsset, ToAsset = MockCustodian.TradeToAsset, Qty = new TradeQuantity(MockCustodian.TradeQtyBought, TradeQuantity.ValueType.Exact) };
|
||||
await AssertHttpError(401, async () => await unauthClient.MarketTradeCustodianAccountAsset(storeId, accountId, tradeRequest));
|
||||
|
||||
// Test: Trade, auth, but wrong permission
|
||||
@ -4049,11 +4057,11 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
|
||||
|
||||
|
||||
// Test: GetTradeQuote, unauth
|
||||
await AssertHttpError(401, async () => await unauthClient.GetCustodianAccountTradeQuote(storeId, accountId, MockCustodian.TradeFromAsset, MockCustodian.TradeToAsset));
|
||||
|
||||
await AssertHttpError(401, async () => await unauthClient.GetCustodianAccountTradeQuote(storeId, accountId, MockCustodian.TradeFromAsset, MockCustodian.TradeToAsset));
|
||||
|
||||
// Test: GetTradeQuote, auth, but wrong permission
|
||||
await AssertHttpError(403, async () => await managerClient.GetCustodianAccountTradeQuote(storeId, accountId, MockCustodian.TradeFromAsset, MockCustodian.TradeToAsset));
|
||||
|
||||
await AssertHttpError(403, async () => await managerClient.GetCustodianAccountTradeQuote(storeId, accountId, MockCustodian.TradeFromAsset, MockCustodian.TradeToAsset));
|
||||
|
||||
// Test: GetTradeQuote, auth, correct permission
|
||||
var tradeQuote = await tradeClient.GetCustodianAccountTradeQuote(storeId, accountId, MockCustodian.TradeFromAsset, MockCustodian.TradeToAsset);
|
||||
Assert.NotNull(tradeQuote);
|
||||
@ -4063,28 +4071,28 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
|
||||
Assert.Equal(MockCustodian.BtcPriceInEuro, tradeQuote.Ask);
|
||||
|
||||
// Test: GetTradeQuote, SATS
|
||||
await AssertApiError(400, "use-asset-synonym", async () => await tradeClient.GetCustodianAccountTradeQuote(storeId, accountId, MockCustodian.TradeFromAsset, "SATS"));
|
||||
|
||||
await AssertApiError(400, "use-asset-synonym", async () => await tradeClient.GetCustodianAccountTradeQuote(storeId, accountId, MockCustodian.TradeFromAsset, "SATS"));
|
||||
|
||||
// Test: GetTradeQuote, wrong asset
|
||||
await AssertHttpError(404, async () => await tradeClient.GetCustodianAccountTradeQuote(storeId, accountId, "WRONG-ASSET", MockCustodian.TradeToAsset));
|
||||
await AssertHttpError(404, async () => await tradeClient.GetCustodianAccountTradeQuote(storeId, accountId, MockCustodian.TradeFromAsset , "WRONG-ASSET"));
|
||||
await AssertHttpError(404, async () => await tradeClient.GetCustodianAccountTradeQuote(storeId, accountId, "WRONG-ASSET", MockCustodian.TradeToAsset));
|
||||
await AssertHttpError(404, async () => await tradeClient.GetCustodianAccountTradeQuote(storeId, accountId, MockCustodian.TradeFromAsset, "WRONG-ASSET"));
|
||||
|
||||
// Test: wrong account ID
|
||||
await AssertHttpError(404, async () => await tradeClient.GetCustodianAccountTradeQuote(storeId, "WRONG-ACCOUNT-ID", MockCustodian.TradeFromAsset, MockCustodian.TradeToAsset));
|
||||
|
||||
await AssertHttpError(404, async () => await tradeClient.GetCustodianAccountTradeQuote(storeId, "WRONG-ACCOUNT-ID", MockCustodian.TradeFromAsset, MockCustodian.TradeToAsset));
|
||||
|
||||
// Test: wrong store ID
|
||||
await AssertHttpError(403, async () => await tradeClient.GetCustodianAccountTradeQuote("WRONG-STORE-ID", accountId, MockCustodian.TradeFromAsset, MockCustodian.TradeToAsset));
|
||||
await AssertHttpError(403, async () => await tradeClient.GetCustodianAccountTradeQuote("WRONG-STORE-ID", accountId, MockCustodian.TradeFromAsset, MockCustodian.TradeToAsset));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Test: GetTradeInfo, unauth
|
||||
await AssertHttpError(401, async () => await unauthClient.GetCustodianAccountTradeInfo(storeId, accountId, MockCustodian.TradeId));
|
||||
|
||||
await AssertHttpError(401, async () => await unauthClient.GetCustodianAccountTradeInfo(storeId, accountId, MockCustodian.TradeId));
|
||||
|
||||
// Test: GetTradeInfo, auth, but wrong permission
|
||||
await AssertHttpError(403, async () => await managerClient.GetCustodianAccountTradeInfo(storeId, accountId, MockCustodian.TradeId));
|
||||
|
||||
await AssertHttpError(403, async () => await managerClient.GetCustodianAccountTradeInfo(storeId, accountId, MockCustodian.TradeId));
|
||||
|
||||
// Test: GetTradeInfo, auth, correct permission
|
||||
var tradeResult = await tradeClient.GetCustodianAccountTradeInfo(storeId, accountId, MockCustodian.TradeId);
|
||||
Assert.NotNull(tradeResult);
|
||||
@ -4107,36 +4115,36 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
|
||||
|
||||
// Test: GetTradeInfo, wrong trade ID
|
||||
await AssertHttpError(404, async () => await tradeClient.GetCustodianAccountTradeInfo(storeId, accountId, "WRONG-TRADE-ID"));
|
||||
|
||||
|
||||
// Test: wrong account ID
|
||||
await AssertHttpError(404, async () => await tradeClient.GetCustodianAccountTradeInfo(storeId, "WRONG-ACCOUNT-ID", MockCustodian.TradeId));
|
||||
|
||||
|
||||
// Test: wrong store ID
|
||||
await AssertHttpError(403, async () => await tradeClient.GetCustodianAccountTradeInfo("WRONG-STORE-ID", accountId, MockCustodian.TradeId));
|
||||
|
||||
var qty = new TradeQuantity(MockCustodian.WithdrawalAmount, TradeQuantity.ValueType.Exact);
|
||||
// Test: SimulateWithdrawal, unauth
|
||||
// Test: SimulateWithdrawal, unauth
|
||||
var simulateWithdrawalRequest = new WithdrawRequestData(MockCustodian.WithdrawalPaymentMethod, qty);
|
||||
await AssertHttpError(401, async () => await unauthClient.SimulateCustodianAccountWithdrawal(storeId, accountId, simulateWithdrawalRequest));
|
||||
|
||||
|
||||
// Test: SimulateWithdrawal, auth, but wrong permission
|
||||
await AssertHttpError(403, async () => await managerClient.SimulateCustodianAccountWithdrawal(storeId, accountId, simulateWithdrawalRequest));
|
||||
|
||||
|
||||
// Test: SimulateWithdrawal, correct payment method, correct amount
|
||||
var simulateWithdrawResponse = await withdrawalClient.SimulateCustodianAccountWithdrawal(storeId, accountId, simulateWithdrawalRequest);
|
||||
AssertMockWithdrawal(simulateWithdrawResponse, custodianAccountData);
|
||||
|
||||
|
||||
// Test: SimulateWithdrawal, wrong payment method
|
||||
var wrongPaymentMethodSimulateWithdrawalRequest = new WithdrawRequestData("WRONG-PAYMENT-METHOD", qty);
|
||||
await AssertApiError( 400, "unsupported-payment-method", async () => await withdrawalClient.SimulateCustodianAccountWithdrawal(storeId, accountId, wrongPaymentMethodSimulateWithdrawalRequest));
|
||||
|
||||
await AssertApiError(400, "unsupported-payment-method", async () => await withdrawalClient.SimulateCustodianAccountWithdrawal(storeId, accountId, wrongPaymentMethodSimulateWithdrawalRequest));
|
||||
|
||||
// Test: SimulateWithdrawal, wrong account ID
|
||||
await AssertHttpError(404, async () => await withdrawalClient.SimulateCustodianAccountWithdrawal(storeId, "WRONG-ACCOUNT-ID", simulateWithdrawalRequest));
|
||||
|
||||
|
||||
// Test: SimulateWithdrawal, wrong store ID
|
||||
// TODO it is wierd that 403 is considered normal, but it is like this for all calls where the store is wrong... I'd have preferred a 404 error, because the store cannot be found.
|
||||
await AssertHttpError(403, async () => await withdrawalClient.SimulateCustodianAccountWithdrawal( "WRONG-STORE-ID",accountId, simulateWithdrawalRequest));
|
||||
|
||||
await AssertHttpError(403, async () => await withdrawalClient.SimulateCustodianAccountWithdrawal("WRONG-STORE-ID", accountId, simulateWithdrawalRequest));
|
||||
|
||||
// Test: SimulateWithdrawal, correct payment method, wrong amount
|
||||
var wrongAmountSimulateWithdrawalRequest = new WithdrawRequestData(MockCustodian.WithdrawalPaymentMethod, TradeQuantity.Parse("0.666"));
|
||||
await AssertHttpError(400, async () => await withdrawalClient.SimulateCustodianAccountWithdrawal(storeId, accountId, wrongAmountSimulateWithdrawalRequest));
|
||||
@ -4145,53 +4153,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
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ namespace BTCPayServer.Tests
|
||||
Driver.AssertNoError();
|
||||
}
|
||||
|
||||
public void PayInvoice(bool mine = false, decimal? amount= null)
|
||||
public void PayInvoice(bool mine = false, decimal? amount = null)
|
||||
{
|
||||
|
||||
if (amount is not null)
|
||||
|
@ -148,7 +148,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.LinkText("Remove")).Click();
|
||||
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
|
||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
|
||||
|
||||
Assert.DoesNotContain("Custom Form 1", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Id("CreateForm")).Click();
|
||||
s.Driver.FindElement(By.Name("Name")).SendKeys("Custom Form 2");
|
||||
@ -163,23 +163,23 @@ namespace BTCPayServer.Tests
|
||||
formurl = s.Driver.Url;
|
||||
result = await s.Server.PayTester.HttpClient.GetAsync(formurl);
|
||||
Assert.NotEqual(HttpStatusCode.NotFound, result.StatusCode);
|
||||
|
||||
|
||||
s.GoToHome();
|
||||
s.GoToStore(StoreNavPages.Forms);
|
||||
Assert.Contains("Custom Form 2", s.Driver.PageSource);
|
||||
|
||||
|
||||
s.Driver.FindElement(By.LinkText("Custom Form 2")).Click();
|
||||
|
||||
|
||||
s.Driver.FindElement(By.Name("Name")).Clear();
|
||||
s.Driver.FindElement(By.Name("Name")).SendKeys("Custom Form 3");
|
||||
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
||||
s.GoToStore(StoreNavPages.Forms);
|
||||
Assert.Contains("Custom Form 3", s.Driver.PageSource);
|
||||
|
||||
|
||||
s.Driver.FindElement(By.Id("StoreNav-PaymentRequests")).Click();
|
||||
s.Driver.FindElement(By.Id("CreatePaymentRequest")).Click();
|
||||
Assert.Equal(4, new SelectElement(s.Driver.FindElement(By.Id("FormId"))).Options.Count);
|
||||
|
||||
Assert.Equal(4, new SelectElement(s.Driver.FindElement(By.Id("FormId"))).Options.Count);
|
||||
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
@ -1377,9 +1377,9 @@ namespace BTCPayServer.Tests
|
||||
await Task.Delay(500);
|
||||
s.Driver.WaitForElement(By.CssSelector("div.label-manager input")).SendKeys("label2" + Keys.Enter);
|
||||
});
|
||||
|
||||
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
Assert.NotNull(s.Driver.FindElement(By.CssSelector("[data-value='test-label']")));
|
||||
});
|
||||
@ -1401,10 +1401,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);
|
||||
});
|
||||
|
@ -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]);
|
||||
|
@ -61,34 +61,34 @@ namespace BTCPayServer.Tests
|
||||
return description;
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// This will take the translations from v1 or v2
|
||||
// /// and upload them to transifex if not found
|
||||
// /// </summary>
|
||||
// [FactWithSecret("TransifexAPIToken")]
|
||||
// [Trait("Utilities", "Utilities")]
|
||||
//#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
// public async Task UpdateTransifex()
|
||||
// {
|
||||
// // DO NOT RUN IT, THIS WILL ERASE THE CURRENT TRANSIFEX TRANSLATIONS
|
||||
// /// <summary>
|
||||
// /// This will take the translations from v1 or v2
|
||||
// /// and upload them to transifex if not found
|
||||
// /// </summary>
|
||||
// [FactWithSecret("TransifexAPIToken")]
|
||||
// [Trait("Utilities", "Utilities")]
|
||||
//#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
// public async Task UpdateTransifex()
|
||||
// {
|
||||
// // DO NOT RUN IT, THIS WILL ERASE THE CURRENT TRANSIFEX TRANSLATIONS
|
||||
|
||||
// var client = GetTransifexClient();
|
||||
// var translations = JsonTranslation.GetTranslations(TranslationFolder.CheckoutV2);
|
||||
// var enTranslations = translations["en"];
|
||||
// translations.Remove("en");
|
||||
// var client = GetTransifexClient();
|
||||
// var translations = JsonTranslation.GetTranslations(TranslationFolder.CheckoutV2);
|
||||
// var enTranslations = translations["en"];
|
||||
// translations.Remove("en");
|
||||
|
||||
// foreach (var t in translations)
|
||||
// {
|
||||
// foreach (var w in t.Value.Words.ToArray())
|
||||
// {
|
||||
// if (t.Value.Words[w.Key] == null)
|
||||
// t.Value.Words[w.Key] = enTranslations.Words[w.Key];
|
||||
// }
|
||||
// t.Value.Words.Remove("code");
|
||||
// t.Value.Words.Remove("NOTICE_WARN");
|
||||
// }
|
||||
// await client.UpdateTranslations(translations);
|
||||
// }
|
||||
// foreach (var t in translations)
|
||||
// {
|
||||
// foreach (var w in t.Value.Words.ToArray())
|
||||
// {
|
||||
// if (t.Value.Words[w.Key] == null)
|
||||
// t.Value.Words[w.Key] = enTranslations.Words[w.Key];
|
||||
// }
|
||||
// t.Value.Words.Remove("code");
|
||||
// t.Value.Words.Remove("NOTICE_WARN");
|
||||
// }
|
||||
// await client.UpdateTranslations(translations);
|
||||
// }
|
||||
|
||||
//#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
|
||||
@ -147,7 +147,7 @@ namespace BTCPayServer.Tests
|
||||
public async Task AutoTranslateChatGPT()
|
||||
{
|
||||
var file = TranslationFolder.CheckoutV2;
|
||||
|
||||
|
||||
using var driver = new ChromeDriver(new ChromeOptions()
|
||||
{
|
||||
DebuggerAddress = "127.0.0.1:9222"
|
||||
@ -547,7 +547,7 @@ retry:
|
||||
|
||||
|
||||
public string FullPath { get; set; }
|
||||
public string TransifexProject { get; set; }
|
||||
public string TransifexProject { get; set; }
|
||||
public string TransifexResource { get; private set; }
|
||||
|
||||
public void Save()
|
||||
@ -577,7 +577,7 @@ retry:
|
||||
}
|
||||
}
|
||||
|
||||
public void Translate(Dictionary<string,string> sourceTranslations)
|
||||
public void Translate(Dictionary<string, string> sourceTranslations)
|
||||
{
|
||||
foreach (var o in sourceTranslations)
|
||||
if (o.Value != null)
|
||||
|
@ -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;
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -59,7 +59,7 @@ public class StoreRecentTransactions : ViewComponent
|
||||
var network = derivationSettings.Network;
|
||||
var wallet = _walletProvider.GetWallet(network);
|
||||
var allTransactions = await wallet.FetchTransactionHistory(derivationSettings.AccountDerivation, 0, 5, TimeSpan.FromDays(31.0));
|
||||
var walletTransactionsInfo = await _walletRepository.GetWalletTransactionsInfo( vm.WalletId , allTransactions.Select(t => t.TransactionId.ToString()).ToArray());
|
||||
var walletTransactionsInfo = await _walletRepository.GetWalletTransactionsInfo(vm.WalletId, allTransactions.Select(t => t.TransactionId.ToString()).ToArray());
|
||||
|
||||
transactions = allTransactions
|
||||
.Select(tx =>
|
||||
|
@ -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()
|
||||
}
|
||||
)
|
||||
),
|
||||
|
@ -362,10 +362,10 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
return UnsupportedAsset(asset, ex.Message);
|
||||
}
|
||||
|
||||
|
||||
var simulateWithdrawResult =
|
||||
await withdrawableCustodian.SimulateWithdrawalAsync(request.PaymentMethod, qty, custodianAccount.GetBlob(), cancellationToken);
|
||||
var result = new WithdrawalSimulationResponseData(simulateWithdrawResult.PaymentMethod, simulateWithdrawResult.Asset,
|
||||
var result = new WithdrawalSimulationResponseData(simulateWithdrawResult.PaymentMethod, simulateWithdrawResult.Asset,
|
||||
accountId, custodian.Code, simulateWithdrawResult.LedgerEntries, simulateWithdrawResult.MinQty, simulateWithdrawResult.MaxQty);
|
||||
return Ok(result);
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ using BTCPayServer.Security;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
using PayoutProcessorData = BTCPayServer.Client.Models.PayoutProcessorData;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
|
@ -264,7 +264,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
pullPaymentId = pullPaymentId
|
||||
}, Request.Scheme, Request.Host.ToString())!);
|
||||
|
||||
return base.Ok(new PullPaymentLNURL() {
|
||||
return base.Ok(new PullPaymentLNURL()
|
||||
{
|
||||
LNURLBech32 = LNURL.LNURL.EncodeUri(lnurlEndpoint, "withdrawRequest", true).ToString(),
|
||||
LNURLUri = LNURL.LNURL.EncodeUri(lnurlEndpoint, "withdrawRequest", false).ToString()
|
||||
});
|
||||
@ -359,7 +360,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return this.CreateAPIPermissionError(Policies.CanCreatePullPayments);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (request is null || !PaymentMethodId.TryParse(request?.PaymentMethod, out var paymentMethodId))
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method");
|
||||
|
@ -33,7 +33,10 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return new LightningAddressData();
|
||||
return new LightningAddressData()
|
||||
{
|
||||
Username = data.Username, Max = blob.Max, Min = blob.Min, CurrencyCode = blob.CurrencyCode
|
||||
Username = data.Username,
|
||||
Max = blob.Max,
|
||||
Min = blob.Min,
|
||||
CurrencyCode = blob.CurrencyCode
|
||||
};
|
||||
}
|
||||
|
||||
@ -41,7 +44,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
[HttpGet("~/api/v1/stores/{storeId}/lightning-addresses")]
|
||||
public async Task<IActionResult> GetStoreLightningAddresses(string storeId)
|
||||
{
|
||||
return Ok((await _lightningAddressService.Get(new LightningAddressQuery() {StoreIds = new[] {storeId}}))
|
||||
return Ok((await _lightningAddressService.Get(new LightningAddressQuery() { StoreIds = new[] { storeId } }))
|
||||
.Select(ToModel).ToArray());
|
||||
}
|
||||
|
||||
@ -64,7 +67,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
var res = await _lightningAddressService.Get(new LightningAddressQuery()
|
||||
{
|
||||
Usernames = new[] {username}, StoreIds = new[] {storeId},
|
||||
Usernames = new[] { username },
|
||||
StoreIds = new[] { storeId },
|
||||
});
|
||||
return res?.Any() is true ? Ok(ToModel(res.First())) : this.CreateAPIError(404, "lightning-address-not-found", "The lightning address was not present.");
|
||||
}
|
||||
@ -79,17 +83,17 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
ModelState.AddModelError(nameof(data.Min), "Minimum must be greater than 0 if provided.");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
|
||||
if (await _lightningAddressService.Set(new Data.LightningAddressData()
|
||||
{
|
||||
StoreDataId = storeId,
|
||||
Username = username
|
||||
}.SetBlob(new LightningAddressDataBlob()
|
||||
{
|
||||
Max = data.Max,
|
||||
Min = data.Min,
|
||||
CurrencyCode = data.CurrencyCode
|
||||
})))
|
||||
{
|
||||
StoreDataId = storeId,
|
||||
Username = username
|
||||
}.SetBlob(new LightningAddressDataBlob()
|
||||
{
|
||||
Max = data.Max,
|
||||
Min = data.Min,
|
||||
CurrencyCode = data.CurrencyCode
|
||||
})))
|
||||
{
|
||||
return await GetStoreLightningAddress(storeId, username);
|
||||
}
|
||||
|
@ -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,7 +249,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
if (string.IsNullOrEmpty(pmc.CurrencyCode))
|
||||
{
|
||||
request.AddModelError(data => data.PaymentMethodCriteria[index].CurrencyCode, "CurrencyCode is required", this);
|
||||
}else if (CurrencyNameTable.Instance.GetCurrencyData(pmc.CurrencyCode, false) is null)
|
||||
}
|
||||
else if (CurrencyNameTable.Instance.GetCurrencyData(pmc.CurrencyCode, false) is null)
|
||||
{
|
||||
request.AddModelError(data => data.PaymentMethodCriteria[index].CurrencyCode, "CurrencyCode is invalid", this);
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
@ -150,11 +150,11 @@ namespace BTCPayServer.Controllers
|
||||
var defaultCurrency = await GetStoreDefaultCurrentIfEmpty(appData.StoreDataId, null);
|
||||
await _appService.SetDefaultSettings(appData, defaultCurrency);
|
||||
await _appService.UpdateOrCreateApp(appData);
|
||||
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] = "App successfully created";
|
||||
CreatedAppId = appData.Id;
|
||||
|
||||
|
||||
|
||||
var url = await type.ConfigureLink(appData);
|
||||
return Redirect(url);
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ namespace BTCPayServer.Controllers
|
||||
var metaData = PosDataParser.ParsePosData(invoice.Metadata.ToJObject());
|
||||
var additionalData = metaData
|
||||
.Where(dict => !InvoiceAdditionalDataExclude.Contains(dict.Key))
|
||||
.ToDictionary(dict=> dict.Key, dict=> dict.Value);
|
||||
.ToDictionary(dict => dict.Key, dict => dict.Value);
|
||||
var model = new InvoiceDetailsModel
|
||||
{
|
||||
StoreId = store.Id,
|
||||
@ -201,12 +201,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);
|
||||
|
||||
@ -897,22 +897,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);
|
||||
}
|
||||
|
||||
|
@ -192,7 +192,7 @@ namespace BTCPayServer
|
||||
case PayResult.Error:
|
||||
default:
|
||||
await _pullPaymentHostedService.Cancel(
|
||||
new PullPaymentHostedService.CancelRequest(new []
|
||||
new PullPaymentHostedService.CancelRequest(new[]
|
||||
{ claimResponse.PayoutData.Id }, null));
|
||||
|
||||
return BadRequest(new LNUrlStatusResponse
|
||||
@ -305,7 +305,7 @@ namespace BTCPayServer
|
||||
};
|
||||
|
||||
var invoiceMetadata = new InvoiceMetadata();
|
||||
invoiceMetadata.OrderId =AppService.GetAppOrderId(app);
|
||||
invoiceMetadata.OrderId = AppService.GetAppOrderId(app);
|
||||
if (item != null)
|
||||
{
|
||||
invoiceMetadata.ItemCode = item.Id;
|
||||
@ -355,8 +355,8 @@ namespace BTCPayServer
|
||||
public string InvoiceMetadata { get; set; }
|
||||
}
|
||||
|
||||
public ConcurrentDictionary<string, LightningAddressItem> Items { get; } = new ();
|
||||
public ConcurrentDictionary<string, string[]> StoreToItemMap { get; } = new ();
|
||||
public ConcurrentDictionary<string, LightningAddressItem> Items { get; } = new();
|
||||
public ConcurrentDictionary<string, string[]> StoreToItemMap { get; } = new();
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
@ -466,7 +466,7 @@ namespace BTCPayServer
|
||||
lnurlRequest ??= new LNURLPayRequest();
|
||||
lnUrlMetadata ??= new Dictionary<string, string>();
|
||||
|
||||
if (lnUrlMetadata?.TryGetValue("text/identifier", out var lnAddress) is true && lnAddress is string)
|
||||
if (lnUrlMetadata?.TryGetValue("text/identifier", out var lnAddress) is true && lnAddress is not null)
|
||||
{
|
||||
var pm = i.GetPaymentMethod(pmi);
|
||||
var paymentMethodDetails = (LNURLPayPaymentMethodDetails)pm.GetPaymentMethodDetails();
|
||||
@ -642,7 +642,7 @@ namespace BTCPayServer
|
||||
{
|
||||
var expiry = i.ExpirationTime.ToUniversalTime() - DateTimeOffset.UtcNow;
|
||||
var metadata = JsonConvert.SerializeObject(lnurlPayRequest.Metadata);
|
||||
var description = (await _pluginHookService.ApplyFilter("modify-lnurlp-description", metadata)) as string;
|
||||
var description = (await _pluginHookService.ApplyFilter("modify-lnurlp-description", metadata)) as string;
|
||||
if (description is null)
|
||||
return NotFound();
|
||||
|
||||
@ -704,7 +704,7 @@ namespace BTCPayServer
|
||||
Reason = "Invoice not in a valid payable state"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[HttpGet("~/stores/{storeId}/plugins/lightning-address")]
|
||||
@ -759,11 +759,11 @@ namespace BTCPayServer
|
||||
}
|
||||
|
||||
JObject metadata = null;
|
||||
if (!string.IsNullOrEmpty(vm.Add.InvoiceMetadata) )
|
||||
if (!string.IsNullOrEmpty(vm.Add.InvoiceMetadata))
|
||||
{
|
||||
try
|
||||
{
|
||||
metadata = JObject.Parse(vm.Add.InvoiceMetadata);
|
||||
metadata = JObject.Parse(vm.Add.InvoiceMetadata);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -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,14 +224,14 @@ 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);
|
||||
@ -239,11 +239,11 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
form.ApplyValuesFromForm(Request.Form);
|
||||
if (FormDataService.Validate(form, ModelState))
|
||||
{
|
||||
{
|
||||
prBlob.FormResponse = FormDataService.GetValues(form);
|
||||
result.SetBlob(prBlob);
|
||||
await _PaymentRequestRepository.CreateOrUpdatePaymentRequest(result);
|
||||
return RedirectToAction("PayPaymentRequest", new {payReqId});
|
||||
return RedirectToAction("PayPaymentRequest", new { payReqId });
|
||||
}
|
||||
}
|
||||
viewModel.FormName = formData.Name;
|
||||
@ -283,7 +283,7 @@ namespace BTCPayServer.Controllers
|
||||
var formData = await FormDataService.GetForm(result.FormId);
|
||||
if (formData is not null)
|
||||
{
|
||||
return RedirectToAction("ViewPaymentRequestForm", new {payReqId});
|
||||
return RedirectToAction("ViewPaymentRequestForm", new { payReqId });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -730,7 +730,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
await _fileService.RemoveFile(blob.CssFileId, userId);
|
||||
}
|
||||
|
||||
|
||||
// add new CSS file
|
||||
try
|
||||
{
|
||||
|
@ -267,7 +267,7 @@ namespace BTCPayServer.Controllers
|
||||
ModelState.Remove(nameof(vm.PSBT));
|
||||
ModelState.Remove(nameof(vm.FileName));
|
||||
ModelState.Remove(nameof(vm.UploadedPSBTFile));
|
||||
await FetchTransactionDetails(walletId,derivationSchemeSettings, vm, network);
|
||||
await FetchTransactionDetails(walletId, derivationSchemeSettings, vm, network);
|
||||
return View("WalletPSBTDecoded", vm);
|
||||
|
||||
case "save-psbt":
|
||||
@ -386,15 +386,15 @@ namespace BTCPayServer.Controllers
|
||||
inputVm.BalanceChange = ValueToString(balanceChange2, network);
|
||||
inputVm.Positive = balanceChange2 >= Money.Zero;
|
||||
inputVm.Index = (int)input.Index;
|
||||
|
||||
|
||||
var walletObjectIds = new List<ObjectTypeId>();
|
||||
walletObjectIds.Add(new ObjectTypeId(WalletObjectData.Types.Utxo, input.PrevOut.ToString()));
|
||||
walletObjectIds.Add(new ObjectTypeId(WalletObjectData.Types.Tx, input.PrevOut.Hash.ToString()));
|
||||
walletObjectIds.Add(new ObjectTypeId(WalletObjectData.Types.Utxo, input.PrevOut.ToString()));
|
||||
walletObjectIds.Add(new ObjectTypeId(WalletObjectData.Types.Tx, input.PrevOut.Hash.ToString()));
|
||||
var address = txOut?.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork)?.ToString();
|
||||
if(address != null)
|
||||
if (address != null)
|
||||
walletObjectIds.Add(new ObjectTypeId(WalletObjectData.Types.Address, address));
|
||||
inputToObjects.Add(input.Index, walletObjectIds.ToArray());
|
||||
|
||||
|
||||
}
|
||||
vm.Destinations = new List<WalletPSBTReadyViewModel.DestinationViewModel>();
|
||||
foreach (var output in psbtObject.Outputs)
|
||||
@ -409,9 +409,9 @@ namespace BTCPayServer.Controllers
|
||||
dest.Positive = balanceChange2 >= Money.Zero;
|
||||
dest.Destination = output.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork)?.ToString() ?? output.ScriptPubKey.ToString();
|
||||
var address = output.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork)?.ToString();
|
||||
if(address != null)
|
||||
if (address != null)
|
||||
outputToObjects.Add(dest.Destination, new ObjectTypeId(WalletObjectData.Types.Address, address));
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (psbtObject.TryGetFee(out var fee))
|
||||
@ -442,13 +442,14 @@ namespace BTCPayServer.Controllers
|
||||
.DistinctBy(id => $"{id.Type}:{id.Id}").ToArray();
|
||||
|
||||
var labelInfo = await WalletRepository.GetWalletTransactionsInfo(walletId, combinedTypeIds);
|
||||
foreach (KeyValuePair<uint,ObjectTypeId[]> inputToObject in inputToObjects)
|
||||
foreach (KeyValuePair<uint, ObjectTypeId[]> inputToObject in inputToObjects)
|
||||
{
|
||||
var keys = inputToObject.Value.Select(id => id.Id).ToArray();
|
||||
WalletTransactionInfo ix = null;
|
||||
foreach (var key in keys)
|
||||
{
|
||||
if (!labelInfo.TryGetValue(key, out var i)) continue;
|
||||
if (!labelInfo.TryGetValue(key, out var i))
|
||||
continue;
|
||||
if (ix is null)
|
||||
{
|
||||
ix = i;
|
||||
@ -458,20 +459,22 @@ namespace BTCPayServer.Controllers
|
||||
ix.Merge(i);
|
||||
}
|
||||
}
|
||||
if (ix is null) continue;
|
||||
|
||||
if (ix is null)
|
||||
continue;
|
||||
|
||||
var labels = _labelService.CreateTransactionTagModels(ix, Request);
|
||||
var input = vm.Inputs.First(model => model.Index == inputToObject.Key);
|
||||
input.Labels = labels;
|
||||
}
|
||||
foreach (var outputToObject in outputToObjects)
|
||||
{
|
||||
if (!labelInfo.TryGetValue(outputToObject.Value.Id, out var ix)) continue;
|
||||
if (!labelInfo.TryGetValue(outputToObject.Value.Id, out var ix))
|
||||
continue;
|
||||
var labels = _labelService.CreateTransactionTagModels(ix, Request);
|
||||
var destination = vm.Destinations.First(model => model.Destination == outputToObject.Key);
|
||||
destination.Labels = labels;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
[HttpPost("{walletId}/psbt/ready")]
|
||||
@ -491,7 +494,7 @@ namespace BTCPayServer.Controllers
|
||||
if (derivationSchemeSettings == null)
|
||||
return NotFound();
|
||||
|
||||
await FetchTransactionDetails(walletId,derivationSchemeSettings, vm, network);
|
||||
await FetchTransactionDetails(walletId, derivationSchemeSettings, vm, network);
|
||||
|
||||
switch (command)
|
||||
{
|
||||
@ -622,7 +625,7 @@ namespace BTCPayServer.Controllers
|
||||
BackUrl = vm.BackUrl
|
||||
});
|
||||
case "decode":
|
||||
await FetchTransactionDetails(walletId,derivationSchemeSettings, vm, network);
|
||||
await FetchTransactionDetails(walletId, derivationSchemeSettings, vm, network);
|
||||
return View("WalletPSBTDecoded", vm);
|
||||
default:
|
||||
vm.Errors.Add("Unknown command");
|
||||
|
@ -340,7 +340,7 @@ namespace BTCPayServer.Controllers
|
||||
CryptoImage = GetImage(paymentMethod.PaymentId, network),
|
||||
PaymentLink = bip21.ToString(),
|
||||
ReturnUrl = returnUrl ?? HttpContext.Request.GetTypedHeaders().Referer?.AbsolutePath,
|
||||
SelectedLabels = labels?? Array.Empty<string>()
|
||||
SelectedLabels = labels ?? Array.Empty<string>()
|
||||
});
|
||||
}
|
||||
|
||||
@ -733,14 +733,14 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var labels = transactionOutput.Labels.Where(s => !string.IsNullOrWhiteSpace(s)).ToArray();
|
||||
var walletObjectAddress = new WalletObjectId(walletId, WalletObjectData.Types.Address, transactionOutput.DestinationAddress.ToLowerInvariant());
|
||||
var obj = await WalletRepository.GetWalletObject(walletObjectAddress);
|
||||
if (obj is null)
|
||||
var obj = await WalletRepository.GetWalletObject(walletObjectAddress);
|
||||
if (obj is null)
|
||||
{
|
||||
await WalletRepository.EnsureWalletObject(walletObjectAddress);
|
||||
await WalletRepository.EnsureWalletObject(walletObjectAddress);
|
||||
}
|
||||
await WalletRepository.AddWalletObjectLabels(walletObjectAddress, labels);
|
||||
}
|
||||
|
||||
|
||||
var derivationScheme = GetDerivationSchemeSettings(walletId);
|
||||
if (derivationScheme is null)
|
||||
return NotFound();
|
||||
@ -787,10 +787,10 @@ namespace BTCPayServer.Controllers
|
||||
switch (response.Result)
|
||||
{
|
||||
case ClaimRequest.ClaimResult.Duplicate:
|
||||
errorMessage += $"{claimRequest.Value} to {claimRequest.Destination.ToString() } - address reuse<br/>";
|
||||
errorMessage += $"{claimRequest.Value} to {claimRequest.Destination.ToString()} - address reuse<br/>";
|
||||
break;
|
||||
case ClaimRequest.ClaimResult.AmountTooLow:
|
||||
errorMessage += $"{claimRequest.Value} to {claimRequest.Destination.ToString() } - amount too low<br/>";
|
||||
errorMessage += $"{claimRequest.Value} to {claimRequest.Destination.ToString()} - amount too low<br/>";
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -919,8 +919,8 @@ namespace BTCPayServer.Controllers
|
||||
ModelState.Clear();
|
||||
if (address is not null)
|
||||
{
|
||||
var addressLabels = await WalletRepository.GetWalletLabels(new WalletObjectId(walletId, WalletObjectData.Types.Address, address.ToString()));
|
||||
vm.Outputs.Last().Labels = addressLabels.Select(tuple => tuple.Label).ToArray();
|
||||
var addressLabels = await WalletRepository.GetWalletLabels(new WalletObjectId(walletId, WalletObjectData.Types.Address, address.ToString()));
|
||||
vm.Outputs.Last().Labels = addressLabels.Select(tuple => tuple.Label).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1350,14 +1350,14 @@ namespace BTCPayServer.Controllers
|
||||
Response.Headers.Add("X-Content-Type-Options", "nosniff");
|
||||
return Content(res, mimeType);
|
||||
}
|
||||
|
||||
|
||||
public class UpdateLabelsRequest
|
||||
{
|
||||
public string? Id { get; set; }
|
||||
public string? Type { get; set; }
|
||||
public string[]? Labels { get; set; }
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("{walletId}/update-labels")]
|
||||
[IgnoreAntiforgeryToken]
|
||||
public async Task<IActionResult> UpdateLabels(
|
||||
@ -1366,23 +1366,23 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (string.IsNullOrEmpty(request.Type) || string.IsNullOrEmpty(request.Id) || request.Labels is null)
|
||||
return BadRequest();
|
||||
|
||||
|
||||
var objid = new WalletObjectId(walletId, request.Type, request.Id);
|
||||
var obj = await WalletRepository.GetWalletObject(objid);
|
||||
if (obj is null)
|
||||
var obj = await WalletRepository.GetWalletObject(objid);
|
||||
if (obj is null)
|
||||
{
|
||||
await WalletRepository.EnsureWalletObject(objid);
|
||||
await WalletRepository.EnsureWalletObject(objid);
|
||||
}
|
||||
else
|
||||
{
|
||||
var currentLabels = obj.GetNeighbours().Where(data => data.Type == WalletObjectData.Types.Label).ToArray();
|
||||
var toRemove = currentLabels.Where(data => !request.Labels.Contains(data.Id)).Select(data => data.Id).ToArray();
|
||||
await WalletRepository.RemoveWalletObjectLabels(objid, toRemove);
|
||||
var currentLabels = obj.GetNeighbours().Where(data => data.Type == WalletObjectData.Types.Label).ToArray();
|
||||
var toRemove = currentLabels.Where(data => !request.Labels.Contains(data.Id)).Select(data => data.Id).ToArray();
|
||||
await WalletRepository.RemoveWalletObjectLabels(objid, toRemove);
|
||||
}
|
||||
await WalletRepository.AddWalletObjectLabels(objid, request.Labels);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("{walletId}/labels")]
|
||||
[IgnoreAntiforgeryToken]
|
||||
public async Task<IActionResult> GetLabels(
|
||||
@ -1399,11 +1399,11 @@ namespace BTCPayServer.Controllers
|
||||
: await WalletRepository.GetWalletLabels(walletObjectId);
|
||||
return Ok(labels
|
||||
.Where(l => !excludeTypes || !WalletObjectData.Types.AllTypes.Contains(l.Label))
|
||||
.Select(tuple => new
|
||||
.Select(tuple => new
|
||||
{
|
||||
label = tuple.Label,
|
||||
color = tuple.Color,
|
||||
textColor = ColorPalette.Default.TextColor(tuple.Color)
|
||||
textColor = ColorPalette.Default.TextColor(tuple.Color)
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
var explorerClient = _explorerClientProvider.GetExplorerClient(network);
|
||||
if (claimRequest.Destination is IBitcoinLikeClaimDestination bitcoinLikeClaimDestination)
|
||||
{
|
||||
|
||||
|
||||
await explorerClient.TrackAsync(TrackedSource.Create(bitcoinLikeClaimDestination.Address));
|
||||
await WalletRepository.AddWalletTransactionAttachment(
|
||||
new WalletId(claimRequest.StoreId, claimRequest.PaymentMethodId.CryptoCode),
|
||||
@ -210,11 +210,13 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
await using (var context = _dbContextFactory.CreateContext())
|
||||
{
|
||||
var payouts = (await PullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery()
|
||||
{
|
||||
States = new[] {PayoutState.AwaitingPayment}, Stores = new[] {storeId}, PayoutIds = payoutIds
|
||||
}, context)).Where(data =>
|
||||
PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId) &&
|
||||
CanHandle(paymentMethodId))
|
||||
{
|
||||
States = new[] { PayoutState.AwaitingPayment },
|
||||
Stores = new[] { storeId },
|
||||
PayoutIds = payoutIds
|
||||
}, context)).Where(data =>
|
||||
PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId) &&
|
||||
CanHandle(paymentMethodId))
|
||||
.Select(data => (data, ParseProof(data) as PayoutTransactionOnChainBlob)).Where(tuple => tuple.Item2 != null && tuple.Item2.TransactionId != null && tuple.Item2.Accounted == false);
|
||||
foreach (var valueTuple in payouts)
|
||||
{
|
||||
@ -230,14 +232,16 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
Severity = StatusMessageModel.StatusSeverity.Success
|
||||
};
|
||||
case "reject-payment":
|
||||
await using (var context = _dbContextFactory.CreateContext())
|
||||
await using (var context = _dbContextFactory.CreateContext())
|
||||
{
|
||||
var payouts = (await PullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery()
|
||||
{
|
||||
States = new[] {PayoutState.AwaitingPayment}, Stores = new[] {storeId}, PayoutIds = payoutIds
|
||||
}, context)).Where(data =>
|
||||
PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId) &&
|
||||
CanHandle(paymentMethodId))
|
||||
{
|
||||
States = new[] { PayoutState.AwaitingPayment },
|
||||
Stores = new[] { storeId },
|
||||
PayoutIds = payoutIds
|
||||
}, context)).Where(data =>
|
||||
PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId) &&
|
||||
CanHandle(paymentMethodId))
|
||||
.Select(data => (data, ParseProof(data) as PayoutTransactionOnChainBlob)).Where(tuple => tuple.Item2 != null && tuple.Item2.TransactionId != null && tuple.Item2.Accounted == true);
|
||||
foreach (var valueTuple in payouts)
|
||||
{
|
||||
|
@ -225,11 +225,11 @@ namespace BTCPayServer.Data
|
||||
[DefaultValue(true)]
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
public bool ShowPayInWalletButton { get; set; } = true;
|
||||
|
||||
|
||||
[DefaultValue(true)]
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
public bool ShowStoreHeader { get; set; } = true;
|
||||
|
||||
|
||||
[DefaultValue(true)]
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
public bool CelebratePayment { get; set; } = true;
|
||||
|
@ -186,12 +186,12 @@ namespace BTCPayServer.Data
|
||||
{
|
||||
return IsPaymentTypeEnabled(storeData, networks, cryptoCode, LNURLPayPaymentType.Instance);
|
||||
}
|
||||
|
||||
|
||||
private static bool IsPaymentTypeEnabled(this StoreData storeData, BTCPayNetworkProvider networks, string cryptoCode, PaymentType paymentType)
|
||||
{
|
||||
var paymentMethods = storeData.GetSupportedPaymentMethods(networks);
|
||||
var excludeFilters = storeData.GetStoreBlob().GetExcludedPaymentMethods();
|
||||
return paymentMethods.Any(method =>
|
||||
return paymentMethods.Any(method =>
|
||||
method.PaymentId.CryptoCode == cryptoCode &&
|
||||
method.PaymentId.PaymentType == paymentType &&
|
||||
!excludeFilters.Match(method.PaymentId));
|
||||
|
@ -49,7 +49,7 @@ namespace BTCPayServer
|
||||
{
|
||||
return AccountDerivation is null ? null : DBUtils.nbxv1_get_wallet_id(Network.CryptoCode, AccountDerivation.ToString());
|
||||
}
|
||||
|
||||
|
||||
private static bool TryParseXpub(string xpub, DerivationSchemeParser derivationSchemeParser, ref DerivationSchemeSettings derivationSchemeSettings, ref string error, bool electrum = true)
|
||||
{
|
||||
if (!electrum)
|
||||
@ -87,9 +87,12 @@ namespace BTCPayServer
|
||||
var match = derivationRegex.Match(xpub.Trim());
|
||||
if (match.Success)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(match.Groups[1].Value)) rootFingerprint = HDFingerprint.Parse(match.Groups[1].Value);
|
||||
if (!string.IsNullOrEmpty(match.Groups[2].Value)) accountKeyPath = KeyPath.Parse(match.Groups[2].Value);
|
||||
if (!string.IsNullOrEmpty(match.Groups[3].Value)) xpub = match.Groups[3].Value;
|
||||
if (!string.IsNullOrEmpty(match.Groups[1].Value))
|
||||
rootFingerprint = HDFingerprint.Parse(match.Groups[1].Value);
|
||||
if (!string.IsNullOrEmpty(match.Groups[2].Value))
|
||||
accountKeyPath = KeyPath.Parse(match.Groups[2].Value);
|
||||
if (!string.IsNullOrEmpty(match.Groups[3].Value))
|
||||
xpub = match.Groups[3].Value;
|
||||
}
|
||||
derivationSchemeSettings.AccountOriginal = xpub.Trim();
|
||||
derivationSchemeSettings.AccountDerivation = electrum ? derivationSchemeParser.ParseElectrum(derivationSchemeSettings.AccountOriginal) : derivationSchemeParser.Parse(derivationSchemeSettings.AccountOriginal);
|
||||
|
@ -41,13 +41,13 @@ namespace BTCPayServer
|
||||
{
|
||||
pattern = pattern.Replace(" ", "");
|
||||
int[] res = new int[pattern.Length / 2];
|
||||
for (int i = 0; i < pattern.Length; i+=2)
|
||||
for (int i = 0; i < pattern.Length; i += 2)
|
||||
{
|
||||
var b = pattern[i..(i + 2)];
|
||||
if (b == "XX")
|
||||
res[i/2] = -1;
|
||||
res[i / 2] = -1;
|
||||
else
|
||||
res[i/2] = byte.Parse(b, System.Globalization.NumberStyles.HexNumber);
|
||||
res[i / 2] = byte.Parse(b, System.Globalization.NumberStyles.HexNumber);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
@ -13,12 +13,12 @@ namespace BTCPayServer.Filters
|
||||
public DomainMappingConstraintAttribute()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public DomainMappingConstraintAttribute(string appType)
|
||||
{
|
||||
AppType = appType;
|
||||
}
|
||||
|
||||
|
||||
public int Order => 100;
|
||||
private string AppType { get; }
|
||||
|
||||
@ -33,21 +33,22 @@ namespace BTCPayServer.Filters
|
||||
{
|
||||
var appId = (string)context.RouteContext.RouteData.Values["appId"];
|
||||
var matchedDomainMapping = mapping.FirstOrDefault(item => item.AppId == appId);
|
||||
|
||||
|
||||
// App is accessed via path, redirect to canonical domain
|
||||
var req = context.RouteContext.HttpContext.Request;
|
||||
if (matchedDomainMapping != null && req.Method != "POST" && !req.HasFormContentType)
|
||||
{
|
||||
var uri = new UriBuilder(req.Scheme, matchedDomainMapping.Domain);
|
||||
if (req.Host.Port.HasValue) uri.Port = req.Host.Port.Value;
|
||||
if (req.Host.Port.HasValue)
|
||||
uri.Port = req.Host.Port.Value;
|
||||
context.RouteContext.HttpContext.Response.Redirect(uri.ToString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (hasDomainMapping)
|
||||
{
|
||||
var matchedDomainMapping = mapping.FirstOrDefault(item =>
|
||||
var matchedDomainMapping = mapping.FirstOrDefault(item =>
|
||||
item.Domain.Equals(context.RouteContext.HttpContext.Request.Host.Host,
|
||||
StringComparison.InvariantCultureIgnoreCase));
|
||||
if (matchedDomainMapping != null)
|
||||
|
@ -16,8 +16,8 @@ public static class FormDataExtensions
|
||||
serviceCollection.AddSingleton<IFormComponentProvider, HtmlSelectFormProvider>();
|
||||
serviceCollection.AddSingleton<IFormComponentProvider, FieldValueMirror>();
|
||||
}
|
||||
|
||||
public static JObject Deserialize(this FormData form)
|
||||
|
||||
public static JObject Deserialize(this FormData form)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<JObject>(form.Config);
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ public class FormDataService
|
||||
private readonly FormComponentProviders _formProviders;
|
||||
|
||||
public FormDataService(
|
||||
ApplicationDbContextFactory applicationDbContextFactory,
|
||||
ApplicationDbContextFactory applicationDbContextFactory,
|
||||
FormComponentProviders formProviders)
|
||||
{
|
||||
_applicationDbContextFactory = applicationDbContextFactory;
|
||||
@ -47,16 +47,16 @@ public class FormDataService
|
||||
Field.Create("State", "buyerState", null, false, null),
|
||||
new SelectField()
|
||||
{
|
||||
Name = "buyerCountry",
|
||||
Name = "buyerCountry",
|
||||
Label = "Country",
|
||||
Required = true,
|
||||
Type = "select",
|
||||
Options = "Afghanistan, Albania, Algeria, Andorra, Angola, Antigua and Barbuda, Argentina, Armenia, Australia, Austria, Azerbaijan, The Bahamas, Bahrain, Bangladesh, Barbados, Belarus, Belgium, Belize, Benin, Bhutan, Bolivia, Bosnia and Herzegovina, Botswana, Brazil, Brunei, Bulgaria, Burkina Faso, Burundi, Cabo Verde, Cambodia, Cameroon, Canada, Central African Republic (CAR), Chad, Chile, China, Colombia, Comoros, Democratic Republic of the Congo, Republic of the Congo, Costa Rica, Cote d'Ivoire, Croatia, Cuba, Cyprus, Czech Republic, Denmark, Djibouti, Dominica, Dominican Republic, Ecuador, Egypt, El Salvador, Equatorial Guinea, Eritrea, Estonia, Eswatini (formerly Swaziland), Ethiopia, Fiji, Finland, France, Gabon, The Gambia, Georgia, Germany, Ghana, Greece, Grenada, Guatemala, Guinea, Guinea-Bissau, Guyana, Haiti, Honduras, Hungary, Iceland, India, Indonesia, Iran, Iraq, Ireland, Israel, Italy, Jamaica, Japan, Jordan, Kazakhstan, Kenya, Kiribati, Kosovo, Kuwait, Kyrgyzstan, Laos, Latvia, Lebanon, Lesotho, Liberia, Libya, Liechtenstein, Lithuania, Luxembourg, Madagascar, Malawi, Malaysia, Maldives, Mali, Malta, Marshall Islands, Mauritania, Mauritius, Mexico, Micronesia, Moldova, Monaco, Mongolia, Montenegro, Morocco, Mozambique, Myanmar (formerly Burma), Namibia, Nauru, Nepal, Netherlands, New Zealand, Nicaragua, Niger, Nigeria, North Korea, North Macedonia (formerly Macedonia), Norway, Oman, Pakistan, Palau, Palestine, Panama, Papua New Guinea, Paraguay, Peru, Philippines, Poland, Portugal, Qatar, Romania, Russia, Rwanda, Saint Kitts and Nevis, Saint Lucia, Saint Vincent and the Grenadines, Samoa, San Marino, Sao Tome and Principe, Saudi Arabia, Senegal, Serbia, Seychelles, Sierra Leone, Singapore, Slovakia, Slovenia, Solomon Islands, Somalia, South Africa, South Korea, South Sudan, Spain, Sri Lanka, Sudan, Suriname, Sweden, Switzerland, Syria, Taiwan, Tajikistan, Tanzania, Thailand, Timor-Leste (formerly East Timor), Togo, Tonga, Trinidad and Tobago, Tunisia, Turkey, Turkmenistan, Tuvalu, Uganda, Ukraine, United Arab Emirates (UAE), United Kingdom (UK), United States of America (USA), Uruguay, Uzbekistan, Vanuatu, Vatican City (Holy See), Venezuela, Vietnam, Yemen, Zambia, Zimbabwe.".Split(',').Select(s => new SelectListItem(s,s)).ToList()
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private static readonly Dictionary<string, (string selectText, string name, Form form)> _hardcodedOptions = new()
|
||||
{
|
||||
{"", ("Do not request any information", null, null)!},
|
||||
@ -64,13 +64,13 @@ public class FormDataService
|
||||
{"Address", ("Request shipping address", "Provide your address", StaticFormAddress)},
|
||||
};
|
||||
|
||||
public async Task<SelectList> GetSelect(string storeId ,string selectedFormId)
|
||||
public async Task<SelectList> GetSelect(string storeId, string selectedFormId)
|
||||
{
|
||||
var forms = await GetForms(storeId);
|
||||
return new SelectList(_hardcodedOptions.Select(pair => new SelectListItem(pair.Value.selectText, pair.Key, selectedFormId == pair.Key)).Concat(forms.Select(data => new SelectListItem(data.Name, data.Id, data.Id == selectedFormId))),
|
||||
nameof(SelectListItem.Value), nameof(SelectListItem.Text));
|
||||
}
|
||||
|
||||
|
||||
public async Task<List<FormData>> GetForms(string storeId)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(storeId);
|
||||
@ -129,7 +129,7 @@ public class FormDataService
|
||||
{
|
||||
return _formProviders.Validate(form, modelState);
|
||||
}
|
||||
|
||||
|
||||
public bool IsFormSchemaValid(string schema, [MaybeNullWhen(false)] out Form form, [MaybeNullWhen(false)] out string error)
|
||||
{
|
||||
error = null;
|
||||
@ -144,7 +144,7 @@ public class FormDataService
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = $"Form config was invalid: {ex.Message}";
|
||||
error = $"Form config was invalid: {ex.Message}";
|
||||
}
|
||||
return error is null && form is not null;
|
||||
}
|
||||
@ -177,7 +177,7 @@ public class FormDataService
|
||||
public JObject GetValues(Form form)
|
||||
{
|
||||
var r = new JObject();
|
||||
|
||||
|
||||
foreach (var f in form.GetAllFields())
|
||||
{
|
||||
var node = r;
|
||||
|
@ -12,7 +12,7 @@ public class FieldValueMirror : IFormComponentProvider
|
||||
{
|
||||
if (form.GetFieldByFullName(field.Value) is null)
|
||||
{
|
||||
field.ValidationErrors = new List<string>() {$"{field.Name} requires {field.Value} to be present"};
|
||||
field.ValidationErrors = new List<string>() { $"{field.Name} requires {field.Value} to be present" };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ public class HtmlSelectFormProvider : FormComponentProviderBase
|
||||
}
|
||||
}
|
||||
|
||||
public class SelectField: Field
|
||||
public class SelectField : Field
|
||||
{
|
||||
public List<SelectListItem> Options { get; set; }
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ public class ModifyForm
|
||||
|
||||
[DisplayName("Form configuration (JSON)")]
|
||||
public string FormConfig { get; set; }
|
||||
|
||||
|
||||
[DisplayName("Allow form for public use")]
|
||||
public bool Public { get; set; }
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ public class UIFormsController : Controller
|
||||
[HttpGet("~/stores/{storeId}/forms/new")]
|
||||
public IActionResult Create(string storeId)
|
||||
{
|
||||
var vm = new ModifyForm {FormConfig = new Form().ToString()};
|
||||
var vm = new ModifyForm { FormConfig = new Form().ToString() };
|
||||
return View("Modify", vm);
|
||||
}
|
||||
|
||||
@ -57,10 +57,11 @@ public class UIFormsController : Controller
|
||||
public async Task<IActionResult> Modify(string storeId, string id)
|
||||
{
|
||||
var form = await _formDataService.GetForm(storeId, id);
|
||||
if (form is null) return NotFound();
|
||||
if (form is null)
|
||||
return NotFound();
|
||||
|
||||
var config = Form.Parse(form.Config);
|
||||
return View(new ModifyForm {Name = form.Name, FormConfig = config.ToString(), Public = form.Public});
|
||||
return View(new ModifyForm { Name = form.Name, FormConfig = config.ToString(), Public = form.Public });
|
||||
}
|
||||
|
||||
[HttpPost("~/stores/{storeId}/forms/modify/{id?}")]
|
||||
@ -76,7 +77,7 @@ public class UIFormsController : Controller
|
||||
|
||||
if (!_formDataService.IsFormSchemaValid(modifyForm.FormConfig, out var form, out var error))
|
||||
{
|
||||
|
||||
|
||||
ModelState.AddModelError(nameof(modifyForm.FormConfig),
|
||||
$"Form config was invalid: {error})");
|
||||
}
|
||||
@ -84,7 +85,7 @@ public class UIFormsController : Controller
|
||||
{
|
||||
modifyForm.FormConfig = form.ToString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
@ -95,7 +96,11 @@ public class UIFormsController : Controller
|
||||
{
|
||||
var formData = new FormData
|
||||
{
|
||||
Id = id, StoreId = storeId, Name = modifyForm.Name, Config = modifyForm.FormConfig,Public = modifyForm.Public
|
||||
Id = id,
|
||||
StoreId = storeId,
|
||||
Name = modifyForm.Name,
|
||||
Config = modifyForm.FormConfig,
|
||||
Public = modifyForm.Public
|
||||
};
|
||||
var isNew = id is null;
|
||||
await _formDataService.AddOrUpdateForm(formData);
|
||||
@ -106,7 +111,7 @@ public class UIFormsController : Controller
|
||||
});
|
||||
if (isNew)
|
||||
{
|
||||
return RedirectToAction("Modify", new {storeId, id = formData.Id});
|
||||
return RedirectToAction("Modify", new { storeId, id = formData.Id });
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@ -123,9 +128,10 @@ public class UIFormsController : Controller
|
||||
await _formDataService.RemoveForm(id, storeId);
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Success, Message = "Form removed"
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
Message = "Form removed"
|
||||
});
|
||||
return RedirectToAction("FormsList", new {storeId});
|
||||
return RedirectToAction("FormsList", new { storeId });
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
@ -154,7 +160,7 @@ public class UIFormsController : Controller
|
||||
form.ApplyValuesFromForm(Request.Query);
|
||||
var store = formData.Store ?? await _storeRepository.FindStore(formData.StoreId);
|
||||
var storeBlob = store?.GetStoreBlob();
|
||||
|
||||
|
||||
return View("View", new FormViewModel
|
||||
{
|
||||
FormName = formData.Name,
|
||||
@ -187,7 +193,7 @@ public class UIFormsController : Controller
|
||||
|
||||
if (!Request.HasFormContentType)
|
||||
return await GetFormView(formData);
|
||||
|
||||
|
||||
var form = Form.Parse(formData.Config);
|
||||
form.ApplyValuesFromForm(Request.Form);
|
||||
|
||||
@ -202,6 +208,6 @@ public class UIFormsController : Controller
|
||||
var request = _formDataService.GenerateInvoiceParametersFromForm(form);
|
||||
var inv = await invoiceController.CreateInvoiceCoreRaw(request, store, Request.GetAbsoluteRoot());
|
||||
|
||||
return RedirectToAction("Checkout", "UIInvoice", new {invoiceId = inv.Id});
|
||||
return RedirectToAction("Checkout", "UIInvoice", new { invoiceId = inv.Id });
|
||||
}
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ namespace BTCPayServer.HostedServices
|
||||
o.Period = create.Period is TimeSpan period ? (long?)period.TotalSeconds : null;
|
||||
o.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(20));
|
||||
o.StoreId = create.StoreId;
|
||||
|
||||
|
||||
o.SetBlob(new PullPaymentBlob()
|
||||
{
|
||||
Name = create.Name ?? string.Empty,
|
||||
@ -203,7 +203,7 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
query = query.Include(data => data.StoreData);
|
||||
}
|
||||
|
||||
|
||||
if (payoutQuery.IncludePullPaymentData || !payoutQuery.IncludeArchived)
|
||||
{
|
||||
query = query.Include(data => data.PullPaymentData);
|
||||
|
@ -28,7 +28,7 @@ public class StoreEmailRuleProcessorSender : EventHostedServiceBase
|
||||
public StoreEmailRuleProcessorSender(StoreRepository storeRepository, EventAggregator eventAggregator,
|
||||
ILogger<InvoiceEventSaverService> logger,
|
||||
EmailSenderFactory emailSenderFactory,
|
||||
LinkGenerator linkGenerator,
|
||||
LinkGenerator linkGenerator,
|
||||
CurrencyNameTable currencyNameTable) : base(
|
||||
eventAggregator, logger)
|
||||
{
|
||||
|
@ -78,21 +78,21 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
await _walletRepository.EnsureWalletObjectLink(txWalletObject, walletObjectData.Key);
|
||||
//if the object is an address, we also link the labels to the tx
|
||||
if(walletObjectData.Value.Type == WalletObjectData.Types.Address)
|
||||
if (walletObjectData.Value.Type == WalletObjectData.Types.Address)
|
||||
{
|
||||
var neighbours = walletObjectData.Value.GetNeighbours().ToArray();
|
||||
var labels = neighbours
|
||||
.Where(data => data.Type == WalletObjectData.Types.Label).Select(data =>
|
||||
new WalletObjectId(walletObjectDatas.Key, data.Type, data.Id));
|
||||
foreach (var label in labels)
|
||||
{
|
||||
await _walletRepository.EnsureWalletObjectLink(label, txWalletObject);
|
||||
var attachments = neighbours.Where(data => data.Type == label.Id);
|
||||
foreach (var attachment in attachments)
|
||||
{
|
||||
await _walletRepository.EnsureWalletObjectLink(new WalletObjectId(walletObjectDatas.Key, attachment.Type, attachment.Id), txWalletObject);
|
||||
}
|
||||
}
|
||||
foreach (var label in labels)
|
||||
{
|
||||
await _walletRepository.EnsureWalletObjectLink(label, txWalletObject);
|
||||
var attachments = neighbours.Where(data => data.Type == label.Id);
|
||||
foreach (var attachment in attachments)
|
||||
{
|
||||
await _walletRepository.EnsureWalletObjectLink(new WalletObjectId(walletObjectDatas.Key, attachment.Type, attachment.Id), txWalletObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Configuration.Provider;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
@ -26,6 +27,8 @@ using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Payments.PayJoin;
|
||||
using BTCPayServer.PayoutProcessors;
|
||||
using BTCPayServer.Plugins;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Rating.Providers;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
using BTCPayServer.Security.Greenfield;
|
||||
@ -42,6 +45,7 @@ using BTCPayServer.Services.PaymentRequests;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using ExchangeSharp;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
@ -61,10 +65,6 @@ using NBXplorer.DerivationStrategy;
|
||||
using Newtonsoft.Json;
|
||||
using NicolasDorier.RateLimits;
|
||||
using Serilog;
|
||||
using ExchangeSharp;
|
||||
using BTCPayServer.Rating;
|
||||
using System.Configuration.Provider;
|
||||
using BTCPayServer.Rating.Providers;
|
||||
#if ALTCOINS
|
||||
using BTCPayServer.Services.Altcoins.Monero;
|
||||
using BTCPayServer.Services.Altcoins.Zcash;
|
||||
|
@ -11,12 +11,12 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
public CreateAppViewModel()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public CreateAppViewModel(AppService appService)
|
||||
{
|
||||
SetApps(appService);
|
||||
}
|
||||
|
||||
|
||||
[Required]
|
||||
[MaxLength(50)]
|
||||
[MinLength(1)]
|
||||
@ -37,7 +37,7 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
var defaultAppType = PointOfSaleAppType.AppType;
|
||||
var choices = appService.GetAvailableAppTypes().Select(pair =>
|
||||
new SelectListItem(pair.Value, pair.Key, pair.Key == defaultAppType));
|
||||
|
||||
|
||||
var chosen = choices.FirstOrDefault(f => f.Value == defaultAppType) ?? choices.FirstOrDefault();
|
||||
AppTypes = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Text), chosen);
|
||||
SelectedAppType = chosen.Value;
|
||||
|
@ -14,7 +14,7 @@ public class AssetBalanceInfo
|
||||
public string FormattedFiatValue { get; set; }
|
||||
public decimal? FiatValue { get; set; }
|
||||
public Dictionary<string, AssetPairData> TradableAssetPairs { get; set; }
|
||||
|
||||
|
||||
public List<string> WithdrawablePaymentMethods { get; set; } = new();
|
||||
public string FormattedBid { get; set; }
|
||||
public string FormattedAsk { get; set; }
|
||||
|
@ -8,6 +8,6 @@ namespace BTCPayServer.Models.CustodianAccountViewModels
|
||||
|
||||
public CustodianAccountData CustodianAccount { get; set; }
|
||||
public Form ConfigForm { get; set; }
|
||||
public string Config { get; set; }
|
||||
public string Config { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -5,5 +5,5 @@ namespace BTCPayServer.Models.CustodianAccountViewModels;
|
||||
public class TradePrepareViewModel : AssetQuoteResult
|
||||
{
|
||||
public decimal MaxQty { get; set; }
|
||||
|
||||
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ namespace BTCPayServer.Models
|
||||
public string FormUrl { get; set; }
|
||||
public bool AllowExternal { get; set; }
|
||||
|
||||
public MultiValueDictionary<string, string> FormParameters { get; set; } = new ();
|
||||
public Dictionary<string, string> RouteParameters { get; set; } = new ();
|
||||
public MultiValueDictionary<string, string> FormParameters { get; set; } = new();
|
||||
public Dictionary<string, string> RouteParameters { get; set; } = new();
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
|
||||
[Display(Name = "Show \"Pay in wallet\" button")]
|
||||
public bool ShowPayInWalletButton { get; set; }
|
||||
|
||||
|
||||
[Display(Name = "Show the store header")]
|
||||
public bool ShowStoreHeader { get; set; }
|
||||
|
||||
|
@ -14,10 +14,10 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||
public string Link { get; set; }
|
||||
public bool Positive { get; set; }
|
||||
public string Balance { get; set; }
|
||||
public HashSet<TransactionTagModel> Tags { get; set; } = new ();
|
||||
public HashSet<TransactionTagModel> Tags { get; set; } = new();
|
||||
}
|
||||
public HashSet<(string Text, string Color, string TextColor)> Labels { get; set; } = new ();
|
||||
public List<TransactionViewModel> Transactions { get; set; } = new ();
|
||||
public HashSet<(string Text, string Color, string TextColor)> Labels { get; set; } = new();
|
||||
public List<TransactionViewModel> Transactions { get; set; } = new();
|
||||
public override int CurrentPageCount => Transactions.Count;
|
||||
public string CryptoCode { get; set; }
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
if (lightningInfo is not null && !string.IsNullOrEmpty(lightningInfo.PaymentUrls?.BOLT11))
|
||||
{
|
||||
lightningFallback = lightningInfo.PaymentUrls.BOLT11;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var lnurlInfo = invoiceResponse.CryptoInfo.FirstOrDefault(a =>
|
||||
@ -84,7 +84,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
if (lnurlInfo is not null)
|
||||
{
|
||||
lightningFallback = lnurlInfo.PaymentUrls?.AdditionalData["LNURLP"].ToObject<string>();
|
||||
|
||||
|
||||
// This seems to be an edge case in the Selenium tests, in which the LNURLP isn't populated.
|
||||
// I have come across it only in the tests and this is supposed to make them happy.
|
||||
if (string.IsNullOrEmpty(lightningFallback))
|
||||
@ -122,7 +122,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
var delimiterUrl = model.InvoiceBitcoinUrl.Contains("?") ? "&" : "?";
|
||||
model.InvoiceBitcoinUrl += $"{delimiterUrl}{lightningFallback}";
|
||||
// model.InvoiceBitcoinUrl: bitcoin:bcrt1qxp2qa5dhn7?amount=0.00044007&lightning=lnbcrt440070n1...
|
||||
|
||||
|
||||
var delimiterUrlQR = model.InvoiceBitcoinUrlQR.Contains("?") ? "&" : "?";
|
||||
model.InvoiceBitcoinUrlQR += $"{delimiterUrlQR}{lightningFallback.ToUpperInvariant().Replace("LIGHTNING=", "lightning=", StringComparison.OrdinalIgnoreCase)}";
|
||||
// model.InvoiceBitcoinUrlQR: bitcoin:bcrt1qxp2qa5dhn7?amount=0.00044007&lightning=LNBCRT4400...
|
||||
@ -140,7 +140,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
{
|
||||
model.InvoiceBitcoinUrl = model.InvoiceBitcoinUrlQR = string.Empty;
|
||||
}
|
||||
|
||||
|
||||
if (model.Activated && amountInSats)
|
||||
{
|
||||
base.PreparePaymentModelForAmountInSats(model, paymentMethod, _displayFormatter);
|
||||
|
@ -100,11 +100,11 @@ namespace BTCPayServer.Payments
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public virtual void PreparePaymentModelForAmountInSats(PaymentModel model, IPaymentMethod paymentMethod, DisplayFormatter displayFormatter)
|
||||
{
|
||||
var satoshiCulture = new CultureInfo(CultureInfo.InvariantCulture.Name)
|
||||
{
|
||||
{
|
||||
NumberFormat = { NumberGroupSeparator = " " }
|
||||
};
|
||||
model.CryptoCode = "sats";
|
||||
|
@ -41,7 +41,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
public override PaymentType PaymentType => PaymentTypes.LightningLike;
|
||||
|
||||
private const string UriScheme = "lightning:";
|
||||
|
||||
|
||||
public IOptions<LightningNetworkOptions> Options { get; }
|
||||
|
||||
public override async Task<IPaymentMethodDetails> CreatePaymentMethodDetails(
|
||||
@ -121,13 +121,13 @@ namespace BTCPayServer.Payments.Lightning
|
||||
var network = _networkProvider.GetNetwork<BTCPayNetwork>(model.CryptoCode);
|
||||
var cryptoInfo = invoiceResponse.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId);
|
||||
var lnurl = cryptoInfo.PaymentUrls?.AdditionalData["LNURLP"].ToObject<string>();
|
||||
|
||||
|
||||
model.PaymentMethodName = GetPaymentMethodName(network);
|
||||
model.BtcAddress = lnurl?.Replace(UriScheme, "");
|
||||
model.InvoiceBitcoinUrl = lnurl;
|
||||
model.InvoiceBitcoinUrlQR = lnurl?.ToUpperInvariant().Replace(UriScheme.ToUpperInvariant(), UriScheme);
|
||||
model.PeerInfo = ((LNURLPayPaymentMethodDetails)paymentMethod.GetPaymentMethodDetails()).NodeInfo;
|
||||
|
||||
|
||||
if (storeBlob.LightningAmountInSatoshi && model.CryptoCode == "BTC")
|
||||
{
|
||||
base.PreparePaymentModelForAmountInSats(model, paymentMethod, _displayFormatter);
|
||||
|
@ -11,19 +11,19 @@ namespace BTCPayServer.Payments.Lightning
|
||||
{
|
||||
[JsonIgnore]
|
||||
public BTCPayNetworkBase Network { get; set; }
|
||||
|
||||
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
public LightMoney Amount { get; set; }
|
||||
|
||||
|
||||
public string BOLT11 { get; set; }
|
||||
|
||||
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.UInt256JsonConverter))]
|
||||
public uint256 PaymentHash { get; set; }
|
||||
|
||||
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.UInt256JsonConverter))]
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public uint256 Preimage { get; set; }
|
||||
|
||||
|
||||
public string PaymentType { get; set; }
|
||||
|
||||
public string GetDestination()
|
||||
|
@ -212,12 +212,12 @@ namespace BTCPayServer.Payments.Lightning
|
||||
var paymentMethodId = paymentMethod.GetId();
|
||||
var cryptoInfo = invoiceResponse.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId);
|
||||
var network = _networkProvider.GetNetwork<BTCPayNetwork>(model.CryptoCode);
|
||||
|
||||
|
||||
model.PaymentMethodName = GetPaymentMethodName(network);
|
||||
model.InvoiceBitcoinUrl = cryptoInfo.PaymentUrls?.BOLT11;
|
||||
model.InvoiceBitcoinUrlQR = $"lightning:{cryptoInfo.PaymentUrls?.BOLT11?.ToUpperInvariant()?.Substring("LIGHTNING:".Length)}";
|
||||
model.PeerInfo = ((LightningLikePaymentMethodDetails)paymentMethod.GetPaymentMethodDetails()).NodeInfo;
|
||||
|
||||
|
||||
if (storeBlob.LightningAmountInSatoshi && model.CryptoCode == "BTC")
|
||||
{
|
||||
base.PreparePaymentModelForAmountInSats(model, paymentMethod, _displayFormatter);
|
||||
|
@ -62,9 +62,9 @@ public abstract class BaseAutomatedPayoutProcessor<T> : BaseAsyncService where T
|
||||
var blob = GetBlob(_PayoutProcesserSettings);
|
||||
if (paymentMethod is not null)
|
||||
{
|
||||
|
||||
|
||||
// Allow plugins to do something before the automatic payouts are executed
|
||||
await _pluginHookService.ApplyFilter("before-automated-payout-processing",
|
||||
await _pluginHookService.ApplyFilter("before-automated-payout-processing",
|
||||
new BeforePayoutFilterData(store, paymentMethod));
|
||||
|
||||
await using var context = _applicationDbContextFactory.CreateContext();
|
||||
@ -80,9 +80,9 @@ public abstract class BaseAutomatedPayoutProcessor<T> : BaseAsyncService where T
|
||||
Logs.PayServer.LogInformation($"{payouts.Count} found to process. Starting (and after will sleep for {blob.Interval})");
|
||||
await Process(paymentMethod, payouts);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
|
||||
// Allow plugins do to something after automatic payout processing
|
||||
await _pluginHookService.ApplyFilter("after-automated-payout-processing",
|
||||
await _pluginHookService.ApplyFilter("after-automated-payout-processing",
|
||||
new AfterPayoutFilterData(store, paymentMethod, payouts));
|
||||
}
|
||||
}
|
||||
|
@ -403,7 +403,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var info = (ViewCrowdfundViewModel) await _app.GetInfo(app);
|
||||
var info = (ViewCrowdfundViewModel)await _app.GetInfo(app);
|
||||
info.HubPath = AppHub.GetHubPath(Request);
|
||||
info.SimpleDisplay = Request.Query.ContainsKey("simple");
|
||||
return info;
|
||||
|
@ -33,12 +33,12 @@ namespace BTCPayServer.Plugins.Crowdfund
|
||||
services.AddSingleton<IUIExtension>(new UIExtension("Crowdfund/NavExtension", "header-nav"));
|
||||
services.AddSingleton<CrowdfundAppType>();
|
||||
services.AddSingleton<AppBaseType, CrowdfundAppType>();
|
||||
|
||||
|
||||
base.Execute(services);
|
||||
}
|
||||
}
|
||||
|
||||
public class CrowdfundAppType: AppBaseType, IHasSaleStatsAppType, IHasItemStatsAppType
|
||||
|
||||
public class CrowdfundAppType : AppBaseType, IHasSaleStatsAppType, IHasItemStatsAppType
|
||||
{
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
private readonly IOptions<BTCPayServerOptions> _options;
|
||||
@ -146,7 +146,7 @@ namespace BTCPayServer.Plugins.Crowdfund
|
||||
}
|
||||
}
|
||||
|
||||
var invoices = await AppService.GetInvoicesForApp(_invoiceRepository,appData, lastResetDate);
|
||||
var invoices = await AppService.GetInvoicesForApp(_invoiceRepository, appData, lastResetDate);
|
||||
var completeInvoices = invoices.Where(IsComplete).ToArray();
|
||||
var pendingInvoices = invoices.Where(IsPending).ToArray();
|
||||
var paidInvoices = invoices.Where(IsPaid).ToArray();
|
||||
@ -256,7 +256,7 @@ namespace BTCPayServer.Plugins.Crowdfund
|
||||
public override Task<string> ViewLink(AppData app)
|
||||
{
|
||||
return Task.FromResult(_linkGenerator.GetPathByAction(nameof(UICrowdfundController.ViewCrowdfund),
|
||||
"UICrowdfund", new {appId = app.Id}, _options.Value.RootPath)!);
|
||||
"UICrowdfund", new { appId = app.Id }, _options.Value.RootPath)!);
|
||||
}
|
||||
|
||||
private static bool IsPaid(InvoiceEntity entity)
|
||||
|
@ -60,8 +60,8 @@ namespace BTCPayServer.Plugins.Crowdfund.Models
|
||||
public DateTime? LastResetDate { get; set; }
|
||||
public DateTime? NextResetDate { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public bool Started => !StartDate.HasValue || DateTime.UtcNow > StartDate;
|
||||
|
||||
|
@ -120,7 +120,7 @@ public class FakeCustodian : ICustodian, ICanDeposit, ICanWithdraw, ICanTrade
|
||||
if (ValidWithdrawalPaymentMethod.Equals(paymentMethod))
|
||||
{
|
||||
LedgerEntryData ledgerEntryWithdrawal = new(ValidAsset, -amount, LedgerEntryData.LedgerEntryType.Withdrawal);
|
||||
LedgerEntryData ledgerEntryFee = new(ValidAsset, - _btcWithdrawalFee, LedgerEntryData.LedgerEntryType.Fee);
|
||||
LedgerEntryData ledgerEntryFee = new(ValidAsset, -_btcWithdrawalFee, LedgerEntryData.LedgerEntryType.Fee);
|
||||
List<LedgerEntryData> ledgerEntries = new();
|
||||
ledgerEntries.Add(ledgerEntryWithdrawal);
|
||||
ledgerEntries.Add(ledgerEntryFee);
|
||||
|
@ -94,7 +94,7 @@ namespace BTCPayServer.Plugins.NFC
|
||||
var details = ex.InnerException?.Message ?? ex.Message;
|
||||
return BadRequest($"Could not fetch info from LNURL-Withdraw: {details}");
|
||||
}
|
||||
|
||||
|
||||
if (info?.Callback is null)
|
||||
{
|
||||
return BadRequest("Could not fetch info from LNURL-Withdraw");
|
||||
@ -127,7 +127,7 @@ namespace BTCPayServer.Plugins.NFC
|
||||
{
|
||||
due = new LightMoney(lnPaymentMethod.Calculate().Due);
|
||||
}
|
||||
|
||||
|
||||
if (info.MinWithdrawable > due || due > info.MaxWithdrawable)
|
||||
{
|
||||
return BadRequest("Invoice amount is not payable with the LNURL allowed amounts.");
|
||||
|
@ -7,18 +7,18 @@ namespace BTCPayServer.Plugins.NFC
|
||||
{
|
||||
public class NFCPlugin : BaseBTCPayServerPlugin
|
||||
{
|
||||
|
||||
|
||||
public override string Identifier => "BTCPayServer.Plugins.NFC";
|
||||
public override string Name => "NFC";
|
||||
public override string Description => "Allows you to support contactless card payments over NFC and LNURL Withdraw!";
|
||||
|
||||
|
||||
|
||||
public override void Execute(IServiceCollection applicationBuilder)
|
||||
{
|
||||
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("NFC/CheckoutEnd",
|
||||
"checkout-end"));
|
||||
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("NFC/LNURLNFCPostContent",
|
||||
"checkout-lightning-post-content"));
|
||||
"checkout-lightning-post-content"));
|
||||
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("NFC/CheckoutEnd",
|
||||
"checkout-v2-end"));
|
||||
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("NFC/LNURLNFCPostContent-v2",
|
||||
|
@ -59,7 +59,7 @@ namespace BTCPayServer.Plugins
|
||||
public async Task<PublishedVersion[]> GetPublishedVersions(string btcpayVersion, bool includePreRelease)
|
||||
{
|
||||
var queryString = $"?includePreRelease={includePreRelease}";
|
||||
if(btcpayVersion is not null)
|
||||
if (btcpayVersion is not null)
|
||||
queryString += $"&btcpayVersion={btcpayVersion}&";
|
||||
var result = await httpClient.GetStringAsync($"api/v1/plugins{queryString}");
|
||||
return JsonConvert.DeserializeObject<PublishedVersion[]>(result, serializerSettings) ?? throw new InvalidOperationException();
|
||||
|
@ -98,7 +98,7 @@ namespace BTCPayServer.Plugins
|
||||
// Formatted either as "<PLUGIN_IDENTIFIER>::<PathToDll>" or "<PathToDll>"
|
||||
var idx = plugin.IndexOf("::");
|
||||
if (idx != -1)
|
||||
pluginsToLoad.Add((plugin[0..idx], plugin[(idx+1)..]));
|
||||
pluginsToLoad.Add((plugin[0..idx], plugin[(idx + 1)..]));
|
||||
else
|
||||
pluginsToLoad.Add((Path.GetFileNameWithoutExtension(plugin), plugin));
|
||||
}
|
||||
@ -198,7 +198,7 @@ namespace BTCPayServer.Plugins
|
||||
if (ordersByPlugin.TryAdd(p.PluginIdentifier, order))
|
||||
order++;
|
||||
}
|
||||
pluginsToLoad.Sort((a,b) => ordersByPlugin[a.PluginIdentifier] - ordersByPlugin[b.PluginIdentifier]);
|
||||
pluginsToLoad.Sort((a, b) => ordersByPlugin[a.PluginIdentifier] - ordersByPlugin[b.PluginIdentifier]);
|
||||
}
|
||||
|
||||
public static void UsePlugins(this IApplicationBuilder applicationBuilder)
|
||||
|
@ -200,7 +200,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||
price = amount;
|
||||
title = settings.Title;
|
||||
//if cart IS enabled and we detect posdata that matches the cart system's, check inventory for the items
|
||||
|
||||
|
||||
if (currentView == PosViewType.Cart &&
|
||||
AppService.TryParsePosCartItems(jposData, out cartItems))
|
||||
{
|
||||
@ -242,7 +242,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||
var store = await _appService.GetStore(app);
|
||||
var posFormId = settings.FormId;
|
||||
var formData = await FormDataService.GetForm(posFormId);
|
||||
|
||||
|
||||
JObject formResponseJObject = null;
|
||||
switch (formData)
|
||||
{
|
||||
@ -308,7 +308,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||
var receiptData = new JObject();
|
||||
if (choice is not null)
|
||||
{
|
||||
receiptData = JObject.FromObject(new Dictionary<string, string>()
|
||||
receiptData = JObject.FromObject(new Dictionary<string, string>()
|
||||
{
|
||||
{"Title", choice.Title}, {"Description", choice.Description},
|
||||
});
|
||||
@ -337,19 +337,20 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||
if (appPosData.DiscountAmount > 0)
|
||||
{
|
||||
receiptData.Add("Discount",
|
||||
$"{_displayFormatter.Currency(appPosData.DiscountAmount, settings.Currency, DisplayFormatter.CurrencyFormat.Symbol) } {(appPosData.DiscountPercentage > 0 ? $"({appPosData.DiscountPercentage}%)" : string.Empty)}");
|
||||
$"{_displayFormatter.Currency(appPosData.DiscountAmount, settings.Currency, DisplayFormatter.CurrencyFormat.Symbol)} {(appPosData.DiscountPercentage > 0 ? $"({appPosData.DiscountPercentage}%)" : string.Empty)}");
|
||||
}
|
||||
|
||||
if (appPosData.Tip > 0)
|
||||
{
|
||||
receiptData.Add("Tip",
|
||||
$"{_displayFormatter.Currency(appPosData.Tip, settings.Currency, DisplayFormatter.CurrencyFormat.Symbol) }");
|
||||
$"{_displayFormatter.Currency(appPosData.Tip, settings.Currency, DisplayFormatter.CurrencyFormat.Symbol)}");
|
||||
}
|
||||
|
||||
}
|
||||
entity.Metadata.SetAdditionalData("receiptData", receiptData);
|
||||
|
||||
if (formResponseJObject is null) return;
|
||||
if (formResponseJObject is null)
|
||||
return;
|
||||
var meta = entity.Metadata.ToJObject();
|
||||
meta.Merge(formResponseJObject);
|
||||
entity.Metadata = InvoiceMetadata.FromJObject(meta);
|
||||
@ -388,14 +389,14 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||
var app = await _appService.GetApp(appId, PointOfSaleAppType.AppType);
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
|
||||
|
||||
var settings = app.GetSettings<PointOfSaleSettings>();
|
||||
var formData = await FormDataService.GetForm(settings.FormId);
|
||||
if (formData is null)
|
||||
{
|
||||
return RedirectToAction(nameof(ViewPointOfSale), new { appId, viewType });
|
||||
}
|
||||
|
||||
|
||||
var prefix = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16)) + "_";
|
||||
var formParameters = Request.Form
|
||||
.Where(pair => pair.Key != "__RequestVerificationToken")
|
||||
@ -422,7 +423,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||
{
|
||||
vm.RouteParameters.Add("viewType", viewType.Value.ToString());
|
||||
}
|
||||
|
||||
|
||||
return View("Views/UIForms/View", vm);
|
||||
}
|
||||
|
||||
@ -434,7 +435,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||
var app = await _appService.GetApp(appId, PointOfSaleAppType.AppType);
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
|
||||
|
||||
var settings = app.GetSettings<PointOfSaleSettings>();
|
||||
var formData = await FormDataService.GetForm(settings.FormId);
|
||||
if (formData is null)
|
||||
@ -447,16 +448,16 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||
.Where(pair => pair.Key.StartsWith(viewModel.FormParameterPrefix))
|
||||
.ToDictionary(pair => pair.Key.Replace(viewModel.FormParameterPrefix, string.Empty), pair => pair.Value)
|
||||
.ToMultiValueDictionary(p => p.Key, p => p.Value.ToString());
|
||||
|
||||
|
||||
if (Request is { Method: "POST", HasFormContentType: true })
|
||||
{
|
||||
form.ApplyValuesFromForm(Request.Form.Where(pair => formFieldNames.Contains(pair.Key)));
|
||||
|
||||
|
||||
if (FormDataService.Validate(form, ModelState))
|
||||
{
|
||||
var controller = nameof(UIPointOfSaleController).TrimEnd("Controller", StringComparison.InvariantCulture);
|
||||
var redirectUrl =
|
||||
Request.GetAbsoluteUri(Url.Action(nameof(ViewPointOfSale), controller, new {appId, viewType}));
|
||||
Request.GetAbsoluteUri(Url.Action(nameof(ViewPointOfSale), controller, new { appId, viewType }));
|
||||
formParameters.Add("formResponse", FormDataService.GetValues(form).ToString());
|
||||
return View("PostRedirect", new PostRedirectViewModel
|
||||
{
|
||||
|
@ -33,7 +33,7 @@ namespace BTCPayServer.Plugins.PointOfSale
|
||||
base.Execute(services);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum PosViewType
|
||||
{
|
||||
[Display(Name = "Product list")]
|
||||
@ -46,7 +46,7 @@ namespace BTCPayServer.Plugins.PointOfSale
|
||||
Print
|
||||
}
|
||||
|
||||
public class PointOfSaleAppType: AppBaseType, IHasSaleStatsAppType, IHasItemStatsAppType
|
||||
public class PointOfSaleAppType : AppBaseType, IHasSaleStatsAppType, IHasItemStatsAppType
|
||||
{
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
private readonly IOptions<BTCPayServerOptions> _btcPayServerOptions;
|
||||
|
@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace BTCPayServer
|
||||
|
@ -48,7 +48,7 @@ namespace BTCPayServer.Services.Apps
|
||||
StoreRepository storeRepository,
|
||||
HtmlSanitizer htmlSanitizer)
|
||||
{
|
||||
_appTypes = apps.ToDictionary(a => a.Type, a=> a);
|
||||
_appTypes = apps.ToDictionary(a => a.Type, a => a);
|
||||
_ContextFactory = contextFactory;
|
||||
_InvoiceRepository = invoiceRepository;
|
||||
_Currencies = currencies;
|
||||
@ -83,8 +83,8 @@ namespace BTCPayServer.Services.Apps
|
||||
{
|
||||
if (GetAppType(appData.AppType) is not IHasItemStatsAppType salesType)
|
||||
throw new InvalidOperationException("This app isn't a SalesAppBaseType");
|
||||
var paidInvoices = await GetInvoicesForApp(_InvoiceRepository,appData,
|
||||
null, new []
|
||||
var paidInvoices = await GetInvoicesForApp(_InvoiceRepository, appData,
|
||||
null, new[]
|
||||
{
|
||||
InvoiceState.ToString(InvoiceStatusLegacy.Paid),
|
||||
InvoiceState.ToString(InvoiceStatusLegacy.Confirmed),
|
||||
@ -94,7 +94,7 @@ namespace BTCPayServer.Services.Apps
|
||||
}
|
||||
|
||||
public static Task<SalesStats> GetSalesStatswithPOSItems(ViewPointOfSaleViewModel.Item[] items,
|
||||
InvoiceEntity[] paidInvoices, int numberOfDays)
|
||||
InvoiceEntity[] paidInvoices, int numberOfDays)
|
||||
{
|
||||
var series = paidInvoices
|
||||
.Aggregate(new List<InvoiceStatsItem>(), AggregateInvoiceEntitiesForStats(items))
|
||||
@ -126,19 +126,19 @@ namespace BTCPayServer.Services.Apps
|
||||
Series = series.OrderBy(i => i.Label)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public async Task<SalesStats> GetSalesStats(AppData app, int numberOfDays = 7)
|
||||
{
|
||||
if (GetAppType(app.AppType) is not IHasSaleStatsAppType salesType)
|
||||
throw new InvalidOperationException("This app isn't a SalesAppBaseType");
|
||||
var paidInvoices = await GetInvoicesForApp(_InvoiceRepository, app, DateTimeOffset.UtcNow - TimeSpan.FromDays(numberOfDays),
|
||||
new []
|
||||
new[]
|
||||
{
|
||||
InvoiceState.ToString(InvoiceStatusLegacy.Paid),
|
||||
InvoiceState.ToString(InvoiceStatusLegacy.Confirmed),
|
||||
InvoiceState.ToString(InvoiceStatusLegacy.Complete)
|
||||
});
|
||||
|
||||
|
||||
return await salesType.GetSalesStats(app, paidInvoices, numberOfDays);
|
||||
}
|
||||
|
||||
@ -195,7 +195,7 @@ namespace BTCPayServer.Services.Apps
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public static string GetAppOrderId(AppData app) => GetAppOrderId(app.AppType, app.Id);
|
||||
public static string GetAppOrderId(string appType, string appId) =>
|
||||
appType switch
|
||||
@ -211,13 +211,13 @@ namespace BTCPayServer.Services.Apps
|
||||
return invoice.GetInternalTags("APP#");
|
||||
}
|
||||
|
||||
public static async Task<InvoiceEntity[]> GetInvoicesForApp(InvoiceRepository invoiceRepository, AppData appData, DateTimeOffset? startDate = null, string[]? status = null)
|
||||
public static async Task<InvoiceEntity[]> GetInvoicesForApp(InvoiceRepository invoiceRepository, AppData appData, DateTimeOffset? startDate = null, string[]? status = null)
|
||||
{
|
||||
var invoices = await invoiceRepository.GetInvoices(new InvoiceQuery
|
||||
{
|
||||
StoreId = new[] { appData.StoreDataId },
|
||||
OrderId = appData.TagAllInvoices ? null : new[] { GetAppOrderId(appData) },
|
||||
Status = status?? new[]{
|
||||
Status = status ?? new[]{
|
||||
InvoiceState.ToString(InvoiceStatusLegacy.New),
|
||||
InvoiceState.ToString(InvoiceStatusLegacy.Paid),
|
||||
InvoiceState.ToString(InvoiceStatusLegacy.Confirmed),
|
||||
@ -270,7 +270,7 @@ namespace BTCPayServer.Services.Apps
|
||||
})
|
||||
.OrderBy(b => b.Created)
|
||||
.ToArrayAsync();
|
||||
|
||||
|
||||
// allowNoUser can lead to apps being included twice, unify them with distinct
|
||||
if (allowNoUser)
|
||||
{
|
||||
@ -295,7 +295,7 @@ namespace BTCPayServer.Services.Apps
|
||||
string posViewStyle = (settings.EnableShoppingCart ? PosViewType.Cart : settings.DefaultView).ToString();
|
||||
style = typeof(PosViewType).DisplayName(posViewStyle);
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
style = string.Empty;
|
||||
break;
|
||||
@ -386,7 +386,7 @@ namespace BTCPayServer.Services.Apps
|
||||
return serializer.Serialize(mappingNode);
|
||||
}
|
||||
|
||||
public ViewPointOfSaleViewModel.Item[] Parse( string template, string currency)
|
||||
public ViewPointOfSaleViewModel.Item[] Parse(string template, string currency)
|
||||
{
|
||||
return Parse(_HtmlSanitizer, _displayFormatter, template, currency);
|
||||
}
|
||||
@ -401,7 +401,7 @@ namespace BTCPayServer.Services.Apps
|
||||
if (string.IsNullOrWhiteSpace(template))
|
||||
return Array.Empty<ViewPointOfSaleViewModel.Item>();
|
||||
using var input = new StringReader(template);
|
||||
YamlStream stream = new ();
|
||||
YamlStream stream = new();
|
||||
stream.Load(input);
|
||||
var root = (YamlMappingNode)stream.Documents[0].RootNode;
|
||||
return root
|
||||
@ -410,7 +410,7 @@ namespace BTCPayServer.Services.Apps
|
||||
.Where(kv => kv.Value != null)
|
||||
.Select(c =>
|
||||
{
|
||||
ViewPointOfSaleViewModel.Item.ItemPrice price = new ();
|
||||
ViewPointOfSaleViewModel.Item.ItemPrice price = new();
|
||||
var pValue = c.GetDetail("price")?.FirstOrDefault();
|
||||
|
||||
switch (c.GetDetailString("custom") ?? c.GetDetailString("price_type")?.ToLowerInvariant())
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user