Refactor Get Store Payment Methods

Add tests + docs + new pluggbale format for fetching payment method data + client
This commit is contained in:
Kukks
2021-07-23 10:05:15 +02:00
committed by Andrew Camilleri
parent 17e6179fec
commit 4d538c61b1
20 changed files with 316 additions and 55 deletions

View File

@@ -9,12 +9,13 @@ namespace BTCPayServer.Client
public partial class BTCPayServerClient public partial class BTCPayServerClient
{ {
public virtual async Task<IEnumerable<LightningNetworkPaymentMethodData>> public virtual async Task<IEnumerable<LightningNetworkPaymentMethodData>>
GetStoreLightningNetworkPaymentMethods(string storeId, GetStoreLightningNetworkPaymentMethods(string storeId, bool enabledOnly = false,
CancellationToken token = default) CancellationToken token = default)
{ {
var response = var response =
await _httpClient.SendAsync( await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LightningNetwork"), token); CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LightningNetwork",
new Dictionary<string, object>() {{nameof(enabledOnly), enabledOnly}}), token);
return await HandleResponse<IEnumerable<LightningNetworkPaymentMethodData>>(response); return await HandleResponse<IEnumerable<LightningNetworkPaymentMethodData>>(response);
} }
@@ -49,15 +50,9 @@ namespace BTCPayServer.Client
return await HandleResponse<LightningNetworkPaymentMethodData>(response); return await HandleResponse<LightningNetworkPaymentMethodData>(response);
} }
public virtual async Task<LightningNetworkPaymentMethodData> public virtual Task<LightningNetworkPaymentMethodData>
UpdateStoreLightningNetworkPaymentMethodToInternalNode(string storeId, UpdateStoreLightningNetworkPaymentMethodToInternalNode(string storeId,
string cryptoCode, LightningNetworkPaymentMethodData paymentMethod, string cryptoCode, CancellationToken token = default) => UpdateStoreLightningNetworkPaymentMethod(
CancellationToken token = default) storeId, cryptoCode, new LightningNetworkPaymentMethodData(cryptoCode, "Internal Node", true), token);
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}/internal",
method: HttpMethod.Put), token);
return await HandleResponse<LightningNetworkPaymentMethodData>(response);
}
} }
} }

View File

@@ -9,11 +9,13 @@ namespace BTCPayServer.Client
public partial class BTCPayServerClient public partial class BTCPayServerClient
{ {
public virtual async Task<IEnumerable<OnChainPaymentMethodData>> GetStoreOnChainPaymentMethods(string storeId, public virtual async Task<IEnumerable<OnChainPaymentMethodData>> GetStoreOnChainPaymentMethods(string storeId,
bool enabledOnly = false,
CancellationToken token = default) CancellationToken token = default)
{ {
var response = var response =
await _httpClient.SendAsync( await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain"), token); CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain",
new Dictionary<string, object>() {{nameof(enabledOnly), enabledOnly}}), token);
return await HandleResponse<IEnumerable<OnChainPaymentMethodData>>(response); return await HandleResponse<IEnumerable<OnChainPaymentMethodData>>(response);
} }

View File

@@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public virtual async Task<Dictionary<string, GenericPaymentMethodData>> GetStorePaymentMethods(string storeId,
bool enabledOnly = false,
CancellationToken token = default)
{
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods",
new Dictionary<string, object>() {{nameof(enabledOnly), enabledOnly}}), token);
return await HandleResponse<Dictionary<string, GenericPaymentMethodData>>(response);
}
}
}

View File

