Separate Checkout Experience settings from General store settings

This commit is contained in:
nicolas.dorier 2018-03-27 14:48:32 +09:00
parent a0f3698701
commit a562e90bdb
7 changed files with 179 additions and 109 deletions

View File

@ -183,6 +183,65 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(StoreUsers), new { storeId = storeId, userId = userId });
}
[HttpGet]
[Route("{storeId}/checkout")]
public async Task<IActionResult> CheckoutExperience(string storeId)
{
var store = await _Repo.FindStore(storeId, GetUserId());
if (store == null)
return NotFound();
var storeBlob = store.GetStoreBlob();
var vm = new CheckoutExperienceViewModel();
vm.SetCryptoCurrencies(_ExplorerProvider, store.GetDefaultCrypto());
vm.SetLanguages(_LangService, storeBlob.DefaultLang);
vm.LightningMaxValue = storeBlob.LightningMaxValue?.ToString() ?? "";
vm.AllowCoinConversion = storeBlob.AllowCoinConversion;
return View(vm);
}
[HttpPost]
[Route("{storeId}/checkout")]
public async Task<IActionResult> CheckoutExperience(string storeId, CheckoutExperienceViewModel model)
{
CurrencyValue currencyValue = null;
if (!string.IsNullOrWhiteSpace(model.LightningMaxValue))
{
if (!CurrencyValue.TryParse(model.LightningMaxValue, out currencyValue))
{
ModelState.AddModelError(nameof(model.LightningMaxValue), "Invalid currency value");
}
}
var store = await _Repo.FindStore(storeId, GetUserId());
if (store == null)
return NotFound();
bool needUpdate = false;
var blob = store.GetStoreBlob();
if (store.GetDefaultCrypto() != model.DefaultCryptoCurrency)
{
needUpdate = true;
store.SetDefaultCrypto(model.DefaultCryptoCurrency);
}
model.SetCryptoCurrencies(_ExplorerProvider, model.DefaultCryptoCurrency);
model.SetLanguages(_LangService, model.DefaultLang);
blob.DefaultLang = model.DefaultLang;
blob.AllowCoinConversion = model.AllowCoinConversion;
blob.LightningMaxValue = currencyValue;
if (store.SetStoreBlob(blob))
{
needUpdate = true;
}
if (needUpdate)
{
await _Repo.UpdateStore(store);
StatusMessage = "Store successfully updated";
}
return RedirectToAction(nameof(CheckoutExperience), new
{
storeId = storeId
});
}
[HttpGet]
[Route("{storeId}")]
public async Task<IActionResult> UpdateStore(string storeId)
@ -195,19 +254,14 @@ namespace BTCPayServer.Controllers
var vm = new StoreViewModel();
vm.Id = store.Id;
vm.StoreName = store.StoreName;
vm.SetCryptoCurrencies(_ExplorerProvider, store.GetDefaultCrypto());
vm.SetLanguages(_LangService, storeBlob.DefaultLang);
vm.StoreWebsite = store.StoreWebsite;
vm.NetworkFee = !storeBlob.NetworkFeeDisabled;
vm.LightningMaxValue = storeBlob.LightningMaxValue?.ToString() ?? "";
vm.SpeedPolicy = store.SpeedPolicy;
AddPaymentMethods(store, vm);
vm.StatusMessage = StatusMessage;
vm.MonitoringExpiration = storeBlob.MonitoringExpiration;
vm.InvoiceExpiration = storeBlob.InvoiceExpiration;
vm.RateMultiplier = (double)storeBlob.GetRateMultiplier();
vm.PreferredExchange = storeBlob.PreferredExchange.IsCoinAverage() ? "coinaverage" : storeBlob.PreferredExchange;
vm.AllowCoinConversion = storeBlob.AllowCoinConversion;
return View(vm);
}
@ -249,14 +303,6 @@ namespace BTCPayServer.Controllers
[Route("{storeId}")]
public async Task<IActionResult> UpdateStore(string storeId, StoreViewModel model)
{
CurrencyValue currencyValue = null;
if (!string.IsNullOrWhiteSpace(model.LightningMaxValue))
{
if(!CurrencyValue.TryParse(model.LightningMaxValue, out currencyValue))
{
ModelState.AddModelError(nameof(model.LightningMaxValue), "Invalid currency value");
}
}
if (!ModelState.IsValid)
{
return View(model);
@ -285,26 +331,15 @@ namespace BTCPayServer.Controllers
store.StoreWebsite = model.StoreWebsite;
}
if (store.GetDefaultCrypto() != model.DefaultCryptoCurrency)
{
needUpdate = true;
store.SetDefaultCrypto(model.DefaultCryptoCurrency);
}
model.SetCryptoCurrencies(_ExplorerProvider, model.DefaultCryptoCurrency);
model.SetLanguages(_LangService, model.DefaultLang);
var blob = store.GetStoreBlob();
blob.NetworkFeeDisabled = !model.NetworkFee;
blob.MonitoringExpiration = model.MonitoringExpiration;
blob.InvoiceExpiration = model.InvoiceExpiration;
blob.DefaultLang = model.DefaultLang;
blob.LightningMaxValue = currencyValue;
bool newExchange = blob.PreferredExchange != model.PreferredExchange;
blob.PreferredExchange = model.PreferredExchange;
blob.SetRateMultiplier(model.RateMultiplier);
blob.AllowCoinConversion = model.AllowCoinConversion;
if (store.SetStoreBlob(blob))
{

View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Services;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace BTCPayServer.Models.StoreViewModels
{
public class CheckoutExperienceViewModel
{
class Format
{
public string Name { get; set; }
public string Value { get; set; }
}
public SelectList CryptoCurrencies { get; set; }
public SelectList Languages { get; set; }
[Display(Name = "Default crypto currency on checkout")]
public string DefaultCryptoCurrency { get; set; }
[Display(Name = "Default language on checkout")]
public string DefaultLang { get; set; }
[Display(Name = "Allow conversion through third party (Shapeshift, Changelly...)")]
public bool AllowCoinConversion
{
get; set;
}
[Display(Name = "Do not propose lightning payment if value of the invoice is above...")]
[MaxLength(20)]
public string LightningMaxValue { get; set; }
public void SetCryptoCurrencies(ExplorerClientProvider explorerProvider, string defaultCrypto)
{
var choices = explorerProvider.GetAll().Select(o => new Format() { Name = o.Item1.CryptoCode, Value = o.Item1.CryptoCode }).ToArray();
var chosen = choices.FirstOrDefault(f => f.Value == defaultCrypto) ?? choices.FirstOrDefault();
CryptoCurrencies = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen);
DefaultCryptoCurrency = chosen.Name;
}
public void SetLanguages(LanguageService langService, string defaultLang)
{
defaultLang = defaultLang ?? "en-US";
var choices = langService.GetLanguages().Select(o => new Format() { Name = o.DisplayName, Value = o.Code }).ToArray();
var chosen = choices.FirstOrDefault(f => f.Value == defaultLang) ?? choices.FirstOrDefault();
Languages = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen);
DefaultLang = chosen.Value;
}
}
}

