diff --git a/BTCPayServer/Controllers/UIPublicController.cs b/BTCPayServer/Controllers/UIPublicController.cs index 15182aefa..74759e128 100644 --- a/BTCPayServer/Controllers/UIPublicController.cs +++ b/BTCPayServer/Controllers/UIPublicController.cs @@ -67,7 +67,8 @@ namespace BTCPayServer.Controllers NotificationEmail = model.NotifyEmail, NotificationURL = model.ServerIpn, RedirectURL = model.BrowserRedirect, - FullNotifications = true + FullNotifications = true, + DefaultPaymentMethod = model.DefaultPaymentMethod }, store, HttpContext.Request.GetAbsoluteRoot(), cancellationToken: cancellationToken); } catch (BitpayHttpException e) diff --git a/BTCPayServer/Controllers/UIStoresController.cs b/BTCPayServer/Controllers/UIStoresController.cs index d20db1c1b..682b59757 100644 --- a/BTCPayServer/Controllers/UIStoresController.cs +++ b/BTCPayServer/Controllers/UIStoresController.cs @@ -1,3 +1,4 @@ +#nullable enable using System; using System.Collections.Generic; using System.Globalization; @@ -229,7 +230,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/rates")] - public async Task Rates(RatesViewModel model, string command = null, string storeId = null, CancellationToken cancellationToken = default) + public async Task Rates(RatesViewModel model, string? command = null, string? storeId = null, CancellationToken cancellationToken = default) { if (command == "scripting-on") { @@ -243,7 +244,7 @@ namespace BTCPayServer.Controllers var exchanges = GetSupportedExchanges(); model.SetExchangeRates(exchanges, model.PreferredExchange); model.StoreId = storeId ?? model.StoreId; - CurrencyPair[] currencyPairs = null; + CurrencyPair[]? currencyPairs = null; try { currencyPairs = model.DefaultCurrencyPairs? @@ -277,7 +278,7 @@ namespace BTCPayServer.Controllers return View(model); } } - RateRules rules = null; + RateRules? rules = null; if (model.ShowScripting) { if (!RateRules.TryParse(model.Script, out rules, out var errors)) @@ -421,6 +422,29 @@ namespace BTCPayServer.Controllers } void SetCryptoCurrencies(CheckoutAppearanceViewModel vm, Data.StoreData storeData) + { + var choices = GetEnabledPaymentMethodChoices(storeData); + var chosen = GetDefaultPaymentMethodChoice(storeData); + + vm.PaymentMethods = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen?.Value); + vm.DefaultPaymentMethod = chosen?.Value; + } + + PaymentMethodOptionViewModel.Format[] GetEnabledPaymentMethodChoices(Data.StoreData storeData) + { + var enabled = storeData.GetEnabledPaymentIds(_NetworkProvider); + + return enabled + .Select(o => + new PaymentMethodOptionViewModel.Format() + { + Name = o.ToPrettyString(), + Value = o.ToString(), + PaymentId = o + }).ToArray(); + } + + PaymentMethodOptionViewModel.Format? GetDefaultPaymentMethodChoice(Data.StoreData storeData) { var enabled = storeData.GetEnabledPaymentIds(_NetworkProvider); var defaultPaymentId = storeData.GetDefaultPaymentId(); @@ -431,17 +455,9 @@ namespace BTCPayServer.Controllers enabled.FirstOrDefault(e => e.CryptoCode == "BTC" && e.PaymentType == PaymentTypes.LightningLike) ?? enabled.FirstOrDefault(); } - var choices = enabled - .Select(o => - new CheckoutAppearanceViewModel.Format() - { - Name = o.ToPrettyString(), - Value = o.ToString(), - PaymentId = o - }).ToArray(); - var chosen = defaultChoice is null ? null : choices.FirstOrDefault(c => defaultChoice.ToString().Equals(c.Value, StringComparison.OrdinalIgnoreCase)); - vm.PaymentMethods = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen?.Value); - vm.DefaultPaymentMethod = chosen?.Value; + var choices = GetEnabledPaymentMethodChoices(storeData); + + return defaultChoice is null ? null : choices.FirstOrDefault(c => defaultChoice.ToString().Equals(c.Value, StringComparison.OrdinalIgnoreCase)); } [HttpPost] @@ -616,7 +632,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/settings")] - public async Task GeneralSettings(GeneralSettingsViewModel model, string command = null) + public async Task GeneralSettings(GeneralSettingsViewModel model, string? command = null) { bool needUpdate = false; if (CurrentStore.StoreName != model.StoreName) @@ -747,7 +763,7 @@ namespace BTCPayServer.Controllers TempData[WellKnownTempData.ErrorMessage] = "Failure to revoke this token."; else TempData[WellKnownTempData.SuccessMessage] = "Token revoked"; - return RedirectToAction(nameof(ListTokens), new { storeId = token.StoreId }); + return RedirectToAction(nameof(ListTokens), new { storeId = token?.StoreId }); } [HttpGet] @@ -782,7 +798,7 @@ namespace BTCPayServer.Controllers Id = model.PublicKey == null ? null : NBitpayClient.Extensions.BitIdExtensions.GetBitIDSIN(new PubKey(model.PublicKey).Compress()) }; - string pairingCode = null; + string? pairingCode = null; if (model.PublicKey == null) { tokenRequest.PairingCode = await _TokenRepository.CreatePairingCodeAsync(); @@ -807,7 +823,7 @@ namespace BTCPayServer.Controllers }); } - public string GeneratedPairingCode { get; set; } + public string? GeneratedPairingCode { get; set; } public WebhookSender WebhookNotificationManager { get; } public IDataProtector DataProtector { get; } @@ -880,7 +896,7 @@ namespace BTCPayServer.Controllers [HttpGet] [Route("/api-access-request")] [AllowAnonymous] - public async Task RequestPairing(string pairingCode, string selectedStore = null) + public async Task RequestPairing(string pairingCode, string? selectedStore = null) { var userId = GetUserId(); if (userId == null) @@ -956,9 +972,9 @@ namespace BTCPayServer.Controllers } } - private string GetUserId() + private string? GetUserId() { - if (User.Identity.AuthenticationType != AuthenticationSchemes.Cookie) + if (User.Identity?.AuthenticationType != AuthenticationSchemes.Cookie) return null; return _UserManager.GetUserId(User); } @@ -990,6 +1006,8 @@ namespace BTCPayServer.Controllers { Price = null, Currency = storeBlob.DefaultCurrency, + DefaultPaymentMethod = String.Empty, + PaymentMethods = GetEnabledPaymentMethodChoices(store), ButtonSize = 2, UrlRoot = appUrl, PayButtonImageUrl = appUrl + "img/paybutton/pay.svg", diff --git a/BTCPayServer/Data/StoreDataExtensions.cs b/BTCPayServer/Data/StoreDataExtensions.cs index 6ae6281a8..fa32208b2 100644 --- a/BTCPayServer/Data/StoreDataExtensions.cs +++ b/BTCPayServer/Data/StoreDataExtensions.cs @@ -38,7 +38,7 @@ namespace BTCPayServer.Data return paymentMethodIds; } - public static void SetDefaultPaymentId(this StoreData storeData, PaymentMethodId defaultPaymentId) + public static void SetDefaultPaymentId(this StoreData storeData, PaymentMethodId? defaultPaymentId) { storeData.DefaultCrypto = defaultPaymentId?.ToString(); } diff --git a/BTCPayServer/Models/StoreViewModels/CheckoutAppearanceViewModel.cs b/BTCPayServer/Models/StoreViewModels/CheckoutAppearanceViewModel.cs index 4900729de..a2ba37a63 100644 --- a/BTCPayServer/Models/StoreViewModels/CheckoutAppearanceViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/CheckoutAppearanceViewModel.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; -using BTCPayServer.Payments; using BTCPayServer.Services; using Microsoft.AspNetCore.Mvc.Rendering; @@ -10,18 +9,12 @@ namespace BTCPayServer.Models.StoreViewModels { public class CheckoutAppearanceViewModel { - public class Format - { - public string Name { get; set; } - public string Value { get; set; } - public PaymentMethodId PaymentId { get; set; } - } public SelectList PaymentMethods { get; set; } public void SetLanguages(LanguageService langService, string defaultLang) { defaultLang = langService.GetLanguages().Any(language => language.Code == defaultLang) ? defaultLang : "en"; - var choices = langService.GetLanguages().Select(o => new Format() { Name = o.DisplayName, Value = o.Code }).ToArray().OrderBy(o => o.Name); + var choices = langService.GetLanguages().Select(o => new PaymentMethodOptionViewModel.Format() { Name = o.DisplayName, Value = o.Code }).ToArray().OrderBy(o => o.Name); var chosen = choices.FirstOrDefault(f => f.Value == defaultLang) ?? choices.FirstOrDefault(); Languages = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen); DefaultLang = chosen.Value; diff --git a/BTCPayServer/Models/StoreViewModels/PayButtonViewModel.cs b/BTCPayServer/Models/StoreViewModels/PayButtonViewModel.cs index 244adec89..195f72a1b 100644 --- a/BTCPayServer/Models/StoreViewModels/PayButtonViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/PayButtonViewModel.cs @@ -13,6 +13,8 @@ namespace BTCPayServer.Models.StoreViewModels public string InvoiceId { get; set; } [Required] public string Currency { get; set; } + public string DefaultPaymentMethod { get; set; } + public PaymentMethodOptionViewModel.Format[] PaymentMethods { get; set; } public string CheckoutDesc { get; set; } public string OrderId { get; set; } public int ButtonSize { get; set; } diff --git a/BTCPayServer/Models/StoreViewModels/PaymentMethodOptionViewModel.cs b/BTCPayServer/Models/StoreViewModels/PaymentMethodOptionViewModel.cs new file mode 100644 index 000000000..c232cb7c3 --- /dev/null +++ b/BTCPayServer/Models/StoreViewModels/PaymentMethodOptionViewModel.cs @@ -0,0 +1,14 @@ +using BTCPayServer.Payments; + +namespace BTCPayServer.Models.StoreViewModels +{ + public class PaymentMethodOptionViewModel + { + public class Format + { + public string Name { get; set; } + public string Value { get; set; } + public PaymentMethodId PaymentId { get; set; } + } + } +} diff --git a/BTCPayServer/Views/UIStores/PayButton.cshtml b/BTCPayServer/Views/UIStores/PayButton.cshtml index 0909214c3..e270fad98 100644 --- a/BTCPayServer/Views/UIStores/PayButton.cshtml +++ b/BTCPayServer/Views/UIStores/PayButton.cshtml @@ -159,6 +159,13 @@ v-model="srvModel.currency" v-on:change="inputChanges" :class="{'is-invalid': errors.has('currency') }"> +
+ + +
value.id === srvModel.appIdEndpoint ): null; let allowCurrencySelection = true; + let allowDefaultPaymentMethodSelection = true; if (app) { if (app.appType.toLowerCase() === 'pointofsale') { actionUrl = `apps/${app.id}/pos`; @@ -97,6 +98,7 @@ function inputChanges(event, buttonSize) { if (actionUrl !== 'api/v1/invoices') { priceInputName = 'amount'; allowCurrencySelection = false; + allowDefaultPaymentMethodSelection = false; srvModel.useModal = false; } } @@ -150,6 +152,15 @@ function inputChanges(event, buttonSize) { html += addSlider(srvModel.price, srvModel.min, srvModel.max, srvModel.step, width); html += '
\n'; } + + if ( + allowDefaultPaymentMethodSelection && + // Only add default payment method to HTML if user explicitly selected it + event && event.target.id === 'default-payment-method' && event.target.value !== "" + ) + { + html += addInput("defaultPaymentMethod", srvModel.defaultPaymentMethod) + } html += srvModel.payButtonText ? `