2020-06-29 04:44:35 +02:00
|
|
|
using System;
|
2022-07-22 15:41:14 +02:00
|
|
|
using System.Collections.Generic;
|
2021-12-27 05:46:31 +01:00
|
|
|
using System.Globalization;
|
2018-12-19 07:07:05 +01:00
|
|
|
using System.Linq;
|
|
|
|
using System.Text;
|
2018-08-30 20:16:24 +02:00
|
|
|
using System.Text.Encodings.Web;
|
2018-12-19 07:07:05 +01:00
|
|
|
using System.Text.RegularExpressions;
|
2022-07-22 15:41:14 +02:00
|
|
|
using System.Threading;
|
2018-04-03 04:50:41 +02:00
|
|
|
using System.Threading.Tasks;
|
2022-02-21 15:46:43 +01:00
|
|
|
using BTCPayServer.Abstractions.Constants;
|
2022-03-02 18:28:12 +01:00
|
|
|
using BTCPayServer.Abstractions.Extensions;
|
2022-11-25 08:11:13 +01:00
|
|
|
using BTCPayServer.Abstractions.Form;
|
2022-07-22 15:41:14 +02:00
|
|
|
using BTCPayServer.Abstractions.Models;
|
2022-07-18 20:51:53 +02:00
|
|
|
using BTCPayServer.Client;
|
2022-07-22 15:41:14 +02:00
|
|
|
using BTCPayServer.Controllers;
|
2022-07-18 20:51:53 +02:00
|
|
|
using BTCPayServer.Data;
|
2022-07-22 15:41:14 +02:00
|
|
|
using BTCPayServer.Filters;
|
2022-11-25 10:14:33 +01:00
|
|
|
using BTCPayServer.Forms;
|
2023-02-20 11:35:54 +01:00
|
|
|
using BTCPayServer.Forms.Models;
|
2022-07-22 15:41:14 +02:00
|
|
|
using BTCPayServer.ModelBinders;
|
|
|
|
using BTCPayServer.Models;
|
2022-07-18 20:51:53 +02:00
|
|
|
using BTCPayServer.Plugins.PointOfSale.Models;
|
2023-04-05 15:42:23 +02:00
|
|
|
using BTCPayServer.Services;
|
2018-04-03 04:50:41 +02:00
|
|
|
using BTCPayServer.Services.Apps;
|
2022-11-25 02:42:55 +01:00
|
|
|
using BTCPayServer.Services.Invoices;
|
2022-07-18 20:51:53 +02:00
|
|
|
using BTCPayServer.Services.Rates;
|
|
|
|
using BTCPayServer.Services.Stores;
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
2022-07-22 15:41:14 +02:00
|
|
|
using Microsoft.AspNetCore.Cors;
|
2023-02-23 09:52:37 +01:00
|
|
|
using Microsoft.AspNetCore.Http;
|
2022-07-22 15:41:14 +02:00
|
|
|
using Microsoft.AspNetCore.Http.Extensions;
|
2018-08-30 20:16:24 +02:00
|
|
|
using Microsoft.AspNetCore.Mvc;
|
2023-02-23 09:52:37 +01:00
|
|
|
using NBitcoin;
|
|
|
|
using NBitcoin.DataEncoders;
|
2022-07-22 15:41:14 +02:00
|
|
|
using NBitpayClient;
|
2022-11-25 02:42:55 +01:00
|
|
|
using Newtonsoft.Json.Linq;
|
2022-07-22 15:41:14 +02:00
|
|
|
using NicolasDorier.RateLimits;
|
2018-04-03 04:50:41 +02:00
|
|
|
|
2022-07-18 20:51:53 +02:00
|
|
|
namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
2018-04-03 04:50:41 +02:00
|
|
|
{
|
2022-07-18 20:51:53 +02:00
|
|
|
[AutoValidateAntiforgeryToken]
|
|
|
|
[Route("apps")]
|
|
|
|
public class UIPointOfSaleController : Controller
|
2018-04-03 04:50:41 +02:00
|
|
|
{
|
2022-07-18 20:51:53 +02:00
|
|
|
public UIPointOfSaleController(
|
2022-07-22 15:41:14 +02:00
|
|
|
AppService appService,
|
2022-07-18 20:51:53 +02:00
|
|
|
CurrencyNameTable currencies,
|
|
|
|
StoreRepository storeRepository,
|
2022-11-25 10:14:33 +01:00
|
|
|
UIInvoiceController invoiceController,
|
2023-04-05 15:42:23 +02:00
|
|
|
FormDataService formDataService,
|
|
|
|
DisplayFormatter displayFormatter)
|
2022-07-18 20:51:53 +02:00
|
|
|
{
|
|
|
|
_currencies = currencies;
|
|
|
|
_appService = appService;
|
2022-07-22 15:41:14 +02:00
|
|
|
_storeRepository = storeRepository;
|
|
|
|
_invoiceController = invoiceController;
|
2023-04-05 15:42:23 +02:00
|
|
|
_displayFormatter = displayFormatter;
|
2023-02-20 11:35:54 +01:00
|
|
|
FormDataService = formDataService;
|
2022-07-18 20:51:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private readonly CurrencyNameTable _currencies;
|
|
|
|
private readonly StoreRepository _storeRepository;
|
|
|
|
private readonly AppService _appService;
|
2022-07-22 15:41:14 +02:00
|
|
|
private readonly UIInvoiceController _invoiceController;
|
2023-04-05 15:42:23 +02:00
|
|
|
private readonly DisplayFormatter _displayFormatter;
|
2023-02-20 11:35:54 +01:00
|
|
|
public FormDataService FormDataService { get; }
|
2022-11-25 10:14:33 +01:00
|
|
|
|
2022-07-22 15:41:14 +02:00
|
|
|
[HttpGet("/")]
|
2023-03-01 07:27:18 +01:00
|
|
|
[HttpGet("/apps/{appId}/pos")]
|
2022-07-22 15:41:14 +02:00
|
|
|
[HttpGet("/apps/{appId}/pos/{viewType?}")]
|
2023-03-20 02:39:26 +01:00
|
|
|
[DomainMappingConstraint(PointOfSaleAppType.AppType)]
|
2023-03-01 07:27:18 +01:00
|
|
|
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.Unset)]
|
2022-07-22 15:41:14 +02:00
|
|
|
public async Task<IActionResult> ViewPointOfSale(string appId, PosViewType? viewType = null)
|
|
|
|
{
|
2023-03-20 02:39:26 +01:00
|
|
|
var app = await _appService.GetApp(appId, PointOfSaleAppType.AppType);
|
2022-07-22 15:41:14 +02:00
|
|
|
if (app == null)
|
|
|
|
return NotFound();
|
|
|
|
var settings = app.GetSettings<PointOfSaleSettings>();
|
2023-01-06 14:18:07 +01:00
|
|
|
var numberFormatInfo = _appService.Currencies.GetNumberFormatInfo(settings.Currency) ??
|
2022-07-22 15:41:14 +02:00
|
|
|
_appService.Currencies.GetNumberFormatInfo("USD");
|
|
|
|
double step = Math.Pow(10, -numberFormatInfo.CurrencyDecimalDigits);
|
|
|
|
viewType ??= settings.EnableShoppingCart ? PosViewType.Cart : settings.DefaultView;
|
|
|
|
var store = await _appService.GetStore(app);
|
|
|
|
var storeBlob = store.GetStoreBlob();
|
|
|
|
|
|
|
|
return View($"PointOfSale/Public/{viewType}", new ViewPointOfSaleViewModel
|
|
|
|
{
|
|
|
|
Title = settings.Title,
|
2023-01-30 09:23:49 +01:00
|
|
|
StoreName = store.StoreName,
|
|
|
|
BrandColor = storeBlob.BrandColor,
|
|
|
|
CssFileId = storeBlob.CssFileId,
|
|
|
|
LogoFileId = storeBlob.LogoFileId,
|
2022-07-22 15:41:14 +02:00
|
|
|
Step = step.ToString(CultureInfo.InvariantCulture),
|
|
|
|
ViewType = (PosViewType)viewType,
|
|
|
|
ShowCustomAmount = settings.ShowCustomAmount,
|
|
|
|
ShowDiscount = settings.ShowDiscount,
|
|
|
|
EnableTips = settings.EnableTips,
|
|
|
|
CurrencyCode = settings.Currency,
|
|
|
|
CurrencySymbol = numberFormatInfo.CurrencySymbol,
|
|
|
|
CurrencyInfo = new ViewPointOfSaleViewModel.CurrencyInfoData
|
|
|
|
{
|
|
|
|
CurrencySymbol = string.IsNullOrEmpty(numberFormatInfo.CurrencySymbol) ? settings.Currency : numberFormatInfo.CurrencySymbol,
|
|
|
|
Divisibility = numberFormatInfo.CurrencyDecimalDigits,
|
|
|
|
DecimalSeparator = numberFormatInfo.CurrencyDecimalSeparator,
|
|
|
|
ThousandSeparator = numberFormatInfo.NumberGroupSeparator,
|
|
|
|
Prefixed = new[] { 0, 2 }.Contains(numberFormatInfo.CurrencyPositivePattern),
|
|
|
|
SymbolSpace = new[] { 2, 3 }.Contains(numberFormatInfo.CurrencyPositivePattern)
|
|
|
|
},
|
2023-05-23 02:18:57 +02:00
|
|
|
Items = AppService.Parse(settings.Template, false),
|
2022-07-22 15:41:14 +02:00
|
|
|
ButtonText = settings.ButtonText,
|
|
|
|
CustomButtonText = settings.CustomButtonText,
|
|
|
|
CustomTipText = settings.CustomTipText,
|
|
|
|
CustomTipPercentages = settings.CustomTipPercentages,
|
|
|
|
CustomCSSLink = settings.CustomCSSLink,
|
|
|
|
CustomLogoLink = storeBlob.CustomLogo,
|
|
|
|
AppId = appId,
|
|
|
|
StoreId = store.Id,
|
|
|
|
Description = settings.Description,
|
|
|
|
EmbeddedCSS = settings.EmbeddedCSS,
|
|
|
|
RequiresRefundEmail = settings.RequiresRefundEmail
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
[HttpPost("/")]
|
|
|
|
[HttpPost("/apps/{appId}/pos/{viewType?}")]
|
|
|
|
[IgnoreAntiforgeryToken]
|
|
|
|
[EnableCors(CorsPolicies.All)]
|
2023-03-20 02:39:26 +01:00
|
|
|
[DomainMappingConstraint(PointOfSaleAppType.AppType)]
|
2022-07-22 15:41:14 +02:00
|
|
|
[RateLimitsFilter(ZoneLimits.PublicInvoices, Scope = RateLimitsScope.RemoteAddress)]
|
2023-03-01 07:27:18 +01:00
|
|
|
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.Unset)]
|
2022-07-22 15:41:14 +02:00
|
|
|
public async Task<IActionResult> ViewPointOfSale(string appId,
|
2023-02-20 11:35:54 +01:00
|
|
|
PosViewType? viewType = null,
|
|
|
|
[ModelBinder(typeof(InvariantDecimalModelBinder))] decimal? amount = null,
|
2023-06-16 16:02:14 +02:00
|
|
|
[ModelBinder(typeof(InvariantDecimalModelBinder))] decimal? tip = null,
|
|
|
|
[ModelBinder(typeof(InvariantDecimalModelBinder))] decimal? discount = null,
|
2023-02-20 11:35:54 +01:00
|
|
|
string email = null,
|
|
|
|
string orderId = null,
|
|
|
|
string notificationUrl = null,
|
|
|
|
string redirectUrl = null,
|
|
|
|
string choiceKey = null,
|
2022-07-22 15:41:14 +02:00
|
|
|
string posData = null,
|
2023-02-20 11:35:54 +01:00
|
|
|
string formResponse = null,
|
2022-07-22 15:41:14 +02:00
|
|
|
RequiresRefundEmail requiresRefundEmail = RequiresRefundEmail.InheritFromStore,
|
|
|
|
CancellationToken cancellationToken = default)
|
|
|
|
{
|
2023-03-20 02:39:26 +01:00
|
|
|
var app = await _appService.GetApp(appId, PointOfSaleAppType.AppType);
|
2022-07-22 15:41:14 +02:00
|
|
|
if (string.IsNullOrEmpty(choiceKey) && amount <= 0)
|
|
|
|
{
|
|
|
|
return RedirectToAction(nameof(ViewPointOfSale), new { appId });
|
|
|
|
}
|
|
|
|
if (app == null)
|
|
|
|
return NotFound();
|
|
|
|
var settings = app.GetSettings<PointOfSaleSettings>();
|
|
|
|
settings.DefaultView = settings.EnableShoppingCart ? PosViewType.Cart : settings.DefaultView;
|
2022-10-08 05:41:56 +02:00
|
|
|
var currentView = viewType ?? settings.DefaultView;
|
2023-01-06 14:18:07 +01:00
|
|
|
if (string.IsNullOrEmpty(choiceKey) && !settings.ShowCustomAmount &&
|
2022-10-08 05:41:56 +02:00
|
|
|
currentView != PosViewType.Cart && currentView != PosViewType.Light)
|
2022-07-22 15:41:14 +02:00
|
|
|
{
|
|
|
|
return RedirectToAction(nameof(ViewPointOfSale), new { appId, viewType });
|
|
|
|
}
|
2023-02-25 15:34:49 +01:00
|
|
|
var jposData = TryParseJObject(posData);
|
2022-10-08 05:41:56 +02:00
|
|
|
string title;
|
|
|
|
decimal? price;
|
2022-07-22 15:41:14 +02:00
|
|
|
Dictionary<string, InvoiceSupportedTransactionCurrency> paymentMethods = null;
|
|
|
|
ViewPointOfSaleViewModel.Item choice = null;
|
2023-04-05 15:42:23 +02:00
|
|
|
Dictionary<string, int> cartItems = null;
|
|
|
|
ViewPointOfSaleViewModel.Item[] choices = null;
|
2022-07-22 15:41:14 +02:00
|
|
|
if (!string.IsNullOrEmpty(choiceKey))
|
|
|
|
{
|
2023-05-23 02:18:57 +02:00
|
|
|
choices = AppService.Parse(settings.Template, false);
|
2022-07-22 15:41:14 +02:00
|
|
|
choice = choices.FirstOrDefault(c => c.Id == choiceKey);
|
|
|
|
if (choice == null)
|
|
|
|
return NotFound();
|
|
|
|
title = choice.Title;
|
2023-05-23 02:18:57 +02:00
|
|
|
if (choice.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Topup)
|
2022-07-22 15:41:14 +02:00
|
|
|
{
|
|
|
|
price = null;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
price = choice.Price.Value;
|
|
|
|
if (amount > price)
|
|
|
|
price = amount;
|
|
|
|
}
|
|
|
|
|
2022-10-08 05:41:56 +02:00
|
|
|
if (choice.Inventory is <= 0)
|
2022-07-22 15:41:14 +02:00
|
|
|
{
|
2022-10-08 05:41:56 +02:00
|
|
|
return RedirectToAction(nameof(ViewPointOfSale), new { appId });
|
2022-07-22 15:41:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (choice?.PaymentMethods?.Any() is true)
|
|
|
|
{
|
|
|
|
paymentMethods = choice?.PaymentMethods.ToDictionary(s => s,
|
|
|
|
s => new InvoiceSupportedTransactionCurrency() { Enabled = true });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-10-08 05:41:56 +02:00
|
|
|
if (!settings.ShowCustomAmount && currentView != PosViewType.Cart && currentView != PosViewType.Light)
|
2022-07-22 15:41:14 +02:00
|
|
|
return NotFound();
|
2023-01-06 14:18:07 +01:00
|
|
|
|
2022-07-22 15:41:14 +02:00
|
|
|
title = settings.Title;
|
|
|
|
//if cart IS enabled and we detect posdata that matches the cart system's, check inventory for the items
|
2023-04-10 04:07:03 +02:00
|
|
|
|
2023-02-25 15:34:49 +01:00
|
|
|
if (currentView == PosViewType.Cart &&
|
2023-04-05 15:42:23 +02:00
|
|
|
AppService.TryParsePosCartItems(jposData, out cartItems))
|
2022-07-22 15:41:14 +02:00
|
|
|
{
|
2023-06-16 16:18:47 +02:00
|
|
|
price = 0.0m;
|
2023-05-23 02:18:57 +02:00
|
|
|
choices = AppService.Parse(settings.Template, false);
|
2022-07-22 15:41:14 +02:00
|
|
|
foreach (var cartItem in cartItems)
|
|
|
|
{
|
|
|
|
var itemChoice = choices.FirstOrDefault(c => c.Id == cartItem.Key);
|
|
|
|
if (itemChoice == null)
|
|
|
|
return NotFound();
|
|
|
|
|
|
|
|
if (itemChoice.Inventory.HasValue)
|
|
|
|
{
|
|
|
|
switch (itemChoice.Inventory)
|
|
|
|
{
|
|
|
|
case int i when i <= 0:
|
|
|
|
return RedirectToAction(nameof(ViewPointOfSale), new { appId });
|
|
|
|
case int inventory when inventory < cartItem.Value:
|
|
|
|
return RedirectToAction(nameof(ViewPointOfSale), new { appId });
|
|
|
|
}
|
|
|
|
}
|
2022-11-18 11:51:33 +01:00
|
|
|
|
|
|
|
decimal expectedCartItemPrice = 0;
|
2023-05-23 02:18:57 +02:00
|
|
|
if (itemChoice.PriceType != ViewPointOfSaleViewModel.ItemPriceType.Topup)
|
2022-11-18 11:51:33 +01:00
|
|
|
{
|
2023-05-23 02:18:57 +02:00
|
|
|
expectedCartItemPrice = itemChoice.Price ?? 0;
|
2022-11-18 11:51:33 +01:00
|
|
|
}
|
|
|
|
|
2023-06-16 16:02:14 +02:00
|
|
|
price += expectedCartItemPrice * cartItem.Value;
|
2022-07-22 15:41:14 +02:00
|
|
|
}
|
2023-06-16 16:02:14 +02:00
|
|
|
if (discount is decimal d)
|
|
|
|
price -= price * d/100.0m;
|
|
|
|
if (tip is decimal t)
|
|
|
|
price += t;
|
2022-07-22 15:41:14 +02:00
|
|
|
}
|
|
|
|
}
|
2022-11-25 02:42:55 +01:00
|
|
|
|
2022-07-22 15:41:14 +02:00
|
|
|
var store = await _appService.GetStore(app);
|
2023-05-30 10:04:23 +02:00
|
|
|
var storeBlob = store.GetStoreBlob();
|
2022-11-25 02:42:55 +01:00
|
|
|
var posFormId = settings.FormId;
|
2023-02-20 11:35:54 +01:00
|
|
|
var formData = await FormDataService.GetForm(posFormId);
|
2023-04-10 04:07:03 +02:00
|
|
|
|
2023-02-20 11:35:54 +01:00
|
|
|
JObject formResponseJObject = null;
|
|
|
|
switch (formData)
|
2022-11-25 02:42:55 +01:00
|
|
|
{
|
|
|
|
case null:
|
|
|
|
break;
|
2023-02-20 11:35:54 +01:00
|
|
|
case not null:
|
|
|
|
if (formResponse is null)
|
2022-11-25 02:42:55 +01:00
|
|
|
{
|
2023-02-23 09:52:37 +01:00
|
|
|
var vm = new PostRedirectViewModel
|
2023-02-20 11:35:54 +01:00
|
|
|
{
|
2023-04-26 09:45:35 +02:00
|
|
|
FormUrl = Url.Action(nameof(POSForm), "UIPointOfSale", new {appId, buyerEmail = email}),
|
|
|
|
FormParameters = new MultiValueDictionary<string, string>(Request.Form.Select(pair =>
|
|
|
|
new KeyValuePair<string, IReadOnlyCollection<string>>(pair.Key, pair.Value)))
|
2023-02-23 09:52:37 +01:00
|
|
|
};
|
|
|
|
if (viewType.HasValue)
|
|
|
|
{
|
|
|
|
vm.RouteParameters.Add("viewType", viewType.Value.ToString());
|
|
|
|
}
|
2023-04-26 09:45:35 +02:00
|
|
|
|
2023-02-23 09:52:37 +01:00
|
|
|
return View("PostRedirect", vm);
|
2022-11-25 02:42:55 +01:00
|
|
|
}
|
2023-01-06 14:18:07 +01:00
|
|
|
|
2023-02-25 15:34:49 +01:00
|
|
|
formResponseJObject = TryParseJObject(formResponse) ?? new JObject();
|
2023-02-20 11:35:54 +01:00
|
|
|
var form = Form.Parse(formData.Config);
|
|
|
|
form.SetValues(formResponseJObject);
|
|
|
|
if (!FormDataService.Validate(form, ModelState))
|
2022-11-25 02:42:55 +01:00
|
|
|
{
|
2023-02-20 11:35:54 +01:00
|
|
|
//someone tried to bypass validation
|
2023-02-23 09:52:37 +01:00
|
|
|
return RedirectToAction(nameof(ViewPointOfSale), new { appId, viewType });
|
2022-11-25 02:42:55 +01:00
|
|
|
}
|
2023-01-06 14:18:07 +01:00
|
|
|
|
2023-04-04 04:01:34 +02:00
|
|
|
formResponseJObject = FormDataService.GetValues(form);
|
2023-02-20 11:35:54 +01:00
|
|
|
break;
|
2022-11-25 02:42:55 +01:00
|
|
|
}
|
2022-07-22 15:41:14 +02:00
|
|
|
try
|
|
|
|
{
|
2022-10-08 05:41:56 +02:00
|
|
|
var invoice = await _invoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest
|
2022-07-22 15:41:14 +02:00
|
|
|
{
|
|
|
|
ItemCode = choice?.Id,
|
|
|
|
ItemDesc = title,
|
|
|
|
Currency = settings.Currency,
|
|
|
|
Price = price,
|
|
|
|
BuyerEmail = email,
|
|
|
|
OrderId = orderId ?? AppService.GetAppOrderId(app),
|
|
|
|
NotificationURL =
|
|
|
|
string.IsNullOrEmpty(notificationUrl) ? settings.NotificationUrl : notificationUrl,
|
2023-01-06 14:18:07 +01:00
|
|
|
RedirectURL = !string.IsNullOrEmpty(redirectUrl) ? redirectUrl
|
2022-07-22 15:41:14 +02:00
|
|
|
: !string.IsNullOrEmpty(settings.RedirectUrl) ? settings.RedirectUrl
|
2023-02-23 09:52:37 +01:00
|
|
|
: Request.GetAbsoluteUri(Url.Action(nameof(ViewPointOfSale), "UIPointOfSale", new { appId, viewType })),
|
2022-07-22 15:41:14 +02:00
|
|
|
FullNotifications = true,
|
|
|
|
ExtendedNotifications = true,
|
|
|
|
RedirectAutomatically = settings.RedirectAutomatically,
|
|
|
|
SupportedTransactionCurrencies = paymentMethods,
|
|
|
|
RequiresRefundEmail = requiresRefundEmail == RequiresRefundEmail.InheritFromStore
|
2023-05-30 10:04:23 +02:00
|
|
|
? storeBlob.RequiresRefundEmail
|
2022-07-22 15:41:14 +02:00
|
|
|
: requiresRefundEmail == RequiresRefundEmail.On,
|
|
|
|
}, store, HttpContext.Request.GetAbsoluteRoot(),
|
2022-10-08 05:41:56 +02:00
|
|
|
new List<string> { AppService.GetAppInternalTag(appId) },
|
2023-02-20 11:35:54 +01:00
|
|
|
cancellationToken, entity =>
|
2022-07-22 15:41:14 +02:00
|
|
|
{
|
|
|
|
entity.Metadata.OrderUrl = Request.GetDisplayUrl();
|
2023-02-25 15:34:49 +01:00
|
|
|
entity.Metadata.PosData = jposData;
|
2023-04-05 15:42:23 +02:00
|
|
|
var receiptData = new JObject();
|
|
|
|
if (choice is not null)
|
|
|
|
{
|
2023-04-10 04:07:03 +02:00
|
|
|
receiptData = JObject.FromObject(new Dictionary<string, string>()
|
2023-04-05 15:42:23 +02:00
|
|
|
{
|
|
|
|
{"Title", choice.Title}, {"Description", choice.Description},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else if (jposData is not null)
|
|
|
|
{
|
|
|
|
var appPosData = jposData.ToObject<PosAppData>();
|
|
|
|
receiptData = new JObject();
|
|
|
|
if (cartItems is not null && choices is not null)
|
|
|
|
{
|
|
|
|
var selectedChoices = choices.Where(item => cartItems.Keys.Contains(item.Id))
|
|
|
|
.ToDictionary(item => item.Id);
|
|
|
|
var cartData = new JObject();
|
|
|
|
foreach (KeyValuePair<string, int> cartItem in cartItems)
|
|
|
|
{
|
|
|
|
if (selectedChoices.TryGetValue(cartItem.Key, out var selectedChoice))
|
|
|
|
{
|
|
|
|
cartData.Add(selectedChoice.Title ?? selectedChoice.Id,
|
2023-05-23 02:18:57 +02:00
|
|
|
$"{(selectedChoice.Price is null ? "Any price" : $"{_displayFormatter.Currency((decimal)selectedChoice.Price.Value, settings.Currency, DisplayFormatter.CurrencyFormat.Symbol)}")} x {cartItem.Value} = {(selectedChoice.Price is null ? "Any price" : $"{_displayFormatter.Currency(((decimal)selectedChoice.Price.Value) * cartItem.Value, settings.Currency, DisplayFormatter.CurrencyFormat.Symbol)}")}");
|
2023-04-05 15:42:23 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
receiptData.Add("Cart", cartData);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (appPosData.DiscountAmount > 0)
|
|
|
|
{
|
|
|
|
receiptData.Add("Discount",
|
2023-04-10 04:07:03 +02:00
|
|
|
$"{_displayFormatter.Currency(appPosData.DiscountAmount, settings.Currency, DisplayFormatter.CurrencyFormat.Symbol)} {(appPosData.DiscountPercentage > 0 ? $"({appPosData.DiscountPercentage}%)" : string.Empty)}");
|
2023-04-05 15:42:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (appPosData.Tip > 0)
|
|
|
|
{
|
|
|
|
receiptData.Add("Tip",
|
2023-04-10 04:07:03 +02:00
|
|
|
$"{_displayFormatter.Currency(appPosData.Tip, settings.Currency, DisplayFormatter.CurrencyFormat.Symbol)}");
|
2023-04-05 15:42:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
entity.Metadata.SetAdditionalData("receiptData", receiptData);
|
|
|
|
|
2023-04-10 04:07:03 +02:00
|
|
|
if (formResponseJObject is null)
|
|
|
|
return;
|
2023-02-25 14:38:28 +01:00
|
|
|
var meta = entity.Metadata.ToJObject();
|
2023-02-25 15:34:49 +01:00
|
|
|
meta.Merge(formResponseJObject);
|
|
|
|
entity.Metadata = InvoiceMetadata.FromJObject(meta);
|
2023-01-06 14:18:07 +01:00
|
|
|
});
|
2023-05-31 04:27:03 +02:00
|
|
|
if (price is 0 && storeBlob.ReceiptOptions?.Enabled is true)
|
2023-05-30 10:04:23 +02:00
|
|
|
{
|
|
|
|
return RedirectToAction(nameof(UIInvoiceController.InvoiceReceipt), "UIInvoice", new { invoiceId = invoice.Data.Id });
|
|
|
|
}
|
2022-07-22 15:41:14 +02:00
|
|
|
return RedirectToAction(nameof(UIInvoiceController.Checkout), "UIInvoice", new { invoiceId = invoice.Data.Id });
|
|
|
|
}
|
|
|
|
catch (BitpayHttpException e)
|
|
|
|
{
|
|
|
|
TempData.SetStatusMessageModel(new StatusMessageModel
|
|
|
|
{
|
|
|
|
Html = e.Message.Replace("\n", "<br />", StringComparison.OrdinalIgnoreCase),
|
|
|
|
Severity = StatusMessageModel.StatusSeverity.Error,
|
|
|
|
AllowDismiss = true
|
|
|
|
});
|
2023-02-20 11:35:54 +01:00
|
|
|
return RedirectToAction(nameof(ViewPointOfSale), new { appId });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-25 15:34:49 +01:00
|
|
|
private JObject TryParseJObject(string posData)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
return JObject.Parse(posData);
|
|
|
|
}
|
|
|
|
catch
|
|
|
|
{
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2023-02-23 09:52:37 +01:00
|
|
|
[HttpPost("/apps/{appId}/pos/form/{viewType?}")]
|
2023-03-01 07:27:18 +01:00
|
|
|
[IgnoreAntiforgeryToken]
|
|
|
|
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.Unset)]
|
2023-02-23 09:52:37 +01:00
|
|
|
public async Task<IActionResult> POSForm(string appId, PosViewType? viewType = null)
|
2023-02-20 11:35:54 +01:00
|
|
|
{
|
2023-03-20 02:39:26 +01:00
|
|
|
var app = await _appService.GetApp(appId, PointOfSaleAppType.AppType);
|
2023-02-20 11:35:54 +01:00
|
|
|
if (app == null)
|
|
|
|
return NotFound();
|
2023-04-10 04:07:03 +02:00
|
|
|
|
2023-02-20 11:35:54 +01:00
|
|
|
var settings = app.GetSettings<PointOfSaleSettings>();
|
|
|
|
var formData = await FormDataService.GetForm(settings.FormId);
|
|
|
|
if (formData is null)
|
|
|
|
{
|
2023-02-23 09:52:37 +01:00
|
|
|
return RedirectToAction(nameof(ViewPointOfSale), new { appId, viewType });
|
2023-02-20 11:35:54 +01:00
|
|
|
}
|
2023-04-10 04:07:03 +02:00
|
|
|
|
2023-02-23 09:52:37 +01:00
|
|
|
var prefix = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16)) + "_";
|
|
|
|
var formParameters = Request.Form
|
2023-02-20 11:35:54 +01:00
|
|
|
.Where(pair => pair.Key != "__RequestVerificationToken")
|
2023-02-23 09:52:37 +01:00
|
|
|
.ToMultiValueDictionary(p => p.Key, p => p.Value.ToString());
|
2023-03-17 03:56:32 +01:00
|
|
|
var controller = nameof(UIPointOfSaleController).TrimEnd("Controller", StringComparison.InvariantCulture);
|
2023-02-20 11:35:54 +01:00
|
|
|
var store = await _appService.GetStore(app);
|
|
|
|
var storeBlob = store.GetStoreBlob();
|
|
|
|
var form = Form.Parse(formData.Config);
|
2023-04-26 09:45:35 +02:00
|
|
|
form.ApplyValuesFromForm(Request.Query);
|
2023-02-23 09:52:37 +01:00
|
|
|
var vm = new FormViewModel
|
2023-02-20 11:35:54 +01:00
|
|
|
{
|
|
|
|
StoreName = store.StoreName,
|
|
|
|
BrandColor = storeBlob.BrandColor,
|
|
|
|
CssFileId = storeBlob.CssFileId,
|
|
|
|
LogoFileId = storeBlob.LogoFileId,
|
|
|
|
FormName = formData.Name,
|
|
|
|
Form = form,
|
|
|
|
AspController = controller,
|
|
|
|
AspAction = nameof(POSFormSubmit),
|
|
|
|
RouteParameters = new Dictionary<string, string> { { "appId", appId } },
|
2023-02-23 09:52:37 +01:00
|
|
|
FormParameters = formParameters,
|
|
|
|
FormParameterPrefix = prefix
|
|
|
|
};
|
|
|
|
if (viewType.HasValue)
|
|
|
|
{
|
|
|
|
vm.RouteParameters.Add("viewType", viewType.Value.ToString());
|
|
|
|
}
|
2023-04-10 04:07:03 +02:00
|
|
|
|
2023-02-23 09:52:37 +01:00
|
|
|
return View("Views/UIForms/View", vm);
|
2023-02-20 11:35:54 +01:00
|
|
|
}
|
|
|
|
|
2023-02-23 09:52:37 +01:00
|
|
|
[HttpPost("/apps/{appId}/pos/form/submit/{viewType?}")]
|
2023-03-01 07:27:18 +01:00
|
|
|
[IgnoreAntiforgeryToken]
|
|
|
|
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.Unset)]
|
2023-02-23 09:52:37 +01:00
|
|
|
public async Task<IActionResult> POSFormSubmit(string appId, FormViewModel viewModel, PosViewType? viewType = null)
|
2023-02-20 11:35:54 +01:00
|
|
|
{
|
2023-03-20 02:39:26 +01:00
|
|
|
var app = await _appService.GetApp(appId, PointOfSaleAppType.AppType);
|
2023-02-20 11:35:54 +01:00
|
|
|
if (app == null)
|
|
|
|
return NotFound();
|
2023-04-10 04:07:03 +02:00
|
|
|
|
2023-02-20 11:35:54 +01:00
|
|
|
var settings = app.GetSettings<PointOfSaleSettings>();
|
|
|
|
var formData = await FormDataService.GetForm(settings.FormId);
|
2023-02-23 09:52:37 +01:00
|
|
|
if (formData is null)
|
2023-02-20 11:35:54 +01:00
|
|
|
{
|
2023-02-23 09:52:37 +01:00
|
|
|
return RedirectToAction(nameof(ViewPointOfSale), new { appId, viewType });
|
2022-07-22 15:41:14 +02:00
|
|
|
}
|
2023-02-20 11:35:54 +01:00
|
|
|
var form = Form.Parse(formData.Config);
|
2023-02-23 09:52:37 +01:00
|
|
|
var formFieldNames = form.GetAllFields().Select(tuple => tuple.FullName).Distinct().ToArray();
|
|
|
|
var formParameters = Request.Form
|
|
|
|
.Where(pair => pair.Key.StartsWith(viewModel.FormParameterPrefix))
|
|
|
|
.ToDictionary(pair => pair.Key.Replace(viewModel.FormParameterPrefix, string.Empty), pair => pair.Value)
|
|
|
|
.ToMultiValueDictionary(p => p.Key, p => p.Value.ToString());
|
2023-04-10 04:07:03 +02:00
|
|
|
|
2023-02-23 09:52:37 +01:00
|
|
|
if (Request is { Method: "POST", HasFormContentType: true })
|
2023-02-20 11:35:54 +01:00
|
|
|
{
|
2023-02-23 09:52:37 +01:00
|
|
|
form.ApplyValuesFromForm(Request.Form.Where(pair => formFieldNames.Contains(pair.Key)));
|
2023-04-10 04:07:03 +02:00
|
|
|
|
2023-02-20 11:35:54 +01:00
|
|
|
if (FormDataService.Validate(form, ModelState))
|
|
|
|
{
|
2023-03-17 03:56:32 +01:00
|
|
|
var controller = nameof(UIPointOfSaleController).TrimEnd("Controller", StringComparison.InvariantCulture);
|
2023-02-23 09:52:37 +01:00
|
|
|
var redirectUrl =
|
2023-04-10 04:07:03 +02:00
|
|
|
Request.GetAbsoluteUri(Url.Action(nameof(ViewPointOfSale), controller, new { appId, viewType }));
|
2023-04-04 04:01:34 +02:00
|
|
|
formParameters.Add("formResponse", FormDataService.GetValues(form).ToString());
|
2023-02-20 11:35:54 +01:00
|
|
|
return View("PostRedirect", new PostRedirectViewModel
|
|
|
|
{
|
2023-02-23 09:52:37 +01:00
|
|
|
FormUrl = redirectUrl,
|
|
|
|
FormParameters = formParameters
|
2023-02-20 11:35:54 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
viewModel.FormName = formData.Name;
|
|
|
|
viewModel.Form = form;
|
2023-02-23 09:52:37 +01:00
|
|
|
|
|
|
|
viewModel.FormParameters = formParameters;
|
2023-02-20 11:35:54 +01:00
|
|
|
return View("Views/UIForms/View", viewModel);
|
2022-07-22 15:41:14 +02:00
|
|
|
}
|
2023-01-06 14:18:07 +01:00
|
|
|
|
2022-07-22 15:41:14 +02:00
|
|
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
2021-12-11 04:32:23 +01:00
|
|
|
[HttpGet("{appId}/settings/pos")]
|
2022-06-20 04:55:47 +02:00
|
|
|
public async Task<IActionResult> UpdatePointOfSale(string appId)
|
2018-04-03 04:50:41 +02:00
|
|
|
{
|
2021-12-20 15:15:32 +01:00
|
|
|
var app = GetCurrentApp();
|
|
|
|
if (app == null)
|
2018-04-03 04:50:41 +02:00
|
|
|
return NotFound();
|
2021-12-31 08:59:02 +01:00
|
|
|
|
2021-12-20 15:15:32 +01:00
|
|
|
var settings = app.GetSettings<PointOfSaleSettings>();
|
2020-05-27 14:20:21 +02:00
|
|
|
settings.DefaultView = settings.EnableShoppingCart ? PosViewType.Cart : settings.DefaultView;
|
|
|
|
settings.EnableShoppingCart = false;
|
2022-11-02 10:21:33 +01:00
|
|
|
|
2021-04-08 15:32:42 +02:00
|
|
|
var vm = new UpdatePointOfSaleViewModel
|
2018-05-24 16:54:48 +02:00
|
|
|
{
|
2019-01-31 08:56:21 +01:00
|
|
|
Id = appId,
|
2021-12-20 15:15:32 +01:00
|
|
|
StoreId = app.StoreDataId,
|
|
|
|
StoreName = app.StoreData?.StoreName,
|
2022-06-20 04:55:47 +02:00
|
|
|
StoreDefaultCurrency = await GetStoreDefaultCurrentIfEmpty(app.StoreDataId, settings.Currency),
|
2021-12-20 15:15:32 +01:00
|
|
|
AppName = app.Name,
|
2018-05-24 16:54:48 +02:00
|
|
|
Title = settings.Title,
|
2020-05-26 16:48:47 +02:00
|
|
|
DefaultView = settings.DefaultView,
|
2018-05-24 16:54:48 +02:00
|
|
|
ShowCustomAmount = settings.ShowCustomAmount,
|
2019-02-25 07:11:03 +01:00
|
|
|
ShowDiscount = settings.ShowDiscount,
|
|
|
|
EnableTips = settings.EnableTips,
|
2018-05-24 16:54:48 +02:00
|
|
|
Currency = settings.Currency,
|
2018-11-17 03:39:43 +01:00
|
|
|
Template = settings.Template,
|
|
|
|
ButtonText = settings.ButtonText ?? PointOfSaleSettings.BUTTON_TEXT_DEF,
|
|
|
|
CustomButtonText = settings.CustomButtonText ?? PointOfSaleSettings.CUSTOM_BUTTON_TEXT_DEF,
|
2018-11-27 07:14:32 +01:00
|
|
|
CustomTipText = settings.CustomTipText ?? PointOfSaleSettings.CUSTOM_TIP_TEXT_DEF,
|
2018-12-19 07:07:05 +01:00
|
|
|
CustomTipPercentages = settings.CustomTipPercentages != null ? string.Join(",", settings.CustomTipPercentages) : string.Join(",", PointOfSaleSettings.CUSTOM_TIP_PERCENTAGES_DEF),
|
2019-03-29 07:51:00 +01:00
|
|
|
CustomCSSLink = settings.CustomCSSLink,
|
2019-08-19 07:13:42 +02:00
|
|
|
EmbeddedCSS = settings.EmbeddedCSS,
|
|
|
|
Description = settings.Description,
|
2019-04-11 11:08:42 +02:00
|
|
|
NotificationUrl = settings.NotificationUrl,
|
2020-10-13 09:51:28 +02:00
|
|
|
RedirectUrl = settings.RedirectUrl,
|
2022-06-28 07:05:02 +02:00
|
|
|
SearchTerm = app.TagAllInvoices ? $"storeid:{app.StoreDataId}" : $"orderid:{AppService.GetAppOrderId(app)}",
|
2021-10-27 16:32:56 +02:00
|
|
|
RedirectAutomatically = settings.RedirectAutomatically.HasValue ? settings.RedirectAutomatically.Value ? "true" : "false" : "",
|
2022-11-25 02:42:55 +01:00
|
|
|
FormId = settings.FormId
|
2018-05-24 16:54:48 +02:00
|
|
|
};
|
2023-02-20 11:35:54 +01:00
|
|
|
if (HttpContext.Request != null)
|
2018-05-24 16:54:48 +02:00
|
|
|
{
|
2021-10-25 09:54:36 +02:00
|
|
|
var appUrl = HttpContext.Request.GetAbsoluteUri($"/apps/{appId}/pos");
|
2018-05-24 16:54:48 +02:00
|
|
|
var encoder = HtmlEncoder.Default;
|
|
|
|
if (settings.ShowCustomAmount)
|
|
|
|
{
|
2023-02-20 11:35:54 +01:00
|
|
|
var builder = new StringBuilder();
|
2021-12-27 05:46:31 +01:00
|
|
|
builder.AppendLine(CultureInfo.InvariantCulture, $"<form method=\"POST\" action=\"{encoder.Encode(appUrl)}\">");
|
2018-05-24 16:54:48 +02:00
|
|
|
builder.AppendLine($" <input type=\"hidden\" name=\"amount\" value=\"100\" />");
|
|
|
|
builder.AppendLine($" <input type=\"hidden\" name=\"email\" value=\"customer@example.com\" />");
|
|
|
|
builder.AppendLine($" <input type=\"hidden\" name=\"orderId\" value=\"CustomOrderId\" />");
|
|
|
|
builder.AppendLine($" <input type=\"hidden\" name=\"notificationUrl\" value=\"https://example.com/callbacks\" />");
|
|
|
|
builder.AppendLine($" <input type=\"hidden\" name=\"redirectUrl\" value=\"https://example.com/thanksyou\" />");
|
|
|
|
builder.AppendLine($" <button type=\"submit\">Buy now</button>");
|
|
|
|
builder.AppendLine($"</form>");
|
|
|
|
vm.Example1 = builder.ToString();
|
|
|
|
}
|
|
|
|
try
|
|
|
|
{
|
2023-05-23 02:18:57 +02:00
|
|
|
var items = AppService.Parse(settings.Template);
|
2018-05-24 16:54:48 +02:00
|
|
|
var builder = new StringBuilder();
|
2021-12-27 05:46:31 +01:00
|
|
|
builder.AppendLine(CultureInfo.InvariantCulture, $"<form method=\"POST\" action=\"{encoder.Encode(appUrl)}\">");
|
2018-05-24 16:54:48 +02:00
|
|
|
builder.AppendLine($" <input type=\"hidden\" name=\"email\" value=\"customer@example.com\" />");
|
|
|
|
builder.AppendLine($" <input type=\"hidden\" name=\"orderId\" value=\"CustomOrderId\" />");
|
|
|
|
builder.AppendLine($" <input type=\"hidden\" name=\"notificationUrl\" value=\"https://example.com/callbacks\" />");
|
|
|
|
builder.AppendLine($" <input type=\"hidden\" name=\"redirectUrl\" value=\"https://example.com/thanksyou\" />");
|
2021-12-27 05:46:31 +01:00
|
|
|
builder.AppendLine(CultureInfo.InvariantCulture, $" <button type=\"submit\" name=\"choiceKey\" value=\"{items[0].Id}\">Buy now</button>");
|
2018-05-24 16:54:48 +02:00
|
|
|
builder.AppendLine($"</form>");
|
|
|
|
vm.Example2 = builder.ToString();
|
|
|
|
}
|
|
|
|
catch { }
|
|
|
|
vm.InvoiceUrl = appUrl + "invoices/SkdsDghkdP3D3qkj7bLq3";
|
|
|
|
}
|
|
|
|
|
2018-05-25 10:35:01 +02:00
|
|
|
vm.ExampleCallback = "{\n \"id\":\"SkdsDghkdP3D3qkj7bLq3\",\n \"url\":\"https://btcpay.example.com/invoice?id=SkdsDghkdP3D3qkj7bLq3\",\n \"status\":\"paid\",\n \"price\":10,\n \"currency\":\"EUR\",\n \"invoiceTime\":1520373130312,\n \"expirationTime\":1520374030312,\n \"currentTime\":1520373179327,\n \"exceptionStatus\":false,\n \"buyerFields\":{\n \"buyerEmail\":\"customer@example.com\",\n \"buyerNotify\":false\n },\n \"paymentSubtotals\": {\n \"BTC\":114700\n },\n \"paymentTotals\": {\n \"BTC\":118400\n },\n \"transactionCurrency\": \"BTC\",\n \"amountPaid\": \"1025900\",\n \"exchangeRates\": {\n \"BTC\": {\n \"EUR\": 8721.690715789999,\n \"USD\": 10817.99\n }\n }\n}";
|
2022-07-18 20:51:53 +02:00
|
|
|
return View("PointOfSale/UpdatePointOfSale", vm);
|
2018-04-03 04:50:41 +02:00
|
|
|
}
|
2021-12-11 04:32:23 +01:00
|
|
|
|
2022-07-22 15:41:14 +02:00
|
|
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
2021-12-11 04:32:23 +01:00
|
|
|
[HttpPost("{appId}/settings/pos")]
|
2018-04-03 04:50:41 +02:00
|
|
|
public async Task<IActionResult> UpdatePointOfSale(string appId, UpdatePointOfSaleViewModel vm)
|
2021-12-20 15:15:32 +01:00
|
|
|
{
|
|
|
|
var app = GetCurrentApp();
|
|
|
|
if (app == null)
|
2021-10-25 09:54:36 +02:00
|
|
|
return NotFound();
|
2021-12-31 08:59:02 +01:00
|
|
|
|
2021-03-30 07:26:33 +02:00
|
|
|
if (!ModelState.IsValid)
|
2022-07-18 20:51:53 +02:00
|
|
|
return View("PointOfSale/UpdatePointOfSale", vm);
|
2021-12-16 17:37:19 +01:00
|
|
|
|
2021-12-20 15:15:32 +01:00
|
|
|
vm.Currency = await GetStoreDefaultCurrentIfEmpty(app.StoreDataId, vm.Currency);
|
2019-02-17 08:53:41 +01:00
|
|
|
if (_currencies.GetCurrencyData(vm.Currency, false) == null)
|
2018-04-03 04:50:41 +02:00
|
|
|
ModelState.AddModelError(nameof(vm.Currency), "Invalid currency");
|
|
|
|
try
|
|
|
|
{
|
2023-05-23 02:18:57 +02:00
|
|
|
vm.Template = AppService.SerializeTemplate(AppService.Parse(vm.Template));
|
2018-04-03 04:50:41 +02:00
|
|
|
}
|
|
|
|
catch
|
|
|
|
{
|
|
|
|
ModelState.AddModelError(nameof(vm.Template), "Invalid template");
|
|
|
|
}
|
|
|
|
if (!ModelState.IsValid)
|
|
|
|
{
|
2022-07-18 20:51:53 +02:00
|
|
|
return View("PointOfSale/UpdatePointOfSale", vm);
|
2018-04-03 04:50:41 +02:00
|
|
|
}
|
2021-10-29 12:29:02 +02:00
|
|
|
|
2022-11-02 10:21:33 +01:00
|
|
|
var storeBlob = GetCurrentStore().GetStoreBlob();
|
|
|
|
var settings = new PointOfSaleSettings
|
2018-04-03 04:50:41 +02:00
|
|
|
{
|
|
|
|
Title = vm.Title,
|
2020-05-26 16:48:47 +02:00
|
|
|
DefaultView = vm.DefaultView,
|
2018-04-26 15:09:18 +02:00
|
|
|
ShowCustomAmount = vm.ShowCustomAmount,
|
2019-02-25 07:11:03 +01:00
|
|
|
ShowDiscount = vm.ShowDiscount,
|
|
|
|
EnableTips = vm.EnableTips,
|
2021-10-25 09:54:36 +02:00
|
|
|
Currency = vm.Currency,
|
2018-11-17 03:39:43 +01:00
|
|
|
Template = vm.Template,
|
|
|
|
ButtonText = vm.ButtonText,
|
|
|
|
CustomButtonText = vm.CustomButtonText,
|
2018-11-27 07:14:32 +01:00
|
|
|
CustomTipText = vm.CustomTipText,
|
2018-12-19 07:07:05 +01:00
|
|
|
CustomTipPercentages = ListSplit(vm.CustomTipPercentages),
|
2019-04-11 09:14:39 +02:00
|
|
|
CustomCSSLink = vm.CustomCSSLink,
|
|
|
|
NotificationUrl = vm.NotificationUrl,
|
2020-10-13 09:51:28 +02:00
|
|
|
RedirectUrl = vm.RedirectUrl,
|
2019-08-19 07:13:42 +02:00
|
|
|
Description = vm.Description,
|
|
|
|
EmbeddedCSS = vm.EmbeddedCSS,
|
2022-11-02 10:21:33 +01:00
|
|
|
RedirectAutomatically =
|
2023-02-20 11:35:54 +01:00
|
|
|
string.IsNullOrEmpty(vm.RedirectAutomatically) ? null : bool.Parse(vm.RedirectAutomatically)
|
2022-11-02 10:21:33 +01:00
|
|
|
};
|
|
|
|
|
2022-11-25 02:42:55 +01:00
|
|
|
settings.FormId = vm.FormId;
|
2022-11-02 10:21:33 +01:00
|
|
|
app.Name = vm.AppName;
|
|
|
|
app.SetSettings(settings);
|
2021-12-20 15:15:32 +01:00
|
|
|
await _appService.UpdateOrCreateApp(app);
|
2019-10-31 04:29:59 +01:00
|
|
|
TempData[WellKnownTempData.SuccessMessage] = "App updated";
|
2019-08-01 08:55:41 +02:00
|
|
|
return RedirectToAction(nameof(UpdatePointOfSale), new { appId });
|
2018-04-03 04:50:41 +02:00
|
|
|
}
|
2021-12-31 08:59:02 +01:00
|
|
|
|
2018-12-19 07:07:05 +01:00
|
|
|
private int[] ListSplit(string list, string separator = ",")
|
|
|
|
{
|
|
|
|
if (string.IsNullOrEmpty(list))
|
|
|
|
{
|
|
|
|
return Array.Empty<int>();
|
2020-01-24 03:19:24 +01:00
|
|
|
}
|
2021-12-31 08:59:02 +01:00
|
|
|
|
2021-12-16 17:37:19 +01:00
|
|
|
// Remove all characters except numeric and comma
|
|
|
|
Regex charsToDestroy = new Regex(@"[^\d|\" + separator + "]");
|
|
|
|
list = charsToDestroy.Replace(list, "");
|
2018-12-19 07:07:05 +01:00
|
|
|
|
2021-12-16 17:37:19 +01:00
|
|
|
return list.Split(separator, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();
|
2018-12-19 07:07:05 +01:00
|
|
|
}
|
2022-07-18 20:51:53 +02:00
|
|
|
|
2022-07-22 15:41:14 +02:00
|
|
|
private async Task<string> GetStoreDefaultCurrentIfEmpty(string storeId, string currency)
|
2022-07-18 20:51:53 +02:00
|
|
|
{
|
|
|
|
if (string.IsNullOrWhiteSpace(currency))
|
|
|
|
{
|
|
|
|
currency = (await _storeRepository.FindStore(storeId)).GetStoreBlob().DefaultCurrency;
|
|
|
|
}
|
|
|
|
return currency.Trim().ToUpperInvariant();
|
|
|
|
}
|
2022-11-02 10:21:33 +01:00
|
|
|
|
|
|
|
private StoreData GetCurrentStore() => HttpContext.GetStoreData();
|
2023-01-06 14:18:07 +01:00
|
|
|
|
2022-07-18 20:51:53 +02:00
|
|
|
private AppData GetCurrentApp() => HttpContext.GetAppData();
|
2018-04-03 04:50:41 +02:00
|
|
|
}
|
|
|
|
}
|