View File

@ -17,11 +17,7 @@ namespace BTCPayServer.Models.StoreViewModels
public string Crypto { get; set; }
public string Value { get; set; }
}
class Format
{
public string Name { get; set; }
public string Value { get; set; }
}
public StoreViewModel()
{
@ -83,11 +79,6 @@ namespace BTCPayServer.Models.StoreViewModels
set;
}
[Display(Name = "Do not propose lightning payment if value of the invoice is above...")]
[MaxLength(20)]
public string LightningMaxValue { get; set; }
[Display(Name = "Consider the invoice confirmed when the payment transaction...")]
public SpeedPolicy SpeedPolicy
{
@ -100,24 +91,6 @@ namespace BTCPayServer.Models.StoreViewModels
get; set;
}
[Display(Name = "Allow conversion through third party (Shapeshift, Changelly...)")]
public bool AllowCoinConversion
{
get; set;
}
public string StatusMessage
{
get; set;
}
public SelectList CryptoCurrencies { get; set; }
public SelectList Languages { get; set; }
[Display(Name = "Default crypto currency on checkout")]
public string DefaultCryptoCurrency { get; set; }
[Display(Name = "Default language on checkout")]
public string DefaultLang { get; set; }
public class LightningNode
{
public string CryptoCode { get; set; }
@ -128,21 +101,5 @@ namespace BTCPayServer.Models.StoreViewModels
get; set;
} = new List<LightningNode>();
public void SetCryptoCurrencies(ExplorerClientProvider explorerProvider, string defaultCrypto)
{
var choices = explorerProvider.GetAll().Select(o => new Format() { Name = o.Item1.CryptoCode, Value = o.Item1.CryptoCode }).ToArray();
var chosen = choices.FirstOrDefault(f => f.Value == defaultCrypto) ?? choices.FirstOrDefault();
CryptoCurrencies = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen);
DefaultCryptoCurrency = chosen.Name;
}
public void SetLanguages(LanguageService langService, string defaultLang)
{
defaultLang = defaultLang ?? "en-US";
var choices = langService.GetLanguages().Select(o => new Format() { Name = o.DisplayName, Value = o.Code }).ToArray();
var chosen = choices.FirstOrDefault(f => f.Value == defaultLang) ?? choices.FirstOrDefault();
Languages = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen);
DefaultLang = chosen.Value;
}
}
}

