mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-12 02:08:32 +01:00
Convert public app parts
This commit is contained in:
parent
8c6705bccb
commit
701ba59bd8
23 changed files with 477 additions and 511 deletions
|
@ -651,7 +651,7 @@ donation:
|
||||||
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||||
Assert.Equal("hello", vmpos.Title);
|
Assert.Equal("hello", vmpos.Title);
|
||||||
|
|
||||||
var publicApps = user.GetController<UIAppsPublicController>();
|
var publicApps = user.GetController<UIPointOfSaleController>();
|
||||||
var vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync<ViewPointOfSaleViewModel>();
|
var vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync<ViewPointOfSaleViewModel>();
|
||||||
Assert.Equal("hello", vmview.Title);
|
Assert.Equal("hello", vmview.Title);
|
||||||
Assert.Equal(3, vmview.Items.Length);
|
Assert.Equal(3, vmview.Items.Length);
|
||||||
|
@ -720,7 +720,7 @@ donation:
|
||||||
custom: true
|
custom: true
|
||||||
";
|
";
|
||||||
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||||
publicApps = user.GetController<UIAppsPublicController>();
|
publicApps = user.GetController<UIPointOfSaleController>();
|
||||||
vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync<ViewPointOfSaleViewModel>();
|
vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync<ViewPointOfSaleViewModel>();
|
||||||
Assert.Equal(test.Code, vmview.CurrencyCode);
|
Assert.Equal(test.Code, vmview.CurrencyCode);
|
||||||
Assert.Equal(test.ExpectedSymbol,
|
Assert.Equal(test.ExpectedSymbol,
|
||||||
|
|
|
@ -97,8 +97,8 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||||
|
|
||||||
var anonAppPubsController = tester.PayTester.GetController<UIAppsPublicController>();
|
var anonAppPubsController = tester.PayTester.GetController<UICrowdfundController>();
|
||||||
var publicApps = user.GetController<UIAppsPublicController>();
|
var crowdfundController = user.GetController<UICrowdfundController>();
|
||||||
|
|
||||||
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
|
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
|
||||||
{
|
{
|
||||||
|
@ -108,12 +108,12 @@ namespace BTCPayServer.Tests
|
||||||
Assert.IsType<NotFoundResult>(await anonAppPubsController.ViewCrowdfund(app.Id, string.Empty));
|
Assert.IsType<NotFoundResult>(await anonAppPubsController.ViewCrowdfund(app.Id, string.Empty));
|
||||||
|
|
||||||
//Scenario 2: Not Enabled But Admin - Allowed
|
//Scenario 2: Not Enabled But Admin - Allowed
|
||||||
Assert.IsType<OkObjectResult>(await publicApps.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
|
Assert.IsType<OkObjectResult>(await crowdfundController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
|
||||||
{
|
{
|
||||||
RedirectToCheckout = false,
|
RedirectToCheckout = false,
|
||||||
Amount = new decimal(0.01)
|
Amount = new decimal(0.01)
|
||||||
}, default));
|
}, default));
|
||||||
Assert.IsType<ViewResult>(await publicApps.ViewCrowdfund(app.Id, string.Empty));
|
Assert.IsType<ViewResult>(await crowdfundController.ViewCrowdfund(app.Id, string.Empty));
|
||||||
Assert.IsType<NotFoundResult>(await anonAppPubsController.ViewCrowdfund(app.Id, string.Empty));
|
Assert.IsType<NotFoundResult>(await anonAppPubsController.ViewCrowdfund(app.Id, string.Empty));
|
||||||
|
|
||||||
//Scenario 3: Enabled But Start Date > Now - Not Allowed
|
//Scenario 3: Enabled But Start Date > Now - Not Allowed
|
||||||
|
@ -190,8 +190,7 @@ namespace BTCPayServer.Tests
|
||||||
crowdfundViewModel.EnforceTargetAmount = true;
|
crowdfundViewModel.EnforceTargetAmount = true;
|
||||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||||
|
|
||||||
var anonAppPubsController = tester.PayTester.GetController<UIAppsPublicController>();
|
var publicApps = user.GetController<UICrowdfundController>();
|
||||||
var publicApps = user.GetController<UIAppsPublicController>();
|
|
||||||
|
|
||||||
var model = Assert.IsType<ViewCrowdfundViewModel>(Assert
|
var model = Assert.IsType<ViewCrowdfundViewModel>(Assert
|
||||||
.IsType<ViewResult>(publicApps.ViewCrowdfund(app.Id, String.Empty).Result).Model);
|
.IsType<ViewResult>(publicApps.ViewCrowdfund(app.Id, String.Empty).Result).Model);
|
||||||
|
|
|
@ -55,7 +55,7 @@ donation:
|
||||||
";
|
";
|
||||||
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||||
await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||||
var publicApps = user.GetController<UIAppsPublicController>();
|
var publicApps = user.GetController<UIPointOfSaleController>();
|
||||||
var vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync<ViewPointOfSaleViewModel>();
|
var vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync<ViewPointOfSaleViewModel>();
|
||||||
|
|
||||||
// apple shouldn't be available since we it's set to "disabled: true" above
|
// apple shouldn't be available since we it's set to "disabled: true" above
|
||||||
|
|
|
@ -16,7 +16,6 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
|
||||||
[AutoValidateAntiforgeryToken]
|
[AutoValidateAntiforgeryToken]
|
||||||
[Route("apps")]
|
[Route("apps")]
|
||||||
public partial class UIAppsController : Controller
|
public partial class UIAppsController : Controller
|
||||||
|
@ -37,7 +36,6 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
public string CreatedAppId { get; set; }
|
public string CreatedAppId { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public class AppUpdated
|
public class AppUpdated
|
||||||
{
|
{
|
||||||
public string AppId { get; set; }
|
public string AppId { get; set; }
|
||||||
|
@ -49,6 +47,22 @@ namespace BTCPayServer.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("/apps/{appId}")]
|
||||||
|
public async Task<IActionResult> RedirectToApp(string appId)
|
||||||
|
{
|
||||||
|
var app = await _appService.GetApp(appId, null);
|
||||||
|
if (app is null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
return app.AppType switch
|
||||||
|
{
|
||||||
|
nameof(AppType.Crowdfund) => RedirectToAction(nameof(UICrowdfundController.ViewCrowdfund), "UICrowdfund", new { appId }),
|
||||||
|
nameof(AppType.PointOfSale) => RedirectToAction(nameof(UIPointOfSaleController.ViewPointOfSale), "UIPointOfSale", new { appId }),
|
||||||
|
_ => NotFound()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
[HttpGet("/stores/{storeId}/apps")]
|
[HttpGet("/stores/{storeId}/apps")]
|
||||||
public async Task<IActionResult> ListApps(
|
public async Task<IActionResult> ListApps(
|
||||||
string storeId,
|
string storeId,
|
||||||
|
@ -94,6 +108,7 @@ namespace BTCPayServer.Controllers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
[HttpGet("/stores/{storeId}/apps/create")]
|
[HttpGet("/stores/{storeId}/apps/create")]
|
||||||
public IActionResult CreateApp(string storeId)
|
public IActionResult CreateApp(string storeId)
|
||||||
{
|
{
|
||||||
|
@ -103,6 +118,7 @@ namespace BTCPayServer.Controllers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
[HttpPost("/stores/{storeId}/apps/create")]
|
[HttpPost("/stores/{storeId}/apps/create")]
|
||||||
public async Task<IActionResult> CreateApp(string storeId, CreateAppViewModel vm)
|
public async Task<IActionResult> CreateApp(string storeId, CreateAppViewModel vm)
|
||||||
{
|
{
|
||||||
|
@ -151,6 +167,7 @@ namespace BTCPayServer.Controllers
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
[HttpGet("{appId}/delete")]
|
[HttpGet("{appId}/delete")]
|
||||||
public IActionResult DeleteApp(string appId)
|
public IActionResult DeleteApp(string appId)
|
||||||
{
|
{
|
||||||
|
@ -161,6 +178,7 @@ namespace BTCPayServer.Controllers
|
||||||
return View("Confirm", new ConfirmModel("Delete app", $"The app <strong>{app.Name}</strong> and its settings will be permanently deleted. Are you sure?", "Delete"));
|
return View("Confirm", new ConfirmModel("Delete app", $"The app <strong>{app.Name}</strong> and its settings will be permanently deleted. Are you sure?", "Delete"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
[HttpPost("{appId}/delete")]
|
[HttpPost("{appId}/delete")]
|
||||||
public async Task<IActionResult> DeleteAppPost(string appId)
|
public async Task<IActionResult> DeleteAppPost(string appId)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,430 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BTCPayServer.Abstractions.Extensions;
|
|
||||||
using BTCPayServer.Abstractions.Models;
|
|
||||||
using BTCPayServer.Configuration;
|
|
||||||
using BTCPayServer.Data;
|
|
||||||
using BTCPayServer.Filters;
|
|
||||||
using BTCPayServer.ModelBinders;
|
|
||||||
using BTCPayServer.Models;
|
|
||||||
using BTCPayServer.Models.AppViewModels;
|
|
||||||
using BTCPayServer.Plugins.Crowdfund.Models;
|
|
||||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
|
||||||
using BTCPayServer.Services.Apps;
|
|
||||||
using Microsoft.AspNetCore.Cors;
|
|
||||||
using Microsoft.AspNetCore.Http.Extensions;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using NBitpayClient;
|
|
||||||
using NicolasDorier.RateLimits;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
|
||||||
{
|
|
||||||
public class UIAppsPublicController : Controller
|
|
||||||
{
|
|
||||||
public UIAppsPublicController(AppService appService,
|
|
||||||
BTCPayServerOptions btcPayServerOptions,
|
|
||||||
UIInvoiceController invoiceController,
|
|
||||||
UserManager<ApplicationUser> userManager)
|
|
||||||
{
|
|
||||||
_AppService = appService;
|
|
||||||
_BtcPayServerOptions = btcPayServerOptions;
|
|
||||||
_InvoiceController = invoiceController;
|
|
||||||
_UserManager = userManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly AppService _AppService;
|
|
||||||
private readonly BTCPayServerOptions _BtcPayServerOptions;
|
|
||||||
private readonly UIInvoiceController _InvoiceController;
|
|
||||||
private readonly UserManager<ApplicationUser> _UserManager;
|
|
||||||
|
|
||||||
[HttpGet("/apps/{appId}")]
|
|
||||||
public async Task<IActionResult> RedirectToApp(string appId)
|
|
||||||
{
|
|
||||||
var app = await _AppService.GetApp(appId, null);
|
|
||||||
if (app is null)
|
|
||||||
return NotFound();
|
|
||||||
switch (app.AppType)
|
|
||||||
{
|
|
||||||
case nameof(AppType.Crowdfund):
|
|
||||||
return RedirectToAction("ViewCrowdfund", new { appId });
|
|
||||||
|
|
||||||
case nameof(AppType.PointOfSale):
|
|
||||||
return RedirectToAction("ViewPointOfSale", new { appId });
|
|
||||||
}
|
|
||||||
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[Route("/")]
|
|
||||||
[Route("/apps/{appId}/pos/{viewType?}")]
|
|
||||||
[XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
|
||||||
[DomainMappingConstraint(AppType.PointOfSale)]
|
|
||||||
public async Task<IActionResult> ViewPointOfSale(string appId, PosViewType? viewType = null)
|
|
||||||
{
|
|
||||||
var app = await _AppService.GetApp(appId, AppType.PointOfSale);
|
|
||||||
if (app == null)
|
|
||||||
return NotFound();
|
|
||||||
var settings = app.GetSettings<PointOfSaleSettings>();
|
|
||||||
var numberFormatInfo = _AppService.Currencies.GetNumberFormatInfo(settings.Currency) ?? _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/" + viewType, new ViewPointOfSaleViewModel()
|
|
||||||
{
|
|
||||||
Title = settings.Title,
|
|
||||||
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)
|
|
||||||
},
|
|
||||||
Items = _AppService.GetPOSItems(settings.Template, settings.Currency),
|
|
||||||
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]
|
|
||||||
[Route("/")]
|
|
||||||
[Route("/apps/{appId}/pos/{viewType?}")]
|
|
||||||
[XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
|
||||||
[IgnoreAntiforgeryToken]
|
|
||||||
[EnableCors(CorsPolicies.All)]
|
|
||||||
[DomainMappingConstraint(AppType.PointOfSale)]
|
|
||||||
[RateLimitsFilter(ZoneLimits.PublicInvoices, Scope = RateLimitsScope.RemoteAddress)]
|
|
||||||
public async Task<IActionResult> ViewPointOfSale(string appId,
|
|
||||||
PosViewType viewType,
|
|
||||||
[ModelBinder(typeof(InvariantDecimalModelBinder))] decimal? amount,
|
|
||||||
string email,
|
|
||||||
string orderId,
|
|
||||||
string notificationUrl,
|
|
||||||
string redirectUrl,
|
|
||||||
string choiceKey,
|
|
||||||
string posData = null,
|
|
||||||
RequiresRefundEmail requiresRefundEmail = RequiresRefundEmail.InheritFromStore,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var app = await _AppService.GetApp(appId, AppType.PointOfSale);
|
|
||||||
if (string.IsNullOrEmpty(choiceKey) && amount <= 0)
|
|
||||||
{
|
|
||||||
return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId });
|
|
||||||
}
|
|
||||||
if (app == null)
|
|
||||||
return NotFound();
|
|
||||||
var settings = app.GetSettings<PointOfSaleSettings>();
|
|
||||||
settings.DefaultView = settings.EnableShoppingCart ? PosViewType.Cart : settings.DefaultView;
|
|
||||||
if (string.IsNullOrEmpty(choiceKey) && !settings.ShowCustomAmount && settings.DefaultView != PosViewType.Cart)
|
|
||||||
{
|
|
||||||
return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId, viewType = viewType });
|
|
||||||
}
|
|
||||||
string title = null;
|
|
||||||
decimal? price = null;
|
|
||||||
Dictionary<string, InvoiceSupportedTransactionCurrency> paymentMethods = null;
|
|
||||||
ViewPointOfSaleViewModel.Item choice = null;
|
|
||||||
if (!string.IsNullOrEmpty(choiceKey))
|
|
||||||
{
|
|
||||||
var choices = _AppService.GetPOSItems(settings.Template, settings.Currency);
|
|
||||||
choice = choices.FirstOrDefault(c => c.Id == choiceKey);
|
|
||||||
if (choice == null)
|
|
||||||
return NotFound();
|
|
||||||
title = choice.Title;
|
|
||||||
if (choice.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup)
|
|
||||||
{
|
|
||||||
price = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
price = choice.Price.Value;
|
|
||||||
if (amount > price)
|
|
||||||
price = amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (choice.Inventory.HasValue)
|
|
||||||
{
|
|
||||||
if (choice.Inventory <= 0)
|
|
||||||
{
|
|
||||||
return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (choice?.PaymentMethods?.Any() is true)
|
|
||||||
{
|
|
||||||
paymentMethods = choice?.PaymentMethods.ToDictionary(s => s,
|
|
||||||
s => new InvoiceSupportedTransactionCurrency() { Enabled = true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!settings.ShowCustomAmount && settings.DefaultView != PosViewType.Cart)
|
|
||||||
return NotFound();
|
|
||||||
price = amount;
|
|
||||||
title = settings.Title;
|
|
||||||
|
|
||||||
//if cart IS enabled and we detect posdata that matches the cart system's, check inventory for the items
|
|
||||||
if (!string.IsNullOrEmpty(posData) &&
|
|
||||||
settings.DefaultView == PosViewType.Cart &&
|
|
||||||
AppService.TryParsePosCartItems(posData, out var cartItems))
|
|
||||||
{
|
|
||||||
|
|
||||||
var choices = _AppService.GetPOSItems(settings.Template, settings.Currency);
|
|
||||||
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 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var store = await _AppService.GetStore(app);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var invoice = await _InvoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest()
|
|
||||||
{
|
|
||||||
ItemCode = choice?.Id,
|
|
||||||
ItemDesc = title,
|
|
||||||
Currency = settings.Currency,
|
|
||||||
Price = price,
|
|
||||||
BuyerEmail = email,
|
|
||||||
OrderId = orderId ?? AppService.GetAppOrderId(app),
|
|
||||||
NotificationURL =
|
|
||||||
string.IsNullOrEmpty(notificationUrl) ? settings.NotificationUrl : notificationUrl,
|
|
||||||
RedirectURL = !string.IsNullOrEmpty(redirectUrl) ? redirectUrl
|
|
||||||
: !string.IsNullOrEmpty(settings.RedirectUrl) ? settings.RedirectUrl
|
|
||||||
: Request.GetDisplayUrl(),
|
|
||||||
FullNotifications = true,
|
|
||||||
ExtendedNotifications = true,
|
|
||||||
PosData = string.IsNullOrEmpty(posData) ? null : posData,
|
|
||||||
RedirectAutomatically = settings.RedirectAutomatically,
|
|
||||||
SupportedTransactionCurrencies = paymentMethods,
|
|
||||||
RequiresRefundEmail = requiresRefundEmail == RequiresRefundEmail.InheritFromStore
|
|
||||||
? store.GetStoreBlob().RequiresRefundEmail
|
|
||||||
: requiresRefundEmail == RequiresRefundEmail.On,
|
|
||||||
}, store, HttpContext.Request.GetAbsoluteRoot(),
|
|
||||||
new List<string>() { AppService.GetAppInternalTag(appId) },
|
|
||||||
cancellationToken, (entity) =>
|
|
||||||
{
|
|
||||||
entity.Metadata.OrderUrl = Request.GetDisplayUrl();
|
|
||||||
} );
|
|
||||||
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
|
|
||||||
});
|
|
||||||
return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[Route("/")]
|
|
||||||
[Route("/apps/{appId}/crowdfund")]
|
|
||||||
[XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
|
||||||
[DomainMappingConstraintAttribute(AppType.Crowdfund)]
|
|
||||||
public async Task<IActionResult> ViewCrowdfund(string appId, string statusMessage)
|
|
||||||
{
|
|
||||||
var app = await _AppService.GetApp(appId, AppType.Crowdfund, true);
|
|
||||||
|
|
||||||
if (app == null)
|
|
||||||
return NotFound();
|
|
||||||
var settings = app.GetSettings<CrowdfundSettings>();
|
|
||||||
|
|
||||||
var isAdmin = await _AppService.GetAppDataIfOwner(GetUserId(), appId, AppType.Crowdfund) != null;
|
|
||||||
|
|
||||||
var hasEnoughSettingsToLoad = !string.IsNullOrEmpty(settings.TargetCurrency);
|
|
||||||
if (!hasEnoughSettingsToLoad)
|
|
||||||
{
|
|
||||||
if (!isAdmin)
|
|
||||||
return NotFound();
|
|
||||||
|
|
||||||
return NotFound("A Target Currency must be set for this app in order to be loadable.");
|
|
||||||
}
|
|
||||||
var appInfo = await GetAppInfo(appId);
|
|
||||||
|
|
||||||
if (settings.Enabled)
|
|
||||||
return View(appInfo);
|
|
||||||
if (!isAdmin)
|
|
||||||
return NotFound();
|
|
||||||
|
|
||||||
return View(appInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost]
|
|
||||||
[Route("/")]
|
|
||||||
[Route("/apps/{appId}/crowdfund")]
|
|
||||||
[XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
|
||||||
[IgnoreAntiforgeryToken]
|
|
||||||
[EnableCors(CorsPolicies.All)]
|
|
||||||
[DomainMappingConstraintAttribute(AppType.Crowdfund)]
|
|
||||||
[RateLimitsFilter(ZoneLimits.PublicInvoices, Scope = RateLimitsScope.RemoteAddress)]
|
|
||||||
public async Task<IActionResult> ContributeToCrowdfund(string appId, ContributeToCrowdfund request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
|
|
||||||
var app = await _AppService.GetApp(appId, AppType.Crowdfund, true);
|
|
||||||
|
|
||||||
if (app == null)
|
|
||||||
return NotFound();
|
|
||||||
var settings = app.GetSettings<CrowdfundSettings>();
|
|
||||||
|
|
||||||
var isAdmin = await _AppService.GetAppDataIfOwner(GetUserId(), appId, AppType.Crowdfund) != null;
|
|
||||||
|
|
||||||
if (!settings.Enabled && !isAdmin)
|
|
||||||
{
|
|
||||||
return NotFound("Crowdfund is not currently active");
|
|
||||||
}
|
|
||||||
|
|
||||||
var info = await GetAppInfo(appId);
|
|
||||||
if (!isAdmin &&
|
|
||||||
((settings.StartDate.HasValue && DateTime.UtcNow < settings.StartDate) ||
|
|
||||||
(settings.EndDate.HasValue && DateTime.UtcNow > settings.EndDate) ||
|
|
||||||
(settings.EnforceTargetAmount &&
|
|
||||||
(info.Info.PendingProgressPercentage.GetValueOrDefault(0) +
|
|
||||||
info.Info.ProgressPercentage.GetValueOrDefault(0)) >= 100)))
|
|
||||||
{
|
|
||||||
return NotFound("Crowdfund is not currently active");
|
|
||||||
}
|
|
||||||
|
|
||||||
var store = await _AppService.GetStore(app);
|
|
||||||
var title = settings.Title;
|
|
||||||
decimal? price = request.Amount;
|
|
||||||
Dictionary<string, InvoiceSupportedTransactionCurrency> paymentMethods = null;
|
|
||||||
ViewPointOfSaleViewModel.Item choice = null;
|
|
||||||
if (!string.IsNullOrEmpty(request.ChoiceKey))
|
|
||||||
{
|
|
||||||
var choices = _AppService.GetPOSItems(settings.PerksTemplate, settings.TargetCurrency);
|
|
||||||
choice = choices.FirstOrDefault(c => c.Id == request.ChoiceKey);
|
|
||||||
if (choice == null)
|
|
||||||
return NotFound("Incorrect option provided");
|
|
||||||
title = choice.Title;
|
|
||||||
|
|
||||||
if (choice.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup)
|
|
||||||
{
|
|
||||||
price = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
price = choice.Price.Value;
|
|
||||||
if (request.Amount > price)
|
|
||||||
price = request.Amount;
|
|
||||||
}
|
|
||||||
if (choice.Inventory.HasValue)
|
|
||||||
{
|
|
||||||
if (choice.Inventory <= 0)
|
|
||||||
{
|
|
||||||
return NotFound("Option was out of stock");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (choice?.PaymentMethods?.Any() is true)
|
|
||||||
{
|
|
||||||
paymentMethods = choice?.PaymentMethods.ToDictionary(s => s,
|
|
||||||
s => new InvoiceSupportedTransactionCurrency() { Enabled = true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (request.Amount < 0)
|
|
||||||
{
|
|
||||||
return NotFound("Please provide an amount greater than 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
price = request.Amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isAdmin && (settings.EnforceTargetAmount && info.TargetAmount.HasValue && price >
|
|
||||||
(info.TargetAmount - (info.Info.CurrentAmount + info.Info.CurrentPendingAmount))))
|
|
||||||
{
|
|
||||||
return NotFound("Contribution Amount is more than is currently allowed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var invoice = await _InvoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest()
|
|
||||||
{
|
|
||||||
OrderId = AppService.GetAppOrderId(app),
|
|
||||||
Currency = settings.TargetCurrency,
|
|
||||||
ItemCode = request.ChoiceKey ?? string.Empty,
|
|
||||||
ItemDesc = title,
|
|
||||||
BuyerEmail = request.Email,
|
|
||||||
Price = price,
|
|
||||||
NotificationURL = settings.NotificationUrl,
|
|
||||||
FullNotifications = true,
|
|
||||||
ExtendedNotifications = true,
|
|
||||||
SupportedTransactionCurrencies = paymentMethods,
|
|
||||||
RedirectURL = request.RedirectUrl ?? Request.GetDisplayUrl(),
|
|
||||||
}, store, HttpContext.Request.GetAbsoluteRoot(),
|
|
||||||
new List<string>() {AppService.GetAppInternalTag(appId)},
|
|
||||||
cancellationToken, (entity) =>
|
|
||||||
{
|
|
||||||
entity.Metadata.OrderUrl = Request.GetDisplayUrl();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (request.RedirectToCheckout)
|
|
||||||
{
|
|
||||||
return RedirectToAction(nameof(UIInvoiceController.Checkout), "UIInvoice",
|
|
||||||
new {invoiceId = invoice.Data.Id});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(invoice.Data.Id);
|
|
||||||
}
|
|
||||||
catch (BitpayHttpException e)
|
|
||||||
{
|
|
||||||
return BadRequest(e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<ViewCrowdfundViewModel> GetAppInfo(string appId)
|
|
||||||
{
|
|
||||||
var info = (ViewCrowdfundViewModel)await _AppService.GetAppInfo(appId);
|
|
||||||
info.HubPath = AppHub.GetHubPath(Request);
|
|
||||||
info.SimpleDisplay = Request.Query.ContainsKey("simple");
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetUserId()
|
|
||||||
{
|
|
||||||
return _UserManager.GetUserId(User);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -40,8 +40,8 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
public static string AppLink(this LinkGenerator urlHelper, string appId, string scheme, HostString host, string pathbase)
|
public static string AppLink(this LinkGenerator urlHelper, string appId, string scheme, HostString host, string pathbase)
|
||||||
{
|
{
|
||||||
return urlHelper.GetUriByAction(
|
return urlHelper.GetUriByAction(
|
||||||
action: nameof(UIAppsPublicController.RedirectToApp),
|
action: nameof(UIAppsController.RedirectToApp),
|
||||||
controller: "UIAppsPublic",
|
controller: "UIApps",
|
||||||
values: new { appId },
|
values: new { appId },
|
||||||
scheme, host, pathbase);
|
scheme, host, pathbase);
|
||||||
}
|
}
|
||||||
|
|
|
@ -415,7 +415,6 @@ namespace BTCPayServer.Hosting
|
||||||
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
|
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
|
||||||
services.AddTransient<BitpayAccessTokenController>();
|
services.AddTransient<BitpayAccessTokenController>();
|
||||||
services.AddTransient<UIInvoiceController>();
|
services.AddTransient<UIInvoiceController>();
|
||||||
services.AddTransient<UIAppsPublicController>();
|
|
||||||
services.AddTransient<UIPaymentRequestController>();
|
services.AddTransient<UIPaymentRequestController>();
|
||||||
// Add application services.
|
// Add application services.
|
||||||
services.AddSingleton<EmailSenderFactory>();
|
services.AddSingleton<EmailSenderFactory>();
|
||||||
|
|
|
@ -1,40 +1,210 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Abstractions.Constants;
|
using BTCPayServer.Abstractions.Constants;
|
||||||
|
using BTCPayServer.Abstractions.Extensions;
|
||||||
using BTCPayServer.Client;
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Controllers;
|
using BTCPayServer.Controllers;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Filters;
|
||||||
|
using BTCPayServer.Models;
|
||||||
using BTCPayServer.Plugins.Crowdfund.Models;
|
using BTCPayServer.Plugins.Crowdfund.Models;
|
||||||
|
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||||
using BTCPayServer.Services.Apps;
|
using BTCPayServer.Services.Apps;
|
||||||
using BTCPayServer.Services.Rates;
|
using BTCPayServer.Services.Rates;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Cors;
|
||||||
|
using Microsoft.AspNetCore.Http.Extensions;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using NBitpayClient;
|
||||||
|
using NicolasDorier.RateLimits;
|
||||||
|
|
||||||
namespace BTCPayServer.Plugins.Crowdfund.Controllers
|
namespace BTCPayServer.Plugins.Crowdfund.Controllers
|
||||||
{
|
{
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
|
||||||
[AutoValidateAntiforgeryToken]
|
[AutoValidateAntiforgeryToken]
|
||||||
[Route("apps")]
|
[Route("apps")]
|
||||||
public class UICrowdfundController : Controller
|
public class UICrowdfundController : Controller
|
||||||
{
|
{
|
||||||
public UICrowdfundController(
|
public UICrowdfundController(
|
||||||
EventAggregator eventAggregator,
|
AppService appService,
|
||||||
CurrencyNameTable currencies,
|
CurrencyNameTable currencies,
|
||||||
|
EventAggregator eventAggregator,
|
||||||
StoreRepository storeRepository,
|
StoreRepository storeRepository,
|
||||||
AppService appService)
|
UIInvoiceController invoiceController,
|
||||||
|
UserManager<ApplicationUser> userManager)
|
||||||
{
|
{
|
||||||
_eventAggregator = eventAggregator;
|
|
||||||
_currencies = currencies;
|
_currencies = currencies;
|
||||||
_storeRepository = storeRepository;
|
|
||||||
_appService = appService;
|
_appService = appService;
|
||||||
|
_userManager = userManager;
|
||||||
|
_storeRepository = storeRepository;
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
|
_invoiceController = invoiceController;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly EventAggregator _eventAggregator;
|
private readonly EventAggregator _eventAggregator;
|
||||||
private readonly CurrencyNameTable _currencies;
|
private readonly CurrencyNameTable _currencies;
|
||||||
private readonly StoreRepository _storeRepository;
|
private readonly StoreRepository _storeRepository;
|
||||||
private readonly AppService _appService;
|
private readonly AppService _appService;
|
||||||
|
private readonly UIInvoiceController _invoiceController;
|
||||||
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
|
||||||
|
[HttpGet("/")]
|
||||||
|
[HttpGet("/apps/{appId}/crowdfund")]
|
||||||
|
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
||||||
|
[DomainMappingConstraint(AppType.Crowdfund)]
|
||||||
|
public async Task<IActionResult> ViewCrowdfund(string appId, string statusMessage)
|
||||||
|
{
|
||||||
|
var app = await _appService.GetApp(appId, AppType.Crowdfund, true);
|
||||||
|
|
||||||
|
if (app == null)
|
||||||
|
return NotFound();
|
||||||
|
var settings = app.GetSettings<CrowdfundSettings>();
|
||||||
|
|
||||||
|
var isAdmin = await _appService.GetAppDataIfOwner(GetUserId(), appId, AppType.Crowdfund) != null;
|
||||||
|
|
||||||
|
var hasEnoughSettingsToLoad = !string.IsNullOrEmpty(settings.TargetCurrency);
|
||||||
|
if (!hasEnoughSettingsToLoad)
|
||||||
|
{
|
||||||
|
if (!isAdmin)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
return NotFound("A Target Currency must be set for this app in order to be loadable.");
|
||||||
|
}
|
||||||
|
var appInfo = await GetAppInfo(appId);
|
||||||
|
|
||||||
|
if (settings.Enabled)
|
||||||
|
return View("Crowdfund/Public/ViewCrowdfund", appInfo);
|
||||||
|
if (!isAdmin)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
return View("Crowdfund/Public/ViewCrowdfund", appInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("/")]
|
||||||
|
[HttpPost("/apps/{appId}/crowdfund")]
|
||||||
|
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
||||||
|
[IgnoreAntiforgeryToken]
|
||||||
|
[EnableCors(CorsPolicies.All)]
|
||||||
|
[DomainMappingConstraint(AppType.Crowdfund)]
|
||||||
|
[RateLimitsFilter(ZoneLimits.PublicInvoices, Scope = RateLimitsScope.RemoteAddress)]
|
||||||
|
public async Task<IActionResult> ContributeToCrowdfund(string appId, ContributeToCrowdfund request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
|
||||||
|
var app = await _appService.GetApp(appId, AppType.Crowdfund, true);
|
||||||
|
|
||||||
|
if (app == null)
|
||||||
|
return NotFound();
|
||||||
|
var settings = app.GetSettings<CrowdfundSettings>();
|
||||||
|
|
||||||
|
var isAdmin = await _appService.GetAppDataIfOwner(GetUserId(), appId, AppType.Crowdfund) != null;
|
||||||
|
|
||||||
|
if (!settings.Enabled && !isAdmin)
|
||||||
|
{
|
||||||
|
return NotFound("Crowdfund is not currently active");
|
||||||
|
}
|
||||||
|
|
||||||
|
var info = await GetAppInfo(appId);
|
||||||
|
if (!isAdmin &&
|
||||||
|
((settings.StartDate.HasValue && DateTime.UtcNow < settings.StartDate) ||
|
||||||
|
(settings.EndDate.HasValue && DateTime.UtcNow > settings.EndDate) ||
|
||||||
|
(settings.EnforceTargetAmount &&
|
||||||
|
(info.Info.PendingProgressPercentage.GetValueOrDefault(0) +
|
||||||
|
info.Info.ProgressPercentage.GetValueOrDefault(0)) >= 100)))
|
||||||
|
{
|
||||||
|
return NotFound("Crowdfund is not currently active");
|
||||||
|
}
|
||||||
|
|
||||||
|
var store = await _appService.GetStore(app);
|
||||||
|
var title = settings.Title;
|
||||||
|
decimal? price = request.Amount;
|
||||||
|
Dictionary<string, InvoiceSupportedTransactionCurrency> paymentMethods = null;
|
||||||
|
ViewPointOfSaleViewModel.Item choice = null;
|
||||||
|
if (!string.IsNullOrEmpty(request.ChoiceKey))
|
||||||
|
{
|
||||||
|
var choices = _appService.GetPOSItems(settings.PerksTemplate, settings.TargetCurrency);
|
||||||
|
choice = choices.FirstOrDefault(c => c.Id == request.ChoiceKey);
|
||||||
|
if (choice == null)
|
||||||
|
return NotFound("Incorrect option provided");
|
||||||
|
title = choice.Title;
|
||||||
|
|
||||||
|
if (choice.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup)
|
||||||
|
{
|
||||||
|
price = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
price = choice.Price.Value;
|
||||||
|
if (request.Amount > price)
|
||||||
|
price = request.Amount;
|
||||||
|
}
|
||||||
|
if (choice.Inventory.HasValue)
|
||||||
|
{
|
||||||
|
if (choice.Inventory <= 0)
|
||||||
|
{
|
||||||
|
return NotFound("Option was out of stock");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (choice?.PaymentMethods?.Any() is true)
|
||||||
|
{
|
||||||
|
paymentMethods = choice?.PaymentMethods.ToDictionary(s => s,
|
||||||
|
s => new InvoiceSupportedTransactionCurrency() { Enabled = true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (request.Amount < 0)
|
||||||
|
{
|
||||||
|
return NotFound("Please provide an amount greater than 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
price = request.Amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAdmin && (settings.EnforceTargetAmount && info.TargetAmount.HasValue && price >
|
||||||
|
(info.TargetAmount - (info.Info.CurrentAmount + info.Info.CurrentPendingAmount))))
|
||||||
|
{
|
||||||
|
return NotFound("Contribution Amount is more than is currently allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var invoice = await _invoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest()
|
||||||
|
{
|
||||||
|
OrderId = AppService.GetAppOrderId(app),
|
||||||
|
Currency = settings.TargetCurrency,
|
||||||
|
ItemCode = request.ChoiceKey ?? string.Empty,
|
||||||
|
ItemDesc = title,
|
||||||
|
BuyerEmail = request.Email,
|
||||||
|
Price = price,
|
||||||
|
NotificationURL = settings.NotificationUrl,
|
||||||
|
FullNotifications = true,
|
||||||
|
ExtendedNotifications = true,
|
||||||
|
SupportedTransactionCurrencies = paymentMethods,
|
||||||
|
RedirectURL = request.RedirectUrl ?? Request.GetDisplayUrl(),
|
||||||
|
}, store, HttpContext.Request.GetAbsoluteRoot(),
|
||||||
|
new List<string>() {AppService.GetAppInternalTag(appId)},
|
||||||
|
cancellationToken, (entity) =>
|
||||||
|
{
|
||||||
|
entity.Metadata.OrderUrl = Request.GetDisplayUrl();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (request.RedirectToCheckout)
|
||||||
|
{
|
||||||
|
return RedirectToAction(nameof(UIInvoiceController.Checkout), "UIInvoice",
|
||||||
|
new {invoiceId = invoice.Data.Id});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(invoice.Data.Id);
|
||||||
|
}
|
||||||
|
catch (BitpayHttpException e)
|
||||||
|
{
|
||||||
|
return BadRequest(e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
[HttpGet("{appId}/settings/crowdfund")]
|
[HttpGet("{appId}/settings/crowdfund")]
|
||||||
|
@ -85,6 +255,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
|
||||||
return View("Crowdfund/UpdateCrowdfund", vm);
|
return View("Crowdfund/UpdateCrowdfund", vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
[HttpPost("{appId}/settings/crowdfund")]
|
[HttpPost("{appId}/settings/crowdfund")]
|
||||||
public async Task<IActionResult> UpdateCrowdfund(string appId, UpdateCrowdfundViewModel vm, string command)
|
public async Task<IActionResult> UpdateCrowdfund(string appId, UpdateCrowdfundViewModel vm, string command)
|
||||||
{
|
{
|
||||||
|
@ -199,7 +370,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
|
||||||
return RedirectToAction(nameof(UpdateCrowdfund), new { appId });
|
return RedirectToAction(nameof(UpdateCrowdfund), new { appId });
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<string> GetStoreDefaultCurrentIfEmpty(string storeId, string currency)
|
private async Task<string> GetStoreDefaultCurrentIfEmpty(string storeId, string currency)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(currency))
|
if (string.IsNullOrWhiteSpace(currency))
|
||||||
{
|
{
|
||||||
|
@ -209,5 +380,15 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppData GetCurrentApp() => HttpContext.GetAppData();
|
private AppData GetCurrentApp() => HttpContext.GetAppData();
|
||||||
|
|
||||||
|
private string GetUserId() => _userManager.GetUserId(User);
|
||||||
|
|
||||||
|
private async Task<ViewCrowdfundViewModel> GetAppInfo(string appId)
|
||||||
|
{
|
||||||
|
var info = (ViewCrowdfundViewModel)await _appService.GetAppInfo(appId);
|
||||||
|
info.HubPath = AppHub.GetHubPath(Request);
|
||||||
|
info.SimpleDisplay = Request.Query.ContainsKey("simple");
|
||||||
|
return info;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,252 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Abstractions.Constants;
|
using BTCPayServer.Abstractions.Constants;
|
||||||
using BTCPayServer.Abstractions.Extensions;
|
using BTCPayServer.Abstractions.Extensions;
|
||||||
|
using BTCPayServer.Abstractions.Models;
|
||||||
using BTCPayServer.Client;
|
using BTCPayServer.Client;
|
||||||
|
using BTCPayServer.Controllers;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Filters;
|
||||||
|
using BTCPayServer.ModelBinders;
|
||||||
|
using BTCPayServer.Models;
|
||||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||||
using BTCPayServer.Services.Apps;
|
using BTCPayServer.Services.Apps;
|
||||||
using BTCPayServer.Services.Rates;
|
using BTCPayServer.Services.Rates;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Cors;
|
||||||
|
using Microsoft.AspNetCore.Http.Extensions;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using NBitpayClient;
|
||||||
|
using NicolasDorier.RateLimits;
|
||||||
|
|
||||||
namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||||
{
|
{
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
|
||||||
[AutoValidateAntiforgeryToken]
|
[AutoValidateAntiforgeryToken]
|
||||||
[Route("apps")]
|
[Route("apps")]
|
||||||
public class UIPointOfSaleController : Controller
|
public class UIPointOfSaleController : Controller
|
||||||
{
|
{
|
||||||
public UIPointOfSaleController(
|
public UIPointOfSaleController(
|
||||||
|
AppService appService,
|
||||||
CurrencyNameTable currencies,
|
CurrencyNameTable currencies,
|
||||||
StoreRepository storeRepository,
|
StoreRepository storeRepository,
|
||||||
AppService appService)
|
UIInvoiceController invoiceController)
|
||||||
{
|
{
|
||||||
_currencies = currencies;
|
_currencies = currencies;
|
||||||
_storeRepository = storeRepository;
|
|
||||||
_appService = appService;
|
_appService = appService;
|
||||||
|
_storeRepository = storeRepository;
|
||||||
|
_invoiceController = invoiceController;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly CurrencyNameTable _currencies;
|
private readonly CurrencyNameTable _currencies;
|
||||||
private readonly StoreRepository _storeRepository;
|
private readonly StoreRepository _storeRepository;
|
||||||
private readonly AppService _appService;
|
private readonly AppService _appService;
|
||||||
|
private readonly UIInvoiceController _invoiceController;
|
||||||
|
|
||||||
|
[HttpGet("/")]
|
||||||
|
[HttpGet("/apps/{appId}/pos/{viewType?}")]
|
||||||
|
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
||||||
|
[DomainMappingConstraint(AppType.PointOfSale)]
|
||||||
|
public async Task<IActionResult> ViewPointOfSale(string appId, PosViewType? viewType = null)
|
||||||
|
{
|
||||||
|
var app = await _appService.GetApp(appId, AppType.PointOfSale);
|
||||||
|
if (app == null)
|
||||||
|
return NotFound();
|
||||||
|
var settings = app.GetSettings<PointOfSaleSettings>();
|
||||||
|
var numberFormatInfo = _appService.Currencies.GetNumberFormatInfo(settings.Currency) ??
|
||||||
|
_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,
|
||||||
|
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)
|
||||||
|
},
|
||||||
|
Items = _appService.GetPOSItems(settings.Template, settings.Currency),
|
||||||
|
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?}")]
|
||||||
|
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
||||||
|
[IgnoreAntiforgeryToken]
|
||||||
|
[EnableCors(CorsPolicies.All)]
|
||||||
|
[DomainMappingConstraint(AppType.PointOfSale)]
|
||||||
|
[RateLimitsFilter(ZoneLimits.PublicInvoices, Scope = RateLimitsScope.RemoteAddress)]
|
||||||
|
public async Task<IActionResult> ViewPointOfSale(string appId,
|
||||||
|
PosViewType viewType,
|
||||||
|
[ModelBinder(typeof(InvariantDecimalModelBinder))] decimal? amount,
|
||||||
|
string email,
|
||||||
|
string orderId,
|
||||||
|
string notificationUrl,
|
||||||
|
string redirectUrl,
|
||||||
|
string choiceKey,
|
||||||
|
string posData = null,
|
||||||
|
RequiresRefundEmail requiresRefundEmail = RequiresRefundEmail.InheritFromStore,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var app = await _appService.GetApp(appId, AppType.PointOfSale);
|
||||||
|
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;
|
||||||
|
if (string.IsNullOrEmpty(choiceKey) && !settings.ShowCustomAmount && settings.DefaultView != PosViewType.Cart)
|
||||||
|
{
|
||||||
|
return RedirectToAction(nameof(ViewPointOfSale), new { appId, viewType });
|
||||||
|
}
|
||||||
|
string title = null;
|
||||||
|
decimal? price = null;
|
||||||
|
Dictionary<string, InvoiceSupportedTransactionCurrency> paymentMethods = null;
|
||||||
|
ViewPointOfSaleViewModel.Item choice = null;
|
||||||
|
if (!string.IsNullOrEmpty(choiceKey))
|
||||||
|
{
|
||||||
|
var choices = _appService.GetPOSItems(settings.Template, settings.Currency);
|
||||||
|
choice = choices.FirstOrDefault(c => c.Id == choiceKey);
|
||||||
|
if (choice == null)
|
||||||
|
return NotFound();
|
||||||
|
title = choice.Title;
|
||||||
|
if (choice.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup)
|
||||||
|
{
|
||||||
|
price = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
price = choice.Price.Value;
|
||||||
|
if (amount > price)
|
||||||
|
price = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choice.Inventory.HasValue)
|
||||||
|
{
|
||||||
|
if (choice.Inventory <= 0)
|
||||||
|
{
|
||||||
|
return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choice?.PaymentMethods?.Any() is true)
|
||||||
|
{
|
||||||
|
paymentMethods = choice?.PaymentMethods.ToDictionary(s => s,
|
||||||
|
s => new InvoiceSupportedTransactionCurrency() { Enabled = true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!settings.ShowCustomAmount && settings.DefaultView != PosViewType.Cart)
|
||||||
|
return NotFound();
|
||||||
|
price = amount;
|
||||||
|
title = settings.Title;
|
||||||
|
|
||||||
|
//if cart IS enabled and we detect posdata that matches the cart system's, check inventory for the items
|
||||||
|
if (!string.IsNullOrEmpty(posData) &&
|
||||||
|
settings.DefaultView == PosViewType.Cart &&
|
||||||
|
AppService.TryParsePosCartItems(posData, out var cartItems))
|
||||||
|
{
|
||||||
|
|
||||||
|
var choices = _appService.GetPOSItems(settings.Template, settings.Currency);
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var store = await _appService.GetStore(app);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var invoice = await _invoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest()
|
||||||
|
{
|
||||||
|
ItemCode = choice?.Id,
|
||||||
|
ItemDesc = title,
|
||||||
|
Currency = settings.Currency,
|
||||||
|
Price = price,
|
||||||
|
BuyerEmail = email,
|
||||||
|
OrderId = orderId ?? AppService.GetAppOrderId(app),
|
||||||
|
NotificationURL =
|
||||||
|
string.IsNullOrEmpty(notificationUrl) ? settings.NotificationUrl : notificationUrl,
|
||||||
|
RedirectURL = !string.IsNullOrEmpty(redirectUrl) ? redirectUrl
|
||||||
|
: !string.IsNullOrEmpty(settings.RedirectUrl) ? settings.RedirectUrl
|
||||||
|
: Request.GetDisplayUrl(),
|
||||||
|
FullNotifications = true,
|
||||||
|
ExtendedNotifications = true,
|
||||||
|
PosData = string.IsNullOrEmpty(posData) ? null : posData,
|
||||||
|
RedirectAutomatically = settings.RedirectAutomatically,
|
||||||
|
SupportedTransactionCurrencies = paymentMethods,
|
||||||
|
RequiresRefundEmail = requiresRefundEmail == RequiresRefundEmail.InheritFromStore
|
||||||
|
? store.GetStoreBlob().RequiresRefundEmail
|
||||||
|
: requiresRefundEmail == RequiresRefundEmail.On,
|
||||||
|
}, store, HttpContext.Request.GetAbsoluteRoot(),
|
||||||
|
new List<string>() { AppService.GetAppInternalTag(appId) },
|
||||||
|
cancellationToken, (entity) =>
|
||||||
|
{
|
||||||
|
entity.Metadata.OrderUrl = Request.GetDisplayUrl();
|
||||||
|
} );
|
||||||
|
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
|
||||||
|
});
|
||||||
|
return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
[HttpGet("{appId}/settings/pos")]
|
[HttpGet("{appId}/settings/pos")]
|
||||||
public async Task<IActionResult> UpdatePointOfSale(string appId)
|
public async Task<IActionResult> UpdatePointOfSale(string appId)
|
||||||
{
|
{
|
||||||
|
@ -113,6 +323,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||||
return View("PointOfSale/UpdatePointOfSale", vm);
|
return View("PointOfSale/UpdatePointOfSale", vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
[HttpPost("{appId}/settings/pos")]
|
[HttpPost("{appId}/settings/pos")]
|
||||||
public async Task<IActionResult> UpdatePointOfSale(string appId, UpdatePointOfSaleViewModel vm)
|
public async Task<IActionResult> UpdatePointOfSale(string appId, UpdatePointOfSaleViewModel vm)
|
||||||
{
|
{
|
||||||
|
@ -180,7 +391,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||||
return list.Split(separator, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();
|
return list.Split(separator, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<string> GetStoreDefaultCurrentIfEmpty(string storeId, string currency)
|
private async Task<string> GetStoreDefaultCurrentIfEmpty(string storeId, string currency)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(currency))
|
if (string.IsNullOrWhiteSpace(currency))
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||||
using BTCPayServer.Abstractions.Extensions;
|
using BTCPayServer.Abstractions.Extensions;
|
||||||
using BTCPayServer.Controllers;
|
using BTCPayServer.Controllers;
|
||||||
using BTCPayServer.Models.AppViewModels;
|
using BTCPayServer.Models.AppViewModels;
|
||||||
|
using BTCPayServer.Plugins.Crowdfund.Controllers;
|
||||||
using BTCPayServer.Plugins.Crowdfund.Models;
|
using BTCPayServer.Plugins.Crowdfund.Models;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
@ -18,12 +19,14 @@ namespace BTCPayServer.Services.Apps
|
||||||
public const string PaymentReceived = "PaymentReceived";
|
public const string PaymentReceived = "PaymentReceived";
|
||||||
public const string InfoUpdated = "InfoUpdated";
|
public const string InfoUpdated = "InfoUpdated";
|
||||||
public const string InvoiceError = "InvoiceError";
|
public const string InvoiceError = "InvoiceError";
|
||||||
private readonly UIAppsPublicController _AppsPublicController;
|
|
||||||
|
|
||||||
public AppHub(UIAppsPublicController appsPublicController)
|
private readonly UICrowdfundController _crowdfundController;
|
||||||
|
|
||||||
|
public AppHub(UICrowdfundController crowdfundController)
|
||||||
{
|
{
|
||||||
_AppsPublicController = appsPublicController;
|
_crowdfundController = crowdfundController;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ListenToCrowdfundApp(string appId)
|
public async Task ListenToCrowdfundApp(string appId)
|
||||||
{
|
{
|
||||||
if (Context.Items.ContainsKey("app"))
|
if (Context.Items.ContainsKey("app"))
|
||||||
|
@ -35,16 +38,15 @@ namespace BTCPayServer.Services.Apps
|
||||||
await Groups.AddToGroupAsync(Context.ConnectionId, appId);
|
await Groups.AddToGroupAsync(Context.ConnectionId, appId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task CreateInvoice(ContributeToCrowdfund model)
|
public async Task CreateInvoice(ContributeToCrowdfund model)
|
||||||
{
|
{
|
||||||
model.RedirectToCheckout = false;
|
model.RedirectToCheckout = false;
|
||||||
_AppsPublicController.ControllerContext.HttpContext = Context.GetHttpContext();
|
_crowdfundController.ControllerContext.HttpContext = Context.GetHttpContext();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
var result =
|
var result =
|
||||||
await _AppsPublicController.ContributeToCrowdfund(Context.Items["app"].ToString(), model, Context.ConnectionAborted);
|
await _crowdfundController.ContributeToCrowdfund(Context.Items["app"].ToString(), model, Context.ConnectionAborted);
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
case OkObjectResult okObjectResult:
|
case OkObjectResult okObjectResult:
|
||||||
|
@ -54,16 +56,14 @@ namespace BTCPayServer.Services.Apps
|
||||||
await Clients.Caller.SendCoreAsync(InvoiceError, new[] { objectResult.Value });
|
await Clients.Caller.SendCoreAsync(InvoiceError, new[] { objectResult.Value });
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
await Clients.Caller.SendCoreAsync(InvoiceError, System.Array.Empty<object>());
|
await Clients.Caller.SendCoreAsync(InvoiceError, Array.Empty<object>());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
await Clients.Caller.SendCoreAsync(InvoiceError, System.Array.Empty<object>());
|
await Clients.Caller.SendCoreAsync(InvoiceError, Array.Empty<object>());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetHubPath(HttpRequest request)
|
public static string GetHubPath(HttpRequest request)
|
||||||
|
@ -75,6 +75,5 @@ namespace BTCPayServer.Services.Apps
|
||||||
{
|
{
|
||||||
route.MapHub<AppHub>("/apps/hub");
|
route.MapHub<AppHub>("/apps/hub");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -252,7 +252,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 col-sm-12">
|
<div class="col-md-4 col-sm-12">
|
||||||
<partial
|
<partial
|
||||||
name="/Views/UIAppsPublic/Crowdfund/ContributeForm.cshtml"
|
name="Crowdfund/Public/ContributeForm"
|
||||||
model="@(new ContributeToCrowdfund { ViewCrowdfundViewModel = Model, RedirectToCheckout = true })">
|
model="@(new ContributeToCrowdfund { ViewCrowdfundViewModel = Model, RedirectToCheckout = true })">
|
||||||
</partial>
|
</partial>
|
||||||
</div>
|
</div>
|
|
@ -31,7 +31,7 @@
|
||||||
<button type="submit" class="btn btn-primary order-sm-1" id="SaveSettings">Save</button>
|
<button type="submit" class="btn btn-primary order-sm-1" id="SaveSettings">Save</button>
|
||||||
@if (Model.ModelWithMinimumData)
|
@if (Model.ModelWithMinimumData)
|
||||||
{
|
{
|
||||||
<a class="btn btn-secondary" asp-action="ViewCrowdfund" asp-controller="UIAppsPublic" asp-route-appId="@Model.AppId" id="ViewApp" target="_blank">View</a>
|
<a class="btn btn-secondary" asp-action="ViewCrowdfund" asp-route-appId="@Model.AppId" id="ViewApp" target="_blank">View</a>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
@using BTCPayServer.Models.AppViewModels
|
|
||||||
@using BTCPayServer.Plugins.PointOfSale.Models
|
@using BTCPayServer.Plugins.PointOfSale.Models
|
||||||
@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
|
@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
|
||||||
@{
|
@{
|
||||||
Layout = "_LayoutPos";
|
Layout = "PointOfSale/Public/_LayoutPos";
|
||||||
int[] customTipPercentages = Model.CustomTipPercentages;
|
var customTipPercentages = Model.CustomTipPercentages;
|
||||||
var anyInventoryItems = Model.Items.Any(item => item.Inventory.HasValue);
|
var anyInventoryItems = Model.Items.Any(item => item.Inventory.HasValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +161,6 @@
|
||||||
<form
|
<form
|
||||||
id="js-cart-pay-form"
|
id="js-cart-pay-form"
|
||||||
method="post"
|
method="post"
|
||||||
asp-controller="UIAppsPublic"
|
|
||||||
asp-action="ViewPointOfSale"
|
asp-action="ViewPointOfSale"
|
||||||
asp-route-appId="@Model.AppId"
|
asp-route-appId="@Model.AppId"
|
||||||
asp-antiforgery="false"
|
asp-antiforgery="false"
|
18
BTCPayServer/Views/Shared/PointOfSale/Public/Light.cshtml
Normal file
18
BTCPayServer/Views/Shared/PointOfSale/Public/Light.cshtml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
|
||||||
|
@{
|
||||||
|
Layout = "PointOfSale/Public/_LayoutPos";
|
||||||
|
}
|
||||||
|
|
||||||
|
<partial name="_StatusMessage" />
|
||||||
|
|
||||||
|
@if (Context.Request.Query.ContainsKey("simple"))
|
||||||
|
{
|
||||||
|
<partial name="PointOfSale/Public/MinimalLight" model="Model" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<noscript>
|
||||||
|
<partial name="PointOfSale/Public/MinimalLight" model="Model" />
|
||||||
|
</noscript>
|
||||||
|
<partial name="PointOfSale/Public/VueLight" model="Model" />
|
||||||
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="py-5 px-3">
|
<div class="py-5 px-3">
|
||||||
<form method="post" asp-controller="UIAppsPublic" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false" data-buy>
|
<form method="post" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false" data-buy>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-text">@Model.CurrencySymbol</span>
|
<span class="input-group-text">@Model.CurrencySymbol</span>
|
||||||
<input class="form-control" type="number" step="@Model.Step" name="amount" placeholder="Amount">
|
<input class="form-control" type="number" step="@Model.Step" name="amount" placeholder="Amount">
|
|
@ -1,4 +1,3 @@
|
||||||
@using BTCPayServer.Models.AppViewModels
|
|
||||||
@using BTCPayServer.Payments.Lightning
|
@using BTCPayServer.Payments.Lightning
|
||||||
@using BTCPayServer.Plugins.PointOfSale.Models
|
@using BTCPayServer.Plugins.PointOfSale.Models
|
||||||
@using BTCPayServer.Services.Stores
|
@using BTCPayServer.Services.Stores
|
||||||
|
@ -20,7 +19,7 @@
|
||||||
|
|
||||||
@{
|
@{
|
||||||
var store = await StoreRepository.FindStore(Model.StoreId);
|
var store = await StoreRepository.FindStore(Model.StoreId);
|
||||||
Layout = "_LayoutPos";
|
Layout = "PointOfSale/Public/_LayoutPos";
|
||||||
Context.Request.Query.TryGetValue("cryptocode", out var cryptoCodeValues);
|
Context.Request.Query.TryGetValue("cryptocode", out var cryptoCodeValues);
|
||||||
var cryptoCode = cryptoCodeValues.FirstOrDefault() ?? "BTC";
|
var cryptoCode = cryptoCodeValues.FirstOrDefault() ?? "BTC";
|
||||||
var supported = store.GetSupportedPaymentMethods(BTCPayNetworkProvider).OfType<LNURLPaySupportedPaymentMethod>().OrderBy(method => method.CryptoCode == cryptoCode).FirstOrDefault();
|
var supported = store.GetSupportedPaymentMethods(BTCPayNetworkProvider).OfType<LNURLPaySupportedPaymentMethod>().OrderBy(method => method.CryptoCode == cryptoCode).FirstOrDefault();
|
|
@ -1,8 +1,7 @@
|
||||||
@using BTCPayServer.Models.AppViewModels
|
|
||||||
@using BTCPayServer.Plugins.PointOfSale.Models
|
@using BTCPayServer.Plugins.PointOfSale.Models
|
||||||
@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
|
@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
|
||||||
@{
|
@{
|
||||||
Layout = "_LayoutPos";
|
Layout = "PointOfSale/Public/_LayoutPos";
|
||||||
var anyInventoryItems = Model.Items.Any(item => item.Inventory.HasValue);
|
var anyInventoryItems = Model.Items.Any(item => item.Inventory.HasValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,15 +17,14 @@
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="card-deck my-3 mx-auto">
|
<div class="card-deck my-3 mx-auto">
|
||||||
@for (int x = 0; x < Model.Items.Length; x++)
|
@for (var x = 0; x < Model.Items.Length; x++)
|
||||||
{
|
{
|
||||||
var item = Model.Items[x];
|
var item = Model.Items[x];
|
||||||
var buttonText = string.IsNullOrEmpty(item.BuyButtonText) ? item.Price.Type != ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Fixed ? Model.CustomButtonText : Model.ButtonText : item.BuyButtonText;
|
var buttonText = string.IsNullOrEmpty(item.BuyButtonText) ? item.Price.Type != ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Fixed ? Model.CustomButtonText : Model.ButtonText : item.BuyButtonText;
|
||||||
buttonText = buttonText.Replace("{0}",item.Price.Formatted)
|
buttonText = buttonText.Replace("{0}",item.Price.Formatted).Replace("{Price}",item.Price.Formatted);
|
||||||
?.Replace("{Price}",item.Price.Formatted);
|
|
||||||
|
|
||||||
<div class="card px-0" data-id="@x">
|
<div class="card px-0" data-id="@x">
|
||||||
@if (!String.IsNullOrWhiteSpace(item.Image))
|
@if (!string.IsNullOrWhiteSpace(item.Image))
|
||||||
{
|
{
|
||||||
<img class="card-img-top" src="@item.Image" alt="Card image cap" asp-append-version="true">
|
<img class="card-img-top" src="@item.Image" alt="Card image cap" asp-append-version="true">
|
||||||
}
|
}
|
||||||
|
@ -36,7 +34,7 @@
|
||||||
{
|
{
|
||||||
@if (item.Price.Type != ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup)
|
@if (item.Price.Type != ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup)
|
||||||
{
|
{
|
||||||
<form method="post" asp-controller="UIAppsPublic" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false" data-buy>
|
<form method="post" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false" data-buy>
|
||||||
<input type="hidden" name="choicekey" value="@item.Id"/>
|
<input type="hidden" name="choicekey" value="@item.Id"/>
|
||||||
<input type="hidden" name="requiresRefundEmail" value="@Model.RequiresRefundEmail.ToString()" />
|
<input type="hidden" name="requiresRefundEmail" value="@Model.RequiresRefundEmail.ToString()" />
|
||||||
@{PayFormInputContent(item.BuyButtonText ?? Model.CustomButtonText, item.Price.Type, item.Price.Value, item.Price.Value);}
|
@{PayFormInputContent(item.BuyButtonText ?? Model.CustomButtonText, item.Price.Type, item.Price.Value, item.Price.Value);}
|
||||||
|
@ -44,7 +42,7 @@
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<form method="post" asp-controller="UIAppsPublic" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false">
|
<form method="post" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false">
|
||||||
<input type="hidden" name="requiresRefundEmail" value="@Model.RequiresRefundEmail.ToString()" />
|
<input type="hidden" name="requiresRefundEmail" value="@Model.RequiresRefundEmail.ToString()" />
|
||||||
<button type="submit" name="choiceKey" class="js-add-cart btn btn-primary" value="@item.Id">
|
<button type="submit" name="choiceKey" class="js-add-cart btn btn-primary" value="@item.Id">
|
||||||
@Safe.Raw(buttonText)
|
@Safe.Raw(buttonText)
|
||||||
|
@ -54,7 +52,6 @@
|
||||||
}
|
}
|
||||||
@if (item.Inventory.HasValue)
|
@if (item.Inventory.HasValue)
|
||||||
{
|
{
|
||||||
|
|
||||||
<div class="w-100 pt-2 text-center text-muted">
|
<div class="w-100 pt-2 text-center text-muted">
|
||||||
@if (item.Inventory > 0)
|
@if (item.Inventory > 0)
|
||||||
{
|
{
|
||||||
|
@ -78,7 +75,7 @@
|
||||||
<div class="card px-0">
|
<div class="card px-0">
|
||||||
@{CardBody("Custom Amount", "Create invoice to pay custom amount");}
|
@{CardBody("Custom Amount", "Create invoice to pay custom amount");}
|
||||||
<div class="card-footer bg-transparent border-0 pb-3">
|
<div class="card-footer bg-transparent border-0 pb-3">
|
||||||
<form method="post" asp-controller="UIAppsPublic" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false" data-buy>
|
<form method="post" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false" data-buy>
|
||||||
@{PayFormInputContent(Model.CustomButtonText, ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Minimum);}
|
@{PayFormInputContent(Model.CustomButtonText, ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Minimum);}
|
||||||
</form>
|
</form>
|
||||||
@if (anyInventoryItems)
|
@if (anyInventoryItems)
|
||||||
|
@ -111,14 +108,13 @@
|
||||||
<button class="btn btn-primary text-nowrap" type="submit">@buttonText</button>
|
<button class="btn btn-primary text-nowrap" type="submit">@buttonText</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CardBody(string title, string description)
|
private void CardBody(string title, string description)
|
||||||
{
|
{
|
||||||
<div class="card-body my-auto pb-0">
|
<div class="card-body my-auto pb-0">
|
||||||
<h5 class="card-title">@title</h5>
|
<h5 class="card-title">@title</h5>
|
||||||
@if (!String.IsNullOrWhiteSpace(description))
|
@if (!string.IsNullOrWhiteSpace(description))
|
||||||
{
|
{
|
||||||
<p class="card-text">@Safe.Raw(description)</p>
|
<p class="card-text">@Safe.Raw(description)</p>
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
|
@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
|
||||||
<div id="app" class="l-pos-wrapper" v-cloak>
|
<div id="app" class="l-pos-wrapper" v-cloak>
|
||||||
<form method="post" asp-controller="UIAppsPublic" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false" data-buy v-on:submit="handleFormSubmit">
|
<form method="post" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false" data-buy v-on:submit="handleFormSubmit">
|
||||||
<div class="l-pos-header bg-primary py-3 px-3">
|
<div class="l-pos-header bg-primary py-3 px-3">
|
||||||
@if (!string.IsNullOrEmpty(Model.CustomLogoLink))
|
@if (!string.IsNullOrEmpty(Model.CustomLogoLink))
|
||||||
{
|
{
|
|
@ -13,7 +13,7 @@
|
||||||
<h2 class="mb-0">@ViewData["Title"]</h2>
|
<h2 class="mb-0">@ViewData["Title"]</h2>
|
||||||
<div class="d-flex gap-3 mt-3 mt-sm-0">
|
<div class="d-flex gap-3 mt-3 mt-sm-0">
|
||||||
<button type="submit" class="btn btn-primary order-sm-1" id="SaveSettings">Save</button>
|
<button type="submit" class="btn btn-primary order-sm-1" id="SaveSettings">Save</button>
|
||||||
<a class="btn btn-secondary" asp-action="ViewPointOfSale" asp-controller="UIAppsPublic" asp-route-appId="@Model.Id" id="ViewApp" target="_blank">View</a>
|
<a class="btn btn-secondary" asp-action="ViewPointOfSale" asp-route-appId="@Model.Id" id="ViewApp" target="_blank">View</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -173,7 +173,7 @@
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
You can embed this POS via an iframe.
|
You can embed this POS via an iframe.
|
||||||
@{
|
@{
|
||||||
var iframe = $"<iframe src='{(Url.Action("ViewPointOfSale", "UIAppsPublic", new { appId = Model.Id }, Context.Request.Scheme))}' style='max-width: 100%; border: 0;'></iframe>";
|
var iframe = $"<iframe src='{Url.Action("ViewPointOfSale", "UIPointOfSale", new { appId = Model.Id }, Context.Request.Scheme)}' style='max-width: 100%; border: 0;'></iframe>";
|
||||||
}
|
}
|
||||||
<pre class="p-3">@iframe</pre>
|
<pre class="p-3">@iframe</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -103,10 +103,6 @@
|
||||||
<a asp-action="@app.UpdateAction" asp-controller="UIApps" asp-route-appId="@app.Id" asp-route-storeId="@app.StoreId">Settings</a>
|
<a asp-action="@app.UpdateAction" asp-controller="UIApps" asp-route-appId="@app.Id" asp-route-storeId="@app.StoreId">Settings</a>
|
||||||
<span> - </span>
|
<span> - </span>
|
||||||
}
|
}
|
||||||
<a asp-action="@app.ViewAction" asp-controller="UIAppsPublic" asp-route-appId="@app.Id">View</a>
|
|
||||||
<a asp-action="@app.ViewAction" asp-controller="UIAppsPublic" asp-route-appId="@app.Id" target="_blank"
|
|
||||||
title="View in New Window"><span class="fa fa-external-link"></span></a>
|
|
||||||
<span> - </span>
|
|
||||||
<a asp-action="DeleteApp" asp-route-appId="@app.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The app <strong>@app.AppName</strong> and its settings will be permanently deleted from your store <strong>@app.StoreName</strong>." data-confirm-input="DELETE">Delete</a>
|
<a asp-action="DeleteApp" asp-route-appId="@app.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The app <strong>@app.AppName</strong> and its settings will be permanently deleted from your store <strong>@app.StoreName</strong>." data-confirm-input="DELETE">Delete</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
|
|
||||||
@{
|
|
||||||
Layout = "_LayoutPos";
|
|
||||||
}
|
|
||||||
|
|
||||||
<partial name="_StatusMessage" />
|
|
||||||
|
|
||||||
@if (Context.Request.Query.ContainsKey("simple"))
|
|
||||||
{
|
|
||||||
<partial name="/Views/UIAppsPublic/PointOfSale/MinimalLight.cshtml" model="Model" />
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<noscript>
|
|
||||||
<partial name="/Views/UIAppsPublic/PointOfSale/MinimalLight.cshtml" model="Model" />
|
|
||||||
</noscript>
|
|
||||||
<partial name="/Views/UIAppsPublic/PointOfSale/VueLight.cshtml" model="Model" />
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue