2024-04-04 10:47:28 +02:00
#nullable enable
using System ;
using System.Collections.Generic ;
using System.Globalization ;
using System.Linq ;
using System.Threading ;
using System.Threading.Tasks ;
using BTCPayServer.Abstractions.Constants ;
using BTCPayServer.Abstractions.Models ;
using BTCPayServer.Client ;
using BTCPayServer.Data ;
using BTCPayServer.Models.StoreViewModels ;
using BTCPayServer.Rating ;
2024-04-30 11:31:15 +02:00
using BTCPayServer.Services.Rates ;
2024-04-04 10:47:28 +02:00
using Microsoft.AspNetCore.Authorization ;
using Microsoft.AspNetCore.Mvc ;
2024-04-04 11:00:18 +02:00
namespace BTCPayServer.Controllers ;
public partial class UIStoresController
2024-04-04 10:47:28 +02:00
{
2024-04-04 11:00:18 +02:00
[HttpGet("{storeId}/rates")]
public IActionResult Rates ( )
{
var storeBlob = CurrentStore . GetStoreBlob ( ) ;
var vm = new RatesViewModel ( ) ;
2024-05-13 15:29:42 +02:00
FillFromStore ( vm , storeBlob ) ;
2024-04-04 11:00:18 +02:00
return View ( vm ) ;
}
[HttpPost("{storeId}/rates")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task < IActionResult > Rates ( RatesViewModel model , string? command = null , string? storeId = null , CancellationToken cancellationToken = default )
2024-04-04 10:47:28 +02:00
{
2024-04-04 11:00:18 +02:00
if ( command = = "scripting-on" )
2024-04-04 10:47:28 +02:00
{
2024-04-04 11:00:18 +02:00
return RedirectToAction ( nameof ( ShowRateRules ) , new { scripting = true , storeId = model . StoreId } ) ;
}
if ( command = = "scripting-off" )
{
return RedirectToAction ( nameof ( ShowRateRules ) , new { scripting = false , storeId = model . StoreId } ) ;
2024-04-04 10:47:28 +02:00
}
2024-04-04 11:00:18 +02:00
model . StoreId = storeId ? ? model . StoreId ;
CurrencyPair [ ] ? currencyPairs = null ;
try
2024-04-04 10:47:28 +02:00
{
2024-04-04 11:00:18 +02:00
currencyPairs = model . DefaultCurrencyPairs ?
. Split ( new [ ] { ',' } , StringSplitOptions . RemoveEmptyEntries )
. Select ( p = > CurrencyPair . Parse ( p ) )
. ToArray ( ) ;
}
catch
{
2024-10-17 15:51:40 +02:00
ModelState . AddModelError ( nameof ( model . DefaultCurrencyPairs ) , StringLocalizer [ "Invalid currency pairs (should be for example: {0})" , "BTC_USD,BTC_CAD,BTC_JPY" ] ) ;
2024-04-04 11:00:18 +02:00
}
if ( ! ModelState . IsValid )
{
return View ( model ) ;
}
if ( model . PreferredExchange ! = null )
model . PreferredExchange = model . PreferredExchange . Trim ( ) . ToLowerInvariant ( ) ;
2024-05-13 15:29:42 +02:00
if ( string . IsNullOrEmpty ( model . PreferredExchange ) )
model . PreferredExchange = null ;
2024-04-04 10:47:28 +02:00
2024-04-04 11:00:18 +02:00
var blob = CurrentStore . GetStoreBlob ( ) ;
RateRules ? rules ;
if ( model . ShowScripting )
{
if ( ! RateRules . TryParse ( model . Script , out rules , out var errors ) )
2024-04-04 10:47:28 +02:00
{
2024-04-04 11:00:18 +02:00
errors ? ? = [ ] ;
var errorString = string . Join ( ", " , errors . ToArray ( ) ) ;
2024-10-17 15:51:40 +02:00
ModelState . AddModelError ( nameof ( model . Script ) , StringLocalizer [ "Parsing error: {0}" , errorString ] ) ;
2024-05-13 15:29:42 +02:00
FillFromStore ( model , blob ) ;
2024-04-04 10:47:28 +02:00
return View ( model ) ;
}
2024-04-04 11:00:18 +02:00
else
2024-04-04 10:47:28 +02:00
{
2024-04-04 11:00:18 +02:00
blob . RateScript = rules . ToString ( ) ;
ModelState . Remove ( nameof ( model . Script ) ) ;
2024-04-04 10:47:28 +02:00
}
2024-04-04 11:00:18 +02:00
}
2024-05-13 15:29:42 +02:00
blob . PreferredExchange = model . PreferredExchange ;
blob . Spread = ( decimal ) model . Spread / 100.0 m ;
blob . DefaultCurrencyPairs = currencyPairs ;
FillFromStore ( model , blob ) ;
rules = blob . GetRateRules ( _defaultRules ) ;
2024-04-04 11:00:18 +02:00
if ( command = = "Test" )
{
if ( string . IsNullOrWhiteSpace ( model . ScriptTest ) )
2024-04-04 10:47:28 +02:00
{
2024-10-17 15:51:40 +02:00
ModelState . AddModelError ( nameof ( model . ScriptTest ) , StringLocalizer [ "Fill out currency pair to test for (like {0})" , "BTC_USD,BTC_CAD" ] ) ;
2024-04-04 11:00:18 +02:00
return View ( model ) ;
2024-04-04 10:47:28 +02:00
}
2024-04-04 11:00:18 +02:00
var splitted = model . ScriptTest . Split ( ',' , StringSplitOptions . RemoveEmptyEntries ) ;
2024-04-04 10:47:28 +02:00
2024-04-04 11:00:18 +02:00
var pairs = new List < CurrencyPair > ( ) ;
foreach ( var pair in splitted )
2024-04-04 10:47:28 +02:00
{
2024-04-04 11:00:18 +02:00
if ( ! CurrencyPair . TryParse ( pair , out var currencyPair ) )
2024-04-04 10:47:28 +02:00
{
2024-10-17 15:51:40 +02:00
ModelState . AddModelError ( nameof ( model . ScriptTest ) , StringLocalizer [ "Invalid currency pair '{0}' (it should be formatted like {1})" , pair , "BTC_USD,BTC_CAD" ] ) ;
2024-04-04 10:47:28 +02:00
return View ( model ) ;
}
2024-04-04 11:00:18 +02:00
pairs . Add ( currencyPair ) ;
2024-04-04 10:47:28 +02:00
}
2024-04-04 10:58:47 +02:00
2024-04-30 11:31:15 +02:00
var fetchs = _rateFactory . FetchRates ( pairs . ToHashSet ( ) , rules , new StoreIdRateContext ( model . StoreId ) , cancellationToken ) ;
2024-04-04 11:00:18 +02:00
var testResults = new List < RatesViewModel . TestResultViewModel > ( ) ;
foreach ( var fetch in fetchs )
2024-04-04 10:47:28 +02:00
{
2024-04-04 11:00:18 +02:00
var testResult = await ( fetch . Value ) ;
testResults . Add ( new RatesViewModel . TestResultViewModel
{
CurrencyPair = fetch . Key . ToString ( ) ,
Error = testResult . Errors . Count ! = 0 ,
Rule = testResult . Errors . Count = = 0 ? testResult . Rule + " = " + testResult . BidAsk . Bid . ToString ( CultureInfo . InvariantCulture )
: testResult . EvaluatedRule
} ) ;
2024-04-04 10:47:28 +02:00
}
2024-04-04 11:00:18 +02:00
model . TestRateRules = testResults ;
return View ( model ) ;
2024-04-04 10:47:28 +02:00
}
2024-05-13 15:29:42 +02:00
if ( model . PreferredExchange is not null & & ! model . AvailableExchanges . Any ( a = > a . Id = = model . PreferredExchange ) )
{
2024-10-17 15:51:40 +02:00
ModelState . AddModelError ( nameof ( model . PreferredExchange ) , StringLocalizer [ "Unsupported exchange" ] ) ;
2024-05-13 15:29:42 +02:00
return View ( model ) ;
}
2024-04-04 11:00:18 +02:00
// command == Save
if ( CurrentStore . SetStoreBlob ( blob ) )
2024-04-04 10:47:28 +02:00
{
2024-04-04 10:44:08 +02:00
await _storeRepo . UpdateStore ( CurrentStore ) ;
2024-04-04 11:00:18 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = "Rate settings updated" ;
2024-04-04 10:47:28 +02:00
}
2024-04-04 11:00:18 +02:00
return RedirectToAction ( nameof ( Rates ) , new
{
storeId = CurrentStore . Id
} ) ;
}
2024-04-04 10:47:28 +02:00
2024-04-04 11:00:18 +02:00
[HttpGet("{storeId}/rates/confirm")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult ShowRateRules ( bool scripting )
{
return View ( "Confirm" , new ConfirmModel
2024-04-04 10:47:28 +02:00
{
2024-10-17 15:51:40 +02:00
Action = StringLocalizer [ "Continue" ] ,
Title = StringLocalizer [ "Rate rule scripting" ] ,
Description = scripting
? StringLocalizer [ "This action will modify your current rate sources. Are you sure to turn on rate rules scripting? (Advanced users)" ]
: StringLocalizer [ "This action will delete your rate script. Are you sure to turn off rate rules scripting?" ] ,
2024-04-04 11:00:18 +02:00
ButtonClass = scripting ? "btn-primary" : "btn-danger"
} ) ;
}
[HttpPost("{storeId}/rates/confirm")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task < IActionResult > ShowRateRulesPost ( bool scripting )
{
var blob = CurrentStore . GetStoreBlob ( ) ;
blob . RateScripting = scripting ;
2024-05-13 15:29:42 +02:00
blob . RateScript = blob . GetDefaultRateRules ( _defaultRules ) . ToString ( ) ;
2024-04-04 11:00:18 +02:00
CurrentStore . SetStoreBlob ( blob ) ;
await _storeRepo . UpdateStore ( CurrentStore ) ;
TempData [ WellKnownTempData . SuccessMessage ] = "Rate rules scripting " + ( scripting ? "activated" : "deactivated" ) ;
return RedirectToAction ( nameof ( Rates ) , new { storeId = CurrentStore . Id } ) ;
}
2024-05-13 15:29:42 +02:00
private void FillFromStore ( RatesViewModel vm , StoreBlob storeBlob )
2024-04-04 11:00:18 +02:00
{
2024-05-13 15:29:42 +02:00
var sources = _rateFactory . RateProviderFactory . AvailableRateProviders
2024-04-04 11:00:18 +02:00
. OrderBy ( s = > s . DisplayName , StringComparer . OrdinalIgnoreCase ) ;
2024-05-13 15:29:42 +02:00
vm . AvailableExchanges = sources ;
var exchange = storeBlob . GetPreferredExchange ( _defaultRules ) ;
var chosenSource = sources . First ( r = > r . Id = = exchange ) ;
2024-10-14 07:11:00 +02:00
vm . Exchanges = _userStoresController . GetExchangesSelectList ( storeBlob ) ;
2024-05-13 15:29:42 +02:00
vm . PreferredExchange = vm . Exchanges . SelectedValue as string ;
vm . PreferredResolvedExchange = chosenSource . Id ;
vm . RateSource = chosenSource . Url ;
vm . Spread = ( double ) ( storeBlob . Spread * 100 m ) ;
vm . StoreId = CurrentStore . Id ;
vm . Script = storeBlob . GetRateRules ( _defaultRules ) . ToString ( ) ;
vm . DefaultScript = storeBlob . GetDefaultRateRules ( _defaultRules ) . ToString ( ) ;
vm . DefaultCurrencyPairs = storeBlob . GetDefaultCurrencyPairString ( ) ;
vm . ShowScripting = storeBlob . RateScripting ;
2024-04-04 10:47:28 +02:00
}
}