Greenfield: add text search terms to an invoice (#2648)

This commit is contained in:
Nicolas Dorier
2021-07-14 23:32:20 +09:00
committed by GitHub
parent 15be593bbd
commit 73b461f8d0
8 changed files with 275 additions and 215 deletions

View File

@@ -14,6 +14,7 @@ namespace BTCPayServer.Client
public virtual async Task<IEnumerable<InvoiceData>> GetInvoices(string storeId, string orderId = null, InvoiceStatus[] status = null, public virtual async Task<IEnumerable<InvoiceData>> GetInvoices(string storeId, string orderId = null, InvoiceStatus[] status = null,
DateTimeOffset? startDate = null, DateTimeOffset? startDate = null,
DateTimeOffset? endDate = null, DateTimeOffset? endDate = null,
string textSearch = null,
bool includeArchived = false, bool includeArchived = false,
CancellationToken token = default) CancellationToken token = default)
{ {
@@ -28,7 +29,8 @@ namespace BTCPayServer.Client
if (orderId != null) if (orderId != null)
queryPayload.Add(nameof(orderId), orderId); queryPayload.Add(nameof(orderId), orderId);
if (textSearch != null)
queryPayload.Add(nameof(textSearch), textSearch);
if (status != null) if (status != null)
queryPayload.Add(nameof(status), status.Select(s=> s.ToString().ToLower()).ToArray()); queryPayload.Add(nameof(status), status.Select(s=> s.ToString().ToLower()).ToArray());

View File

@@ -7,35 +7,8 @@ using Newtonsoft.Json.Linq;
namespace BTCPayServer.Client.Models namespace BTCPayServer.Client.Models
{ {
public class CreateInvoiceRequest public class CreateInvoiceRequest : InvoiceDataBase
{ {
[JsonConverter(typeof(NumericStringJsonConverter))] public string[] AdditionalSearchTerms { get; set; }
public decimal Amount { get; set; }
public string Currency { get; set; }
public JObject Metadata { get; set; }
public CheckoutOptions Checkout { get; set; } = new CheckoutOptions();
public class CheckoutOptions
{
[JsonConverter(typeof(StringEnumConverter))]
public SpeedPolicy? SpeedPolicy { get; set; }
public string[] PaymentMethods { get; set; }
[JsonConverter(typeof(TimeSpanJsonConverter.Minutes))]
[JsonProperty("expirationMinutes")]
public TimeSpan? Expiration { get; set; }
[JsonConverter(typeof(TimeSpanJsonConverter.Minutes))]
[JsonProperty("monitoringMinutes")]
public TimeSpan? Monitoring { get; set; }
public double? PaymentTolerance { get; set; }
[JsonProperty("redirectURL")]
public string RedirectURL { get; set; }
public bool? RedirectAutomatically { get; set; }
public string DefaultLanguage { get; set; }
}
} }
} }

View File

@@ -1,10 +1,43 @@
using System; using System;
using BTCPayServer.Client.JsonConverters;
using BTCPayServer.JsonConverters;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Client.Models namespace BTCPayServer.Client.Models
{ {
public class InvoiceData : CreateInvoiceRequest public class InvoiceDataBase
{
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Amount { get; set; }
public string Currency { get; set; }
public JObject Metadata { get; set; }
public CheckoutOptions Checkout { get; set; } = new CheckoutOptions();
public class CheckoutOptions
{
[JsonConverter(typeof(StringEnumConverter))]
public SpeedPolicy? SpeedPolicy { get; set; }
public string[] PaymentMethods { get; set; }
[JsonConverter(typeof(TimeSpanJsonConverter.Minutes))]
[JsonProperty("expirationMinutes")]
public TimeSpan? Expiration { get; set; }
[JsonConverter(typeof(TimeSpanJsonConverter.Minutes))]
[JsonProperty("monitoringMinutes")]
public TimeSpan? Monitoring { get; set; }
public double? PaymentTolerance { get; set; }
[JsonProperty("redirectURL")]
public string RedirectURL { get; set; }
public bool? RedirectAutomatically { get; set; }
public string DefaultLanguage { get; set; }
}
}
public class InvoiceData : InvoiceDataBase
{ {
public string Id { get; set; } public string Id { get; set; }
public string StoreId { get; set; } public string StoreId { get; set; }

View File

@@ -1035,10 +1035,17 @@ namespace BTCPayServer.Tests
}); });
await user.RegisterDerivationSchemeAsync("BTC"); await user.RegisterDerivationSchemeAsync("BTC");
var newInvoice = await client.CreateInvoice(user.StoreId, var newInvoice = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest() { Currency = "USD", Amount = 1, Metadata = JObject.Parse("{\"itemCode\": \"testitem\", \"orderId\": \"testOrder\"}"), Checkout = new CreateInvoiceRequest.CheckoutOptions() new CreateInvoiceRequest()
{
Currency = "USD",
Amount = 1,
Metadata = JObject.Parse("{\"itemCode\": \"testitem\", \"orderId\": \"testOrder\"}"),
Checkout = new CreateInvoiceRequest.CheckoutOptions()
{ {
RedirectAutomatically = true RedirectAutomatically = true
}}); },
AdditionalSearchTerms = new string[] { "Banana" }
});
Assert.True(newInvoice.Checkout.RedirectAutomatically); Assert.True(newInvoice.Checkout.RedirectAutomatically);
Assert.Equal(user.StoreId, newInvoice.StoreId); Assert.Equal(user.StoreId, newInvoice.StoreId);
//list //list
@@ -1048,6 +1055,15 @@ namespace BTCPayServer.Tests
Assert.Single(invoices); Assert.Single(invoices);
Assert.Equal(newInvoice.Id, invoices.First().Id); Assert.Equal(newInvoice.Id, invoices.First().Id);
invoices = await viewOnly.GetInvoices(user.StoreId, textSearch: "Banana");
Assert.NotNull(invoices);
Assert.Single(invoices);
Assert.Equal(newInvoice.Id, invoices.First().Id);
invoices = await viewOnly.GetInvoices(user.StoreId, textSearch: "apples");
Assert.NotNull(invoices);
Assert.Empty(invoices);
//list Filtered //list Filtered
var invoicesFiltered = await viewOnly.GetInvoices(user.StoreId, var invoicesFiltered = await viewOnly.GetInvoices(user.StoreId,
orderId: null, status: null, DateTimeOffset.Now.AddHours(-1), orderId: null, status: null, DateTimeOffset.Now.AddHours(-1),
@@ -1092,14 +1108,14 @@ namespace BTCPayServer.Tests
//list Existing Status //list Existing Status
var invoicesExistingStatus = var invoicesExistingStatus =
await viewOnly.GetInvoices(user.StoreId, status:new []{newInvoice.Status}); await viewOnly.GetInvoices(user.StoreId, status: new[] { newInvoice.Status });
Assert.NotNull(invoicesExistingStatus); Assert.NotNull(invoicesExistingStatus);
Assert.Single(invoicesExistingStatus); Assert.Single(invoicesExistingStatus);
Assert.Equal(newInvoice.Id, invoicesExistingStatus.First().Id); Assert.Equal(newInvoice.Id, invoicesExistingStatus.First().Id);
//list NonExisting Status //list NonExisting Status
var invoicesNonExistingStatus = await viewOnly.GetInvoices(user.StoreId, var invoicesNonExistingStatus = await viewOnly.GetInvoices(user.StoreId,
status: new []{BTCPayServer.Client.Models.InvoiceStatus.Invalid}); status: new[] { BTCPayServer.Client.Models.InvoiceStatus.Invalid });
Assert.NotNull(invoicesNonExistingStatus); Assert.NotNull(invoicesNonExistingStatus);
Assert.Empty(invoicesNonExistingStatus); Assert.Empty(invoicesNonExistingStatus);
@@ -1142,13 +1158,13 @@ namespace BTCPayServer.Tests
Metadata = JObject.Parse("{\"itemCode\": \"updated\", newstuff: [1,2,3,4,5]}") Metadata = JObject.Parse("{\"itemCode\": \"updated\", newstuff: [1,2,3,4,5]}")
}); });
Assert.Equal("updated",invoice.Metadata["itemCode"].Value<string>()); Assert.Equal("updated", invoice.Metadata["itemCode"].Value<string>());
Assert.Equal(15,((JArray) invoice.Metadata["newstuff"]).Values<int>().Sum()); Assert.Equal(15, ((JArray)invoice.Metadata["newstuff"]).Values<int>().Sum());
//also test the the metadata actually got saved //also test the the metadata actually got saved
invoice = await client.GetInvoice(user.StoreId, invoice.Id); invoice = await client.GetInvoice(user.StoreId, invoice.Id);
Assert.Equal("updated",invoice.Metadata["itemCode"].Value<string>()); Assert.Equal("updated", invoice.Metadata["itemCode"].Value<string>());
Assert.Equal(15,((JArray) invoice.Metadata["newstuff"]).Values<int>().Sum()); Assert.Equal(15, ((JArray)invoice.Metadata["newstuff"]).Values<int>().Sum());
//archive //archive
await AssertHttpError(403, async () => await AssertHttpError(403, async () =>
@@ -1242,7 +1258,7 @@ namespace BTCPayServer.Tests
JObject.FromObject(store).ToObject<UpdateStoreRequest>()); JObject.FromObject(store).ToObject<UpdateStoreRequest>());
Assert.True(store.LazyPaymentMethods); Assert.True(store.LazyPaymentMethods);
invoice = await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest() {Amount = 1, Currency = "USD"}); invoice = await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest() { Amount = 1, Currency = "USD" });
paymentMethods = await client.GetInvoicePaymentMethods(store.Id, invoice.Id); paymentMethods = await client.GetInvoicePaymentMethods(store.Id, invoice.Id);
Assert.Single(paymentMethods); Assert.Single(paymentMethods);
Assert.False(paymentMethods.First().Activated); Assert.False(paymentMethods.First().Activated);
@@ -1393,7 +1409,7 @@ namespace BTCPayServer.Tests
var client = await user.CreateClient(Policies.CanModifyStoreSettings); var client = await user.CreateClient(Policies.CanModifyStoreSettings);
var viewOnlyClient = await user.CreateClient(Policies.CanViewStoreSettings); var viewOnlyClient = await user.CreateClient(Policies.CanViewStoreSettings);
var store = await client.CreateStore(new CreateStoreRequest() {Name = "test store"}); var store = await client.CreateStore(new CreateStoreRequest() { Name = "test store" });
Assert.Empty(await client.GetStoreOnChainPaymentMethods(store.Id)); Assert.Empty(await client.GetStoreOnChainPaymentMethods(store.Id));
await AssertHttpError(403, async () => await AssertHttpError(403, async () =>
@@ -1410,12 +1426,12 @@ namespace BTCPayServer.Tests
}); });
Assert.Equal(firstAddress, (await viewOnlyClient.PreviewProposedStoreOnChainPaymentMethodAddresses(store.Id, "BTC", Assert.Equal(firstAddress, (await viewOnlyClient.PreviewProposedStoreOnChainPaymentMethodAddresses(store.Id, "BTC",
new OnChainPaymentMethodData() {Enabled = true, DerivationScheme = xpub})).Addresses.First().Address); new OnChainPaymentMethodData() { Enabled = true, DerivationScheme = xpub })).Addresses.First().Address);
var method = await client.UpdateStoreOnChainPaymentMethod(store.Id, "BTC", var method = await client.UpdateStoreOnChainPaymentMethod(store.Id, "BTC",
new OnChainPaymentMethodData() { Enabled = true, DerivationScheme = xpub}); new OnChainPaymentMethodData() { Enabled = true, DerivationScheme = xpub });
Assert.Equal(xpub,method.DerivationScheme); Assert.Equal(xpub, method.DerivationScheme);
method = await client.UpdateStoreOnChainPaymentMethod(store.Id, "BTC", method = await client.UpdateStoreOnChainPaymentMethod(store.Id, "BTC",
new OnChainPaymentMethodData() { Enabled = true, DerivationScheme = xpub, Label = "lol", AccountKeyPath = RootedKeyPath.Parse("01020304/1/2/3") }); new OnChainPaymentMethodData() { Enabled = true, DerivationScheme = xpub, Label = "lol", AccountKeyPath = RootedKeyPath.Parse("01020304/1/2/3") });
@@ -1424,7 +1440,7 @@ namespace BTCPayServer.Tests
Assert.Equal("lol", method.Label); Assert.Equal("lol", method.Label);
Assert.Equal(RootedKeyPath.Parse("01020304/1/2/3"), method.AccountKeyPath); Assert.Equal(RootedKeyPath.Parse("01020304/1/2/3"), method.AccountKeyPath);
Assert.Equal(xpub,method.DerivationScheme); Assert.Equal(xpub, method.DerivationScheme);
Assert.Equal(firstAddress, (await viewOnlyClient.PreviewStoreOnChainPaymentMethodAddresses(store.Id, "BTC")).Addresses.First().Address); Assert.Equal(firstAddress, (await viewOnlyClient.PreviewStoreOnChainPaymentMethodAddresses(store.Id, "BTC")).Addresses.First().Address);
@@ -1530,12 +1546,12 @@ namespace BTCPayServer.Tests
}); });
}); });
var settings = (await tester.PayTester.GetService<SettingsRepository>().GetSettingAsync<PoliciesSettings>())?? new PoliciesSettings(); var settings = (await tester.PayTester.GetService<SettingsRepository>().GetSettingAsync<PoliciesSettings>()) ?? new PoliciesSettings();
settings.AllowLightningInternalNodeForAll = false; settings.AllowLightningInternalNodeForAll = false;
await tester.PayTester.GetService<SettingsRepository>().UpdateSetting(settings); await tester.PayTester.GetService<SettingsRepository>().UpdateSetting(settings);
var nonAdminUser = tester.NewAccount(); var nonAdminUser = tester.NewAccount();
await nonAdminUser.GrantAccessAsync(false); await nonAdminUser.GrantAccessAsync(false);
var nonAdminUserClient= await nonAdminUser.CreateClient(Policies.CanModifyStoreSettings); var nonAdminUserClient = await nonAdminUser.CreateClient(Policies.CanModifyStoreSettings);
await AssertHttpError(404, async () => await AssertHttpError(404, async () =>
{ {
@@ -1570,22 +1586,22 @@ namespace BTCPayServer.Tests
//view only clients can't do jack shit with this API //view only clients can't do jack shit with this API
await AssertHttpError(403, async () => await AssertHttpError(403, async () =>
{ {
await viewOnlyClient.ShowOnChainWalletOverview(walletId.StoreId, walletId.CryptoCode ); await viewOnlyClient.ShowOnChainWalletOverview(walletId.StoreId, walletId.CryptoCode);
}); });
var overview = await client.ShowOnChainWalletOverview(walletId.StoreId, walletId.CryptoCode ); var overview = await client.ShowOnChainWalletOverview(walletId.StoreId, walletId.CryptoCode);
Assert.Equal(0m, overview.Balance); Assert.Equal(0m, overview.Balance);
var fee = await client.GetOnChainFeeRate(walletId.StoreId, walletId.CryptoCode ); var fee = await client.GetOnChainFeeRate(walletId.StoreId, walletId.CryptoCode);
Assert.NotNull( fee.FeeRate); Assert.NotNull(fee.FeeRate);
await AssertHttpError(403, async () => await AssertHttpError(403, async () =>
{ {
await viewOnlyClient.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode ); await viewOnlyClient.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode);
}); });
var address = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode ); var address = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode);
var address2 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode ); var address2 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode);
var address3 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode, true ); var address3 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode, true);
Assert.Equal(address.Address, address2.Address); Assert.Equal(address.Address, address2.Address);
Assert.NotEqual(address.Address, address3.Address); Assert.NotEqual(address.Address, address3.Address);
await AssertHttpError(403, async () => await AssertHttpError(403, async () =>
@@ -1602,17 +1618,17 @@ namespace BTCPayServer.Tests
}); });
await tester.ExplorerNode.GenerateAsync(1); await tester.ExplorerNode.GenerateAsync(1);
var address4 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode, false ); var address4 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode, false);
Assert.NotEqual(address3.Address, address4.Address); Assert.NotEqual(address3.Address, address4.Address);
await client.UnReserveOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode); await client.UnReserveOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode);
var address5 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode, true ); var address5 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode, true);
Assert.Equal(address5.Address, address4.Address); Assert.Equal(address5.Address, address4.Address);
var utxo = Assert.Single(await client.GetOnChainWalletUTXOs(walletId.StoreId, walletId.CryptoCode)); var utxo = Assert.Single(await client.GetOnChainWalletUTXOs(walletId.StoreId, walletId.CryptoCode));
Assert.Equal(0.01m, utxo.Amount); Assert.Equal(0.01m, utxo.Amount);
Assert.Equal(txhash, utxo.Outpoint.Hash); Assert.Equal(txhash, utxo.Outpoint.Hash);
overview = await client.ShowOnChainWalletOverview(walletId.StoreId, walletId.CryptoCode ); overview = await client.ShowOnChainWalletOverview(walletId.StoreId, walletId.CryptoCode);
Assert.Equal(0.01m, overview.Balance); Assert.Equal(0.01m, overview.Balance);
//the simplest request: //the simplest request:
@@ -1631,7 +1647,7 @@ namespace BTCPayServer.Tests
}; };
await AssertHttpError(403, async () => await AssertHttpError(403, async () =>
{ {
await viewOnlyClient.CreateOnChainTransaction(walletId.StoreId, walletId.CryptoCode, createTxRequest ); await viewOnlyClient.CreateOnChainTransaction(walletId.StoreId, walletId.CryptoCode, createTxRequest);
}); });
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () =>
{ {
@@ -1659,12 +1675,12 @@ namespace BTCPayServer.Tests
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode, tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork); createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
Assert.NotNull(tx); Assert.NotNull(tx);
Assert.True(Assert.Single(tx.Outputs).IsTo(nodeAddress) ); Assert.True(Assert.Single(tx.Outputs).IsTo(nodeAddress));
Assert.True((await tester.ExplorerNode.TestMempoolAcceptAsync(tx)).IsAllowed); Assert.True((await tester.ExplorerNode.TestMempoolAcceptAsync(tx)).IsAllowed);
createTxRequest.NoChange = false; createTxRequest.NoChange = false;
//coin selection //coin selection
await AssertValidationError(new []{nameof(createTxRequest.SelectedInputs)}, async () => await AssertValidationError(new[] { nameof(createTxRequest.SelectedInputs) }, async () =>
{ {
createTxRequest.SelectedInputs = new List<OutPoint>(); createTxRequest.SelectedInputs = new List<OutPoint>();
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode, tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
@@ -1679,7 +1695,7 @@ namespace BTCPayServer.Tests
createTxRequest.SelectedInputs = null; createTxRequest.SelectedInputs = null;
//destination testing //destination testing
await AssertValidationError(new []{ "Destinations"}, async () => await AssertValidationError(new[] { "Destinations" }, async () =>
{ {
createTxRequest.Destinations[0].Amount = utxo.Amount; createTxRequest.Destinations[0].Amount = utxo.Amount;
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode, tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
@@ -1691,7 +1707,7 @@ namespace BTCPayServer.Tests
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork); createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
await AssertValidationError(new []{ "Destinations[0]"}, async () => await AssertValidationError(new[] { "Destinations[0]" }, async () =>
{ {
createTxRequest.Destinations[0].Amount = 0m; createTxRequest.Destinations[0].Amount = 0m;
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode, tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
@@ -1703,7 +1719,7 @@ namespace BTCPayServer.Tests
//cant use bip with subtractfromamount //cant use bip with subtractfromamount
createTxRequest.Destinations[0].Amount = null; createTxRequest.Destinations[0].Amount = null;
createTxRequest.Destinations[0].Destination = $"bitcoin:{nodeAddress}?amount=0.001"; createTxRequest.Destinations[0].Destination = $"bitcoin:{nodeAddress}?amount=0.001";
await AssertValidationError(new []{ "Destinations[0]"}, async () => await AssertValidationError(new[] { "Destinations[0]" }, async () =>
{ {
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode, tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork); createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
@@ -1713,11 +1729,11 @@ namespace BTCPayServer.Tests
createTxRequest.Destinations[0].SubtractFromAmount = false; createTxRequest.Destinations[0].SubtractFromAmount = false;
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode, tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork); createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
Assert.Contains(tx.Outputs, txout => txout.Value.GetValue(tester.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC")) ==0.0001m ); Assert.Contains(tx.Outputs, txout => txout.Value.GetValue(tester.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC")) == 0.0001m);
//fee rate test //fee rate test
createTxRequest.FeeRate = FeeRate.Zero; createTxRequest.FeeRate = FeeRate.Zero;
await AssertValidationError(new []{ "FeeRate"}, async () => await AssertValidationError(new[] { "FeeRate" }, async () =>
{ {
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode, tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork); createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
@@ -1735,7 +1751,7 @@ namespace BTCPayServer.Tests
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork); createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
}); });
createTxRequest.ProceedWithBroadcast = true; createTxRequest.ProceedWithBroadcast = true;
var txdata= var txdata =
await client.CreateOnChainTransaction(walletId.StoreId, walletId.CryptoCode, await client.CreateOnChainTransaction(walletId.StoreId, walletId.CryptoCode,
createTxRequest); createTxRequest);
Assert.Equal(TransactionStatus.Unconfirmed, txdata.Status); Assert.Equal(TransactionStatus.Unconfirmed, txdata.Status);
@@ -1755,10 +1771,10 @@ namespace BTCPayServer.Tests
}); });
Assert.True(Assert.Single( Assert.True(Assert.Single(
await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode, await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode,
new[] {TransactionStatus.Confirmed})).TransactionHash == utxo.Outpoint.Hash); new[] { TransactionStatus.Confirmed })).TransactionHash == utxo.Outpoint.Hash);
Assert.Contains( Assert.Contains(
await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode, await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode,
new[] {TransactionStatus.Unconfirmed}), data => data.TransactionHash == txdata.TransactionHash); new[] { TransactionStatus.Unconfirmed }), data => data.TransactionHash == txdata.TransactionHash);
Assert.Contains( Assert.Contains(
await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode), data => data.TransactionHash == txdata.TransactionHash); await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode), data => data.TransactionHash == txdata.TransactionHash);
await tester.WaitForEvent<NewBlockEvent>(async () => await tester.WaitForEvent<NewBlockEvent>(async () =>
@@ -1769,7 +1785,7 @@ namespace BTCPayServer.Tests
Assert.Contains( Assert.Contains(
await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode, await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode,
new[] {TransactionStatus.Confirmed}), data => data.TransactionHash == txdata.TransactionHash); new[] { TransactionStatus.Confirmed }), data => data.TransactionHash == txdata.TransactionHash);
} }

