Generic Forms (#4561)

* Custom Forms

* Update BTCPayServer.Data/Migrations/20230125085242_AddForms.cs

* Cleanups

* Explain public form

* Add store branding

* Add form name to POS form

* add tests

* fix migration

* Minor cleanups

* Code improvements

* Add form validation

Closes #4317.

* Adapt form validation for Bootstrap 5

* update logic for forms

* pr changes

* Minor code cleanup

* Remove unused parameters

* Refactor Form data handling to avoid O(n3) issues

* Rename Hidden to Constant

* Pre-populate FormView from the query string params

* Fix test

---------

Co-authored-by: d11n <mail@dennisreimann.de>
Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
This commit is contained in:
Andrew Camilleri
2023-02-20 11:35:54 +01:00
committed by GitHub
parent 60f84d5e30
commit bbbaacc350
30 changed files with 1174 additions and 354 deletions

View File

@@ -10,6 +10,7 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.Filters;
using BTCPayServer.Forms;
using BTCPayServer.Forms.Models;
using BTCPayServer.Models;
using BTCPayServer.Models.PaymentRequestViewModels;
using BTCPayServer.PaymentRequest;
@@ -40,6 +41,7 @@ namespace BTCPayServer.Controllers
private readonly StoreRepository _storeRepository;
private FormComponentProviders FormProviders { get; }
public FormDataService FormDataService { get; }
public UIPaymentRequestController(
UIInvoiceController invoiceController,
@@ -50,7 +52,8 @@ namespace BTCPayServer.Controllers
CurrencyNameTable currencies,
StoreRepository storeRepository,
InvoiceRepository invoiceRepository,
FormComponentProviders formProviders)
FormComponentProviders formProviders,
FormDataService formDataService)
{
_InvoiceController = invoiceController;
_UserManager = userManager;
@@ -61,6 +64,7 @@ namespace BTCPayServer.Controllers
_storeRepository = storeRepository;
_InvoiceRepository = invoiceRepository;
FormProviders = formProviders;
FormDataService = formDataService;
}
[BitpayAPIConstraint(false)]
@@ -204,7 +208,7 @@ namespace BTCPayServer.Controllers
[HttpGet("{payReqId}/form")]
[HttpPost("{payReqId}/form")]
[AllowAnonymous]
public async Task<IActionResult> ViewPaymentRequestForm(string payReqId)
public async Task<IActionResult> ViewPaymentRequestForm(string payReqId, FormViewModel viewModel)
{
var result = await _PaymentRequestRepository.FindPaymentRequest(payReqId, GetUserId());
if (result == null)
@@ -213,42 +217,34 @@ namespace BTCPayServer.Controllers
}
var prBlob = result.GetBlob();
var prFormId = prBlob.FormId;
var formConfig = prFormId is null ? null : Forms.UIFormsController.GetFormData(prFormId)?.Config;
switch (formConfig)
if (prBlob.FormResponse is not null)
{
case null:
case { } when !this.Request.HasFormContentType && prBlob.FormResponse is not null:
return RedirectToAction("ViewPaymentRequest", new { payReqId });
case { } when !this.Request.HasFormContentType && prBlob.FormResponse is null:
break;
default:
// POST case: Handle form submit
var formData = Form.Parse(formConfig);
formData.ApplyValuesFromForm(Request.Form);
if (FormProviders.Validate(formData, ModelState))
{
prBlob.FormResponse = JObject.FromObject(formData.GetValues());
result.SetBlob(prBlob);
await _PaymentRequestRepository.CreateOrUpdatePaymentRequest(result);
return RedirectToAction("PayPaymentRequest", new { payReqId });
}
break;
return RedirectToAction("PayPaymentRequest", new {payReqId});
}
var prFormId = prBlob.FormId;
var formData = await FormDataService.GetForm(prFormId);
if (formData is null)
{
return RedirectToAction("PayPaymentRequest", new {payReqId});
}
return View("PostRedirect", new PostRedirectViewModel
var form = Form.Parse(formData.Config);
if (Request.Method == "POST" && Request.HasFormContentType)
{
AspController = "UIForms",
AspAction = "ViewPublicForm",
RouteParameters =
{
{ "formId", prFormId }
},
FormParameters =
{
{ "redirectUrl", Request.GetCurrentUrl() }
form.ApplyValuesFromForm(Request.Form);
if (FormDataService.Validate(form, ModelState))
{
prBlob.FormResponse = form.GetValues();
result.SetBlob(prBlob);
await _PaymentRequestRepository.CreateOrUpdatePaymentRequest(result);
return RedirectToAction("PayPaymentRequest", new {payReqId});
}
});
}
viewModel.FormName = formData.Name;
viewModel.Form = form;
return View("Views/UIForms/View", viewModel);
}
[HttpGet("{payReqId}/pay")]
@@ -277,6 +273,15 @@ namespace BTCPayServer.Controllers
return BadRequest("Payment Request cannot be paid as it has been archived");
}
if (!result.FormSubmitted && !string.IsNullOrEmpty(result.FormId))
{
var formData = await FormDataService.GetForm(result.FormId);
if (formData is not null)
{
return RedirectToAction("ViewPaymentRequestForm", new {payReqId});
}
}
result.HubPath = PaymentRequestHub.GetHubPath(Request);
if (result.AmountDue <= 0)
{