From c553dc02a9a74907dd311cfbc0e474f1bb59d41a Mon Sep 17 00:00:00 2001 From: Kukks Date: Mon, 23 Jan 2023 13:51:00 +0100 Subject: [PATCH 1/2] Greenfield: Expose Payment method criteria --- .../Models/PaymentMethodCriteriaData.cs | 13 +++++ BTCPayServer.Client/Models/StoreBaseData.cs | 3 ++ .../GreenField/GreenfieldStoresController.cs | 47 ++++++++++++++++++- .../swagger/v1/swagger.template.stores.json | 27 +++++++++++ 4 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 BTCPayServer.Client/Models/PaymentMethodCriteriaData.cs diff --git a/BTCPayServer.Client/Models/PaymentMethodCriteriaData.cs b/BTCPayServer.Client/Models/PaymentMethodCriteriaData.cs new file mode 100644 index 000000000..7e60d02c8 --- /dev/null +++ b/BTCPayServer.Client/Models/PaymentMethodCriteriaData.cs @@ -0,0 +1,13 @@ +using BTCPayServer.JsonConverters; +using Newtonsoft.Json; + +namespace BTCPayServer.Client.Models; + +public class PaymentMethodCriteriaData +{ + public string PaymentMethod { get; set; } + public string CurrencyCode { get; set; } + [JsonConverter(typeof(NumericStringJsonConverter))] + public decimal Amount { get; set; } + public bool Above { get; set; } +} diff --git a/BTCPayServer.Client/Models/StoreBaseData.cs b/BTCPayServer.Client/Models/StoreBaseData.cs index 5e012c1e0..d78b54e29 100644 --- a/BTCPayServer.Client/Models/StoreBaseData.cs +++ b/BTCPayServer.Client/Models/StoreBaseData.cs @@ -63,6 +63,9 @@ namespace BTCPayServer.Client.Models [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public NetworkFeeMode NetworkFeeMode { get; set; } = NetworkFeeMode.Never; + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public List PaymentMethodCriteria { get; set; } + public bool PayJoinEnabled { get; set; } public InvoiceData.ReceiptOptions Receipt { get; set; } diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldStoresController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldStoresController.cs index 3bdb1843d..15764b2dd 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldStoresController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldStoresController.cs @@ -9,6 +9,7 @@ using BTCPayServer.Client.Models; using BTCPayServer.Data; using BTCPayServer.Payments; using BTCPayServer.Security; +using BTCPayServer.Services.Rates; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; @@ -147,11 +148,18 @@ namespace BTCPayServer.Controllers.Greenfield AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice, LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate, PaymentTolerance = storeBlob.PaymentTolerance, - PayJoinEnabled = storeBlob.PayJoinEnabled + PayJoinEnabled = storeBlob.PayJoinEnabled, + 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() }; } - private void ToModel(StoreBaseData restModel, Data.StoreData model, PaymentMethodId defaultPaymentMethod) + private void ToModel(StoreBaseData restModel, StoreData model, PaymentMethodId defaultPaymentMethod) { var blob = model.GetStoreBlob(); model.StoreName = restModel.Name; @@ -188,6 +196,17 @@ namespace BTCPayServer.Controllers.Greenfield blob.LightningDescriptionTemplate = restModel.LightningDescriptionTemplate; blob.PaymentTolerance = restModel.PaymentTolerance; blob.PayJoinEnabled = restModel.PayJoinEnabled; + blob.PaymentMethodCriteria = restModel.PaymentMethodCriteria?.Select(criteria => + new PaymentMethodCriteria() + { + Above = criteria.Above, + Value = new CurrencyValue() + { + Currency = criteria.CurrencyCode, + Value = criteria.Amount + }, + PaymentMethod = PaymentMethodId.Parse(criteria.PaymentMethod) + }).ToList() ?? new List(); blob.NormalizeToRelativeLinks(Request); model.SetStoreBlob(blob); } @@ -222,6 +241,30 @@ namespace BTCPayServer.Controllers.Greenfield if (request.PaymentTolerance < 0 && request.PaymentTolerance > 100) ModelState.AddModelError(nameof(request.PaymentTolerance), "PaymentTolerance can only be between 0 and 100 percent"); + if (request.PaymentMethodCriteria?.Any() is true) + { + for (int index = 0; index < request.PaymentMethodCriteria.Count; index++) + { + PaymentMethodCriteriaData pmc = request.PaymentMethodCriteria[index]; + 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) + { + request.AddModelError(data => data.PaymentMethodCriteria[index].CurrencyCode, "CurrencyCode is invalid", this); + } + + if (string.IsNullOrEmpty(pmc.PaymentMethod) || PaymentMethodId.TryParse(pmc.PaymentMethod) is null) + { + request.AddModelError(data => data.PaymentMethodCriteria[index].PaymentMethod, "Payment method was invalid", this); + } + + if (pmc.Amount < 0) + { + request.AddModelError(data => data.PaymentMethodCriteria[index].Amount, "Amount must be greater than 0", this); + } + } + } return !ModelState.IsValid ? this.CreateValidationError(ModelState) : null; } diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores.json index faeaee965..40240fbd2 100644 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores.json +++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores.json @@ -281,6 +281,33 @@ } ] }, + "PaymentMethodCriteriaData": { + "type": "object", + "properties": { + "paymentMethod": { + "type": "string", + "description": "The payment method (e.g., \"BTC\" or \"BTC_LightningLike\")", + "nullable": false + }, + "currencyCode": { + "type": "string", + "description": "The currency", + "default": "USD", + "example": "USD" + }, + "amount": { + "type": "string", + "format": "decimal", + "minimum": 0, + "description": "The amount" + }, + "above": { + "type": "boolean", + "default": false, + "description": "If the criterion is for above or below the amount" + } + } + }, "StoreBaseData": { "type": "object", "x-abstract": true, From 8a3ece4a708a0f2b46dd4e87e9e9030496d18344 Mon Sep 17 00:00:00 2001 From: Kukks Date: Tue, 21 Feb 2023 15:31:11 +0100 Subject: [PATCH 2/2] add test --- BTCPayServer.Tests/GreenfieldAPITests.cs | 26 +++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/BTCPayServer.Tests/GreenfieldAPITests.cs b/BTCPayServer.Tests/GreenfieldAPITests.cs index 756c60713..025315093 100644 --- a/BTCPayServer.Tests/GreenfieldAPITests.cs +++ b/BTCPayServer.Tests/GreenfieldAPITests.cs @@ -1217,10 +1217,30 @@ namespace BTCPayServer.Tests var newStore = await client.CreateStore(new CreateStoreRequest() { Name = "A" }); //update store - var updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest() { Name = "B" }); + 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() + { + new() + { + Amount = 10, + Above = true, + PaymentMethod = "BTC", + CurrencyCode = "USD" + } + }}); Assert.Equal("B", updatedStore.Name); - Assert.Equal("B", (await client.GetStore(newStore.Id)).Name); - + var s = (await client.GetStore(newStore.Id)); + Assert.Equal("B", s.Name); + var pmc = Assert.Single(s.PaymentMethodCriteria); + //check that pmc equals the one we set + Assert.Equal(10, pmc.Amount); + Assert.True(pmc.Above); + Assert.Equal("BTC", pmc.PaymentMethod); + Assert.Equal("USD", pmc.CurrencyCode); + 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);