View File

@@ -54,7 +54,9 @@ namespace BTCPayServer.Controllers.GreenField
DateTimeOffset? startDate = null, DateTimeOffset? startDate = null,
[FromQuery] [FromQuery]
[ModelBinder(typeof(ModelBinders.DateTimeOffsetModelBinder))] [ModelBinder(typeof(ModelBinders.DateTimeOffsetModelBinder))]
DateTimeOffset? endDate = null, [FromQuery] bool includeArchived = false) DateTimeOffset? endDate = null,
string textSearch = null,
[FromQuery] bool includeArchived = false)
{ {
var store = HttpContext.GetStoreData(); var store = HttpContext.GetStoreData();
if (store == null) if (store == null)
@@ -79,7 +81,8 @@ namespace BTCPayServer.Controllers.GreenField
StartDate = startDate, StartDate = startDate,
EndDate = endDate, EndDate = endDate,
OrderId = orderId, OrderId = orderId,
Status = status Status = status,
TextSearch = textSearch
}); });
return Ok(invoices.Select(ToModel)); return Ok(invoices.Select(ToModel));

View File

@@ -153,7 +153,7 @@ namespace BTCPayServer.Controllers
excludeFilter = PaymentFilter.Where(p => !supportedTransactionCurrencies.Contains(p)); excludeFilter = PaymentFilter.Where(p => !supportedTransactionCurrencies.Contains(p));
} }
entity.PaymentTolerance = storeBlob.PaymentTolerance; entity.PaymentTolerance = storeBlob.PaymentTolerance;
return await CreateInvoiceCoreRaw(entity, store, excludeFilter, cancellationToken); return await CreateInvoiceCoreRaw(entity, store, excludeFilter, null, cancellationToken);
} }
internal async Task<InvoiceEntity> CreateInvoiceCoreRaw(CreateInvoiceRequest invoice, StoreData store, string serverUrl, List<string> additionalTags = null, CancellationToken cancellationToken = default) internal async Task<InvoiceEntity> CreateInvoiceCoreRaw(CreateInvoiceRequest invoice, StoreData store, string serverUrl, List<string> additionalTags = null, CancellationToken cancellationToken = default)
@@ -183,10 +183,10 @@ namespace BTCPayServer.Controllers
entity.RedirectURLTemplate = invoice.Checkout.RedirectURL?.Trim(); entity.RedirectURLTemplate = invoice.Checkout.RedirectURL?.Trim();
if (additionalTags != null) if (additionalTags != null)
entity.InternalTags.AddRange(additionalTags); entity.InternalTags.AddRange(additionalTags);
return await CreateInvoiceCoreRaw(entity, store, excludeFilter, cancellationToken); return await CreateInvoiceCoreRaw(entity, store, excludeFilter, invoice.AdditionalSearchTerms, cancellationToken);
} }
internal async Task<InvoiceEntity> CreateInvoiceCoreRaw(InvoiceEntity entity, StoreData store, IPaymentFilter invoicePaymentMethodFilter, CancellationToken cancellationToken = default) internal async Task<InvoiceEntity> CreateInvoiceCoreRaw(InvoiceEntity entity, StoreData store, IPaymentFilter invoicePaymentMethodFilter, string[] additionalSearchTerms = null, CancellationToken cancellationToken = default)
{ {
InvoiceLogs logs = new InvoiceLogs(); InvoiceLogs logs = new InvoiceLogs();
logs.Write("Creation of invoice starting", InvoiceEventData.EventSeverity.Info); logs.Write("Creation of invoice starting", InvoiceEventData.EventSeverity.Info);
@@ -273,7 +273,7 @@ namespace BTCPayServer.Controllers
using (logs.Measure("Saving invoice")) using (logs.Measure("Saving invoice"))
{ {
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity); entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, additionalSearchTerms);
} }
_ = Task.Run(async () => _ = Task.Run(async () =>
{ {

View File

@@ -148,9 +148,9 @@ namespace BTCPayServer.Services.Invoices
} }
} }
public async Task<InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice) public async Task<InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice, string[] additionalSearchTerms = null)
{ {
var textSearch = new List<string>(); var textSearch = new HashSet<string>();
invoice = Clone(invoice); invoice = Clone(invoice);
invoice.Networks = _Networks; invoice.Networks = _Networks;
invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16)); invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
@@ -210,6 +210,11 @@ namespace BTCPayServer.Services.Invoices
textSearch.Add(invoice.Metadata.OrderId); textSearch.Add(invoice.Metadata.OrderId);
textSearch.Add(invoice.StoreId); textSearch.Add(invoice.StoreId);
textSearch.Add(invoice.Metadata.BuyerEmail); textSearch.Add(invoice.Metadata.BuyerEmail);
if (additionalSearchTerms != null)
{
textSearch.AddRange(additionalSearchTerms);
}
AddToTextSearch(context, invoiceData, textSearch.ToArray()); AddToTextSearch(context, invoiceData, textSearch.ToArray());
await context.SaveChangesAsync().ConfigureAwait(false); await context.SaveChangesAsync().ConfigureAwait(false);