@@ -0,0 +1,8 @@
namespace BTCPayServer.Client.Models
{
public class GenericPaymentMethodData
{
public bool Enabled { get; set; }
public object Data { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
namespace BTCPayServer.Client.Models
{
public class LightningNetworkPaymentMethodBaseData
{
public string ConnectionString { get; set; }
public LightningNetworkPaymentMethodBaseData()
{
}
}
}

View File

@@ -1,6 +1,6 @@
namespace BTCPayServer.Client.Models namespace BTCPayServer.Client.Models
{ {
public class LightningNetworkPaymentMethodData public class LightningNetworkPaymentMethodData: LightningNetworkPaymentMethodBaseData
{ {
/// <summary> /// <summary>
/// Whether the payment method is enabled /// Whether the payment method is enabled
@@ -12,8 +12,6 @@ namespace BTCPayServer.Client.Models
/// </summary> /// </summary>
public string CryptoCode { get; set; } public string CryptoCode { get; set; }
public string ConnectionString { get; set; }
public LightningNetworkPaymentMethodData() public LightningNetworkPaymentMethodData()
{ {
} }

View File

@@ -0,0 +1,24 @@
using NBitcoin;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class OnChainPaymentMethodBaseData
{
/// <summary>
/// The derivation scheme
/// </summary>
public string DerivationScheme { get; set; }
public string Label { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.KeyPathJsonConverter))]
public RootedKeyPath AccountKeyPath { get; set; }
public OnChainPaymentMethodBaseData()
{
}
}
}

View File

@@ -1,9 +1,6 @@
using NBitcoin;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models namespace BTCPayServer.Client.Models
{ {
public class OnChainPaymentMethodData public class OnChainPaymentMethodData : OnChainPaymentMethodBaseData
{ {
/// <summary> /// <summary>
/// Whether the payment method is enabled /// Whether the payment method is enabled
@@ -15,18 +12,9 @@ namespace BTCPayServer.Client.Models
/// </summary> /// </summary>
public string CryptoCode { get; set; } public string CryptoCode { get; set; }
/// <summary>
/// The derivation scheme
/// </summary>
public string DerivationScheme { get; set; }
public string Label { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.KeyPathJsonConverter))]
public RootedKeyPath AccountKeyPath { get; set; }
public OnChainPaymentMethodData() public OnChainPaymentMethodData()
{ {
} }
public OnChainPaymentMethodData(string cryptoCode, string derivationScheme, bool enabled) public OnChainPaymentMethodData(string cryptoCode, string derivationScheme, bool enabled)

View File

@@ -12,6 +12,7 @@ using BTCPayServer.Events;
using BTCPayServer.JsonConverters; using BTCPayServer.JsonConverters;
using BTCPayServer.Lightning; using BTCPayServer.Lightning;
using BTCPayServer.Models.InvoicingModels; using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Payments;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Notifications; using BTCPayServer.Services.Notifications;
using BTCPayServer.Services.Notifications.Blobs; using BTCPayServer.Services.Notifications.Blobs;
@@ -1829,5 +1830,54 @@ namespace BTCPayServer.Tests
Assert.Equal(1.2, jsonConverter.ReadJson(Get(stringJson), typeof(double?), null, null)); Assert.Equal(1.2, jsonConverter.ReadJson(Get(stringJson), typeof(double?), null, null));
} }
[Fact(Timeout = 60 * 2 * 1000)]
[Trait("Lightning", "Lightning")]
[Trait("Integration", "Integration")]
public async Task StorePaymentMethodsAPITests()
{
using var tester = ServerTester.Create();
tester.ActivateLightning();
await tester.StartAsync();
await tester.EnsureChannelsSetup();
var admin = tester.NewAccount();
await admin.GrantAccessAsync(true);
var adminClient = await admin.CreateClient(Policies.Unrestricted);
var store = await adminClient.GetStore(admin.StoreId);
Assert.Empty(await adminClient.GetStorePaymentMethods(store.Id));
await adminClient.UpdateStoreLightningNetworkPaymentMethodToInternalNode(admin.StoreId, "BTC");
void VerifyLightning(Dictionary<string, GenericPaymentMethodData> dictionary)
{
Assert.True(dictionary.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToStringNormalized(), out var item));
var lightningNetworkPaymentMethodBaseData =Assert.IsType<JObject>(item.Data).ToObject<LightningNetworkPaymentMethodBaseData>();
Assert.Equal("Internal Node", lightningNetworkPaymentMethodBaseData.ConnectionString);
}
var methods = await adminClient.GetStorePaymentMethods(store.Id);
Assert.Equal(1, methods.Count);
VerifyLightning(methods);
var randK = new Mnemonic(Wordlist.English, WordCount.Twelve).DeriveExtKey().Neuter().ToString(Network.RegTest);
await adminClient.UpdateStoreOnChainPaymentMethod(admin.StoreId, "BTC",
new OnChainPaymentMethodData("BTC", randK, true));
void VerifyOnChain(Dictionary<string, GenericPaymentMethodData> dictionary)
{
Assert.True(dictionary.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.BTCLike).ToStringNormalized(), out var item));
var paymentMethodBaseData =Assert.IsType<JObject>(item.Data).ToObject<OnChainPaymentMethodBaseData>();
Assert.Equal(randK, paymentMethodBaseData.DerivationScheme);
}
methods = await adminClient.GetStorePaymentMethods(store.Id);
Assert.Equal(2, methods.Count);
VerifyLightning(methods);
VerifyOnChain(methods);
}
} }
} }