View File

@ -0,0 +1,44 @@
@model CheckoutExperienceViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData["Title"] = "Checkout experience";
ViewData.AddActivePage(BTCPayServer.Views.Stores.StoreNavPages.Checkout);
}
<h4>@ViewData["Title"]</h4>
@Html.Partial("_StatusMessage", TempData["TempDataProperty-StatusMessage"])
<div class="row">
<div class="col-md-6">
<div asp-validation-summary="All" class="text-danger"></div>
</div>
</div>
<div class="row">
<div class="col-md-8">
<form method="post">
<div class="form-group">
<label asp-for="DefaultCryptoCurrency"></label>
<select asp-for="DefaultCryptoCurrency" asp-items="Model.CryptoCurrencies" class="form-control"></select>
</div>
<div class="form-group">
<label asp-for="DefaultLang"></label>
<select asp-for="DefaultLang" asp-items="Model.Languages" class="form-control"></select>
</div>
<div class="form-group">
<label asp-for="AllowCoinConversion"></label>
<input asp-for="AllowCoinConversion" type="checkbox" class="form-check" />
</div>
<div class="form-group">
<label asp-for="LightningMaxValue"></label>
<input asp-for="LightningMaxValue" class="form-control" />
<span asp-validation-for="LightningMaxValue" class="text-danger"></span>
<p class="form-text text-muted">Example: 5.50 USD</p>
</div>
<button name="command" type="submit" class="btn btn-success" value="Save">Save</button>
</form>
</div>
</div>
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
}

View File