View File

@@ -35,6 +35,15 @@
"description": "Array of statuses of invoices to be fetched", "description": "Array of statuses of invoices to be fetched",
"$ref": "#/components/schemas/InvoiceStatus" "$ref": "#/components/schemas/InvoiceStatus"
}, },
{
"name": "textSearch",
"in": "query",
"required": false,
"description": "A term that can help locating specific invoices.",
"schema": {
"type": "string"
}
},
{ {
"name": "startDate", "name": "startDate",
"in": "query", "in": "query",
@@ -736,10 +745,36 @@
"PaidOver" "PaidOver"
] ]
}, },
"InvoiceDataBase": {
"properties": {
"amount": {
"type": "string",
"format": "decimal",
"description": "The amount of the invoice"
},
"currency": {
"type": "string",
"nullable": true,
"description": "The currency the invoice will use"
},
"metadata": {
"$ref": "#/components/schemas/InvoiceMetadata"
},
"checkout": {
"nullable": true,
"oneOf": [
{
"$ref": "#/components/schemas/CheckoutOptions"
}
],
"description": "Additional settings to customize the checkout flow"
}
}
},
"InvoiceData": { "InvoiceData": {
"allOf": [ "allOf": [
{ {
"$ref": "#/components/schemas/CreateInvoiceRequest" "$ref": "#/components/schemas/InvoiceDataBase"
}, },
{ {
"type": "object", "type": "object",
@@ -759,15 +794,15 @@
}, },
"createdTime": { "createdTime": {
"description": "The creation time of the invoice", "description": "The creation time of the invoice",
"allOf": [ {"$ref": "#/components/schemas/UnixTimestamp"}] "allOf": [ { "$ref": "#/components/schemas/UnixTimestamp" } ]
}, },
"expirationTime": { "expirationTime": {
"description": "The expiration time of the invoice", "description": "The expiration time of the invoice",
"allOf": [ {"$ref": "#/components/schemas/UnixTimestamp"}] "allOf": [ { "$ref": "#/components/schemas/UnixTimestamp" } ]
}, },
"monitoringTime": { "monitoringTime": {
"description": "The monitoring time of the invoice", "description": "The monitoring time of the invoice",
"allOf": [ {"$ref": "#/components/schemas/UnixTimestamp"}] "allOf": [ { "$ref": "#/components/schemas/UnixTimestamp" } ]
}, },
"status": { "status": {
"$ref": "#/components/schemas/InvoiceStatus", "$ref": "#/components/schemas/InvoiceStatus",
@@ -929,32 +964,25 @@
] ]
}, },
"CreateInvoiceRequest": { "CreateInvoiceRequest": {
"allOf": [
{
"$ref": "#/components/schemas/InvoiceDataBase"
},
{
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"amount": { "additionalSearchTerms": {
"type": "string", "type": "array",
"format": "decimal", "items": {
"description": "The amount of the invoice" "type": "string"
}, },
"currency": { "description": "Additional search term to help you find this invoice via text search",
"type": "string", "nullable": true
"nullable": true,
"description": "The currency the invoice will use"
},
"metadata": {
"$ref": "#/components/schemas/InvoiceMetadata"
},
"checkout": {
"nullable": true,
"oneOf": [
{
"$ref": "#/components/schemas/CheckoutOptions"
}
],
"description": "Additional settings to customize the checkout flow"
} }
} }
}
]
}, },
"UpdateInvoiceRequest": { "UpdateInvoiceRequest": {
"type": "object", "type": "object",
@@ -989,13 +1017,13 @@
"expirationMinutes": { "expirationMinutes": {
"nullable": true, "nullable": true,
"description": "The number of minutes after which an invoice becomes expired. Defaults to the store's settings. (The default store settings is 15)", "description": "The number of minutes after which an invoice becomes expired. Defaults to the store's settings. (The default store settings is 15)",
"allOf": [ {"$ref": "#/components/schemas/TimeSpanMinutes"}] "allOf": [ { "$ref": "#/components/schemas/TimeSpanMinutes" } ]
}, },
"monitoringMinutes": { "monitoringMinutes": {
"type": "integer", "type": "integer",
"nullable": true, "nullable": true,
"description": "The number of minutes after an invoice expired after which we are still monitoring for incoming payments. Defaults to the store's settings. (The default store settings is 1440, 1 day)", "description": "The number of minutes after an invoice expired after which we are still monitoring for incoming payments. Defaults to the store's settings. (The default store settings is 1440, 1 day)",
"allOf": [ {"$ref": "#/components/schemas/TimeSpanMinutes"}] "allOf": [ { "$ref": "#/components/schemas/TimeSpanMinutes" } ]
}, },
"paymentTolerance": { "paymentTolerance": {
"type": "number", "type": "number",
@@ -1109,7 +1137,7 @@
}, },
"receivedDate": { "receivedDate": {
"description": "The date the payment was recorded", "description": "The date the payment was recorded",
"allOf": [ {"$ref": "#/components/schemas/UnixTimestamp"}] "allOf": [ { "$ref": "#/components/schemas/UnixTimestamp" } ]
}, },
"value": { "value": {
"type": "string", "type": "string",