2018-08-22 13:57:54 +02:00
|
|
|
|
using System;
|
|
|
|
|
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;
|
|
|
|
|
using BTCPayServer.Authentication;
|
2018-02-26 10:58:02 +01:00
|
|
|
|
using BTCPayServer.Configuration;
|
2017-10-17 06:52:30 +02:00
|
|
|
|
using BTCPayServer.Data;
|
2017-09-13 16:50:36 +02:00
|
|
|
|
using BTCPayServer.Models;
|
2018-08-22 13:57:54 +02:00
|
|
|
|
using BTCPayServer.Models.AppViewModels;
|
2017-09-13 16:50:36 +02:00
|
|
|
|
using BTCPayServer.Models.StoreViewModels;
|
2019-01-07 09:52:27 +01:00
|
|
|
|
using BTCPayServer.Payments;
|
2018-10-24 07:52:19 +02:00
|
|
|
|
using BTCPayServer.Payments.Changelly;
|
2019-01-07 09:52:27 +01:00
|
|
|
|
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;
|
2018-02-12 19:27:36 +01:00
|
|
|
|
using BTCPayServer.Services;
|
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;
|
2017-09-13 16:50:36 +02:00
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
2018-08-22 13:57:54 +02:00
|
|
|
|
using Microsoft.AspNetCore.Cors;
|
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;
|
2018-10-31 16:19:25 +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;
|
2017-09-13 16:50:36 +02:00
|
|
|
|
|
|
|
|
|
namespace BTCPayServer.Controllers
|
|
|
|
|
{
|
2017-10-27 10:53:04 +02:00
|
|
|
|
[Route("stores")]
|
2018-04-30 15:00:43 +02:00
|
|
|
|
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
|
2018-04-29 19:33:42 +02:00
|
|
|
|
[Authorize(Policy = Policies.CanModifyStoreSettings.Key)]
|
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
|
|
|
|
{
|
2018-08-22 09:53:40 +02:00
|
|
|
|
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-02-12 19:27:36 +01:00
|
|
|
|
IFeeProviderFactory feeRateProvider,
|
2018-03-23 09:27:48 +01:00
|
|
|
|
LanguageService langService,
|
2018-10-24 07:52:19 +02:00
|
|
|
|
ChangellyClientProvider changellyClientProvider,
|
2018-10-31 16:19:25 +01:00
|
|
|
|
IOptions<MvcJsonOptions> mvcJsonOptions,
|
2019-05-29 16:33:31 +02:00
|
|
|
|
IHostingEnvironment env, IHttpClientFactory httpClientFactory,
|
|
|
|
|
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary)
|
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;
|
2018-10-24 07:52:19 +02:00
|
|
|
|
_changellyClientProvider = changellyClientProvider;
|
2018-10-31 16:19:25 +01:00
|
|
|
|
MvcJsonOptions = mvcJsonOptions;
|
2017-10-27 10:53:04 +02:00
|
|
|
|
_TokenController = tokenController;
|
2018-01-11 06:36:12 +01:00
|
|
|
|
_WalletProvider = walletProvider;
|
2017-10-27 10:53:04 +02:00
|
|
|
|
_Env = env;
|
2018-10-24 07:52:19 +02:00
|
|
|
|
_httpClientFactory = httpClientFactory;
|
2019-05-29 16:33:31 +02:00
|
|
|
|
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
|
2018-01-06 10:57:56 +01:00
|
|
|
|
_NetworkProvider = networkProvider;
|
2018-01-08 14:45:09 +01:00
|
|
|
|
_ExplorerProvider = explorerProvider;
|
2018-02-12 19:27:36 +01:00
|
|
|
|
_FeeRateProvider = feeRateProvider;
|
2018-02-25 16:48:12 +01:00
|
|
|
|
_ServiceProvider = serviceProvider;
|
2018-02-26 10:58:02 +01:00
|
|
|
|
_BtcpayServerOptions = btcpayServerOptions;
|
|
|
|
|
_BTCPayEnv = btcpayEnv;
|
2017-10-27 10:53:04 +02:00
|
|
|
|
}
|
2018-02-26 10:58:02 +01:00
|
|
|
|
BTCPayServerOptions _BtcpayServerOptions;
|
|
|
|
|
BTCPayServerEnvironment _BTCPayEnv;
|
2018-02-25 16:48:12 +01:00
|
|
|
|
IServiceProvider _ServiceProvider;
|
2018-01-06 10:57:56 +01:00
|
|
|
|
BTCPayNetworkProvider _NetworkProvider;
|
2018-01-08 14:45:09 +01:00
|
|
|
|
private ExplorerClientProvider _ExplorerProvider;
|
2018-02-12 19:27:36 +01:00
|
|
|
|
private IFeeProviderFactory _FeeRateProvider;
|
2018-01-11 06:36:12 +01:00
|
|
|
|
BTCPayWalletProvider _WalletProvider;
|
2017-10-27 10:53:04 +02:00
|
|
|
|
AccessTokenController _TokenController;
|
|
|
|
|
StoreRepository _Repo;
|
|
|
|
|
TokenRepository _TokenRepository;
|
|
|
|
|
UserManager<ApplicationUser> _UserManager;
|
2018-03-23 09:27:48 +01:00
|
|
|
|
private LanguageService _LangService;
|
2018-10-24 07:52:19 +02:00
|
|
|
|
private readonly ChangellyClientProvider _changellyClientProvider;
|
2017-10-27 10:53:04 +02:00
|
|
|
|
IHostingEnvironment _Env;
|
2018-10-24 07:52:19 +02:00
|
|
|
|
private IHttpClientFactory _httpClientFactory;
|
2019-05-29 16:33:31 +02:00
|
|
|
|
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
|
2017-10-27 10:53:04 +02:00
|
|
|
|
|
|
|
|
|
[TempData]
|
|
|
|
|
public string StatusMessage
|
|
|
|
|
{
|
|
|
|
|
get; set;
|
|
|
|
|
}
|
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
|
|
|
|
{
|
2018-04-30 15:00:43 +02:00
|
|
|
|
var users = await _Repo.GetStoreUsers(StoreData.Id);
|
|
|
|
|
vm.StoreId = StoreData.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
|
|
|
|
}
|
|
|
|
|
|
2018-04-30 15:00:43 +02:00
|
|
|
|
public StoreData StoreData
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return this.HttpContext.GetStoreData();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
2018-04-30 15:00:43 +02:00
|
|
|
|
if (!await _Repo.AddStoreUser(StoreData.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
|
|
|
|
}
|
2018-03-23 08:24:57 +01:00
|
|
|
|
StatusMessage = "User added successfully";
|
|
|
|
|
return RedirectToAction(nameof(StoreUsers));
|
2018-02-15 05:33:29 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-10-27 10:53:04 +02:00
|
|
|
|
[HttpGet]
|
2018-03-23 08:24:57 +01:00
|
|
|
|
[Route("{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
|
|
|
|
StoreUsersViewModel vm = new StoreUsersViewModel();
|
|
|
|
|
var user = await _UserManager.FindByIdAsync(userId);
|
|
|
|
|
if (user == null)
|
|
|
|
|
return NotFound();
|
2017-10-27 10:53:04 +02:00
|
|
|
|
return View("Confirm", new ConfirmModel()
|
|
|
|
|
{
|
2018-03-23 08:24:57 +01:00
|
|
|
|
Title = $"Remove store user",
|
2019-01-21 05:19:01 +01:00
|
|
|
|
Description = $"Are you sure you want to remove store access for {user.Email}?",
|
2017-10-27 10:53:04 +02:00
|
|
|
|
Action = "Delete"
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpPost]
|
2018-03-23 08:24:57 +01:00
|
|
|
|
[Route("{storeId}/users/{userId}/delete")]
|
|
|
|
|
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);
|
|
|
|
|
StatusMessage = "User removed successfully";
|
|
|
|
|
return RedirectToAction(nameof(StoreUsers), new { storeId = storeId, userId = userId });
|
2017-10-27 10:53:04 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-03 18:46:52 +02:00
|
|
|
|
[HttpGet]
|
|
|
|
|
[Route("{storeId}/rates")]
|
2019-03-11 10:39:21 +01:00
|
|
|
|
public IActionResult Rates(string storeId)
|
2018-05-03 18:46:52 +02:00
|
|
|
|
{
|
|
|
|
|
var storeBlob = StoreData.GetStoreBlob();
|
|
|
|
|
var vm = new RatesViewModel();
|
|
|
|
|
vm.SetExchangeRates(GetSupportedExchanges(), storeBlob.PreferredExchange ?? CoinAverageRateProvider.CoinAverageName);
|
2018-08-01 11:38:46 +02:00
|
|
|
|
vm.Spread = (double)(storeBlob.Spread * 100m);
|
2019-03-11 10:39:21 +01:00
|
|
|
|
vm.StoreId = storeId;
|
2018-05-03 18:46:52 +02:00
|
|
|
|
vm.Script = storeBlob.GetRateRules(_NetworkProvider).ToString();
|
|
|
|
|
vm.DefaultScript = storeBlob.GetDefaultRateRules(_NetworkProvider).ToString();
|
|
|
|
|
vm.AvailableExchanges = GetSupportedExchanges();
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpPost]
|
|
|
|
|
[Route("{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
|
|
|
|
{
|
|
|
|
|
model.SetExchangeRates(GetSupportedExchanges(), 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();
|
|
|
|
|
|
|
|
|
|
var blob = StoreData.GetStoreBlob();
|
|
|
|
|
model.DefaultScript = blob.GetDefaultRateRules(_NetworkProvider).ToString();
|
|
|
|
|
model.AvailableExchanges = GetSupportedExchanges();
|
|
|
|
|
|
|
|
|
|
blob.PreferredExchange = model.PreferredExchange;
|
2018-08-01 11:38:46 +02:00
|
|
|
|
blob.Spread = (decimal)model.Spread / 100.0m;
|
2019-03-11 10:39:21 +01:00
|
|
|
|
blob.DefaultCurrencyPairs = currencyPairs;
|
2018-05-03 18:46:52 +02:00
|
|
|
|
if (!model.ShowScripting)
|
|
|
|
|
{
|
|
|
|
|
if (!GetSupportedExchanges().Select(c => c.Name).Contains(blob.PreferredExchange, StringComparer.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
{
|
|
|
|
|
if (StoreData.SetStoreBlob(blob))
|
|
|
|
|
{
|
|
|
|
|
await _Repo.UpdateStore(StoreData);
|
|
|
|
|
StatusMessage = "Rate settings updated";
|
|
|
|
|
}
|
|
|
|
|
return RedirectToAction(nameof(Rates), new
|
|
|
|
|
{
|
|
|
|
|
storeId = StoreData.Id
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpGet]
|
|
|
|
|
[Route("{storeId}/rates/confirm")]
|
|
|
|
|
public IActionResult ShowRateRules(bool scripting)
|
|
|
|
|
{
|
|
|
|
|
return View("Confirm", new ConfirmModel()
|
|
|
|
|
{
|
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?",
|
|
|
|
|
ButtonClass = "btn-primary"
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpPost]
|
|
|
|
|
[Route("{storeId}/rates/confirm")]
|
|
|
|
|
public async Task<IActionResult> ShowRateRulesPost(bool scripting)
|
|
|
|
|
{
|
|
|
|
|
var blob = StoreData.GetStoreBlob();
|
|
|
|
|
blob.RateScripting = scripting;
|
|
|
|
|
blob.RateScript = blob.GetDefaultRateRules(_NetworkProvider).ToString();
|
|
|
|
|
StoreData.SetStoreBlob(blob);
|
|
|
|
|
await _Repo.UpdateStore(StoreData);
|
|
|
|
|
StatusMessage = "Rate rules scripting activated";
|
|
|
|
|
return RedirectToAction(nameof(Rates), new { storeId = StoreData.Id });
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
{
|
2018-04-30 15:00:43 +02:00
|
|
|
|
var storeBlob = StoreData.GetStoreBlob();
|
2018-03-27 07:48:32 +02:00
|
|
|
|
var vm = new CheckoutExperienceViewModel();
|
2019-01-31 14:03:28 +01:00
|
|
|
|
SetCryptoCurrencies(vm, StoreData);
|
2018-04-05 04:34:25 +02:00
|
|
|
|
vm.CustomCSS = storeBlob.CustomCSS?.AbsoluteUri;
|
|
|
|
|
vm.CustomLogo = storeBlob.CustomLogo?.AbsoluteUri;
|
2018-05-03 23:51:04 +02:00
|
|
|
|
vm.HtmlTitle = storeBlob.HtmlTitle;
|
2019-03-16 04:43:57 +01:00
|
|
|
|
vm.SetLanguages(_LangService, storeBlob.DefaultLang);
|
|
|
|
|
vm.RequiresRefundEmail = storeBlob.RequiresRefundEmail;
|
|
|
|
|
vm.OnChainMinValue = storeBlob.OnChainMinValue?.ToString() ?? "";
|
|
|
|
|
vm.LightningMaxValue = storeBlob.LightningMaxValue?.ToString() ?? "";
|
|
|
|
|
vm.LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi;
|
2019-04-11 11:53:31 +02:00
|
|
|
|
vm.RedirectAutomatically = storeBlob.RedirectAutomatically;
|
2018-03-27 07:48:32 +02:00
|
|
|
|
return View(vm);
|
|
|
|
|
}
|
2019-01-31 14:03:28 +01:00
|
|
|
|
void SetCryptoCurrencies(CheckoutExperienceViewModel vm, Data.StoreData storeData)
|
|
|
|
|
{
|
|
|
|
|
var choices = storeData.GetEnabledPaymentIds(_NetworkProvider)
|
2019-05-29 16:33:31 +02:00
|
|
|
|
.Select(o => new CheckoutExperienceViewModel.Format() { Name = _paymentMethodHandlerDictionary[o].ToPrettyString(o), Value = o.ToString(), PaymentId = o }).ToArray();
|
2019-01-31 14:03:28 +01:00
|
|
|
|
|
|
|
|
|
var defaultPaymentId = storeData.GetDefaultPaymentId(_NetworkProvider);
|
|
|
|
|
var chosen = choices.FirstOrDefault(c => c.PaymentId == defaultPaymentId);
|
|
|
|
|
vm.CryptoCurrencies = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen?.Value);
|
|
|
|
|
vm.DefaultPaymentMethod = chosen?.Value;
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
{
|
2018-04-03 10:39:28 +02:00
|
|
|
|
CurrencyValue lightningMaxValue = null;
|
2018-03-27 07:48:32 +02:00
|
|
|
|
if (!string.IsNullOrWhiteSpace(model.LightningMaxValue))
|
|
|
|
|
{
|
2018-04-03 10:39:28 +02:00
|
|
|
|
if (!CurrencyValue.TryParse(model.LightningMaxValue, out lightningMaxValue))
|
2018-03-27 07:48:32 +02:00
|
|
|
|
{
|
2018-04-03 10:39:28 +02:00
|
|
|
|
ModelState.AddModelError(nameof(model.LightningMaxValue), "Invalid lightning max value");
|
2018-03-27 07:48:32 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-03 10:39:28 +02:00
|
|
|
|
|
|
|
|
|
CurrencyValue onchainMinValue = null;
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(model.OnChainMinValue))
|
|
|
|
|
{
|
|
|
|
|
if (!CurrencyValue.TryParse(model.OnChainMinValue, out onchainMinValue))
|
|
|
|
|
{
|
|
|
|
|
ModelState.AddModelError(nameof(model.OnChainMinValue), "Invalid on chain min value");
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-03-27 07:48:32 +02:00
|
|
|
|
bool needUpdate = false;
|
2018-04-30 15:00:43 +02:00
|
|
|
|
var blob = StoreData.GetStoreBlob();
|
2019-01-31 11:07:38 +01:00
|
|
|
|
var defaultPaymentMethodId = model.DefaultPaymentMethod == null ? null : PaymentMethodId.Parse(model.DefaultPaymentMethod);
|
|
|
|
|
if (StoreData.GetDefaultPaymentId(_NetworkProvider) != defaultPaymentMethodId)
|
2018-03-27 07:48:32 +02:00
|
|
|
|
{
|
|
|
|
|
needUpdate = true;
|
2019-01-31 11:07:38 +01:00
|
|
|
|
StoreData.SetDefaultPaymentId(defaultPaymentMethodId);
|
2018-03-27 07:48:32 +02:00
|
|
|
|
}
|
2019-01-31 14:03:28 +01:00
|
|
|
|
SetCryptoCurrencies(model, StoreData);
|
2018-03-27 07:48:32 +02:00
|
|
|
|
model.SetLanguages(_LangService, model.DefaultLang);
|
2018-04-03 10:54:50 +02:00
|
|
|
|
|
2018-04-18 09:38:17 +02:00
|
|
|
|
if (!ModelState.IsValid)
|
2018-04-03 10:54:50 +02:00
|
|
|
|
{
|
|
|
|
|
return View(model);
|
|
|
|
|
}
|
2018-04-05 04:34:25 +02:00
|
|
|
|
blob.CustomLogo = string.IsNullOrWhiteSpace(model.CustomLogo) ? null : new Uri(model.CustomLogo, UriKind.Absolute);
|
|
|
|
|
blob.CustomCSS = string.IsNullOrWhiteSpace(model.CustomCSS) ? null : new Uri(model.CustomCSS, UriKind.Absolute);
|
2018-05-03 23:51:04 +02:00
|
|
|
|
blob.HtmlTitle = string.IsNullOrWhiteSpace(model.HtmlTitle) ? null : model.HtmlTitle;
|
2019-03-16 04:43:57 +01:00
|
|
|
|
blob.DefaultLang = model.DefaultLang;
|
|
|
|
|
blob.RequiresRefundEmail = model.RequiresRefundEmail;
|
|
|
|
|
blob.OnChainMinValue = onchainMinValue;
|
|
|
|
|
blob.LightningMaxValue = lightningMaxValue;
|
|
|
|
|
blob.LightningAmountInSatoshi = model.LightningAmountInSatoshi;
|
2019-04-11 11:53:31 +02:00
|
|
|
|
blob.RedirectAutomatically = model.RedirectAutomatically;
|
2018-04-30 15:00:43 +02:00
|
|
|
|
if (StoreData.SetStoreBlob(blob))
|
2018-03-27 07:48:32 +02:00
|
|
|
|
{
|
|
|
|
|
needUpdate = true;
|
|
|
|
|
}
|
|
|
|
|
if (needUpdate)
|
|
|
|
|
{
|
2018-04-30 15:00:43 +02:00
|
|
|
|
await _Repo.UpdateStore(StoreData);
|
2018-03-27 07:48:32 +02:00
|
|
|
|
StatusMessage = "Store successfully updated";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return RedirectToAction(nameof(CheckoutExperience), new
|
|
|
|
|
{
|
2018-04-30 15:00:43 +02:00
|
|
|
|
storeId = StoreData.Id
|
2018-03-27 07:48:32 +02:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-27 10:53:04 +02:00
|
|
|
|
[HttpGet]
|
|
|
|
|
[Route("{storeId}")]
|
2018-05-03 18:46:52 +02:00
|
|
|
|
public IActionResult UpdateStore()
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
2018-04-29 19:33:42 +02:00
|
|
|
|
var store = HttpContext.GetStoreData();
|
2017-10-27 10:53:04 +02:00
|
|
|
|
if (store == null)
|
|
|
|
|
return NotFound();
|
|
|
|
|
|
2017-12-21 07:52:04 +01:00
|
|
|
|
var storeBlob = store.GetStoreBlob();
|
2017-10-27 10:53:04 +02:00
|
|
|
|
var vm = new StoreViewModel();
|
2017-12-03 15:35:52 +01:00
|
|
|
|
vm.Id = store.Id;
|
2017-10-27 10:53:04 +02:00
|
|
|
|
vm.StoreName = store.StoreName;
|
|
|
|
|
vm.StoreWebsite = store.StoreWebsite;
|
2019-01-04 16:37:09 +01:00
|
|
|
|
vm.NetworkFeeMode = storeBlob.NetworkFeeMode;
|
2018-09-08 07:32:26 +02:00
|
|
|
|
vm.AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice;
|
2017-10-27 10:53:04 +02:00
|
|
|
|
vm.SpeedPolicy = store.SpeedPolicy;
|
2018-07-19 15:23:14 +02:00
|
|
|
|
vm.CanDelete = _Repo.CanDeleteStores();
|
2018-07-27 13:37:16 +02:00
|
|
|
|
AddPaymentMethods(store, storeBlob, vm);
|
2017-12-03 06:43:52 +01:00
|
|
|
|
vm.MonitoringExpiration = storeBlob.MonitoringExpiration;
|
2018-01-17 07:11:05 +01:00
|
|
|
|
vm.InvoiceExpiration = storeBlob.InvoiceExpiration;
|
2018-04-07 09:27:46 +02:00
|
|
|
|
vm.LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate;
|
2018-05-04 16:15:34 +02:00
|
|
|
|
vm.PaymentTolerance = storeBlob.PaymentTolerance;
|
2017-10-27 10:53:04 +02:00
|
|
|
|
return View(vm);
|
|
|
|
|
}
|
2018-03-24 12:40:26 +01: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>()
|
2018-03-20 18:48:11 +01:00
|
|
|
|
.ToDictionary(c => c.Network.CryptoCode);
|
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>()
|
|
|
|
|
.ToDictionary(c => c.CryptoCode);
|
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
|
|
|
|
{
|
2019-05-29 16:33:31 +02:00
|
|
|
|
switch (paymentMethodId.PaymentType)
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
2019-05-29 16:33:31 +02:00
|
|
|
|
case PaymentTypes.BTCLike:
|
|
|
|
|
var strategy = derivationByCryptoCode.TryGet(paymentMethodId.CryptoCode);
|
|
|
|
|
vm.DerivationSchemes.Add(new StoreViewModel.DerivationScheme()
|
|
|
|
|
{
|
|
|
|
|
Crypto = paymentMethodId.CryptoCode,
|
|
|
|
|
Value = strategy?.ToPrettyString() ?? string.Empty,
|
|
|
|
|
WalletId = new WalletId(store.Id, paymentMethodId.CryptoCode),
|
|
|
|
|
Enabled = !excludeFilters.Match(paymentMethodId)
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
case PaymentTypes.LightningLike:
|
|
|
|
|
var lightning = lightningByCryptoCode.TryGet(paymentMethodId.CryptoCode);
|
|
|
|
|
vm.LightningNodes.Add(new StoreViewModel.LightningNode()
|
|
|
|
|
{
|
|
|
|
|
CryptoCode = paymentMethodId.CryptoCode,
|
|
|
|
|
Address = lightning?.GetLightningUrl()?.BaseUri.AbsoluteUri ?? string.Empty,
|
|
|
|
|
Enabled = !excludeFilters.Match(paymentMethodId)
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
2018-01-08 14:45:09 +01:00
|
|
|
|
}
|
2018-10-24 07:52:19 +02:00
|
|
|
|
|
|
|
|
|
var changellyEnabled = storeBlob.ChangellySettings != null && storeBlob.ChangellySettings.Enabled;
|
|
|
|
|
vm.ThirdPartyPaymentMethods.Add(new StoreViewModel.ThirdPartyPaymentMethod()
|
|
|
|
|
{
|
|
|
|
|
Enabled = changellyEnabled,
|
|
|
|
|
Action = nameof(UpdateChangellySettings),
|
|
|
|
|
Provider = "Changelly"
|
|
|
|
|
});
|
2019-04-04 20:56:12 +02:00
|
|
|
|
|
2018-12-11 12:47:38 +01:00
|
|
|
|
var coinSwitchEnabled = storeBlob.CoinSwitchSettings != null && storeBlob.CoinSwitchSettings.Enabled;
|
|
|
|
|
vm.ThirdPartyPaymentMethods.Add(new StoreViewModel.ThirdPartyPaymentMethod()
|
|
|
|
|
{
|
|
|
|
|
Enabled = coinSwitchEnabled,
|
|
|
|
|
Action = nameof(UpdateCoinSwitchSettings),
|
|
|
|
|
Provider = "CoinSwitch"
|
|
|
|
|
});
|
2018-01-08 14:45:09 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpPost]
|
|
|
|
|
[Route("{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;
|
2018-04-30 15:00:43 +02:00
|
|
|
|
if (StoreData.SpeedPolicy != model.SpeedPolicy)
|
2018-01-08 14:45:09 +01:00
|
|
|
|
{
|
|
|
|
|
needUpdate = true;
|
2018-04-30 15:00:43 +02:00
|
|
|
|
StoreData.SpeedPolicy = model.SpeedPolicy;
|
2018-01-08 14:45:09 +01:00
|
|
|
|
}
|
2018-04-30 15:00:43 +02:00
|
|
|
|
if (StoreData.StoreName != model.StoreName)
|
2018-01-08 14:45:09 +01:00
|
|
|
|
{
|
|
|
|
|
needUpdate = true;
|
2018-04-30 15:00:43 +02:00
|
|
|
|
StoreData.StoreName = model.StoreName;
|
2018-01-08 14:45:09 +01:00
|
|
|
|
}
|
2018-04-30 15:00:43 +02:00
|
|
|
|
if (StoreData.StoreWebsite != model.StoreWebsite)
|
2018-01-08 14:45:09 +01:00
|
|
|
|
{
|
|
|
|
|
needUpdate = true;
|
2018-04-30 15:00:43 +02:00
|
|
|
|
StoreData.StoreWebsite = model.StoreWebsite;
|
2018-01-08 14:45:09 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-30 15:00:43 +02:00
|
|
|
|
var blob = StoreData.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;
|
2018-01-08 14:45:09 +01:00
|
|
|
|
blob.MonitoringExpiration = model.MonitoringExpiration;
|
2018-01-17 07:59:31 +01:00
|
|
|
|
blob.InvoiceExpiration = 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;
|
2018-01-19 08:19:13 +01:00
|
|
|
|
|
2018-04-30 15:00:43 +02:00
|
|
|
|
if (StoreData.SetStoreBlob(blob))
|
2018-01-08 14:45:09 +01:00
|
|
|
|
{
|
|
|
|
|
needUpdate = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (needUpdate)
|
|
|
|
|
{
|
2018-04-30 15:00:43 +02:00
|
|
|
|
await _Repo.UpdateStore(StoreData);
|
2018-01-08 14:45:09 +01:00
|
|
|
|
StatusMessage = "Store successfully updated";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return RedirectToAction(nameof(UpdateStore), new
|
|
|
|
|
{
|
2018-04-30 15:00:43 +02:00
|
|
|
|
storeId = StoreData.Id
|
2018-01-08 14:45:09 +01:00
|
|
|
|
});
|
2018-07-19 15:23:14 +02:00
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpGet]
|
|
|
|
|
[Route("{storeId}/delete")]
|
|
|
|
|
public IActionResult DeleteStore(string storeId)
|
|
|
|
|
{
|
|
|
|
|
return View("Confirm", new ConfirmModel()
|
|
|
|
|
{
|
|
|
|
|
Action = "Delete this store",
|
|
|
|
|
Title = "Delete this store",
|
|
|
|
|
Description = "This action is irreversible and will remove all information related to this store. (Invoices, Apps etc...)",
|
|
|
|
|
ButtonClass = "btn-danger"
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpPost]
|
|
|
|
|
[Route("{storeId}/delete")]
|
|
|
|
|
public async Task<IActionResult> DeleteStorePost(string storeId)
|
|
|
|
|
{
|
|
|
|
|
await _Repo.DeleteStore(StoreData.Id);
|
|
|
|
|
StatusMessage = "Success: Store successfully deleted";
|
|
|
|
|
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
|
2017-10-27 10:53:04 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-02 20:32:42 +02:00
|
|
|
|
private CoinAverageExchange[] GetSupportedExchanges()
|
2018-04-18 09:38:17 +02:00
|
|
|
|
{
|
2018-08-22 09:53:40 +02:00
|
|
|
|
return _RateFactory.RateProviderFactory.GetSupportedExchanges()
|
2018-05-02 20:32:42 +02:00
|
|
|
|
.Select(c => c.Value)
|
|
|
|
|
.OrderBy(s => s.Name, StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
.ToArray();
|
2018-04-18 09:38:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-08 16:39:11 +02:00
|
|
|
|
private DerivationSchemeSettings ParseDerivationStrategy(string derivationScheme, Script hint, BTCPayNetwork network)
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
2019-05-09 09:05:18 +02:00
|
|
|
|
var parser = new DerivationSchemeParser(network);
|
2018-03-24 12:40:26 +01:00
|
|
|
|
parser.HintScriptPubKey = hint;
|
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();
|
2018-04-30 15:00:43 +02:00
|
|
|
|
var tokens = await _TokenRepository.GetTokensByStoreIdAsync(StoreData.Id);
|
2017-10-27 10:53:04 +02:00
|
|
|
|
model.StatusMessage = StatusMessage;
|
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
|
|
|
|
|
2018-04-30 15:00:43 +02:00
|
|
|
|
model.ApiKey = (await _TokenRepository.GetLegacyAPIKeys(StoreData.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);
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-31 09:59:09 +01:00
|
|
|
|
[HttpGet]
|
|
|
|
|
[Route("{storeId}/tokens/{tokenId}/revoke")]
|
|
|
|
|
public async Task<IActionResult> RevokeToken(string tokenId)
|
|
|
|
|
{
|
|
|
|
|
var token = await _TokenRepository.GetToken(tokenId);
|
|
|
|
|
if (token == null || token.StoreId != StoreData.Id)
|
|
|
|
|
return NotFound();
|
|
|
|
|
return View("Confirm", new ConfirmModel()
|
|
|
|
|
{
|
|
|
|
|
Action = "Revoke the token",
|
|
|
|
|
Title = "Revoke the token",
|
|
|
|
|
Description = $"The access token with the label \"{token.Label}\" will be revoked, do you wish to continue?",
|
|
|
|
|
ButtonClass = "btn-danger"
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
[HttpPost]
|
|
|
|
|
[Route("{storeId}/tokens/{tokenId}/revoke")]
|
|
|
|
|
public async Task<IActionResult> RevokeTokenConfirm(string tokenId)
|
|
|
|
|
{
|
|
|
|
|
var token = await _TokenRepository.GetToken(tokenId);
|
|
|
|
|
if (token == null ||
|
|
|
|
|
token.StoreId != StoreData.Id ||
|
|
|
|
|
!await _TokenRepository.DeleteToken(tokenId))
|
|
|
|
|
StatusMessage = "Failure to revoke this token";
|
|
|
|
|
else
|
|
|
|
|
StatusMessage = "Token revoked";
|
|
|
|
|
return RedirectToAction(nameof(ListTokens));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpGet]
|
|
|
|
|
[Route("{storeId}/tokens/{tokenId}")]
|
|
|
|
|
public async Task<IActionResult> ShowToken(string tokenId)
|
|
|
|
|
{
|
|
|
|
|
var token = await _TokenRepository.GetToken(tokenId);
|
|
|
|
|
if (token == null || token.StoreId != StoreData.Id)
|
|
|
|
|
return NotFound();
|
|
|
|
|
return View(token);
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-27 10:53:04 +02:00
|
|
|
|
[HttpPost]
|
|
|
|
|
[Route("/api-tokens")]
|
|
|
|
|
[Route("{storeId}/Tokens/Create")]
|
2018-04-30 15:00:43 +02:00
|
|
|
|
[AllowAnonymous]
|
|
|
|
|
public async Task<IActionResult> CreateToken(CreateTokenViewModel model)
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
|
|
|
|
if (!ModelState.IsValid)
|
|
|
|
|
{
|
|
|
|
|
return View(model);
|
|
|
|
|
}
|
|
|
|
|
model.Label = model.Label ?? String.Empty;
|
2018-03-23 08:24:57 +01:00
|
|
|
|
var userId = GetUserId();
|
|
|
|
|
if (userId == null)
|
2018-04-30 15:00:43 +02:00
|
|
|
|
return Challenge(Policies.CookieAuthentication);
|
|
|
|
|
|
|
|
|
|
var store = StoreData;
|
|
|
|
|
var storeId = StoreData?.Id;
|
|
|
|
|
if (storeId == null)
|
|
|
|
|
{
|
|
|
|
|
storeId = model.StoreId;
|
|
|
|
|
store = await _Repo.FindStore(storeId, userId);
|
|
|
|
|
if (store == null)
|
|
|
|
|
return Challenge(Policies.CookieAuthentication);
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-29 19:33:42 +02:00
|
|
|
|
if (!store.HasClaim(Policies.CanModifyStoreSettings.Key))
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
2018-04-30 15:00:43 +02:00
|
|
|
|
return Challenge(Policies.CookieAuthentication);
|
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
|
|
|
|
|
{
|
|
|
|
|
pairingCode = ((DataWrapper<List<PairingCodeResponse>>)await _TokenController.Tokens(tokenRequest)).Data[0].PairingCode;
|
|
|
|
|
}
|
|
|
|
|
|
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; }
|
2018-10-31 16:19:25 +01:00
|
|
|
|
public IOptions<MvcJsonOptions> MvcJsonOptions { get; }
|
2018-01-09 18:07:42 +01:00
|
|
|
|
|
2017-10-27 10:53:04 +02:00
|
|
|
|
[HttpGet]
|
|
|
|
|
[Route("/api-tokens")]
|
|
|
|
|
[Route("{storeId}/Tokens/Create")]
|
2018-04-30 15:00:43 +02:00
|
|
|
|
[AllowAnonymous]
|
|
|
|
|
public async Task<IActionResult> CreateToken()
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
|
|
|
|
var userId = GetUserId();
|
|
|
|
|
if (string.IsNullOrWhiteSpace(userId))
|
2018-04-30 15:00:43 +02:00
|
|
|
|
return Challenge(Policies.CookieAuthentication);
|
|
|
|
|
var storeId = StoreData?.Id;
|
|
|
|
|
if (StoreData != null)
|
|
|
|
|
{
|
|
|
|
|
if (!StoreData.HasClaim(Policies.CanModifyStoreSettings.Key))
|
|
|
|
|
{
|
|
|
|
|
return Challenge(Policies.CookieAuthentication);
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-10-27 10:53:04 +02:00
|
|
|
|
var model = new CreateTokenViewModel();
|
|
|
|
|
ViewBag.HidePublicKey = storeId == null;
|
|
|
|
|
ViewBag.ShowStores = storeId == null;
|
|
|
|
|
ViewBag.ShowMenu = storeId != null;
|
|
|
|
|
model.StoreId = storeId;
|
|
|
|
|
if (storeId == null)
|
|
|
|
|
{
|
2018-04-30 15:00:43 +02:00
|
|
|
|
var stores = await _Repo.GetStoresByUserId(userId);
|
|
|
|
|
model.Stores = new SelectList(stores.Where(s => s.HasClaim(Policies.CanModifyStoreSettings.Key)), nameof(StoreData.Id), nameof(StoreData.StoreName), storeId);
|
2018-05-06 12:03:30 +02:00
|
|
|
|
if (model.Stores.Count() == 0)
|
|
|
|
|
{
|
|
|
|
|
StatusMessage = "Error: 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);
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-29 11:28:04 +02:00
|
|
|
|
[HttpPost]
|
|
|
|
|
[Route("{storeId}/tokens/apikey")]
|
2018-04-30 15:00:43 +02:00
|
|
|
|
public async Task<IActionResult> GenerateAPIKey()
|
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();
|
2018-04-30 15:00:43 +02:00
|
|
|
|
await _TokenRepository.GenerateLegacyAPIKey(StoreData.Id);
|
2018-04-29 11:28:04 +02:00
|
|
|
|
StatusMessage = "API Key re-generated";
|
|
|
|
|
return RedirectToAction(nameof(ListTokens));
|
|
|
|
|
}
|
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)
|
|
|
|
|
return Challenge(Policies.CookieAuthentication);
|
2018-03-23 08:24:57 +01:00
|
|
|
|
if (pairingCode == null)
|
|
|
|
|
return NotFound();
|
2017-10-27 10:53:04 +02:00
|
|
|
|
var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
|
|
|
|
|
if (pairing == null)
|
|
|
|
|
{
|
|
|
|
|
StatusMessage = "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);
|
2017-10-27 10:53:04 +02:00
|
|
|
|
return View(new PairingModel()
|
|
|
|
|
{
|
|
|
|
|
Id = pairing.Id,
|
|
|
|
|
Label = pairing.Label,
|
|
|
|
|
SIN = pairing.SIN ?? "Server-Initiated Pairing",
|
|
|
|
|
SelectedStore = selectedStore ?? stores.FirstOrDefault()?.Id,
|
2018-04-30 15:00:43 +02:00
|
|
|
|
Stores = stores.Where(u => u.HasClaim(Policies.CanModifyStoreSettings.Key)).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")]
|
2018-04-30 15:00:43 +02:00
|
|
|
|
[AllowAnonymous]
|
2017-10-27 10:53:04 +02:00
|
|
|
|
public async Task<IActionResult> Pair(string pairingCode, string selectedStore)
|
|
|
|
|
{
|
|
|
|
|
if (pairingCode == null)
|
|
|
|
|
return NotFound();
|
2018-04-30 15:00:43 +02:00
|
|
|
|
var userId = GetUserId();
|
|
|
|
|
if (userId == null)
|
|
|
|
|
return Challenge(Policies.CookieAuthentication);
|
|
|
|
|
var store = await _Repo.FindStore(selectedStore, userId);
|
2017-10-27 10:53:04 +02:00
|
|
|
|
var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
|
|
|
|
|
if (store == null || pairing == null)
|
|
|
|
|
return NotFound();
|
|
|
|
|
|
2018-04-29 19:33:42 +02:00
|
|
|
|
if (!store.HasClaim(Policies.CanModifyStoreSettings.Key))
|
2018-03-23 08:24:57 +01:00
|
|
|
|
{
|
2018-04-30 15:00:43 +02:00
|
|
|
|
return Challenge(Policies.CookieAuthentication);
|
2018-03-23 08:24:57 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-10-27 10:53:04 +02:00
|
|
|
|
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();
|
|
|
|
|
StoreNotConfigured = store.GetSupportedPaymentMethods(_NetworkProvider)
|
|
|
|
|
.Where(p => !excludeFilter.Match(p.PaymentId))
|
|
|
|
|
.Count() == 0;
|
2018-03-01 15:09:41 +01:00
|
|
|
|
StatusMessage = "Pairing is successful";
|
2017-10-27 10:53:04 +02:00
|
|
|
|
if (pairingResult == PairingResult.Partial)
|
|
|
|
|
StatusMessage = "Server initiated pairing code: " + pairingCode;
|
|
|
|
|
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
|
|
|
|
|
{
|
|
|
|
|
StatusMessage = $"Pairing failed ({pairingResult})";
|
|
|
|
|
return RedirectToAction(nameof(ListTokens), new
|
|
|
|
|
{
|
|
|
|
|
storeId = store.Id
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string GetUserId()
|
|
|
|
|
{
|
2018-04-30 15:00:43 +02:00
|
|
|
|
if (User.Identity.AuthenticationType != Policies.CookieAuthentication)
|
|
|
|
|
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")]
|
2018-08-23 04:11:39 +02:00
|
|
|
|
public IActionResult PayButton()
|
2018-08-22 13:57:54 +02:00
|
|
|
|
{
|
|
|
|
|
var store = StoreData;
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
|
{
|
|
|
|
|
Price = 10,
|
|
|
|
|
Currency = DEFAULT_CURRENCY,
|
|
|
|
|
ButtonSize = 2,
|
|
|
|
|
UrlRoot = appUrl,
|
2018-08-23 04:11:39 +02:00
|
|
|
|
PayButtonImageUrl = appUrl + "img/paybutton/pay.png",
|
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,
|
|
|
|
|
Step = 1
|
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)
|
|
|
|
|
{
|
|
|
|
|
var blob = StoreData.GetStoreBlob();
|
2018-09-08 07:32:26 +02:00
|
|
|
|
blob.AnyoneCanInvoice = enableStore;
|
2018-09-04 06:48:53 +02:00
|
|
|
|
if (StoreData.SetStoreBlob(blob))
|
|
|
|
|
{
|
|
|
|
|
await _Repo.UpdateStore(StoreData);
|
|
|
|
|
StatusMessage = "Store successfully updated";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return RedirectToAction(nameof(PayButton), new
|
|
|
|
|
{
|
|
|
|
|
storeId = StoreData.Id
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
}
|
2017-10-27 10:53:04 +02:00
|
|
|
|
}
|
2017-09-13 16:50:36 +02:00
|
|
|
|
}
|