Skip to content

Commit

Permalink
Crowfund : Add Buyer information / Additional information(forms) like…
Browse files Browse the repository at this point in the history
… POS
  • Loading branch information
nisaba committed Jan 13, 2024
1 parent 179ae4a commit 2bf7af0
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 5 deletions.
2 changes: 2 additions & 0 deletions BTCPayServer.Tests/NewBlocks.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
PowerShell.exe -command ".\docker-bitcoin-generate.ps1 3"
pause
139 changes: 136 additions & 3 deletions BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Form;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Filters;
using BTCPayServer.Forms;
using BTCPayServer.Forms.Models;
using BTCPayServer.Models;
using BTCPayServer.Plugins.Crowdfund.Models;
using BTCPayServer.Plugins.PointOfSale.Models;
using BTCPayServer.Services.Apps;
Expand All @@ -20,7 +24,10 @@
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
using NBitcoin.DataEncoders;
using NBitpayClient;
using Newtonsoft.Json.Linq;
using NicolasDorier.RateLimits;
using CrowdfundResetEvery = BTCPayServer.Services.Apps.CrowdfundResetEvery;

Expand All @@ -37,6 +44,7 @@ public UICrowdfundController(
StoreRepository storeRepository,
UIInvoiceController invoiceController,
UserManager<ApplicationUser> userManager,
FormDataService formDataService,
CrowdfundAppType app)
{
_currencies = currencies;
Expand All @@ -46,6 +54,7 @@ public UICrowdfundController(
_storeRepository = storeRepository;
_eventAggregator = eventAggregator;
_invoiceController = invoiceController;
FormDataService = formDataService;
}

private readonly EventAggregator _eventAggregator;
Expand All @@ -55,6 +64,7 @@ public UICrowdfundController(
private readonly UIInvoiceController _invoiceController;
private readonly UserManager<ApplicationUser> _userManager;
private readonly CrowdfundAppType _app;
public FormDataService FormDataService { get; }

[HttpGet("/")]
[HttpGet("/apps/{appId}/crowdfund")]
Expand Down Expand Up @@ -95,7 +105,7 @@ public async Task<IActionResult> ViewCrowdfund(string appId)
[EnableCors(CorsPolicies.All)]
[DomainMappingConstraint(CrowdfundAppType.AppType)]
[RateLimitsFilter(ZoneLimits.PublicInvoices, Scope = RateLimitsScope.RemoteAddress)]
public async Task<IActionResult> ContributeToCrowdfund(string appId, ContributeToCrowdfund request, CancellationToken cancellationToken)
public async Task<IActionResult> ContributeToCrowdfund(string appId, ContributeToCrowdfund request, CancellationToken cancellationToken = default, string formResponse = null)
{
var app = await _appService.GetApp(appId, CrowdfundAppType.AppType, true);

Expand All @@ -121,6 +131,26 @@ public async Task<IActionResult> ContributeToCrowdfund(string appId, ContributeT
return NotFound("Crowdfund is not currently active");
}

JObject formResponseJObject = null;

if (settings.FormId is not null)
{
var formData = await FormDataService.GetForm(settings.FormId);
if (formData is not null)
{
formResponseJObject = TryParseJObject(formResponse) ?? new JObject();
var form = Form.Parse(formData.Config);
FormDataService.SetValues(form, formResponseJObject);
if (!FormDataService.Validate(form, ModelState))
{
//someone tried to bypass validation
return RedirectToAction(nameof(ViewCrowdfund), new { appId });
}

}

}

var store = await _appService.GetStore(app);
var title = settings.Title;
decimal? price = request.Amount;
Expand Down Expand Up @@ -203,6 +233,11 @@ public async Task<IActionResult> ContributeToCrowdfund(string appId, ContributeT
entity.FullNotifications = true;
entity.ExtendedNotifications = true;
entity.Metadata.OrderUrl = appUrl;
if (formResponseJObject is null)
return;
var meta = entity.Metadata.ToJObject();
meta.Merge(formResponseJObject);
entity.Metadata = InvoiceMetadata.FromJObject(meta);
});

if (request.RedirectToCheckout)
Expand All @@ -219,6 +254,102 @@ public async Task<IActionResult> ContributeToCrowdfund(string appId, ContributeT
}
}

private JObject TryParseJObject(string posData)
{
try
{
return JObject.Parse(posData);
}
catch
{
}
return null;
}


[HttpGet("/apps/{appId}/crowdfund/form")]
[IgnoreAntiforgeryToken]
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.Unset)]
public async Task<IActionResult> CrowdfundForm(string appId)
{
var app = await _appService.GetApp(appId, CrowdfundAppType.AppType);
if (app == null)
return NotFound();

var settings = app.GetSettings<CrowdfundSettings>();
var formData = await FormDataService.GetForm(settings.FormId);
if (formData is null)
{
return RedirectToAction(nameof(ViewCrowdfund), new { appId });
}

var prefix = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16)) + "_";
var formParameters = new MultiValueDictionary<string, string>();
var controller = nameof(UICrowdfundController).TrimEnd("Controller", StringComparison.InvariantCulture);
var store = await _appService.GetStore(app);
var storeBlob = store.GetStoreBlob();
var form = Form.Parse(formData.Config);
form.ApplyValuesFromForm(Request.Query);
var vm = new FormViewModel
{
StoreName = store.StoreName,
StoreBranding = new StoreBrandingViewModel(storeBlob),
FormName = formData.Name,
Form = form,
AspController = controller,
AspAction = nameof(CrowdfundFormSubmit),
RouteParameters = new Dictionary<string, string> { { "appId", appId } },
FormParameters = formParameters,
FormParameterPrefix = prefix
};

return View("Views/UIForms/View", vm);
}

[HttpPost("/apps/{appId}/crowdfund/form/submit")]
[IgnoreAntiforgeryToken]
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.Unset)]
public async Task<IActionResult> CrowdfundFormSubmit(string appId, FormViewModel viewModel)
{
var app = await _appService.GetApp(appId, CrowdfundAppType.AppType);
if (app == null)
return NotFound();

var settings = app.GetSettings<CrowdfundSettings>();
var formData = await FormDataService.GetForm(settings.FormId);
if (formData is null)
{
return RedirectToAction(nameof(ViewCrowdfund));
}
var form = Form.Parse(formData.Config);
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());

form.ApplyValuesFromForm(Request.Form.Where(pair => formFieldNames.Contains(pair.Key)));

if (FormDataService.Validate(form, ModelState))
{
var appInfo = await GetAppInfo(appId);
var req = new ContributeToCrowdfund()
{
RedirectToCheckout = true,
ViewCrowdfundViewModel = appInfo
};

return ContributeToCrowdfund(appId, req, formResponse: FormDataService.GetValues(form).ToString()).Result;
}

viewModel.FormName = formData.Name;
viewModel.Form = form;

viewModel.FormParameters = formParameters;
return View("Views/UIForms/View", viewModel);
}


[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[HttpGet("{appId}/settings/crowdfund")]
public async Task<IActionResult> UpdateCrowdfund(string appId)
Expand Down Expand Up @@ -264,7 +395,8 @@ public async Task<IActionResult> UpdateCrowdfund(string appId)
DisplayPerksValue = settings.DisplayPerksValue,
SortPerksByPopularity = settings.SortPerksByPopularity,
Sounds = string.Join(Environment.NewLine, settings.Sounds),
AnimationColors = string.Join(Environment.NewLine, settings.AnimationColors)
AnimationColors = string.Join(Environment.NewLine, settings.AnimationColors),
FormId = settings.FormId
};
return View("Crowdfund/UpdateCrowdfund", vm);
}
Expand Down Expand Up @@ -373,7 +505,8 @@ public async Task<IActionResult> UpdateCrowdfund(string appId, UpdateCrowdfundVi
DisplayPerksRanking = vm.DisplayPerksRanking,
SortPerksByPopularity = vm.SortPerksByPopularity,
Sounds = parsedSounds,
AnimationColors = parsedAnimationColors
AnimationColors = parsedAnimationColors,
FormId = vm.FormId
};

