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

View File

@ -9,11 +9,13 @@ namespace BTCPayServer.Client
public partial class BTCPayServerClient
{
public virtual async Task<IEnumerable<OnChainPaymentMethodData>> GetStoreOnChainPaymentMethods(string storeId,
bool enabledOnly = false,
CancellationToken token = default)
{
var response =
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);
}

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
{
public class LightningNetworkPaymentMethodData
public class LightningNetworkPaymentMethodData: LightningNetworkPaymentMethodBaseData
{
/// <summary>
/// Whether the payment method is enabled
@ -12,8 +12,6 @@ namespace BTCPayServer.Client.Models
/// </summary>
public string CryptoCode { get; set; }
public string ConnectionString { get; set; }
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
{
public class OnChainPaymentMethodData
public class OnChainPaymentMethodData : OnChainPaymentMethodBaseData
{
/// <summary>
/// Whether the payment method is enabled
@ -15,18 +12,9 @@ namespace BTCPayServer.Client.Models
/// </summary>
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(string cryptoCode, string derivationScheme, bool enabled)

View File

@ -12,6 +12,7 @@ using BTCPayServer.Events;
using BTCPayServer.JsonConverters;
using BTCPayServer.Lightning;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Payments;
using BTCPayServer.Services;
using BTCPayServer.Services.Notifications;
using BTCPayServer.Services.Notifications.Blobs;
@ -1828,6 +1829,55 @@ 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 readonly StoreRepository _storeRepository;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
private readonly IAuthorizationService _authorizationService;
private readonly CssThemeManager _cssThemeManager;
public StoreLightningNetworkPaymentMethodsController(
StoreRepository storeRepository,
BTCPayNetworkProvider btcPayNetworkProvider,
IOptions<LightningNetworkOptions> lightningNetworkOptions,
IAuthorizationService authorizationService,
CssThemeManager cssThemeManager)
{
_storeRepository = storeRepository;
_btcPayNetworkProvider = btcPayNetworkProvider;
_lightningNetworkOptions = lightningNetworkOptions;
_authorizationService = authorizationService;
_cssThemeManager = cssThemeManager;
}

View File

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

View File

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

View File

@ -1,4 +1,6 @@
using System;
using BTCPayServer.Client.Models;
using BTCPayServer.Controllers.GreenField;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Services.Invoices;
using NBitcoin;
@ -63,6 +65,16 @@ namespace BTCPayServer.Payments
}
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)
{
return paymentType?.Equals("offchain", StringComparison.InvariantCultureIgnoreCase) is true || base.IsPaymentType(paymentType);

View File

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

View File

@ -1,7 +1,6 @@
#if ALTCOINS
using System.Globalization;
using BTCPayServer.Payments;
using BTCPayServer.Services.Altcoins.Monero.Payments;
using BTCPayServer.Services.Invoices;
using NBitcoin;
using Newtonsoft.Json;
@ -54,6 +53,19 @@ namespace BTCPayServer.Services.Altcoins.Ethereum.Payments
}
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

View File

@ -57,6 +57,18 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
}
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

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"
}
},
"LightningNetworkPaymentMethodData": {
"LightningNetworkPaymentMethodBaseData": {
"type": "object",
"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": {
"enabled": {
"type": "boolean",
@ -249,11 +262,6 @@
"cryptoCode": {
"type": "string",
"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"
}
},
"OnChainPaymentMethodData": {
"OnChainPaymentMethodBaseData": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether the payment method is enabled"
},
"cryptoCode": {
"type": "string",
"description": "Crypto code of the payment method"
},
"derivationScheme": {
"type": "string",
"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": {
"type": "object",
"additionalProperties": false,