View File

@@ -27,20 +27,17 @@ namespace BTCPayServer.Controllers.GreenField
private StoreData Store => HttpContext.GetStoreData(); private StoreData Store => HttpContext.GetStoreData();
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
private readonly CssThemeManager _cssThemeManager; private readonly CssThemeManager _cssThemeManager;
public StoreLightningNetworkPaymentMethodsController( public StoreLightningNetworkPaymentMethodsController(
StoreRepository storeRepository, StoreRepository storeRepository,
BTCPayNetworkProvider btcPayNetworkProvider, BTCPayNetworkProvider btcPayNetworkProvider,
IOptions<LightningNetworkOptions> lightningNetworkOptions,
IAuthorizationService authorizationService, IAuthorizationService authorizationService,
CssThemeManager cssThemeManager) CssThemeManager cssThemeManager)
{ {
_storeRepository = storeRepository; _storeRepository = storeRepository;
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
_lightningNetworkOptions = lightningNetworkOptions;
_authorizationService = authorizationService; _authorizationService = authorizationService;
_cssThemeManager = cssThemeManager; _cssThemeManager = cssThemeManager;
} }

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;

View File

@@ -1,4 +1,6 @@
#nullable enable #nullable enable
using System.Collections.Generic;
using System.Linq;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
@@ -23,17 +25,21 @@ namespace BTCPayServer.Controllers.GreenField
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores/{storeId}/payment-methods")] [HttpGet("~/api/v1/stores/{storeId}/payment-methods")]
public ActionResult<LightningNetworkPaymentMethodData> GetPaymentMethods( public ActionResult<Dictionary<string, GenericPaymentMethodData>> GetStorePaymentMethods(
[FromQuery] bool enabledOnly = false [FromQuery] bool enabledOnly = false
) )
{ {
var storeBlob = Store.GetStoreBlob(); var storeBlob = Store.GetStoreBlob();
var excludedPaymentMethods = storeBlob.GetExcludedPaymentMethods(); var excludedPaymentMethods = storeBlob.GetExcludedPaymentMethods();
return Ok(Store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
return Ok(new { .Where(method => !enabledOnly || !excludedPaymentMethods.Match(method.PaymentId))
onchain = StoreOnChainPaymentMethodsController.GetOnChainPaymentMethods(Store, _btcPayNetworkProvider, enabledOnly), .ToDictionary(
lightning = StoreLightningNetworkPaymentMethodsController.GetLightningPaymentMethods(Store, _btcPayNetworkProvider, enabledOnly) method => method.PaymentId.ToStringNormalized(),
}); method => new GenericPaymentMethodData()
{
Enabled = enabledOnly || !excludedPaymentMethods.Match(method.PaymentId),
Data = method.PaymentId.PaymentType.GetGreenfieldData(method)
}));
} }
} }
} }

View File

