2020-06-29 04:44:35 +02:00
using System ;
2018-08-22 13:57:54 +02:00
using System.Collections.Generic ;
using System.Globalization ;
using System.Linq ;
2018-10-24 07:52:19 +02:00
using System.Net.Http ;
2019-03-05 09:09:17 +01:00
using System.Threading ;
2018-08-22 13:57:54 +02:00
using System.Threading.Tasks ;
2020-11-17 13:46:23 +01:00
using BTCPayServer.Abstractions.Constants ;
using BTCPayServer.Abstractions.Extensions ;
using BTCPayServer.Abstractions.Models ;
2020-03-19 11:11:15 +01:00
using BTCPayServer.Client ;
2018-02-26 10:58:02 +01:00
using BTCPayServer.Configuration ;
2017-10-17 06:52:30 +02:00
using BTCPayServer.Data ;
2019-08-20 10:38:15 +02:00
using BTCPayServer.HostedServices ;
2017-09-13 16:50:36 +02:00
using BTCPayServer.Models ;
using BTCPayServer.Models.StoreViewModels ;
2019-01-07 09:52:27 +01:00
using BTCPayServer.Payments ;
using BTCPayServer.Payments.Lightning ;
2018-05-03 18:46:52 +02:00
using BTCPayServer.Rating ;
2018-04-29 19:33:42 +02:00
using BTCPayServer.Security ;
2019-10-18 17:54:20 +02:00
using BTCPayServer.Security.Bitpay ;
2018-02-12 19:27:36 +01:00
using BTCPayServer.Services ;
2020-03-15 10:51:57 +01:00
using BTCPayServer.Services.Apps ;
2019-05-29 16:33:31 +02:00
using BTCPayServer.Services.Invoices ;
2018-04-18 09:38:17 +02:00
using BTCPayServer.Services.Rates ;
2017-09-15 09:06:57 +02:00
using BTCPayServer.Services.Stores ;
using BTCPayServer.Services.Wallets ;
2020-09-18 17:20:31 +02:00
using BundlerMinifier.TagHelpers ;
2017-09-13 16:50:36 +02:00
using Microsoft.AspNetCore.Authorization ;
2021-06-17 11:27:17 +02:00
using Microsoft.AspNetCore.DataProtection ;
2017-09-13 16:50:36 +02:00
using Microsoft.AspNetCore.Hosting ;
using Microsoft.AspNetCore.Identity ;
using Microsoft.AspNetCore.Mvc ;
2017-10-17 06:52:30 +02:00
using Microsoft.AspNetCore.Mvc.Rendering ;
2021-01-02 13:44:28 +01:00
using Microsoft.Extensions.Options ;
2017-09-13 16:50:36 +02:00
using NBitcoin ;
2017-12-06 10:08:21 +01:00
using NBitcoin.DataEncoders ;
2020-05-07 22:37:10 +02:00
using NBXplorer ;
2021-01-11 03:22:42 +01:00
using NBXplorer.DerivationStrategy ;
2020-05-23 21:13:18 +02:00
using StoreData = BTCPayServer . Data . StoreData ;
2017-09-13 16:50:36 +02:00
namespace BTCPayServer.Controllers
{
2017-10-27 10:53:04 +02:00
[Route("stores")]
2019-10-12 13:35:30 +02:00
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
2020-03-20 05:41:47 +01:00
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
2017-10-27 10:53:04 +02:00
[AutoValidateAntiforgeryToken]
2018-02-25 16:48:12 +01:00
public partial class StoresController : Controller
2017-10-27 10:53:04 +02:00
{
2020-06-29 05:07:48 +02:00
readonly RateFetcher _RateFactory ;
2018-03-23 08:24:57 +01:00
public string CreatedStoreId { get ; set ; }
2017-10-27 10:53:04 +02:00
public StoresController (
2018-02-25 16:48:12 +01:00
IServiceProvider serviceProvider ,
2018-02-26 10:58:02 +01:00
BTCPayServerOptions btcpayServerOptions ,
BTCPayServerEnvironment btcpayEnv ,
2017-10-27 10:53:04 +02:00
StoreRepository repo ,
TokenRepository tokenRepo ,
UserManager < ApplicationUser > userManager ,
AccessTokenController tokenController ,
2018-01-11 06:36:12 +01:00
BTCPayWalletProvider walletProvider ,
2017-12-21 07:52:04 +01:00
BTCPayNetworkProvider networkProvider ,
2018-08-22 09:53:40 +02:00
RateFetcher rateFactory ,
2018-01-08 14:45:09 +01:00
ExplorerClientProvider explorerProvider ,
2018-03-23 09:27:48 +01:00
LanguageService langService ,
2019-08-20 10:38:15 +02:00
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary ,
2019-12-18 14:28:03 +01:00
SettingsRepository settingsRepository ,
IAuthorizationService authorizationService ,
2020-01-18 06:12:27 +01:00
EventAggregator eventAggregator ,
2020-09-18 17:20:31 +02:00
AppService appService ,
2021-01-02 13:44:28 +01:00
WebhookNotificationManager webhookNotificationManager ,
2021-09-03 08:37:12 +02:00
IDataProtectionProvider dataProtector ,
NBXplorerDashboard Dashboard )
2017-10-27 10:53:04 +02:00
{
2018-05-03 18:46:52 +02:00
_RateFactory = rateFactory ;
2017-10-27 10:53:04 +02:00
_Repo = repo ;
_TokenRepository = tokenRepo ;
_UserManager = userManager ;
2018-03-23 09:27:48 +01:00
_LangService = langService ;
2017-10-27 10:53:04 +02:00
_TokenController = tokenController ;
2018-01-11 06:36:12 +01:00
_WalletProvider = walletProvider ;
2019-05-29 16:33:31 +02:00
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary ;
2019-12-18 14:28:03 +01:00
_settingsRepository = settingsRepository ;
_authorizationService = authorizationService ;
2020-03-15 10:51:57 +01:00
_appService = appService ;
2021-06-17 11:27:17 +02:00
DataProtector = dataProtector . CreateProtector ( "ConfigProtector" ) ;
2020-11-06 12:42:26 +01:00
WebhookNotificationManager = webhookNotificationManager ;
2020-01-18 06:12:27 +01:00
_EventAggregator = eventAggregator ;
2018-01-06 10:57:56 +01:00
_NetworkProvider = networkProvider ;
2018-01-08 14:45:09 +01:00
_ExplorerProvider = explorerProvider ;
2018-02-25 16:48:12 +01:00
_ServiceProvider = serviceProvider ;
2018-02-26 10:58:02 +01:00
_BtcpayServerOptions = btcpayServerOptions ;
_BTCPayEnv = btcpayEnv ;
2021-09-03 08:37:12 +02:00
_Dashboard = Dashboard ;
2017-10-27 10:53:04 +02:00
}
2020-06-29 05:07:48 +02:00
readonly BTCPayServerOptions _BtcpayServerOptions ;
readonly BTCPayServerEnvironment _BTCPayEnv ;
readonly IServiceProvider _ServiceProvider ;
readonly BTCPayNetworkProvider _NetworkProvider ;
private readonly ExplorerClientProvider _ExplorerProvider ;
readonly BTCPayWalletProvider _WalletProvider ;
readonly AccessTokenController _TokenController ;
readonly StoreRepository _Repo ;
readonly TokenRepository _TokenRepository ;
readonly UserManager < ApplicationUser > _UserManager ;
private readonly LanguageService _LangService ;
2019-05-29 16:33:31 +02:00
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary ;
2019-12-18 14:28:03 +01:00
private readonly SettingsRepository _settingsRepository ;
private readonly IAuthorizationService _authorizationService ;
2020-03-15 10:51:57 +01:00
private readonly AppService _appService ;
2020-01-18 06:12:27 +01:00
private readonly EventAggregator _EventAggregator ;
2021-09-03 08:37:12 +02:00
private readonly NBXplorerDashboard _Dashboard ;
2017-10-27 10:53:04 +02:00
2019-01-18 11:15:31 +01:00
[TempData]
public bool StoreNotConfigured
{
get ; set ;
}
2017-10-27 10:53:04 +02:00
[HttpGet]
2018-03-23 08:24:57 +01:00
[Route("{storeId}/users")]
2018-04-30 15:00:43 +02:00
public async Task < IActionResult > StoreUsers ( )
2018-03-23 08:24:57 +01:00
{
StoreUsersViewModel vm = new StoreUsersViewModel ( ) ;
2018-04-30 15:00:43 +02:00
await FillUsers ( vm ) ;
2018-03-23 08:24:57 +01:00
return View ( vm ) ;
}
2018-04-30 15:00:43 +02:00
private async Task FillUsers ( StoreUsersViewModel vm )
2017-10-27 10:53:04 +02:00
{
2019-10-12 13:35:30 +02:00
var users = await _Repo . GetStoreUsers ( CurrentStore . Id ) ;
vm . StoreId = CurrentStore . Id ;
2018-03-23 08:24:57 +01:00
vm . Users = users . Select ( u = > new StoreUsersViewModel . StoreUserViewModel ( )
2017-10-27 10:53:04 +02:00
{
2018-03-23 08:24:57 +01:00
Email = u . Email ,
Id = u . Id ,
Role = u . Role
} ) . ToList ( ) ;
2017-10-27 10:53:04 +02:00
}
2019-10-12 13:35:30 +02:00
public StoreData CurrentStore
2018-04-30 15:00:43 +02:00
{
get
{
return this . HttpContext . GetStoreData ( ) ;
}
}
2021-09-04 15:07:09 +02:00
public bool TaprootActivated ( string crytoCode )
2021-09-03 08:37:12 +02:00
{
2021-09-04 15:07:09 +02:00
var network = ( BTCPayNetwork ) _NetworkProvider . GetNetwork ( crytoCode ) ;
#pragma warning disable CS0618
if ( ! ( network . IsBTC & & network . NBitcoinNetwork . ChainName = = ChainName . Mainnet ) )
// Consider it activated for everything that is not mainnet bitcoin
return true ;
#pragma warning restore CS0618
2021-09-03 08:37:12 +02:00
var status = _Dashboard . Get ( crytoCode ) . Status ;
2021-09-04 15:07:09 +02:00
return status . ChainHeight > = 709632 ;
2021-09-03 08:37:12 +02:00
}
2018-04-30 15:00:43 +02:00
2018-03-23 08:24:57 +01:00
[HttpPost]
[Route("{storeId}/users")]
2018-04-30 15:00:43 +02:00
public async Task < IActionResult > StoreUsers ( StoreUsersViewModel vm )
2018-02-15 05:33:29 +01:00
{
2018-04-30 15:00:43 +02:00
await FillUsers ( vm ) ;
2018-03-24 12:40:26 +01:00
if ( ! ModelState . IsValid )
2018-02-15 05:33:29 +01:00
{
2018-03-23 08:24:57 +01:00
return View ( vm ) ;
}
var user = await _UserManager . FindByEmailAsync ( vm . Email ) ;
2018-03-24 12:40:26 +01:00
if ( user = = null )
2018-03-23 08:24:57 +01:00
{
ModelState . AddModelError ( nameof ( vm . Email ) , "User not found" ) ;
return View ( vm ) ;
}
2018-03-24 12:40:26 +01:00
if ( ! StoreRoles . AllRoles . Contains ( vm . Role ) )
2018-03-23 08:24:57 +01:00
{
ModelState . AddModelError ( nameof ( vm . Role ) , "Invalid role" ) ;
return View ( vm ) ;
}
2019-10-12 13:35:30 +02:00
if ( ! await _Repo . AddStoreUser ( CurrentStore . Id , user . Id , vm . Role ) )
2018-03-23 08:24:57 +01:00
{
ModelState . AddModelError ( nameof ( vm . Email ) , "The user already has access to this store" ) ;
return View ( vm ) ;
2018-02-15 05:33:29 +01:00
}
2021-09-07 04:55:53 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = "User added successfully." ;
2018-03-23 08:24:57 +01:00
return RedirectToAction ( nameof ( StoreUsers ) ) ;
2018-02-15 05:33:29 +01:00
}
2021-09-07 04:55:53 +02:00
[HttpGet("{storeId}/users/{userId}/delete")]
2018-04-30 15:00:43 +02:00
public async Task < IActionResult > DeleteStoreUser ( string userId )
2017-10-27 10:53:04 +02:00
{
2018-03-23 08:24:57 +01:00
var user = await _UserManager . FindByIdAsync ( userId ) ;
if ( user = = null )
return NotFound ( ) ;
2021-09-07 04:55:53 +02:00
return View ( "Confirm" , new ConfirmModel ( "Remove store user" , $"This action will prevent <strong>{user.Email}</strong> from accessing this store and its settings. Are you sure?" , "Remove" ) ) ;
2017-10-27 10:53:04 +02:00
}
2021-09-07 04:55:53 +02:00
[HttpPost("{storeId}/users/{userId}/delete")]
2018-03-23 08:24:57 +01:00
public async Task < IActionResult > DeleteStoreUserPost ( string storeId , string userId )
2017-10-27 10:53:04 +02:00
{
2018-03-23 08:24:57 +01:00
await _Repo . RemoveStoreUser ( storeId , userId ) ;
2021-09-07 04:55:53 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = "User removed successfully." ;
return RedirectToAction ( nameof ( StoreUsers ) , new { storeId , userId } ) ;
2017-10-27 10:53:04 +02:00
}
2021-09-07 04:55:53 +02:00
[HttpGet("{storeId}/rates")]
2020-01-13 14:20:45 +01:00
public IActionResult Rates ( )
2018-05-03 18:46:52 +02:00
{
2020-01-13 14:20:45 +01:00
var exchanges = GetSupportedExchanges ( ) ;
2019-10-12 13:35:30 +02:00
var storeBlob = CurrentStore . GetStoreBlob ( ) ;
2018-05-03 18:46:52 +02:00
var vm = new RatesViewModel ( ) ;
2020-01-10 14:50:39 +01:00
vm . SetExchangeRates ( exchanges , storeBlob . PreferredExchange ? ? CoinGeckoRateProvider . CoinGeckoName ) ;
2018-08-01 11:38:46 +02:00
vm . Spread = ( double ) ( storeBlob . Spread * 100 m ) ;
2019-10-12 13:35:30 +02:00
vm . StoreId = CurrentStore . Id ;
2018-05-03 18:46:52 +02:00
vm . Script = storeBlob . GetRateRules ( _NetworkProvider ) . ToString ( ) ;
vm . DefaultScript = storeBlob . GetDefaultRateRules ( _NetworkProvider ) . ToString ( ) ;
2020-01-10 14:50:39 +01:00
vm . AvailableExchanges = exchanges ;
2019-03-11 10:39:21 +01:00
vm . DefaultCurrencyPairs = storeBlob . GetDefaultCurrencyPairString ( ) ;
2018-05-03 18:46:52 +02:00
vm . ShowScripting = storeBlob . RateScripting ;
return View ( vm ) ;
}
2021-09-07 04:55:53 +02:00
[HttpPost("{storeId}/rates")]
2019-03-11 10:39:21 +01:00
public async Task < IActionResult > Rates ( RatesViewModel model , string command = null , string storeId = null , CancellationToken cancellationToken = default )
2018-05-03 18:46:52 +02:00
{
2020-01-10 14:50:39 +01:00
if ( command = = "scripting-on" )
{
2020-06-28 10:55:27 +02:00
return RedirectToAction ( nameof ( ShowRateRules ) , new { scripting = true , storeId = model . StoreId } ) ;
}
else if ( command = = "scripting-off" )
2020-01-10 14:50:39 +01:00
{
2020-06-28 10:55:27 +02:00
return RedirectToAction ( nameof ( ShowRateRules ) , new { scripting = false , storeId = model . StoreId } ) ;
2020-01-10 14:50:39 +01:00
}
2020-01-13 14:20:45 +01:00
var exchanges = GetSupportedExchanges ( ) ;
2020-01-10 14:50:39 +01:00
model . SetExchangeRates ( exchanges , model . PreferredExchange ) ;
2019-03-11 10:39:21 +01:00
model . StoreId = storeId ? ? model . StoreId ;
CurrencyPair [ ] currencyPairs = null ;
try
{
currencyPairs = model . DefaultCurrencyPairs ?
. Split ( new [ ] { ',' } , StringSplitOptions . RemoveEmptyEntries )
. Select ( p = > CurrencyPair . Parse ( p ) )
. ToArray ( ) ;
}
catch
{
ModelState . AddModelError ( nameof ( model . DefaultCurrencyPairs ) , "Invalid currency pairs (should be for example: BTC_USD,BTC_CAD,BTC_JPY)" ) ;
}
2018-05-03 18:46:52 +02:00
if ( ! ModelState . IsValid )
{
return View ( model ) ;
}
if ( model . PreferredExchange ! = null )
model . PreferredExchange = model . PreferredExchange . Trim ( ) . ToLowerInvariant ( ) ;
2019-10-12 13:35:30 +02:00
var blob = CurrentStore . GetStoreBlob ( ) ;
2018-05-03 18:46:52 +02:00
model . DefaultScript = blob . GetDefaultRateRules ( _NetworkProvider ) . ToString ( ) ;
2020-01-10 14:50:39 +01:00
model . AvailableExchanges = exchanges ;
2018-05-03 18:46:52 +02:00
blob . PreferredExchange = model . PreferredExchange ;
2018-08-01 11:38:46 +02:00
blob . Spread = ( decimal ) model . Spread / 100.0 m ;
2019-03-11 10:39:21 +01:00
blob . DefaultCurrencyPairs = currencyPairs ;
2018-05-03 18:46:52 +02:00
if ( ! model . ShowScripting )
{
2020-01-10 14:50:39 +01:00
if ( ! exchanges . Any ( provider = > provider . Id . Equals ( model . PreferredExchange , StringComparison . InvariantCultureIgnoreCase ) ) )
2018-05-03 18:46:52 +02:00
{
ModelState . AddModelError ( nameof ( model . PreferredExchange ) , $"Unsupported exchange ({model.RateSource})" ) ;
return View ( model ) ;
}
}
RateRules rules = null ;
if ( model . ShowScripting )
{
if ( ! RateRules . TryParse ( model . Script , out rules , out var errors ) )
{
errors = errors ? ? new List < RateRulesErrors > ( ) ;
var errorString = String . Join ( ", " , errors . ToArray ( ) ) ;
ModelState . AddModelError ( nameof ( model . Script ) , $"Parsing error ({errorString})" ) ;
return View ( model ) ;
}
else
{
blob . RateScript = rules . ToString ( ) ;
2018-05-04 04:48:03 +02:00
ModelState . Remove ( nameof ( model . Script ) ) ;
model . Script = blob . RateScript ;
2018-05-03 18:46:52 +02:00
}
}
rules = blob . GetRateRules ( _NetworkProvider ) ;
if ( command = = "Test" )
{
if ( string . IsNullOrWhiteSpace ( model . ScriptTest ) )
{
ModelState . AddModelError ( nameof ( model . ScriptTest ) , "Fill out currency pair to test for (like BTC_USD,BTC_CAD)" ) ;
return View ( model ) ;
}
var splitted = model . ScriptTest . Split ( ',' , StringSplitOptions . RemoveEmptyEntries ) ;
var pairs = new List < CurrencyPair > ( ) ;
foreach ( var pair in splitted )
{
if ( ! CurrencyPair . TryParse ( pair , out var currencyPair ) )
{
ModelState . AddModelError ( nameof ( model . ScriptTest ) , $"Invalid currency pair '{pair}' (it should be formatted like BTC_USD,BTC_CAD)" ) ;
return View ( model ) ;
}
pairs . Add ( currencyPair ) ;
}
2019-03-05 09:09:17 +01:00
var fetchs = _RateFactory . FetchRates ( pairs . ToHashSet ( ) , rules , cancellationToken ) ;
2018-05-03 18:46:52 +02:00
var testResults = new List < RatesViewModel . TestResultViewModel > ( ) ;
foreach ( var fetch in fetchs )
{
var testResult = await ( fetch . Value ) ;
testResults . Add ( new RatesViewModel . TestResultViewModel ( )
{
CurrencyPair = fetch . Key . ToString ( ) ,
Error = testResult . Errors . Count ! = 0 ,
2018-07-27 11:04:41 +02:00
Rule = testResult . Errors . Count = = 0 ? testResult . Rule + " = " + testResult . BidAsk . Bid . ToString ( CultureInfo . InvariantCulture )
2018-05-03 18:46:52 +02:00
: testResult . EvaluatedRule
} ) ;
}
model . TestRateRules = testResults ;
return View ( model ) ;
}
else // command == Save
{
2019-10-12 13:35:30 +02:00
if ( CurrentStore . SetStoreBlob ( blob ) )
2018-05-03 18:46:52 +02:00
{
2019-10-12 13:35:30 +02:00
await _Repo . UpdateStore ( CurrentStore ) ;
2019-10-31 04:29:59 +01:00
TempData [ WellKnownTempData . SuccessMessage ] = "Rate settings updated" ;
2018-05-03 18:46:52 +02:00
}
return RedirectToAction ( nameof ( Rates ) , new
{
2019-10-12 13:35:30 +02:00
storeId = CurrentStore . Id
2018-05-03 18:46:52 +02:00
} ) ;
}
}
2021-09-07 04:55:53 +02:00
[HttpGet("{storeId}/rates/confirm")]
2018-05-03 18:46:52 +02:00
public IActionResult ShowRateRules ( bool scripting )
{
2021-09-07 04:55:53 +02:00
return View ( "Confirm" , new ConfirmModel
2018-05-03 18:46:52 +02:00
{
2018-05-04 09:09:43 +02:00
Action = "Continue" ,
2018-05-03 18:46:52 +02:00
Title = "Rate rule scripting" ,
Description = scripting ?
2018-06-25 04:58:07 +02:00
"This action will modify your current rate sources. Are you sure to turn on rate rules scripting? (Advanced users)"
2018-05-03 18:46:52 +02:00
: "This action will delete your rate script. Are you sure to turn off rate rules scripting?" ,
2020-01-13 14:20:45 +01:00
ButtonClass = scripting ? "btn-primary" : "btn-danger"
2018-05-03 18:46:52 +02:00
} ) ;
}
2021-09-07 04:55:53 +02:00
[HttpPost("{storeId}/rates/confirm")]
2018-05-03 18:46:52 +02:00
public async Task < IActionResult > ShowRateRulesPost ( bool scripting )
{
2019-10-12 13:35:30 +02:00
var blob = CurrentStore . GetStoreBlob ( ) ;
2018-05-03 18:46:52 +02:00
blob . RateScripting = scripting ;
blob . RateScript = blob . GetDefaultRateRules ( _NetworkProvider ) . ToString ( ) ;
2019-10-12 13:35:30 +02:00
CurrentStore . SetStoreBlob ( blob ) ;
await _Repo . UpdateStore ( CurrentStore ) ;
2021-09-07 04:55:53 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = "Rate rules scripting " + ( scripting ? "activated" : "deactivated" ) ;
2019-10-12 13:35:30 +02:00
return RedirectToAction ( nameof ( Rates ) , new { storeId = CurrentStore . Id } ) ;
2018-05-03 18:46:52 +02:00
}
2018-03-27 07:48:32 +02:00
[HttpGet]
[Route("{storeId}/checkout")]
2018-04-30 15:00:43 +02:00
public IActionResult CheckoutExperience ( )
2018-03-27 07:48:32 +02:00
{
2019-10-12 13:35:30 +02:00
var storeBlob = CurrentStore . GetStoreBlob ( ) ;
2018-03-27 07:48:32 +02:00
var vm = new CheckoutExperienceViewModel ( ) ;
2019-10-12 13:35:30 +02:00
SetCryptoCurrencies ( vm , CurrentStore ) ;
2020-12-29 09:58:35 +01:00
vm . PaymentMethodCriteria = CurrentStore . GetSupportedPaymentMethods ( _NetworkProvider ) . Select ( method = >
{
var existing =
storeBlob . PaymentMethodCriteria . SingleOrDefault ( criteria = >
criteria . PaymentMethod = = method . PaymentId ) ;
if ( existing is null )
{
return new PaymentMethodCriteriaViewModel ( )
2020-09-15 11:09:09 +02:00
{
2020-12-29 09:58:35 +01:00
PaymentMethod = method . PaymentId . ToString ( ) ,
Value = ""
} ;
}
else
{
return new PaymentMethodCriteriaViewModel ( )
{
PaymentMethod = existing . PaymentMethod . ToString ( ) ,
Type = existing . Above
? PaymentMethodCriteriaViewModel . CriteriaType . GreaterThan
: PaymentMethodCriteriaViewModel . CriteriaType . LessThan ,
Value = existing . Value ? . ToString ( ) ? ? ""
} ;
}
} ) . ToList ( ) ;
2019-03-16 04:43:57 +01:00
vm . RequiresRefundEmail = storeBlob . RequiresRefundEmail ;
vm . LightningAmountInSatoshi = storeBlob . LightningAmountInSatoshi ;
2020-05-19 23:26:03 +02:00
vm . LightningPrivateRouteHints = storeBlob . LightningPrivateRouteHints ;
2020-11-09 08:11:03 +01:00
vm . OnChainWithLnInvoiceFallback = storeBlob . OnChainWithLnInvoiceFallback ;
2021-04-07 06:08:42 +02:00
vm . LazyPaymentMethods = storeBlob . LazyPaymentMethods ;
2019-04-11 11:53:31 +02:00
vm . RedirectAutomatically = storeBlob . RedirectAutomatically ;
2020-11-09 08:11:03 +01:00
vm . ShowRecommendedFee = storeBlob . ShowRecommendedFee ;
vm . RecommendedFeeBlockTarget = storeBlob . RecommendedFeeBlockTarget ;
vm . CustomCSS = storeBlob . CustomCSS ;
vm . CustomLogo = storeBlob . CustomLogo ;
vm . HtmlTitle = storeBlob . HtmlTitle ;
2021-07-27 08:17:56 +02:00
vm . AutoDetectLanguage = storeBlob . AutoDetectLanguage ;
2020-11-09 08:11:03 +01:00
vm . SetLanguages ( _LangService , storeBlob . DefaultLang ) ;
2018-03-27 07:48:32 +02:00
return View ( vm ) ;
}
2020-09-15 11:09:09 +02:00
2019-01-31 14:03:28 +01:00
void SetCryptoCurrencies ( CheckoutExperienceViewModel vm , Data . StoreData storeData )
{
var choices = storeData . GetEnabledPaymentIds ( _NetworkProvider )
2020-09-15 11:09:09 +02:00
. Select ( o = >
new CheckoutExperienceViewModel . Format ( )
{
2020-10-16 07:21:37 +02:00
Name = o . ToPrettyString ( ) ,
Value = o . ToString ( ) ,
PaymentId = o
2020-09-15 11:09:09 +02:00
} ) . ToArray ( ) ;
2019-01-31 14:03:28 +01:00
var defaultPaymentId = storeData . GetDefaultPaymentId ( _NetworkProvider ) ;
var chosen = choices . FirstOrDefault ( c = > c . PaymentId = = defaultPaymentId ) ;
2020-09-15 11:09:09 +02:00
vm . PaymentMethods = new SelectList ( choices , nameof ( chosen . Value ) , nameof ( chosen . Name ) , chosen ? . Value ) ;
2019-01-31 14:03:28 +01:00
vm . DefaultPaymentMethod = chosen ? . Value ;
}
2020-09-15 11:09:09 +02:00
2018-03-27 07:48:32 +02:00
[HttpPost]
[Route("{storeId}/checkout")]
2018-04-30 15:00:43 +02:00
public async Task < IActionResult > CheckoutExperience ( CheckoutExperienceViewModel model )
2018-03-27 07:48:32 +02:00
{
bool needUpdate = false ;
2019-10-12 13:35:30 +02:00
var blob = CurrentStore . GetStoreBlob ( ) ;
2019-01-31 11:07:38 +01:00
var defaultPaymentMethodId = model . DefaultPaymentMethod = = null ? null : PaymentMethodId . Parse ( model . DefaultPaymentMethod ) ;
2019-10-12 13:35:30 +02:00
if ( CurrentStore . GetDefaultPaymentId ( _NetworkProvider ) ! = defaultPaymentMethodId )
2018-03-27 07:48:32 +02:00
{
needUpdate = true ;
2019-10-12 13:35:30 +02:00
CurrentStore . SetDefaultPaymentId ( defaultPaymentMethodId ) ;
2018-03-27 07:48:32 +02:00
}
2019-10-12 13:35:30 +02:00
SetCryptoCurrencies ( model , CurrentStore ) ;
2018-03-27 07:48:32 +02:00
model . SetLanguages ( _LangService , model . DefaultLang ) ;
2021-09-03 08:37:12 +02:00
model . PaymentMethodCriteria ? ? = new List < PaymentMethodCriteriaViewModel > ( ) ;
2020-09-15 11:09:09 +02:00
for ( var index = 0 ; index < model . PaymentMethodCriteria . Count ; index + + )
{
var methodCriterion = model . PaymentMethodCriteria [ index ] ;
if ( ! string . IsNullOrWhiteSpace ( methodCriterion . Value ) )
{
if ( ! CurrencyValue . TryParse ( methodCriterion . Value , out var value ) )
{
2020-10-31 11:34:40 +01:00
model . AddModelError ( viewModel = > viewModel . PaymentMethodCriteria [ index ] . Value ,
$"{methodCriterion.PaymentMethod}: invalid format (1.0 USD)" , this ) ;
2020-09-15 11:09:09 +02:00
}
}
}
2021-09-03 08:37:12 +02:00
2018-04-18 09:38:17 +02:00
if ( ! ModelState . IsValid )
2018-04-03 10:54:50 +02:00
{
return View ( model ) ;
}
2020-09-15 11:09:09 +02:00
blob . PaymentMethodCriteria = model . PaymentMethodCriteria
. Where ( viewModel = > ! string . IsNullOrEmpty ( viewModel . Value ) ) . Select ( viewModel = >
{
CurrencyValue . TryParse ( viewModel . Value , out var cv ) ;
2020-10-16 07:21:37 +02:00
return new PaymentMethodCriteria ( ) { Above = viewModel . Type = = PaymentMethodCriteriaViewModel . CriteriaType . GreaterThan , Value = cv , PaymentMethod = PaymentMethodId . Parse ( viewModel . PaymentMethod ) } ;
2020-09-15 11:09:09 +02:00
} ) . ToList ( ) ;
2020-11-09 08:11:03 +01:00
2019-03-16 04:43:57 +01:00
blob . RequiresRefundEmail = model . RequiresRefundEmail ;
2021-04-07 06:08:42 +02:00
blob . LazyPaymentMethods = model . LazyPaymentMethods ;
2019-03-16 04:43:57 +01:00
blob . LightningAmountInSatoshi = model . LightningAmountInSatoshi ;
2020-05-19 23:26:03 +02:00
blob . LightningPrivateRouteHints = model . LightningPrivateRouteHints ;
2020-11-09 08:11:03 +01:00
blob . OnChainWithLnInvoiceFallback = model . OnChainWithLnInvoiceFallback ;
2019-04-11 11:53:31 +02:00
blob . RedirectAutomatically = model . RedirectAutomatically ;
2020-11-09 08:11:03 +01:00
blob . ShowRecommendedFee = model . ShowRecommendedFee ;
blob . RecommendedFeeBlockTarget = model . RecommendedFeeBlockTarget ;
blob . CustomLogo = model . CustomLogo ;
blob . CustomCSS = model . CustomCSS ;
blob . HtmlTitle = string . IsNullOrWhiteSpace ( model . HtmlTitle ) ? null : model . HtmlTitle ;
2021-07-27 08:17:56 +02:00
blob . AutoDetectLanguage = model . AutoDetectLanguage ;
2020-11-09 08:11:03 +01:00
blob . DefaultLang = model . DefaultLang ;
2019-10-12 13:35:30 +02:00
if ( CurrentStore . SetStoreBlob ( blob ) )
2018-03-27 07:48:32 +02:00
{
needUpdate = true ;
}
if ( needUpdate )
{
2019-10-12 13:35:30 +02:00
await _Repo . UpdateStore ( CurrentStore ) ;
2019-10-31 04:29:59 +01:00
TempData [ WellKnownTempData . SuccessMessage ] = "Store successfully updated" ;
2018-03-27 07:48:32 +02:00
}
return RedirectToAction ( nameof ( CheckoutExperience ) , new
{
2019-10-12 13:35:30 +02:00
storeId = CurrentStore . Id
2018-03-27 07:48:32 +02:00
} ) ;
}
2017-10-27 10:53:04 +02:00
2018-07-27 13:37:16 +02:00
private void AddPaymentMethods ( StoreData store , StoreBlob storeBlob , StoreViewModel vm )
2017-10-27 10:53:04 +02:00
{
2018-07-27 13:37:16 +02:00
var excludeFilters = storeBlob . GetExcludedPaymentMethods ( ) ;
2018-03-24 12:40:26 +01:00
var derivationByCryptoCode =
2018-03-20 18:48:11 +01:00
store
. GetSupportedPaymentMethods ( _NetworkProvider )
2019-05-08 16:39:11 +02:00
. OfType < DerivationSchemeSettings > ( )
2019-12-24 08:20:44 +01:00
. ToDictionary ( c = > c . Network . CryptoCode . ToUpperInvariant ( ) ) ;
2018-02-07 13:59:16 +01:00
2018-03-20 18:48:11 +01:00
var lightningByCryptoCode = store
2019-05-29 16:33:31 +02:00
. GetSupportedPaymentMethods ( _NetworkProvider )
. OfType < LightningSupportedPaymentMethod > ( )
2019-12-24 08:20:44 +01:00
. ToDictionary ( c = > c . CryptoCode . ToUpperInvariant ( ) ) ;
2018-03-20 18:48:11 +01:00
2019-05-29 16:33:31 +02:00
foreach ( var paymentMethodId in _paymentMethodHandlerDictionary . Distinct ( ) . SelectMany ( handler = > handler . GetSupportedPaymentMethods ( ) ) )
2018-01-08 14:45:09 +01:00
{
2020-06-28 10:55:27 +02:00
switch ( paymentMethodId . PaymentType )
2017-10-27 10:53:04 +02:00
{
2019-06-04 01:59:01 +02:00
case BitcoinPaymentType _ :
2019-05-29 16:33:31 +02:00
var strategy = derivationByCryptoCode . TryGet ( paymentMethodId . CryptoCode ) ;
2019-12-24 08:20:44 +01:00
var network = _NetworkProvider . GetNetwork < BTCPayNetwork > ( paymentMethodId . CryptoCode ) ;
2020-01-26 11:45:24 +01:00
var value = strategy ? . ToPrettyString ( ) ? ? string . Empty ;
2020-06-28 10:55:27 +02:00
2019-05-29 16:33:31 +02:00
vm . DerivationSchemes . Add ( new StoreViewModel . DerivationScheme ( )
{
Crypto = paymentMethodId . CryptoCode ,
2019-12-24 08:20:44 +01:00
WalletSupported = network . WalletSupported ,
2020-01-26 11:45:24 +01:00
Value = value ,
2019-05-29 16:33:31 +02:00
WalletId = new WalletId ( store . Id , paymentMethodId . CryptoCode ) ,
2020-01-26 11:45:24 +01:00
Enabled = ! excludeFilters . Match ( paymentMethodId ) & & strategy ! = null ,
2020-07-29 11:55:28 +02:00
#if ALTCOINS
2020-01-26 11:45:24 +01:00
Collapsed = network is ElementsBTCPayNetwork elementsBTCPayNetwork & & elementsBTCPayNetwork . NetworkCryptoCode ! = elementsBTCPayNetwork . CryptoCode & & string . IsNullOrEmpty ( value )
2020-07-28 22:48:51 +02:00
#endif
2019-05-29 16:33:31 +02:00
} ) ;
break ;
2019-06-04 01:59:01 +02:00
case LightningPaymentType _ :
2019-05-29 16:33:31 +02:00
var lightning = lightningByCryptoCode . TryGet ( paymentMethodId . CryptoCode ) ;
2021-04-16 15:31:09 +02:00
var isEnabled = ! excludeFilters . Match ( paymentMethodId ) & & lightning ! = null ;
vm . LightningNodes . Add ( new StoreViewModel . LightningNode
2019-05-29 16:33:31 +02:00
{
CryptoCode = paymentMethodId . CryptoCode ,
2021-03-09 09:54:38 +01:00
Address = lightning ? . GetDisplayableConnectionString ( ) ,
2021-04-16 15:31:09 +02:00
Enabled = isEnabled
2019-05-29 16:33:31 +02:00
} ) ;
break ;
2020-06-28 10:55:27 +02:00
}
2018-01-08 14:45:09 +01:00
}
}
2021-09-03 08:37:12 +02:00
2021-06-18 03:25:17 +02:00
[HttpGet("{storeId}")]
public async Task < IActionResult > UpdateStore ( )
2020-10-16 06:30:46 +02:00
{
var store = HttpContext . GetStoreData ( ) ;
if ( store = = null )
return NotFound ( ) ;
var storeBlob = store . GetStoreBlob ( ) ;
var vm = new StoreViewModel ( ) ;
vm . Id = store . Id ;
vm . StoreName = store . StoreName ;
vm . StoreWebsite = store . StoreWebsite ;
vm . NetworkFeeMode = storeBlob . NetworkFeeMode ;
vm . AnyoneCanCreateInvoice = storeBlob . AnyoneCanInvoice ;
vm . SpeedPolicy = store . SpeedPolicy ;
vm . CanDelete = _Repo . CanDeleteStores ( ) ;
AddPaymentMethods ( store , storeBlob , vm ) ;
vm . MonitoringExpiration = ( int ) storeBlob . MonitoringExpiration . TotalMinutes ;
vm . InvoiceExpiration = ( int ) storeBlob . InvoiceExpiration . TotalMinutes ;
vm . LightningDescriptionTemplate = storeBlob . LightningDescriptionTemplate ;
vm . PaymentTolerance = storeBlob . PaymentTolerance ;
vm . PayJoinEnabled = storeBlob . PayJoinEnabled ;
vm . HintWallet = storeBlob . Hints . Wallet ;
2020-10-16 07:21:37 +02:00
vm . HintLightning = storeBlob . Hints . Lightning ;
2021-09-29 14:33:49 +02:00
vm . IsOnchainSetup = vm . DerivationSchemes . Any ( scheme = > ! string . IsNullOrWhiteSpace ( scheme . Value ) ) ;
vm . IsLightningSetup = vm . LightningNodes . Any ( scheme = > ! string . IsNullOrWhiteSpace ( scheme . Address ) ) ;
2021-09-03 08:37:12 +02:00
2021-06-18 03:25:17 +02:00
( bool canUseHotWallet , _ ) = await CanUseHotWallet ( ) ;
vm . CanUsePayJoin = canUseHotWallet & & store
. GetSupportedPaymentMethods ( _NetworkProvider )
. OfType < DerivationSchemeSettings > ( )
. Any ( settings = > settings . Network . SupportPayJoin & & settings . IsHotWallet ) ;
2021-09-03 08:37:12 +02:00
2020-10-16 06:30:46 +02:00
return View ( vm ) ;
}
2021-09-03 08:37:12 +02:00
2021-06-18 03:25:17 +02:00
[HttpPost("{storeId}")]
2018-07-19 15:23:14 +02:00
public async Task < IActionResult > UpdateStore ( StoreViewModel model , string command = null )
2018-01-08 14:45:09 +01:00
{
bool needUpdate = false ;
2019-10-12 13:35:30 +02:00
if ( CurrentStore . SpeedPolicy ! = model . SpeedPolicy )
2018-01-08 14:45:09 +01:00
{
needUpdate = true ;
2019-10-12 13:35:30 +02:00
CurrentStore . SpeedPolicy = model . SpeedPolicy ;
2018-01-08 14:45:09 +01:00
}
2019-10-12 13:35:30 +02:00
if ( CurrentStore . StoreName ! = model . StoreName )
2018-01-08 14:45:09 +01:00
{
needUpdate = true ;
2019-10-12 13:35:30 +02:00
CurrentStore . StoreName = model . StoreName ;
2018-01-08 14:45:09 +01:00
}
2019-10-12 13:35:30 +02:00
if ( CurrentStore . StoreWebsite ! = model . StoreWebsite )
2018-01-08 14:45:09 +01:00
{
needUpdate = true ;
2019-10-12 13:35:30 +02:00
CurrentStore . StoreWebsite = model . StoreWebsite ;
2018-01-08 14:45:09 +01:00
}
2019-10-12 13:35:30 +02:00
var blob = CurrentStore . GetStoreBlob ( ) ;
2018-09-08 07:32:26 +02:00
blob . AnyoneCanInvoice = model . AnyoneCanCreateInvoice ;
2019-01-04 16:37:09 +01:00
blob . NetworkFeeMode = model . NetworkFeeMode ;
2020-08-26 07:01:39 +02:00
blob . MonitoringExpiration = TimeSpan . FromMinutes ( model . MonitoringExpiration ) ;
blob . InvoiceExpiration = TimeSpan . FromMinutes ( model . InvoiceExpiration ) ;
2018-04-07 09:27:46 +02:00
blob . LightningDescriptionTemplate = model . LightningDescriptionTemplate ? ? string . Empty ;
2018-05-04 16:15:34 +02:00
blob . PaymentTolerance = model . PaymentTolerance ;
2020-05-07 22:37:10 +02:00
var payjoinChanged = blob . PayJoinEnabled ! = model . PayJoinEnabled ;
2020-03-17 08:43:42 +01:00
blob . PayJoinEnabled = model . PayJoinEnabled ;
2019-10-12 13:35:30 +02:00
if ( CurrentStore . SetStoreBlob ( blob ) )
2018-01-08 14:45:09 +01:00
{
needUpdate = true ;
}
if ( needUpdate )
{
2019-10-12 13:35:30 +02:00
await _Repo . UpdateStore ( CurrentStore ) ;
2020-06-28 10:55:27 +02:00
2019-10-31 04:29:59 +01:00
TempData [ WellKnownTempData . SuccessMessage ] = "Store successfully updated" ;
2020-05-07 22:37:10 +02:00
if ( payjoinChanged & & blob . PayJoinEnabled )
{
var problematicPayjoinEnabledMethods = CurrentStore . GetSupportedPaymentMethods ( _NetworkProvider )
. OfType < DerivationSchemeSettings > ( )
2021-06-18 03:25:17 +02:00
. Where ( settings = > settings . Network . SupportPayJoin & & ! settings . IsHotWallet )
2020-05-07 22:37:10 +02:00
. Select ( settings = > settings . PaymentId . CryptoCode )
. ToArray ( ) ;
if ( problematicPayjoinEnabledMethods . Any ( ) )
{
TempData . Remove ( WellKnownTempData . SuccessMessage ) ;
TempData . SetStatusMessageModel ( new StatusMessageModel ( )
{
Severity = StatusMessageModel . StatusSeverity . Warning ,
2020-06-11 16:09:33 +02:00
Html = $"The store was updated successfully. However, payjoin will not work for {string.Join(" , ", problematicPayjoinEnabledMethods)} until you configure them to be a <a href='https://docs.btcpayserver.org/HotWallet/' class='alert-link' target='_blank'>hot wallet</a>."
2020-05-07 22:37:10 +02:00
} ) ;
}
}
2018-01-08 14:45:09 +01:00
}
return RedirectToAction ( nameof ( UpdateStore ) , new
{
2019-10-12 13:35:30 +02:00
storeId = CurrentStore . Id
2018-01-08 14:45:09 +01:00
} ) ;
2018-07-19 15:23:14 +02:00
}
2021-09-07 04:55:53 +02:00
[HttpGet("{storeId}/delete")]
2018-07-19 15:23:14 +02:00
public IActionResult DeleteStore ( string storeId )
{
2021-09-07 04:55:53 +02:00
return View ( "Confirm" , new ConfirmModel ( "Delete store" , "The store will be permanently deleted. This action will also delete all invoices, apps and data associated with the store. Are you sure?" , "Delete" ) ) ;
2018-07-19 15:23:14 +02:00
}
2021-09-07 04:55:53 +02:00
[HttpPost("{storeId}/delete")]
2018-07-19 15:23:14 +02:00
public async Task < IActionResult > DeleteStorePost ( string storeId )
{
2019-10-12 13:35:30 +02:00
await _Repo . DeleteStore ( CurrentStore . Id ) ;
2021-09-07 04:55:53 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = "Store successfully deleted." ;
2018-07-19 15:23:14 +02:00
return RedirectToAction ( nameof ( UserStoresController . ListStores ) , "UserStores" ) ;
2017-10-27 10:53:04 +02:00
}
2020-01-13 14:20:45 +01:00
private IEnumerable < AvailableRateProvider > GetSupportedExchanges ( )
2018-04-18 09:38:17 +02:00
{
2020-01-13 14:20:45 +01:00
var exchanges = _RateFactory . RateProviderFactory . GetSupportedExchanges ( ) ;
2020-01-10 14:50:39 +01:00
return exchanges
. Where ( r = > ! string . IsNullOrWhiteSpace ( r . Name ) )
. OrderBy ( s = > s . Id , StringComparer . OrdinalIgnoreCase ) ;
2018-04-18 09:38:17 +02:00
}
2021-06-17 07:11:01 +02:00
private DerivationSchemeSettings ParseDerivationStrategy ( string derivationScheme , BTCPayNetwork network )
2017-10-27 10:53:04 +02:00
{
2019-05-09 09:05:18 +02:00
var parser = new DerivationSchemeParser ( network ) ;
2021-01-11 03:22:42 +01:00
try
{
var derivationSchemeSettings = new DerivationSchemeSettings ( ) ;
derivationSchemeSettings . Network = network ;
var result = parser . ParseOutputDescriptor ( derivationScheme ) ;
derivationSchemeSettings . AccountOriginal = derivationScheme . Trim ( ) ;
derivationSchemeSettings . AccountDerivation = result . Item1 ;
derivationSchemeSettings . AccountKeySettings = result . Item2 ? . Select ( ( path , i ) = > new AccountKeySettings ( )
{
RootFingerprint = path ? . MasterFingerprint ,
AccountKeyPath = path ? . KeyPath ,
AccountKey = result . Item1 . GetExtPubKeys ( ) . ElementAt ( i ) . GetWif ( parser . Network )
} ) . ToArray ( ) ? ? new AccountKeySettings [ result . Item1 . GetExtPubKeys ( ) . Count ( ) ] ;
return derivationSchemeSettings ;
}
catch ( Exception )
{
// ignored
}
2021-09-03 08:37:12 +02:00
2019-05-08 16:39:11 +02:00
return new DerivationSchemeSettings ( parser . Parse ( derivationScheme ) , network ) ;
2017-10-27 10:53:04 +02:00
}
[HttpGet]
[Route("{storeId}/Tokens")]
2018-04-30 15:00:43 +02:00
public async Task < IActionResult > ListTokens ( )
2017-10-27 10:53:04 +02:00
{
var model = new TokensViewModel ( ) ;
2019-10-12 13:35:30 +02:00
var tokens = await _TokenRepository . GetTokensByStoreIdAsync ( CurrentStore . Id ) ;
2019-01-18 11:15:31 +01:00
model . StoreNotConfigured = StoreNotConfigured ;
2017-10-27 10:53:04 +02:00
model . Tokens = tokens . Select ( t = > new TokenViewModel ( )
{
Label = t . Label ,
SIN = t . SIN ,
Id = t . Value
} ) . ToArray ( ) ;
2018-04-29 11:28:04 +02:00
2019-10-12 13:35:30 +02:00
model . ApiKey = ( await _TokenRepository . GetLegacyAPIKeys ( CurrentStore . Id ) ) . FirstOrDefault ( ) ;
2018-04-29 11:28:04 +02:00
if ( model . ApiKey = = null )
model . EncodedApiKey = "*API Key*" ;
else
model . EncodedApiKey = Encoders . Base64 . EncodeData ( Encoders . ASCII . DecodeData ( model . ApiKey ) ) ;
2017-10-27 10:53:04 +02:00
return View ( model ) ;
}
2021-09-07 04:55:53 +02:00
[HttpGet("{storeId}/tokens/{tokenId}/revoke")]
2018-10-31 09:59:09 +01:00
public async Task < IActionResult > RevokeToken ( string tokenId )
{
var token = await _TokenRepository . GetToken ( tokenId ) ;
2019-10-12 13:35:30 +02:00
if ( token = = null | | token . StoreId ! = CurrentStore . Id )
2018-10-31 09:59:09 +01:00
return NotFound ( ) ;
2021-09-07 04:55:53 +02:00
return View ( "Confirm" , new ConfirmModel ( "Revoke the token" , $"The access token with the label <strong>{token.Label}</strong> will be revoked. Do you wish to continue?" , "Revoke" ) ) ;
2018-10-31 09:59:09 +01:00
}
2021-09-07 04:55:53 +02:00
[HttpPost("{storeId}/tokens/{tokenId}/revoke")]
2018-10-31 09:59:09 +01:00
public async Task < IActionResult > RevokeTokenConfirm ( string tokenId )
{
var token = await _TokenRepository . GetToken ( tokenId ) ;
if ( token = = null | |
2019-10-12 13:35:30 +02:00
token . StoreId ! = CurrentStore . Id | |
2018-10-31 09:59:09 +01:00
! await _TokenRepository . DeleteToken ( tokenId ) )
2021-09-07 04:55:53 +02:00
TempData [ WellKnownTempData . ErrorMessage ] = "Failure to revoke this token." ;
2018-10-31 09:59:09 +01:00
else
2019-10-31 04:29:59 +01:00
TempData [ WellKnownTempData . SuccessMessage ] = "Token revoked" ;
2020-06-28 10:55:27 +02:00
return RedirectToAction ( nameof ( ListTokens ) , new { storeId = token . StoreId } ) ;
2018-10-31 09:59:09 +01:00
}
[HttpGet]
[Route("{storeId}/tokens/{tokenId}")]
public async Task < IActionResult > ShowToken ( string tokenId )
{
var token = await _TokenRepository . GetToken ( tokenId ) ;
2019-10-12 13:35:30 +02:00
if ( token = = null | | token . StoreId ! = CurrentStore . Id )
2018-10-31 09:59:09 +01:00
return NotFound ( ) ;
return View ( token ) ;
}
2017-10-27 10:53:04 +02:00
[HttpPost]
[Route("{storeId}/Tokens/Create")]
2019-11-03 08:16:52 +01:00
public async Task < IActionResult > CreateToken ( string storeId , CreateTokenViewModel model )
2017-10-27 10:53:04 +02:00
{
if ( ! ModelState . IsValid )
{
2019-11-03 08:16:52 +01:00
return View ( nameof ( CreateToken ) , model ) ;
2017-10-27 10:53:04 +02:00
}
model . Label = model . Label ? ? String . Empty ;
2018-03-23 08:24:57 +01:00
var userId = GetUserId ( ) ;
if ( userId = = null )
2019-10-12 13:35:30 +02:00
return Challenge ( AuthenticationSchemes . Cookie ) ;
2019-11-03 08:16:52 +01:00
storeId = model . StoreId ;
var store = CurrentStore ? ? await _Repo . FindStore ( storeId , userId ) ;
if ( store = = null )
return Challenge ( AuthenticationSchemes . Cookie ) ;
2017-10-27 10:53:04 +02:00
var tokenRequest = new TokenRequest ( )
{
Label = model . Label ,
Id = model . PublicKey = = null ? null : NBitpayClient . Extensions . BitIdExtensions . GetBitIDSIN ( new PubKey ( model . PublicKey ) )
} ;
string pairingCode = null ;
if ( model . PublicKey = = null )
{
tokenRequest . PairingCode = await _TokenRepository . CreatePairingCodeAsync ( ) ;
await _TokenRepository . UpdatePairingCode ( new PairingCodeEntity ( )
{
Id = tokenRequest . PairingCode ,
Label = model . Label ,
} ) ;
await _TokenRepository . PairWithStoreAsync ( tokenRequest . PairingCode , storeId ) ;
pairingCode = tokenRequest . PairingCode ;
}
else
{
2020-06-29 05:07:48 +02:00
pairingCode = ( await _TokenController . Tokens ( tokenRequest ) ) . Data [ 0 ] . PairingCode ;
2017-10-27 10:53:04 +02:00
}
2018-01-09 18:07:42 +01:00
GeneratedPairingCode = pairingCode ;
2017-10-27 10:53:04 +02:00
return RedirectToAction ( nameof ( RequestPairing ) , new
{
pairingCode = pairingCode ,
selectedStore = storeId
} ) ;
}
2018-01-09 18:07:42 +01:00
public string GeneratedPairingCode { get ; set ; }
2020-11-06 12:42:26 +01:00
public WebhookNotificationManager WebhookNotificationManager { get ; }
2021-06-17 11:27:17 +02:00
public IDataProtector DataProtector { get ; }
2018-01-09 18:07:42 +01:00
2017-10-27 10:53:04 +02:00
[HttpGet]
[Route("{storeId}/Tokens/Create")]
2019-11-03 08:16:52 +01:00
public IActionResult CreateToken ( string storeId )
{
var model = new CreateTokenViewModel ( ) ;
ViewBag . HidePublicKey = storeId = = null ;
ViewBag . ShowStores = storeId = = null ;
ViewBag . ShowMenu = storeId ! = null ;
model . StoreId = storeId ;
return View ( model ) ;
}
[HttpGet]
[Route("/api-tokens")]
[AllowAnonymous]
2018-04-30 15:00:43 +02:00
public async Task < IActionResult > CreateToken ( )
2017-10-27 10:53:04 +02:00
{
var userId = GetUserId ( ) ;
if ( string . IsNullOrWhiteSpace ( userId ) )
2019-10-12 13:35:30 +02:00
return Challenge ( AuthenticationSchemes . Cookie ) ;
var storeId = CurrentStore ? . Id ;
2021-04-08 15:32:42 +02:00
if ( storeId ! = null )
{
var store = await _Repo . FindStore ( storeId , userId ) ;
if ( store ! = null )
HttpContext . SetStoreData ( store ) ;
}
2017-10-27 10:53:04 +02:00
var model = new CreateTokenViewModel ( ) ;
2019-11-03 08:16:52 +01:00
ViewBag . HidePublicKey = true ;
ViewBag . ShowStores = true ;
ViewBag . ShowMenu = false ;
var stores = await _Repo . GetStoresByUserId ( userId ) ;
model . Stores = new SelectList ( stores . Where ( s = > s . Role = = StoreRoles . Owner ) , nameof ( CurrentStore . Id ) , nameof ( CurrentStore . StoreName ) ) ;
2020-01-12 07:32:26 +01:00
if ( ! model . Stores . Any ( ) )
2019-11-03 08:16:52 +01:00
{
TempData [ WellKnownTempData . ErrorMessage ] = "You need to be owner of at least one store before pairing" ;
return RedirectToAction ( nameof ( UserStoresController . ListStores ) , "UserStores" ) ;
2017-10-27 10:53:04 +02:00
}
return View ( model ) ;
}
2019-11-03 08:16:52 +01:00
[HttpPost]
[Route("/api-tokens")]
[AllowAnonymous]
public Task < IActionResult > CreateToken2 ( CreateTokenViewModel model )
{
return CreateToken ( model . StoreId , model ) ;
}
2018-04-29 11:28:04 +02:00
[HttpPost]
[Route("{storeId}/tokens/apikey")]
2020-06-28 10:55:27 +02:00
public async Task < IActionResult > GenerateAPIKey ( string storeId , string command = "" )
2018-04-29 11:28:04 +02:00
{
2018-04-29 19:33:42 +02:00
var store = HttpContext . GetStoreData ( ) ;
2018-04-29 11:28:04 +02:00
if ( store = = null )
return NotFound ( ) ;
2020-02-21 05:40:00 +01:00
if ( command = = "revoke" )
{
await _TokenRepository . RevokeLegacyAPIKeys ( CurrentStore . Id ) ;
TempData [ WellKnownTempData . SuccessMessage ] = "API Key revoked" ;
}
else
{
await _TokenRepository . GenerateLegacyAPIKey ( CurrentStore . Id ) ;
TempData [ WellKnownTempData . SuccessMessage ] = "API Key re-generated" ;
}
2020-06-28 10:55:27 +02:00
2020-01-14 14:06:46 +01:00
return RedirectToAction ( nameof ( ListTokens ) , new
{
storeId
} ) ;
2018-04-29 11:28:04 +02:00
}
2017-10-27 10:53:04 +02:00
[HttpGet]
[Route("/api-access-request")]
2018-04-30 15:00:43 +02:00
[AllowAnonymous]
2017-10-27 10:53:04 +02:00
public async Task < IActionResult > RequestPairing ( string pairingCode , string selectedStore = null )
{
2018-04-30 15:00:43 +02:00
var userId = GetUserId ( ) ;
if ( userId = = null )
2019-10-12 13:35:30 +02:00
return Challenge ( AuthenticationSchemes . Cookie ) ;
2018-03-23 08:24:57 +01:00
if ( pairingCode = = null )
return NotFound ( ) ;
2021-04-08 15:32:42 +02:00
if ( selectedStore ! = null )
{
var store = await _Repo . FindStore ( selectedStore , userId ) ;
if ( store = = null )
return NotFound ( ) ;
HttpContext . SetStoreData ( store ) ;
ViewBag . ShowStores = false ;
}
2017-10-27 10:53:04 +02:00
var pairing = await _TokenRepository . GetPairingAsync ( pairingCode ) ;
if ( pairing = = null )
{
2019-10-31 04:29:59 +01:00
TempData [ WellKnownTempData . ErrorMessage ] = "Unknown pairing code" ;
2018-03-23 08:24:57 +01:00
return RedirectToAction ( nameof ( UserStoresController . ListStores ) , "UserStores" ) ;
2017-10-27 10:53:04 +02:00
}
else
{
2018-04-30 15:00:43 +02:00
var stores = await _Repo . GetStoresByUserId ( userId ) ;
2021-04-08 15:32:42 +02:00
return View ( new PairingModel
2017-10-27 10:53:04 +02:00
{
Id = pairing . Id ,
Label = pairing . Label ,
SIN = pairing . SIN ? ? "Server-Initiated Pairing" ,
2019-10-12 13:35:30 +02:00
StoreId = selectedStore ? ? stores . FirstOrDefault ( ) ? . Id ,
Stores = stores . Where ( u = > u . Role = = StoreRoles . Owner ) . Select ( s = > new PairingModel . StoreViewModel ( )
2017-10-27 10:53:04 +02:00
{
Id = s . Id ,
Name = string . IsNullOrEmpty ( s . StoreName ) ? s . Id : s . StoreName
} ) . ToArray ( )
} ) ;
}
}
[HttpPost]
2018-03-23 08:24:57 +01:00
[Route("/api-access-request")]
2019-10-12 13:35:30 +02:00
public async Task < IActionResult > Pair ( string pairingCode , string storeId )
2017-10-27 10:53:04 +02:00
{
if ( pairingCode = = null )
return NotFound ( ) ;
2019-10-12 13:35:30 +02:00
var store = CurrentStore ;
2017-10-27 10:53:04 +02:00
var pairing = await _TokenRepository . GetPairingAsync ( pairingCode ) ;
if ( store = = null | | pairing = = null )
return NotFound ( ) ;
var pairingResult = await _TokenRepository . PairWithStoreAsync ( pairingCode , store . Id ) ;
if ( pairingResult = = PairingResult . Complete | | pairingResult = = PairingResult . Partial )
{
2019-01-18 11:15:31 +01:00
var excludeFilter = store . GetStoreBlob ( ) . GetExcludedPaymentMethods ( ) ;
2020-01-12 07:32:26 +01:00
StoreNotConfigured = ! store . GetSupportedPaymentMethods ( _NetworkProvider )
2019-01-18 11:15:31 +01:00
. Where ( p = > ! excludeFilter . Match ( p . PaymentId ) )
2020-01-12 07:32:26 +01:00
. Any ( ) ;
2019-10-31 04:29:59 +01:00
TempData [ WellKnownTempData . SuccessMessage ] = "Pairing is successful" ;
2017-10-27 10:53:04 +02:00
if ( pairingResult = = PairingResult . Partial )
2019-10-31 04:29:59 +01:00
TempData [ WellKnownTempData . SuccessMessage ] = "Server initiated pairing code: " + pairingCode ;
2017-10-27 10:53:04 +02:00
return RedirectToAction ( nameof ( ListTokens ) , new
{
2018-11-22 07:13:35 +01:00
storeId = store . Id ,
pairingCode = pairingCode
2017-10-27 10:53:04 +02:00
} ) ;
}
else
{
2019-10-31 04:29:59 +01:00
TempData [ WellKnownTempData . ErrorMessage ] = $"Pairing failed ({pairingResult})" ;
2017-10-27 10:53:04 +02:00
return RedirectToAction ( nameof ( ListTokens ) , new
{
storeId = store . Id
} ) ;
}
}
private string GetUserId ( )
{
2019-10-12 13:35:30 +02:00
if ( User . Identity . AuthenticationType ! = AuthenticationSchemes . Cookie )
2018-04-30 15:00:43 +02:00
return null ;
2017-10-27 10:53:04 +02:00
return _UserManager . GetUserId ( User ) ;
}
2018-08-22 13:57:54 +02:00
// TODO: Need to have talk about how architect default currency implementation
// For now we have also hardcoded USD for Store creation and then Invoice creation
const string DEFAULT_CURRENCY = "USD" ;
[Route("{storeId}/paybutton")]
2020-03-15 10:51:57 +01:00
public async Task < IActionResult > PayButton ( )
2018-08-22 13:57:54 +02:00
{
2019-10-12 13:35:30 +02:00
var store = CurrentStore ;
2018-08-22 13:57:54 +02:00
2018-09-04 06:48:53 +02:00
var storeBlob = store . GetStoreBlob ( ) ;
2018-09-08 07:32:26 +02:00
if ( ! storeBlob . AnyoneCanInvoice )
2018-09-04 06:48:53 +02:00
{
return View ( "PayButtonEnable" , null ) ;
}
2020-03-15 10:51:57 +01:00
var apps = await _appService . GetAllApps ( _UserManager . GetUserId ( User ) , false , store . Id ) ;
2018-08-23 04:11:39 +02:00
var appUrl = HttpContext . Request . GetAbsoluteRoot ( ) . WithTrailingSlash ( ) ;
2018-08-22 13:57:54 +02:00
var model = new PayButtonViewModel
{
2021-08-03 10:03:00 +02:00
Price = null ,
2018-08-22 13:57:54 +02:00
Currency = DEFAULT_CURRENCY ,
ButtonSize = 2 ,
UrlRoot = appUrl ,
2019-09-20 12:14:08 +02:00
PayButtonImageUrl = appUrl + "img/paybutton/pay.svg" ,
2019-04-03 21:43:53 +02:00
StoreId = store . Id ,
2019-04-04 21:32:16 +02:00
ButtonType = 0 ,
2019-04-04 20:56:12 +02:00
Min = 1 ,
Max = 20 ,
2021-05-05 11:27:02 +02:00
Step = "1" ,
2020-03-15 10:51:57 +01:00
Apps = apps
2018-08-22 13:57:54 +02:00
} ;
return View ( model ) ;
}
2018-09-04 06:48:53 +02:00
[HttpPost]
[Route("{storeId}/paybutton")]
public async Task < IActionResult > PayButton ( bool enableStore )
{
2019-10-12 13:35:30 +02:00
var blob = CurrentStore . GetStoreBlob ( ) ;
2018-09-08 07:32:26 +02:00
blob . AnyoneCanInvoice = enableStore ;
2019-10-12 13:35:30 +02:00
if ( CurrentStore . SetStoreBlob ( blob ) )
2018-09-04 06:48:53 +02:00
{
2019-10-12 13:35:30 +02:00
await _Repo . UpdateStore ( CurrentStore ) ;
2019-10-31 04:29:59 +01:00
TempData [ WellKnownTempData . SuccessMessage ] = "Store successfully updated" ;
2018-09-04 06:48:53 +02:00
}
return RedirectToAction ( nameof ( PayButton ) , new
{
2019-10-12 13:35:30 +02:00
storeId = CurrentStore . Id
2018-09-04 06:48:53 +02:00
} ) ;
}
2017-10-27 10:53:04 +02:00
}
2017-09-13 16:50:36 +02:00
}