@ -11,13 +11,14 @@ namespace BTCPayServer.Views.Stores
{
public static string ActivePageKey => "ActivePage";
public static string Index => "Index";
public static string Checkout => "Checkout experience";
public static string Tokens => "Tokens";
public static string Users => "Users";
public static string UsersNavClass(ViewContext viewContext) => PageNavClass(viewContext, Users);
public static string TokensNavClass(ViewContext viewContext) => PageNavClass(viewContext, Tokens);
public static string CheckoutNavClass(ViewContext viewContext) => PageNavClass(viewContext, Checkout);
public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
public static string PageNavClass(ViewContext viewContext, string page)

View File

@ -6,7 +6,7 @@
}
<h4>@ViewData["Title"]</h4>
@Html.Partial("_StatusMessage", Model.StatusMessage)
@Html.Partial("_StatusMessage", TempData["TempDataProperty-StatusMessage"])
<div class="row">
<div class="col-md-6">
@ -30,14 +30,6 @@
<input asp-for="StoreWebsite" class="form-control" />
<span asp-validation-for="StoreWebsite" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="DefaultCryptoCurrency"></label>
<select asp-for="DefaultCryptoCurrency" asp-items="Model.CryptoCurrencies" class="form-control"></select>
</div>
<div class="form-group">
<label asp-for="DefaultLang"></label>
<select asp-for="DefaultLang" asp-items="Model.Languages" class="form-control"></select>
</div>
<div class="form-group">
<label asp-for="NetworkFee"></label>
<input asp-for="NetworkFee" type="checkbox" class="form-check" />
@ -74,10 +66,6 @@
</select>
<span asp-validation-for="SpeedPolicy" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="AllowCoinConversion"></label>
<input asp-for="AllowCoinConversion" type="checkbox" class="form-check" />
</div>
<div class="form-group">
<h5>Derivation Scheme</h5>
<span>The DerivationScheme represents the destination of the funds received by your invoice on chain.</span>
@ -94,19 +82,19 @@
</thead>
<tbody>
@foreach(var scheme in Model.DerivationSchemes)
{
<tr>
<td>@scheme.Crypto</td>
<td style="max-width:300px;overflow:hidden;">@scheme.Value</td>
<td style="text-align:right">
@if(!string.IsNullOrWhiteSpace(scheme.Value))
{
<a asp-action="Wallet" asp-route-cryptoCode="@scheme.Crypto">Wallet</a><span> - </span>
}
<a asp-action="AddDerivationScheme" asp-route-cryptoCode="@scheme.Crypto">Modify</a>
</td>
</tr>
}
{
<tr>
<td>@scheme.Crypto</td>
<td style="max-width:300px;overflow:hidden;">@scheme.Value</td>
<td style="text-align:right">
@if(!string.IsNullOrWhiteSpace(scheme.Value))
{
<a asp-action="Wallet" asp-route-cryptoCode="@scheme.Crypto">Wallet</a><span> - </span>
}
<a asp-action="AddDerivationScheme" asp-route-cryptoCode="@scheme.Crypto">Modify</a>
</td>
</tr>
}
</tbody>
</table>
</div>
@ -119,12 +107,6 @@
<span>This is experimental and not advised for production.</span>
</p>
</div>
<div class="form-group">
<label asp-for="LightningMaxValue"></label>
<input asp-for="LightningMaxValue" class="form-control" />
<span asp-validation-for="LightningMaxValue" class="text-danger"></span>
<p class="form-text text-muted">Example: 5.50 USD</p>
</div>
<div class="form-group">
<table class="table">
<thead class="thead-inverse">
@ -136,13 +118,13 @@
</thead>
<tbody>
@foreach(var scheme in Model.LightningNodes)
{
<tr>
<td>@scheme.CryptoCode</td>
<td>@scheme.Address</td>
<td style="text-align:right"><a asp-action="AddLightningNode" asp-route-cryptoCode="@scheme.CryptoCode">Modify</a></td>
</tr>
}
{
<tr>
<td>@scheme.CryptoCode</td>
<td>@scheme.Address</td>
<td style="text-align:right"><a asp-action="AddLightningNode" asp-route-cryptoCode="@scheme.CryptoCode">Modify</a></td>
</tr>
}
</tbody>
</table>
</div>

View File

@ -2,7 +2,8 @@
@inject SignInManager<ApplicationUser> SignInManager
<ul class="nav nav-pills nav-stacked">
<li class="@StoreNavPages.IndexNavClass(ViewContext)"><a asp-action="UpdateStore">Information</a></li>
<li class="@StoreNavPages.IndexNavClass(ViewContext)"><a asp-action="UpdateStore">General settings</a></li>
<li class="@StoreNavPages.CheckoutNavClass(ViewContext)"><a asp-action="CheckoutExperience">Checkout experience</a></li>
<li class="@StoreNavPages.TokensNavClass(ViewContext)"><a asp-action="ListTokens">Access Tokens</a></li>
<li class="@StoreNavPages.UsersNavClass(ViewContext)"><a asp-action="StoreUsers">Users</a></li>
</ul>