@@ -6,6 +6,7 @@ using BTCPayServer.Services;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using NBitcoin; using NBitcoin;
using BTCPayServer.BIP78.Sender; using BTCPayServer.BIP78.Sender;
using BTCPayServer.Client.Models;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace BTCPayServer.Payments namespace BTCPayServer.Payments
@@ -84,6 +85,18 @@ namespace BTCPayServer.Payments
} }
public override string InvoiceViewPaymentPartialName { get; } = "Bitcoin/ViewBitcoinLikePaymentData"; public override string InvoiceViewPaymentPartialName { get; } = "Bitcoin/ViewBitcoinLikePaymentData";
public override object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod)
{
if (supportedPaymentMethod is DerivationSchemeSettings derivationSchemeSettings)
return new OnChainPaymentMethodBaseData()
{
DerivationScheme = derivationSchemeSettings.AccountDerivation.ToString(),
AccountKeyPath = derivationSchemeSettings.GetSigningAccountKeySettings().GetRootedKeyPath(),
Label = derivationSchemeSettings.Label
};
return null;
}
public override bool IsPaymentType(string paymentType) public override bool IsPaymentType(string paymentType)
{ {
return string.IsNullOrEmpty(paymentType) || base.IsPaymentType(paymentType); return string.IsNullOrEmpty(paymentType) || base.IsPaymentType(paymentType);

View File

@@ -1,4 +1,6 @@
using System; using System;
using BTCPayServer.Client.Models;
using BTCPayServer.Controllers.GreenField;
using BTCPayServer.Payments.Lightning; using BTCPayServer.Payments.Lightning;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using NBitcoin; using NBitcoin;
@@ -63,6 +65,16 @@ namespace BTCPayServer.Payments
} }
public override string InvoiceViewPaymentPartialName { get; } = "Lightning/ViewLightningLikePaymentData"; public override string InvoiceViewPaymentPartialName { get; } = "Lightning/ViewLightningLikePaymentData";
public override object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod)
{
if (supportedPaymentMethod is LightningSupportedPaymentMethod lightningSupportedPaymentMethod)
return new LightningNetworkPaymentMethodBaseData()
{
ConnectionString = lightningSupportedPaymentMethod.GetDisplayableConnectionString()
};
return null;
}
public override bool IsPaymentType(string paymentType) public override bool IsPaymentType(string paymentType)
{ {
return paymentType?.Equals("offchain", StringComparison.InvariantCultureIgnoreCase) is true || base.IsPaymentType(paymentType); return paymentType?.Equals("offchain", StringComparison.InvariantCultureIgnoreCase) is true || base.IsPaymentType(paymentType);

View File

@@ -81,6 +81,8 @@ namespace BTCPayServer.Payments
Money cryptoInfoDue, string serverUri); Money cryptoInfoDue, string serverUri);
public abstract string InvoiceViewPaymentPartialName { get; } public abstract string InvoiceViewPaymentPartialName { get; }
public abstract object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod);
public virtual bool IsPaymentType(string paymentType) public virtual bool IsPaymentType(string paymentType)
{ {
paymentType = paymentType?.ToLowerInvariant(); paymentType = paymentType?.ToLowerInvariant();

View File

@@ -1,7 +1,6 @@
#if ALTCOINS #if ALTCOINS
using System.Globalization; using System.Globalization;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Services.Altcoins.Monero.Payments;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using NBitcoin; using NBitcoin;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -54,6 +53,19 @@ namespace BTCPayServer.Services.Altcoins.Ethereum.Payments
} }
public override string InvoiceViewPaymentPartialName { get; }= "Ethereum/ViewEthereumLikePaymentData"; public override string InvoiceViewPaymentPartialName { get; }= "Ethereum/ViewEthereumLikePaymentData";
public override object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod)
{
if (supportedPaymentMethod is EthereumSupportedPaymentMethod ethereumSupportedPaymentMethod)
{
return new
{
ethereumSupportedPaymentMethod.XPub
//no clue what all those properties saved are and don't care.
};
}
return null;
}
} }
} }
#endif #endif

View File

@@ -57,6 +57,18 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
} }
public override string InvoiceViewPaymentPartialName { get; } = "Monero/ViewMoneroLikePaymentData"; public override string InvoiceViewPaymentPartialName { get; } = "Monero/ViewMoneroLikePaymentData";
public override object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod)
{
if (supportedPaymentMethod is MoneroSupportedPaymentMethod moneroSupportedPaymentMethod)
{
return new
{
moneroSupportedPaymentMethod.AccountIndex,
};
}
return null;
}
} }
} }
#endif #endif

