mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 01:43:50 +01:00
Greenfield: Add store rates api (#4550)
* Add store rates api * Improve doc --------- Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
This commit is contained in:
parent
f821e35cb0
commit
aad06c583e
@ -37,7 +37,7 @@ namespace BTCPayServer.Client
|
||||
return await HandleResponse<StoreRateConfiguration>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<List<StoreRatePreviewResult>> PreviewUpdateStoreRateConfiguration(string storeId,
|
||||
public virtual async Task<List<StoreRateResult>> PreviewUpdateStoreRateConfiguration(string storeId,
|
||||
StoreRateConfiguration request,
|
||||
string[] currencyPair,
|
||||
CancellationToken token = default)
|
||||
@ -47,7 +47,18 @@ namespace BTCPayServer.Client
|
||||
queryPayload: new Dictionary<string, object>() { { "currencyPair", currencyPair } },
|
||||
method: HttpMethod.Post),
|
||||
token);
|
||||
return await HandleResponse<List<StoreRatePreviewResult>>(response);
|
||||
return await HandleResponse<List<StoreRateResult>>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<List<StoreRateResult>> GetStoreRates(string storeId, string[] currencyPair,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/rates",
|
||||
queryPayload: new Dictionary<string, object>() { { "currencyPair", currencyPair } },
|
||||
method: HttpMethod.Get),
|
||||
token);
|
||||
return await HandleResponse<List<StoreRateResult>>(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class StoreRatePreviewResult
|
||||
{
|
||||
public string CurrencyPair { get; set; }
|
||||
public decimal? Rate { get; set; }
|
||||
public List<string> Errors { get; set; }
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class StoreRateResult
|
||||
{
|
||||
public string CurrencyPair { get; set; }
|
||||
public decimal Rate { get; set; }
|
||||
public decimal? Rate { get; set; }
|
||||
public List<string> Errors { get; set; }
|
||||
}
|
||||
|
@ -3594,6 +3594,9 @@ namespace BTCPayServer.Tests
|
||||
new StoreRateConfiguration() { IsCustomScript = true, EffectiveScript = "BTC_XYZ = 1", Spread = 10m, }))
|
||||
.IsCustomScript);
|
||||
|
||||
Assert.Equal(0.9m,
|
||||
Assert.Single(await clientBasic.GetStoreRates(user.StoreId, new[] { "BTC_XYZ" })).Rate);
|
||||
|
||||
config = await clientBasic.GetStoreRateConfiguration(user.StoreId);
|
||||
Assert.NotNull(config);
|
||||
Assert.NotNull(config.EffectiveScript);
|
||||
|
@ -85,24 +85,31 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
[HttpPost("preview")]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> PreviewUpdateStoreRateConfiguration(
|
||||
StoreRateConfiguration configuration, [FromQuery] string[] currencyPair)
|
||||
StoreRateConfiguration configuration, [FromQuery] string[]? currencyPair)
|
||||
{
|
||||
var data = HttpContext.GetStoreData();
|
||||
var blob = data.GetStoreBlob();
|
||||
var parsedCurrencyPairs = new HashSet<CurrencyPair>();
|
||||
|
||||
|
||||
foreach (var pair in currencyPair ?? Array.Empty<string>())
|
||||
if (currencyPair?.Any() is true)
|
||||
{
|
||||
if (!CurrencyPair.TryParse(pair, out var currencyPairParsed))
|
||||
foreach (var pair in currencyPair)
|
||||
{
|
||||
ModelState.AddModelError(nameof(currencyPair),
|
||||
$"Invalid currency pair '{pair}' (it should be formatted like BTC_USD,BTC_CAD)");
|
||||
break;
|
||||
}
|
||||
if (!CurrencyPair.TryParse(pair, out var currencyPairParsed))
|
||||
{
|
||||
ModelState.AddModelError(nameof(currencyPair),
|
||||
$"Invalid currency pair '{pair}' (it should be formatted like BTC_USD,BTC_CAD)");
|
||||
break;
|
||||
}
|
||||
|
||||
parsedCurrencyPairs.Add(currencyPairParsed);
|
||||
parsedCurrencyPairs.Add(currencyPairParsed);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
parsedCurrencyPairs = blob.DefaultCurrencyPairs.ToHashSet();
|
||||
}
|
||||
|
||||
ValidateAndSanitizeConfiguration(configuration, blob);
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
@ -113,12 +120,12 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
|
||||
var rateTasks = _rateProviderFactory.FetchRates(parsedCurrencyPairs, rules, CancellationToken.None);
|
||||
await Task.WhenAll(rateTasks.Values);
|
||||
var result = new List<StoreRatePreviewResult>();
|
||||
var result = new List<StoreRateResult>();
|
||||
foreach (var rateTask in rateTasks)
|
||||
{
|
||||
var rateTaskResult = rateTask.Value.Result;
|
||||
|
||||
result.Add(new StoreRatePreviewResult()
|
||||
result.Add(new StoreRateResult()
|
||||
{
|
||||
CurrencyPair = rateTask.Key.ToString(),
|
||||
Errors = rateTaskResult.Errors.Select(errors => errors.ToString()).ToList(),
|
||||
|
@ -0,0 +1,84 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/v1/stores/{storeId}/rates")]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public class GreenfieldStoreRatesController : ControllerBase
|
||||
{
|
||||
private readonly RateFetcher _rateProviderFactory;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
|
||||
public GreenfieldStoreRatesController(
|
||||
RateFetcher rateProviderFactory,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider)
|
||||
{
|
||||
_rateProviderFactory = rateProviderFactory;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> GetStoreRates([FromQuery] string[]? currencyPair)
|
||||
{
|
||||
var data = HttpContext.GetStoreData();
|
||||
var blob = data.GetStoreBlob();
|
||||
var parsedCurrencyPairs = new HashSet<CurrencyPair>();
|
||||
|
||||
if (currencyPair?.Any() is true)
|
||||
{
|
||||
foreach (var pair in currencyPair)
|
||||
{
|
||||
if (!CurrencyPair.TryParse(pair, out var currencyPairParsed))
|
||||
{
|
||||
ModelState.AddModelError(nameof(currencyPair),
|
||||
$"Invalid currency pair '{pair}' (it should be formatted like BTC_USD,BTC_CAD)");
|
||||
break;
|
||||
}
|
||||
|
||||
parsedCurrencyPairs.Add(currencyPairParsed);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
parsedCurrencyPairs = blob.DefaultCurrencyPairs.ToHashSet();
|
||||
}
|
||||
|
||||
|
||||
var rules = blob.GetRateRules(_btcPayNetworkProvider);
|
||||
|
||||
|
||||
var rateTasks = _rateProviderFactory.FetchRates(parsedCurrencyPairs, rules, CancellationToken.None);
|
||||
await Task.WhenAll(rateTasks.Values);
|
||||
var result = new List<StoreRateResult>();
|
||||
foreach (var rateTask in rateTasks)
|
||||
{
|
||||
var rateTaskResult = rateTask.Value.Result;
|
||||
|
||||
result.Add(new StoreRateResult()
|
||||
{
|
||||
CurrencyPair = rateTask.Key.ToString(),
|
||||
Errors = rateTaskResult.Errors.Select(errors => errors.ToString()).ToList(),
|
||||
Rate = rateTaskResult.Errors.Any() ? null : rateTaskResult.BidAsk.Bid
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1223,12 +1223,18 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return Task.FromResult(GetFromActionResult<StoreRateConfiguration>(GetController<GreenfieldStoreRateConfigurationController>().GetStoreRateConfiguration()));
|
||||
}
|
||||
|
||||
public override async Task<List<StoreRatePreviewResult>> PreviewUpdateStoreRateConfiguration(string storeId,
|
||||
public override async Task<List<StoreRateResult>> GetStoreRates (string storeId,
|
||||
string[] currencyPair, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<List<StoreRateResult>>(await GetController<GreenfieldStoreRatesController>().GetStoreRates(currencyPair));
|
||||
}
|
||||
|
||||
public override async Task<List<StoreRateResult>> PreviewUpdateStoreRateConfiguration(string storeId,
|
||||
StoreRateConfiguration request,
|
||||
string[] currencyPair,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<List<StoreRatePreviewResult>>(
|
||||
return GetFromActionResult<List<StoreRateResult>>(
|
||||
await GetController<GreenfieldStoreRateConfigurationController>().PreviewUpdateStoreRateConfiguration(request,
|
||||
currencyPair));
|
||||
}
|
||||
|
@ -165,7 +165,7 @@
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/StoreRatePreviewResult"
|
||||
"$ref": "#/components/schemas/StoreRateResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -220,13 +220,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"StoreRatePreviewResult": {
|
||||
"StoreRateResult": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"currencyPair": {
|
||||
"type": "string",
|
||||
"description": "Currency pair in the format of BTC_USD"
|
||||
"example": "BTC_USD",
|
||||
"description": "Currency pair in the format of `BTC_USD`"
|
||||
},
|
||||
"errors": {
|
||||
"type": "array",
|
||||
@ -237,8 +238,9 @@
|
||||
"description": "Errors relating to this currency pair fetching based on your config"
|
||||
},
|
||||
"rate": {
|
||||
"type": "string",
|
||||
"description": "the rate fetched based on th currency pair"
|
||||
"type": "number",
|
||||
"example": 24520.23,
|
||||
"description": "the rate fetched based on the currency pair"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,84 @@
|
||||
{
|
||||
"paths": {
|
||||
"/api/v1/stores/{storeId}/rates": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Stores (Rates)"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "storeId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The store to fetch",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "currencyPair",
|
||||
"description": "The currency pairs to fetch rates for",
|
||||
"example": [ "BTC_USD", "BTC_EUR" ],
|
||||
"in": "query",
|
||||
"style": "form",
|
||||
"explode": true,
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"nullable": true,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"x-position": 1
|
||||
}
|
||||
],
|
||||
"summary": "Get rates",
|
||||
"description": "Get rates on the store",
|
||||
"operationId": "Stores_GetStoreRates",
|
||||
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The settings were executed and a preview was returned",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/StoreRateResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "A list of errors that occurred when previewing the settings",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ValidationProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden to modify the store"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"API_Key": [
|
||||
"btcpay.store.canviewstoresettings"
|
||||
],
|
||||
"Basic": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "Stores (Rates)",
|
||||
"description": "Store Rates operations"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user