diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index cbd336ac5..62db109b8 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -1298,6 +1298,25 @@ namespace BTCPayServer.Tests Assert.True(invoice.SupportedTransactionCurrencies["LTC"].Enabled); Assert.True(invoice.PaymentSubtotals.ContainsKey("LTC")); Assert.True(invoice.PaymentTotals.ContainsKey("LTC")); + + + // Check if we can disable LTC + invoice = user.BitPay.CreateInvoice(new Invoice() + { + Price = 5000.0m, + Currency = "USD", + PosData = "posData", + OrderId = "orderId", + ItemDesc = "Some description", + FullNotifications = true, + SupportedTransactionCurrencies = new Dictionary() + { + { "BTC", new InvoiceSupportedTransactionCurrency() { Enabled = true } } + } + }, Facade.Merchant); + + Assert.Single(invoice.CryptoInfo.Where(c => c.CryptoCode == "BTC")); + Assert.Empty(invoice.CryptoInfo.Where(c => c.CryptoCode == "LTC")); } } diff --git a/BTCPayServer/Controllers/InvoiceController.cs b/BTCPayServer/Controllers/InvoiceController.cs index a9da7cae6..99bdeb31a 100644 --- a/BTCPayServer/Controllers/InvoiceController.cs +++ b/BTCPayServer/Controllers/InvoiceController.cs @@ -125,6 +125,17 @@ namespace BTCPayServer.Controllers HashSet currencyPairsToFetch = new HashSet(); var rules = storeBlob.GetRateRules(_NetworkProvider); var excludeFilter = storeBlob.GetExcludedPaymentMethods(); // Here we can compose filters from other origin with PaymentFilter.Any() + + if (invoice.SupportedTransactionCurrencies != null && invoice.SupportedTransactionCurrencies.Count != 0) + { + var supportedTransactionCurrencies = invoice.SupportedTransactionCurrencies + .Where(c => c.Value.Enabled) + .Select(c => PaymentMethodId.TryParse(c.Key, out var p) ? p : null) + .ToHashSet(); + excludeFilter = PaymentFilter.Or(excludeFilter, + PaymentFilter.Where(p => !supportedTransactionCurrencies.Contains(p))); + } + foreach (var network in store.GetSupportedPaymentMethods(_NetworkProvider) .Where(s => !excludeFilter.Match(s.PaymentId)) .Select(c => _NetworkProvider.GetNetwork(c.PaymentId.CryptoCode)) diff --git a/BTCPayServer/Models/CreateInvoiceRequest.cs b/BTCPayServer/Models/CreateInvoiceRequest.cs index bf7784158..acc6bf81b 100644 --- a/BTCPayServer/Models/CreateInvoiceRequest.cs +++ b/BTCPayServer/Models/CreateInvoiceRequest.cs @@ -70,7 +70,7 @@ namespace BTCPayServer.Models [JsonProperty(PropertyName = "exchangeRates", DefaultValueHandling = DefaultValueHandling.Ignore)] public Dictionary> ExchangeRates { get; set; } [JsonProperty(PropertyName = "refundable", DefaultValueHandling = DefaultValueHandling.Ignore)] - public bool Refundable { get; } + public bool Refundable { get; set; } [JsonProperty(PropertyName = "taxIncluded", DefaultValueHandling = DefaultValueHandling.Ignore)] public decimal TaxIncluded { get; set; } [JsonProperty(PropertyName = "nonce", DefaultValueHandling = DefaultValueHandling.Ignore)] diff --git a/BTCPayServer/Payments/IPaymentFilter.cs b/BTCPayServer/Payments/IPaymentFilter.cs index 64582b3e5..67f12f6e5 100644 --- a/BTCPayServer/Payments/IPaymentFilter.cs +++ b/BTCPayServer/Payments/IPaymentFilter.cs @@ -12,6 +12,21 @@ namespace BTCPayServer.Payments public class PaymentFilter { + class OrPaymentFilter : IPaymentFilter + { + private readonly IPaymentFilter _a; + private readonly IPaymentFilter _b; + + public OrPaymentFilter(IPaymentFilter a, IPaymentFilter b) + { + _a = a; + _b = b; + } + public bool Match(PaymentMethodId paymentMethodId) + { + return _a.Match(paymentMethodId) || _b.Match(paymentMethodId); + } + } class NeverPaymentFilter : IPaymentFilter { @@ -54,6 +69,34 @@ namespace BTCPayServer.Payments return paymentMethodId == _paymentMethodId; } } + class PredicateFilter : IPaymentFilter + { + private Func predicate; + + public PredicateFilter(Func predicate) + { + this.predicate = predicate; + } + + public bool Match(PaymentMethodId paymentMethodId) + { + return this.predicate(paymentMethodId); + } + } + public static IPaymentFilter Where(Func predicate) + { + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + return new PredicateFilter(predicate); + } + public static IPaymentFilter Or(IPaymentFilter a, IPaymentFilter b) + { + if (a == null) + throw new ArgumentNullException(nameof(a)); + if (b == null) + throw new ArgumentNullException(nameof(b)); + return new OrPaymentFilter(a, b); + } public static IPaymentFilter Never() { return NeverPaymentFilter.Instance; diff --git a/BTCPayServer/Payments/PaymentMethodId.cs b/BTCPayServer/Payments/PaymentMethodId.cs index aeabca006..7befcbf8f 100644 --- a/BTCPayServer/Payments/PaymentMethodId.cs +++ b/BTCPayServer/Payments/PaymentMethodId.cs @@ -67,10 +67,37 @@ namespace BTCPayServer.Payments return CryptoCode + "_" + PaymentType.ToString(); } + public static bool TryParse(string str, out PaymentMethodId paymentMethodId) + { + paymentMethodId = null; + var parts = str.Split('_', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 0 || parts.Length > 2) + return false; + PaymentTypes type = PaymentTypes.BTCLike; + if (parts.Length == 2) + { + switch (parts[1].ToLowerInvariant()) + { + case "btclike": + case "onchain": + type = PaymentTypes.BTCLike; + break; + case "lightninglike": + case "offchain": + type = PaymentTypes.LightningLike; + break; + default: + return false; + } + } + paymentMethodId = new PaymentMethodId(parts[0], type); + return true; + } public static PaymentMethodId Parse(string str) { - var parts = str.Split('_'); - return new PaymentMethodId(parts[0], parts.Length == 1 ? PaymentTypes.BTCLike : Enum.Parse(parts[1])); + if (!TryParse(str, out var result)) + throw new FormatException("Invalid PaymentMethodId"); + return result; } } }