app.TagAllInvoices = vm.UseAllStoreInvoices;
Expand Down
2 changes: 1 addition & 1 deletion BTCPayServer/Plugins/Crowdfund/CrowdfundPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
using BTCPayServer.Models;
using BTCPayServer.Plugins.Crowdfund.Controllers;
using BTCPayServer.Plugins.Crowdfund.Models;
using BTCPayServer.Plugins.PointOfSale;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
Expand Down Expand Up @@ -210,6 +209,7 @@ public Task<IEnumerable<ItemStats>> GetItemStats(AppData appData, InvoiceEntity[
PerkCount = perkCount,
PerkValue = perkValue,
NeverReset = settings.ResetEvery == CrowdfundResetEvery.Never,
HasFormForExtraValues = (settings.FormId is not null),
Sounds = settings.Sounds,
AnimationColors = settings.AnimationColors,
CurrencyData = _currencyNameTable.GetCurrencyData(settings.TargetCurrency, true),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ public class UpdateCrowdfundViewModel
// NOTE: Improve validation if needed
public bool ModelWithMinimumData => Description != null && Title != null && TargetCurrency != null;


[Display(Name = "Request contributor data on checkout")]
public string FormId { get; set; }

public bool Archived { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class ViewCrowdfundViewModel
public string[] Sounds { get; set; }
public int ResetEveryAmount { get; set; }
public bool NeverReset { get; set; }
public bool HasFormForExtraValues { get; set; }

public Dictionary<string, int> PerkCount { get; set; }

Expand Down
3 changes: 3 additions & 0 deletions BTCPayServer/Services/Apps/CrowdfundSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ public decimal? TargetAmount
public bool DisplayPerksRanking { get; set; }
public bool DisplayPerksValue { get; set; }
public bool SortPerksByPopularity { get; set; }
public string FormId { get; set; } = null;


public string[] AnimationColors { get; set; } =
{
"#FF6138", "#FFBE53", "#2980B9", "#282741"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,13 @@
</div>

<div class="text-center mb-4" id="crowdfund-body-header">
<button v-if="active" id="crowdfund-body-header-cta" class="btn btn-lg btn-primary py-2 px-5 only-for-js" v-on:click="contribute">Contribute</button>
@if (Model.HasFormForExtraValues)
{
<button class="btn btn-lg btn-primary py-2 px-5" onclick="window.location.href='@Url.Action("CrowdfundForm", "UICrowdfund", new {Model.AppId})'">Contribute</button>
}
else {
<button v-if="active" id="crowdfund-body-header-cta" class="btn btn-lg btn-primary py-2 px-5 only-for-js" v-on:click="contribute">Contribute</button>
}
</div>

<div class="row mt-4 justify-content-between gap-5">
Expand Down
8 changes: 8 additions & 0 deletions BTCPayServer/Views/Shared/Crowdfund/UpdateCrowdfund.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
@using BTCPayServer.TagHelpers
@using BTCPayServer.Views.Apps
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Forms
@inject FormDataService FormDataService
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model BTCPayServer.Plugins.Crowdfund.Models.UpdateCrowdfundViewModel
@{
ViewData.SetActivePage(AppsNavPages.Update, "Update Crowdfund", Model.AppId);
Csp.UnsafeEval();
var checkoutFormOptions = await FormDataService.GetSelect(Model.StoreId, Model.FormId);
}

@section PageHeadContent {
Expand Down Expand Up @@ -174,6 +177,11 @@
<div class="row">
<div class="col-xl-8 col-xxl-constrain">
<h3 class="mt-5 mb-4">Contributions</h3>
<div class="form-group">
<label asp-for="FormId" class="form-label"></label>
<select asp-for="FormId" class="form-select w-auto" asp-items="@checkoutFormOptions"></select>
<span asp-validation-for="FormId" class="text-danger"></span>
</div>
<div class="form-check mb-3">
<input asp-for="SortPerksByPopularity" type="checkbox" class="form-check-input" />
<label asp-for="SortPerksByPopularity" class="form-check-label"></label>
Expand Down

0 comments on commit 2bf7af0

Please sign in to comment.