View File

@@ -0,0 +1,94 @@
{
"paths": {
"/api/v1/stores/{storeId}/payment-methods": {
"get": {
"tags": [
"Store Payment Methods"
],
"summary": "Get store payment methods",
"description": "View information about the stores' configured payment methods",
"operationId": "StorePaymentMethods_GetStorePaymentMethods",
"parameters": [
{
"name": "storeId",
"in": "path",
"required": true,
"description": "The store to fetch",
"schema": {
"type": "string"
}
},
{
"name": "enabledOnly",
"in": "query",
"required": false,
"description": "Fetch payment methods that are enable only",
"schema": {
"type": "boolean"
}
}
],
"responses": {
"200": {
"description": "list of payment methods",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": {
"$ref": "#/components/schemas/GenericPaymentMethodData"
}
}
}
}
}
},
"security": [
{
"API Key": [
"btcpay.store.canviewstoresettings"
],
"Basic": []
}
]
}
}
},
"components": {
"schemas": {
"GenericPaymentMethodData": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether the payment method is enabled"
},
"data": {
"type": "object",
"additionalProperties": false,
"description": "Associated dynamic data based on payment method type.",
"oneOf": [
{
"$ref": "#/components/schemas/OnChainPaymentMethodBaseData"
},
{
"$ref": "#/components/schemas/LightningNetworkPaymentMethodBaseData"
},
{
"description": "Any other unofficial payment method data",
"type": "object",
"additionalProperties": true
}
]
}
}
}
}
},
"tags": [
{
"name": "Store Payment Methods"
}
]
}

View File

@@ -238,9 +238,22 @@
"$ref": "#/components/schemas/LightningNetworkPaymentMethodData" "$ref": "#/components/schemas/LightningNetworkPaymentMethodData"
} }
}, },
"LightningNetworkPaymentMethodData": { "LightningNetworkPaymentMethodBaseData": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": {
"connectionString": {
"type": "string",
"description": "The lightning connection string. Set to 'Internal Node' to use the internal node. (See [this doc](https://github.com/btcpayserver/BTCPayServer.Lightning/blob/master/README.md#examples) for some example)",
"example": "type=clightning;server=..."
}
}
},
"LightningNetworkPaymentMethodData": {
"type": "object",
"additionalProperties": {
"$ref": "#/components/schemas/LightningNetworkPaymentMethodBaseData"
},
"properties": { "properties": {
"enabled": { "enabled": {
"type": "boolean", "type": "boolean",
@@ -249,11 +262,6 @@
"cryptoCode": { "cryptoCode": {
"type": "string", "type": "string",
"description": "Crypto code of the payment method" "description": "Crypto code of the payment method"
},
"connectionString": {
"type": "string",
"description": "The lightning connection string. Set to 'Internal Node' to use the internal node. (See [this doc](https://github.com/btcpayserver/BTCPayServer.Lightning/blob/master/README.md#examples) for some example)",
"example": "type=clightning;server=..."
} }
} }
} }

View File

@@ -396,18 +396,10 @@
"$ref": "#/components/schemas/OnChainPaymentMethodData" "$ref": "#/components/schemas/OnChainPaymentMethodData"
} }
}, },
"OnChainPaymentMethodData": { "OnChainPaymentMethodBaseData": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"enabled": {
"type": "boolean",
"description": "Whether the payment method is enabled"
},
"cryptoCode": {
"type": "string",
"description": "Crypto code of the payment method"
},
"derivationScheme": { "derivationScheme": {
"type": "string", "type": "string",
"description": "The derivation scheme", "description": "The derivation scheme",
@@ -424,6 +416,22 @@
} }
} }
}, },
"OnChainPaymentMethodData": {
"type": "object",
"additionalProperties": {
"$ref": "#/components/schemas/OnChainPaymentMethodBaseData"
},
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether the payment method is enabled"
},
"cryptoCode": {
"type": "string",
"description": "Crypto code of the payment method"
}
}
},
"OnChainPaymentMethodPreviewResultData": { "OnChainPaymentMethodPreviewResultData": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,