Compare commits
47 Commits
woirnew
...
form_build
Author | SHA1 | Date | |
---|---|---|---|
71c2bfffc1 | |||
4b952648a4 | |||
8b7b772d34 | |||
713b87d07b | |||
82a690645e | |||
dcade8b4a9 | |||
ad17234a86 | |||
7218c3077d | |||
3e1511c0a8 | |||
3cb7c64321 | |||
9c88c53798 | |||
50d08de78b | |||
15ab7c051b | |||
4e8126a734 | |||
8195acbbf7 | |||
ec35b75324 | |||
d65835b0fe | |||
426a65d3fe | |||
2fe2efcb7a | |||
f671f8fd26 | |||
e0f15c8792 | |||
4e828f4398 | |||
399fbe2827 | |||
6f547750ae | |||
f0f09180d5 | |||
95c6817d2a | |||
bb9c0989c6 | |||
fa2a3dd7f3 | |||
86361d0f75 | |||
a056e9b570 | |||
b89918ff5d | |||
37693399d6 | |||
bc652507d8 | |||
d8094e9fee | |||
33c92c1428 | |||
6747f91d29 | |||
469ff5b11f | |||
b3bc4e5745 | |||
44b0addff5 | |||
383520c453 | |||
0c3b345d1b | |||
3513e602f4 | |||
cec83ab4ec | |||
16537509d9 | |||
6bd6a045f4 | |||
5de665dfff | |||
e329368e21 |
@ -1,18 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Abstractions
|
||||
{
|
||||
public class CamelCaseSerializerSettings
|
||||
{
|
||||
static CamelCaseSerializerSettings()
|
||||
{
|
||||
Settings = new JsonSerializerSettings()
|
||||
{
|
||||
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
|
||||
};
|
||||
Serializer = JsonSerializer.Create(Settings);
|
||||
}
|
||||
public static readonly JsonSerializerSettings Settings;
|
||||
public static readonly JsonSerializer Serializer;
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
@ -10,50 +9,16 @@ namespace BTCPayServer.Abstractions.Form;
|
||||
|
||||
public class Field
|
||||
{
|
||||
public static Field Create(string label, string name, string value, bool required, string helpText, string type = "text")
|
||||
{
|
||||
return new Field()
|
||||
{
|
||||
Label = label,
|
||||
Name = name,
|
||||
Value = value,
|
||||
OriginalValue = value,
|
||||
Required = required,
|
||||
HelpText = helpText,
|
||||
Type = type
|
||||
};
|
||||
}
|
||||
// The name of the HTML5 node. Should be used as the key for the posted data.
|
||||
public string Name;
|
||||
|
||||
public bool Hidden;
|
||||
|
||||
// HTML5 compatible type string like "text", "textarea", "email", "password", etc. Each type is a class and may contain more fields (i.e. "select" would have options).
|
||||
public string Type;
|
||||
|
||||
public static Field CreateFieldset()
|
||||
{
|
||||
return new Field() { Type = "fieldset" };
|
||||
}
|
||||
|
||||
// The value field is what is currently in the DB or what the user entered, but possibly not saved yet due to validation errors.
|
||||
// If this is the first the user sees the form, then value and original value are the same. Value changes as the user starts interacting with the form.
|
||||
public string Value;
|
||||
|
||||
public bool Required;
|
||||
|
||||
// The translated label of the field.
|
||||
public string Label;
|
||||
|
||||
// The original value is the value that is currently saved in the backend. A "reset" button can be used to revert back to this. Should only be set from the constructor.
|
||||
public string OriginalValue;
|
||||
|
||||
// A useful note shown below the field or via a tooltip / info icon. Should be translated for the user.
|
||||
public string HelpText;
|
||||
|
||||
[JsonExtensionData] public IDictionary<string, JToken> AdditionalData { get; set; }
|
||||
public List<Field> Fields { get; set; } = new();
|
||||
|
||||
// The field is considered "valid" if there are no validation errors
|
||||
public List<string> ValidationErrors = new List<string>();
|
||||
|
||||
@ -61,4 +26,9 @@ public class Field
|
||||
{
|
||||
return ValidationErrors.Count == 0 && Fields.All(field => field.IsValid());
|
||||
}
|
||||
|
||||
[JsonExtensionData] public IDictionary<string, JToken> AdditionalData { get; set; }
|
||||
public List<Field> Fields { get; set; } = new();
|
||||
|
||||
|
||||
}
|
||||
|
12
BTCPayServer.Abstractions/Form/Fieldset.cs
Normal file
12
BTCPayServer.Abstractions/Form/Fieldset.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace BTCPayServer.Abstractions.Form;
|
||||
|
||||
public class Fieldset : Field
|
||||
{
|
||||
public bool Hidden { get; set; }
|
||||
public string Label { get; set; }
|
||||
|
||||
public Fieldset()
|
||||
{
|
||||
Type = "fieldset";
|
||||
}
|
||||
}
|
@ -2,24 +2,12 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Form;
|
||||
|
||||
public class Form
|
||||
{
|
||||
#nullable enable
|
||||
public static Form Parse(string str)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(str);
|
||||
return JObject.Parse(str).ToObject<Form>(CamelCaseSerializerSettings.Serializer) ?? throw new InvalidOperationException("Impossible to deserialize Form");
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return JObject.FromObject(this, CamelCaseSerializerSettings.Serializer).ToString(Newtonsoft.Json.Formatting.Indented);
|
||||
}
|
||||
#nullable restore
|
||||
// Messages to be shown at the top of the form indicating user feedback like "Saved successfully" or "Please change X because of Y." or a warning, etc...
|
||||
public List<AlertMessage> TopMessages { get; set; } = new();
|
||||
|
||||
@ -29,7 +17,7 @@ public class Form
|
||||
// Are all the fields valid in the form?
|
||||
public bool IsValid()
|
||||
{
|
||||
return Fields.Select(f => f.IsValid()).All(o => o);
|
||||
return Fields.All(field => field.IsValid());
|
||||
}
|
||||
|
||||
public Field GetFieldByName(string name)
|
||||
@ -64,7 +52,6 @@ public class Form
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<string> GetAllNames()
|
||||
{
|
||||
return GetAllNames(Fields);
|
||||
|
27
BTCPayServer.Abstractions/Form/HtmlInputField.cs
Normal file
27
BTCPayServer.Abstractions/Form/HtmlInputField.cs
Normal file
@ -0,0 +1,27 @@
|
||||
namespace BTCPayServer.Abstractions.Form;
|
||||
|
||||
public class HtmlInputField : Field
|
||||
{
|
||||
// The translated label of the field.
|
||||
public string Label;
|
||||
|
||||
// The original value is the value that is currently saved in the backend. A "reset" button can be used to revert back to this. Should only be set from the constructor.
|
||||
public string OriginalValue;
|
||||
|
||||
// A useful note shown below the field or via a tooltip / info icon. Should be translated for the user.
|
||||
public string HelpText;
|
||||
|
||||
public bool Required;
|
||||
public HtmlInputField(string label, string name, string value, bool required, string helpText, string type = "text")
|
||||
{
|
||||
Label = label;
|
||||
Name = name;
|
||||
Value = value;
|
||||
OriginalValue = value;
|
||||
Required = required;
|
||||
HelpText = helpText;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
// TODO JSON parsing from string to objects again probably won't work out of the box because of the different field types.
|
||||
}
|
@ -27,6 +27,6 @@ namespace BTCPayServer.Client.Models
|
||||
|
||||
public string FormId { get; set; }
|
||||
|
||||
public JObject FormResponse { get; set; }
|
||||
public string FormResponse { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -27,36 +27,36 @@ namespace BTCPayServer.Data
|
||||
public string Id { get; set; }
|
||||
public string Data { get; set; }
|
||||
|
||||
public List<WalletObjectLinkData> Bs { get; set; }
|
||||
public List<WalletObjectLinkData> As { get; set; }
|
||||
public List<WalletObjectLinkData> ChildLinks { get; set; }
|
||||
public List<WalletObjectLinkData> ParentLinks { get; set; }
|
||||
|
||||
public IEnumerable<(string type, string id, string linkdata, string objectdata)> GetLinks()
|
||||
{
|
||||
if (Bs is not null)
|
||||
foreach (var c in Bs)
|
||||
if (ChildLinks is not null)
|
||||
foreach (var c in ChildLinks)
|
||||
{
|
||||
yield return (c.BType, c.BId, c.Data, c.B?.Data);
|
||||
yield return (c.ChildType, c.ChildId, c.Data, c.Child?.Data);
|
||||
}
|
||||
if (As is not null)
|
||||
foreach (var c in As)
|
||||
if (ParentLinks is not null)
|
||||
foreach (var c in ParentLinks)
|
||||
{
|
||||
yield return (c.AType, c.AId, c.Data, c.A?.Data);
|
||||
yield return (c.ParentType, c.ParentId, c.Data, c.Parent?.Data);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<WalletObjectData> GetNeighbours()
|
||||
{
|
||||
if (Bs != null)
|
||||
foreach (var c in Bs)
|
||||
if (ChildLinks != null)
|
||||
foreach (var c in ChildLinks)
|
||||
{
|
||||
if (c.B != null)
|
||||
yield return c.B;
|
||||
if (c.Child != null)
|
||||
yield return c.Child;
|
||||
}
|
||||
if (As != null)
|
||||
foreach (var c in As)
|
||||
if (ParentLinks != null)
|
||||
foreach (var c in ParentLinks)
|
||||
{
|
||||
if (c.A != null)
|
||||
yield return c.A;
|
||||
if (c.Parent != null)
|
||||
yield return c.Parent;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,14 +11,14 @@ namespace BTCPayServer.Data
|
||||
public class WalletObjectLinkData
|
||||
{
|
||||
public string WalletId { get; set; }
|
||||
public string AType { get; set; }
|
||||
public string AId { get; set; }
|
||||
public string BType { get; set; }
|
||||
public string BId { get; set; }
|
||||
public string ParentType { get; set; }
|
||||
public string ParentId { get; set; }
|
||||
public string ChildType { get; set; }
|
||||
public string ChildId { get; set; }
|
||||
public string Data { get; set; }
|
||||
|
||||
public WalletObjectData A { get; set; }
|
||||
public WalletObjectData B { get; set; }
|
||||
public WalletObjectData Parent { get; set; }
|
||||
public WalletObjectData Child { get; set; }
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
|
||||
{
|
||||
@ -26,28 +26,28 @@ namespace BTCPayServer.Data
|
||||
new
|
||||
{
|
||||
o.WalletId,
|
||||
o.AType,
|
||||
o.AId,
|
||||
o.BType,
|
||||
o.BId,
|
||||
o.ParentType,
|
||||
o.ParentId,
|
||||
o.ChildType,
|
||||
o.ChildId,
|
||||
});
|
||||
builder.Entity<WalletObjectLinkData>().HasIndex(o => new
|
||||
{
|
||||
o.WalletId,
|
||||
o.BType,
|
||||
o.BId,
|
||||
o.ChildType,
|
||||
o.ChildId,
|
||||
});
|
||||
|
||||
builder.Entity<WalletObjectLinkData>()
|
||||
.HasOne(o => o.A)
|
||||
.WithMany(o => o.Bs)
|
||||
.HasForeignKey(o => new { o.WalletId, o.AType, o.AId })
|
||||
.HasOne(o => o.Parent)
|
||||
.WithMany(o => o.ChildLinks)
|
||||
.HasForeignKey(o => new { o.WalletId, o.ParentType, o.ParentId })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<WalletObjectLinkData>()
|
||||
.HasOne(o => o.B)
|
||||
.WithMany(o => o.As)
|
||||
.HasForeignKey(o => new { o.WalletId, o.BType, o.BId })
|
||||
.HasOne(o => o.Child)
|
||||
.WithMany(o => o.ParentLinks)
|
||||
.HasForeignKey(o => new { o.WalletId, o.ChildType, o.ChildId })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
if (databaseFacade.IsNpgsql())
|
||||
|
@ -40,33 +40,33 @@ namespace BTCPayServer.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
WalletId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
AType = table.Column<string>(type: "TEXT", nullable: false),
|
||||
AId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
BType = table.Column<string>(type: "TEXT", nullable: false),
|
||||
BId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ParentType = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ParentId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ChildType = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ChildId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Data = table.Column<string>(type: migrationBuilder.IsNpgsql() ? "JSONB" : "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_WalletObjectLinks", x => new { x.WalletId, x.AType, x.AId, x.BType, x.BId });
|
||||
table.PrimaryKey("PK_WalletObjectLinks", x => new { x.WalletId, x.ParentType, x.ParentId, x.ChildType, x.ChildId });
|
||||
table.ForeignKey(
|
||||
name: "FK_WalletObjectLinks_WalletObjects_WalletId_BType_BId",
|
||||
columns: x => new { x.WalletId, x.BType, x.BId },
|
||||
name: "FK_WalletObjectLinks_WalletObjects_WalletId_ChildType_ChildId",
|
||||
columns: x => new { x.WalletId, x.ChildType, x.ChildId },
|
||||
principalTable: "WalletObjects",
|
||||
principalColumns: new[] { "WalletId", "Type", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_WalletObjectLinks_WalletObjects_WalletId_AType_AId",
|
||||
columns: x => new { x.WalletId, x.AType, x.AId },
|
||||
name: "FK_WalletObjectLinks_WalletObjects_WalletId_ParentType_ParentId",
|
||||
columns: x => new { x.WalletId, x.ParentType, x.ParentId },
|
||||
principalTable: "WalletObjects",
|
||||
principalColumns: new[] { "WalletId", "Type", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WalletObjectLinks_WalletId_BType_BId",
|
||||
name: "IX_WalletObjectLinks_WalletId_ChildType_ChildId",
|
||||
table: "WalletObjectLinks",
|
||||
columns: new[] { "WalletId", "BType", "BId" });
|
||||
columns: new[] { "WalletId", "ChildType", "ChildId" });
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
|
@ -872,24 +872,24 @@ namespace BTCPayServer.Migrations
|
||||
b.Property<string>("WalletId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AType")
|
||||
b.Property<string>("ParentType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AId")
|
||||
b.Property<string>("ParentId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("BType")
|
||||
b.Property<string>("ChildType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("BId")
|
||||
b.Property<string>("ChildId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Data")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("WalletId", "AType", "AId", "BType", "BId");
|
||||
b.HasKey("WalletId", "ParentType", "ParentId", "ChildType", "ChildId");
|
||||
|
||||
b.HasIndex("WalletId", "BType", "BId");
|
||||
b.HasIndex("WalletId", "ChildType", "ChildId");
|
||||
|
||||
b.ToTable("WalletObjectLinks");
|
||||
});
|
||||
@ -1384,21 +1384,21 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WalletObjectLinkData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.WalletObjectData", "A")
|
||||
.WithMany("Bs")
|
||||
.HasForeignKey("WalletId", "AType", "AId")
|
||||
b.HasOne("BTCPayServer.Data.WalletObjectData", "Child")
|
||||
.WithMany("ParentLinks")
|
||||
.HasForeignKey("WalletId", "ChildType", "ChildId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BTCPayServer.Data.WalletObjectData", "B")
|
||||
.WithMany("As")
|
||||
.HasForeignKey("WalletId", "BType", "BId")
|
||||
b.HasOne("BTCPayServer.Data.WalletObjectData", "Parent")
|
||||
.WithMany("ChildLinks")
|
||||
.HasForeignKey("WalletId", "ParentType", "ParentId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("A");
|
||||
b.Navigation("Child");
|
||||
|
||||
b.Navigation("B");
|
||||
b.Navigation("Parent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WalletTransactionData", b =>
|
||||
@ -1545,9 +1545,9 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WalletObjectData", b =>
|
||||
{
|
||||
b.Navigation("As");
|
||||
b.Navigation("ChildLinks");
|
||||
|
||||
b.Navigation("Bs");
|
||||
b.Navigation("ParentLinks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WebhookData", b =>
|
||||
|
@ -24,6 +24,7 @@ using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Invoices.Export;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
@ -178,6 +179,11 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
JToken? receiptData = null;
|
||||
i.Metadata?.AdditionalData?.TryGetValue("receiptData", out receiptData);
|
||||
string? formResponse = null;
|
||||
if (i.Metadata?.AdditionalData?.TryGetValue("formResponse", out var formResponseRaw)is true)
|
||||
{
|
||||
formResponseRaw.Value<string>();
|
||||
}
|
||||
|
||||
var payments = i.GetPayments(true)
|
||||
.Select(paymentEntity =>
|
||||
|
@ -193,8 +193,7 @@ namespace BTCPayServer.Controllers
|
||||
Metadata = invoiceMetadata.ToJObject(),
|
||||
Currency = pr.Currency,
|
||||
Amount = amount,
|
||||
Checkout = { RedirectURL = redirectUrl },
|
||||
Receipt = new InvoiceDataBase.ReceiptOptions { Enabled = false }
|
||||
Checkout = { RedirectURL = redirectUrl }
|
||||
};
|
||||
|
||||
var additionalTags = new List<string> { PaymentRequestRepository.GetInternalTag(pr.Id) };
|
||||
|
@ -1,15 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
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.Data;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Forms;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.PaymentRequestViewModels;
|
||||
using BTCPayServer.PaymentRequest;
|
||||
@ -18,8 +17,10 @@ using BTCPayServer.Services.PaymentRequests;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PaymentRequestData = BTCPayServer.Data.PaymentRequestData;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
@ -39,8 +40,6 @@ namespace BTCPayServer.Controllers
|
||||
private readonly InvoiceRepository _InvoiceRepository;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
|
||||
private FormComponentProviders FormProviders { get; }
|
||||
|
||||
public UIPaymentRequestController(
|
||||
UIInvoiceController invoiceController,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
@ -49,8 +48,7 @@ namespace BTCPayServer.Controllers
|
||||
EventAggregator eventAggregator,
|
||||
CurrencyNameTable currencies,
|
||||
StoreRepository storeRepository,
|
||||
InvoiceRepository invoiceRepository,
|
||||
FormComponentProviders formProviders)
|
||||
InvoiceRepository invoiceRepository)
|
||||
{
|
||||
_InvoiceController = invoiceController;
|
||||
_UserManager = userManager;
|
||||
@ -60,7 +58,6 @@ namespace BTCPayServer.Controllers
|
||||
_Currencies = currencies;
|
||||
_storeRepository = storeRepository;
|
||||
_InvoiceRepository = invoiceRepository;
|
||||
FormProviders = formProviders;
|
||||
}
|
||||
|
||||
[BitpayAPIConstraint(false)]
|
||||
@ -184,7 +181,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, [FromForm] string formId, [FromForm] string formData)
|
||||
{
|
||||
var result = await _PaymentRequestRepository.FindPaymentRequest(payReqId, GetUserId());
|
||||
if (result == null)
|
||||
@ -198,37 +195,32 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
case null:
|
||||
case { } when string.IsNullOrEmpty(prFormId):
|
||||
case { } when Request.Method == "GET" && prBlob.FormResponse is not null:
|
||||
return RedirectToAction("ViewPaymentRequest", new { payReqId });
|
||||
case { } when Request.Method == "GET" && prBlob.FormResponse is null:
|
||||
break;
|
||||
break;
|
||||
|
||||
default:
|
||||
// POST case: Handle form submit
|
||||
var formData = Form.Parse(UIFormsController.GetFormData(prFormId).Config);
|
||||
formData.ApplyValuesFromForm(Request.Form);
|
||||
if (FormProviders.Validate(formData, ModelState))
|
||||
if (!string.IsNullOrEmpty(formData) && formId == prFormId)
|
||||
{
|
||||
prBlob.FormResponse = JObject.FromObject(formData.GetValues());
|
||||
prBlob.FormResponse = formData;
|
||||
result.SetBlob(prBlob);
|
||||
await _PaymentRequestRepository.CreateOrUpdatePaymentRequest(result);
|
||||
return RedirectToAction("PayPaymentRequest", new { payReqId });
|
||||
}
|
||||
break;
|
||||
|
||||
// GET or empty form data case: Redirect to form
|
||||
return View("PostRedirect", new PostRedirectViewModel
|
||||
{
|
||||
AspController = "UIForms",
|
||||
AspAction = "ViewPublicForm",
|
||||
FormParameters =
|
||||
{
|
||||
{ "formId", prFormId },
|
||||
{ "redirectUrl", Request.GetCurrentUrl() }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return View("PostRedirect", new PostRedirectViewModel
|
||||
{
|
||||
AspController = "UIForms",
|
||||
AspAction = "ViewPublicForm",
|
||||
RouteParameters =
|
||||
{
|
||||
{ "formId", prFormId }
|
||||
},
|
||||
FormParameters =
|
||||
{
|
||||
{ "redirectUrl", Request.GetCurrentUrl() }
|
||||
}
|
||||
});
|
||||
|
||||
return RedirectToAction("ViewPaymentRequest", new { payReqId });
|
||||
}
|
||||
|
||||
[HttpGet("{payReqId}/pay")]
|
||||
|
20
BTCPayServer/Forms/FormComponentProvider.cs
Normal file
20
BTCPayServer/Forms/FormComponentProvider.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
|
||||
namespace BTCPayServer.Forms;
|
||||
|
||||
public class FormComponentProvider : IFormComponentProvider
|
||||
{
|
||||
private readonly IEnumerable<IFormComponentProvider> _formComponentProviders;
|
||||
|
||||
public FormComponentProvider(IEnumerable<IFormComponentProvider> formComponentProviders)
|
||||
{
|
||||
_formComponentProviders = formComponentProviders;
|
||||
}
|
||||
|
||||
public string CanHandle(Field field)
|
||||
{
|
||||
return _formComponentProviders.Select(formComponentProvider => formComponentProvider.CanHandle(field)).FirstOrDefault(result => !string.IsNullOrEmpty(result));
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace BTCPayServer.Forms;
|
||||
|
||||
public class FormComponentProviders
|
||||
{
|
||||
private readonly IEnumerable<IFormComponentProvider> _formComponentProviders;
|
||||
|
||||
public Dictionary<string, IFormComponentProvider> TypeToComponentProvider = new Dictionary<string, IFormComponentProvider>();
|
||||
|
||||
public FormComponentProviders(IEnumerable<IFormComponentProvider> formComponentProviders)
|
||||
{
|
||||
_formComponentProviders = formComponentProviders;
|
||||
foreach (var prov in _formComponentProviders)
|
||||
prov.Register(TypeToComponentProvider);
|
||||
}
|
||||
|
||||
public bool Validate(Form form, ModelStateDictionary modelState)
|
||||
{
|
||||
foreach (var field in form.Fields)
|
||||
{
|
||||
if (TypeToComponentProvider.TryGetValue(field.Type, out var provider))
|
||||
{
|
||||
provider.Validate(form, field);
|
||||
foreach (var err in field.ValidationErrors)
|
||||
modelState.TryAddModelError(field.Name, err);
|
||||
}
|
||||
}
|
||||
return modelState.IsValid;
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ public static class FormDataExtensions
|
||||
public static void AddForms(this IServiceCollection serviceCollection)
|
||||
{
|
||||
serviceCollection.AddSingleton<FormDataService>();
|
||||
serviceCollection.AddSingleton<FormComponentProviders>();
|
||||
serviceCollection.AddSingleton<FormComponentProvider>();
|
||||
serviceCollection.AddSingleton<IFormComponentProvider, HtmlInputFormProvider>();
|
||||
serviceCollection.AddSingleton<IFormComponentProvider, HtmlFieldsetFormProvider>();
|
||||
}
|
||||
|
@ -15,21 +15,21 @@ public class FormDataService
|
||||
|
||||
public static readonly Form StaticFormEmail = new()
|
||||
{
|
||||
Fields = new List<Field>() {Field.Create("Enter your email", "buyerEmail", null, true, null, "email")}
|
||||
Fields = new List<Field>() {new HtmlInputField("Enter your email", "buyerEmail", null, true, null)}
|
||||
};
|
||||
|
||||
public static readonly Form StaticFormAddress = new()
|
||||
{
|
||||
Fields = new List<Field>()
|
||||
{
|
||||
Field.Create("Enter your email", "buyerEmail", null, true, null, "email"),
|
||||
Field.Create("Name", "buyerName", null, true, null),
|
||||
Field.Create("Address Line 1", "buyerAddress1", null, true, null),
|
||||
Field.Create("Address Line 2", "buyerAddress2", null, false, null),
|
||||
Field.Create("City", "buyerCity", null, true, null),
|
||||
Field.Create("Postcode", "buyerZip", null, false, null),
|
||||
Field.Create("State", "buyerState", null, false, null),
|
||||
Field.Create("Country", "buyerCountry", null, true, null)
|
||||
new HtmlInputField("Enter your email", "buyerEmail", null, true, null, "email"),
|
||||
new HtmlInputField("Name", "buyerName", null, true, null),
|
||||
new HtmlInputField("Address Line 1", "buyerAddress1", null, true, null),
|
||||
new HtmlInputField("Address Line 2", "buyerAddress2", null, false, null),
|
||||
new HtmlInputField("City", "buyerCity", null, true, null),
|
||||
new HtmlInputField("Postcode", "buyerZip", null, false, null),
|
||||
new HtmlInputField("State", "buyerState", null, false, null),
|
||||
new HtmlInputField("Country", "buyerCountry", null, true, null)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,23 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
|
||||
namespace BTCPayServer.Forms;
|
||||
|
||||
public class HtmlFieldsetFormProvider: IFormComponentProvider
|
||||
{
|
||||
public string View => "Forms/FieldSetElement";
|
||||
|
||||
public void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider)
|
||||
public string CanHandle(Field field)
|
||||
{
|
||||
typeToComponentProvider.Add("fieldset", this);
|
||||
return new[] { "fieldset"}.Contains(field.Type) ? "Forms/FieldSetElement" : null;
|
||||
}
|
||||
|
||||
public void Validate(Field field)
|
||||
{
|
||||
}
|
||||
|
||||
public void Validate(Form form, Field field)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
using BTCPayServer.Validation;
|
||||
|
||||
namespace BTCPayServer.Forms;
|
||||
|
||||
public class HtmlInputFormProvider: FormComponentProviderBase
|
||||
public class HtmlInputFormProvider: IFormComponentProvider
|
||||
{
|
||||
public override void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider)
|
||||
public string CanHandle(Field field)
|
||||
{
|
||||
foreach (var t in new[] {
|
||||
return new[] {
|
||||
"text",
|
||||
"radio",
|
||||
"checkbox",
|
||||
@ -32,20 +29,6 @@ public class HtmlInputFormProvider: FormComponentProviderBase
|
||||
"search",
|
||||
"url",
|
||||
"tel",
|
||||
"reset"})
|
||||
typeToComponentProvider.Add(t, this);
|
||||
"reset"}.Contains(field.Type) ? "Forms/InputElement" : null;
|
||||
}
|
||||
public override string View => "Forms/InputElement";
|
||||
|
||||
public override void Validate(Form form, Field field)
|
||||
{
|
||||
if (field.Required)
|
||||
{
|
||||
ValidateField<RequiredAttribute>(field);
|
||||
}
|
||||
if (field.Type == "email")
|
||||
{
|
||||
ValidateField<MailboxAddressAttribute>(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,26 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
|
||||
namespace BTCPayServer.Forms;
|
||||
|
||||
public interface IFormComponentProvider
|
||||
{
|
||||
string View { get; }
|
||||
void Validate(Form form, Field field);
|
||||
void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider);
|
||||
}
|
||||
|
||||
public abstract class FormComponentProviderBase : IFormComponentProvider
|
||||
{
|
||||
public abstract string View { get; }
|
||||
public abstract void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider);
|
||||
public abstract void Validate(Form form, Field field);
|
||||
|
||||
public void ValidateField<T>(Field field) where T : ValidationAttribute, new()
|
||||
{
|
||||
var result = new T().GetValidationResult(field.Value, new ValidationContext(field) { DisplayName = field.Label, MemberName = field.Name });
|
||||
if (result != null)
|
||||
field.ValidationErrors.Add(result.ErrorMessage);
|
||||
}
|
||||
public string CanHandle(Field field);
|
||||
}
|
||||
|
@ -8,6 +8,5 @@ public class FormViewModel
|
||||
{
|
||||
public string RedirectUrl { get; set; }
|
||||
public FormData FormData { get; set; }
|
||||
Form _Form;
|
||||
public Form Form { get => _Form ??= Form.Parse(FormData.Config); }
|
||||
public Form Form { get => JObject.Parse(FormData.Config).ToObject<Form>(); }
|
||||
}
|
||||
|
@ -1,36 +1,36 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Data.Data;
|
||||
using BTCPayServer.Forms.Models;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Forms;
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public class UIFormsController : Controller
|
||||
{
|
||||
private FormComponentProviders FormProviders { get; }
|
||||
|
||||
public UIFormsController(FormComponentProviders formProviders)
|
||||
{
|
||||
FormProviders = formProviders;
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("~/forms/{formId}")]
|
||||
[HttpPost("~/forms")]
|
||||
public IActionResult ViewPublicForm(string? formId, string? redirectUrl)
|
||||
{
|
||||
if (!IsValidRedirectUri(redirectUrl))
|
||||
return BadRequest();
|
||||
|
||||
FormData? formData = string.IsNullOrEmpty(formId) ? null : GetFormData(formId);
|
||||
if (formData == null)
|
||||
{
|
||||
@ -39,35 +39,25 @@ public class UIFormsController : Controller
|
||||
: Redirect(redirectUrl);
|
||||
}
|
||||
|
||||
return GetFormView(formData, redirectUrl);
|
||||
}
|
||||
|
||||
ViewResult GetFormView(FormData formData, string? redirectUrl)
|
||||
{
|
||||
return View("View", new FormViewModel { FormData = formData, RedirectUrl = redirectUrl });
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("~/forms/{formId}")]
|
||||
public IActionResult SubmitForm(string formId, string? redirectUrl, string? command)
|
||||
public IActionResult SubmitForm(
|
||||
string formId, string? redirectUrl,
|
||||
[FromServices] StoreRepository storeRepository,
|
||||
[FromServices] UIInvoiceController invoiceController)
|
||||
{
|
||||
if (!IsValidRedirectUri(redirectUrl))
|
||||
return BadRequest();
|
||||
|
||||
var formData = GetFormData(formId);
|
||||
if (formData?.Config is null)
|
||||
if (formData is null)
|
||||
{
|
||||
return NotFound();
|
||||
|
||||
if (command is not "Submit")
|
||||
return GetFormView(formData, redirectUrl);
|
||||
}
|
||||
|
||||
var conf = Form.Parse(formData.Config);
|
||||
conf.ApplyValuesFromForm(Request.Form);
|
||||
if (!FormProviders.Validate(conf, ModelState))
|
||||
return GetFormView(formData, redirectUrl);
|
||||
|
||||
var form = new MultiValueDictionary<string, string>();
|
||||
foreach (var kv in Request.Form)
|
||||
form.Add(kv.Key, kv.Value);
|
||||
var dbForm = JObject.Parse(formData.Config!).ToObject<Form>()!;
|
||||
dbForm.ApplyValuesFromForm(Request.Form);
|
||||
Dictionary<string, object> data = dbForm.GetValues();
|
||||
|
||||
// With redirect, the form comes from another entity that we need to send the data back to
|
||||
if (!string.IsNullOrEmpty(redirectUrl))
|
||||
@ -75,26 +65,30 @@ public class UIFormsController : Controller
|
||||
return View("PostRedirect", new PostRedirectViewModel
|
||||
{
|
||||
FormUrl = redirectUrl,
|
||||
FormParameters = form
|
||||
FormParameters =
|
||||
{
|
||||
{ "formId", formData.Id },
|
||||
{ "formData", JsonConvert.SerializeObject(data) }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
internal static FormData? GetFormData(string id)
|
||||
private FormData? GetFormData(string id)
|
||||
{
|
||||
FormData? form = id switch
|
||||
{
|
||||
{ } formId when formId == GenericFormOption.Address.ToString() => new FormData
|
||||
{
|
||||
Config = FormDataService.StaticFormAddress.ToString(),
|
||||
Config = JObject.FromObject(FormDataService.StaticFormAddress).ToString(),
|
||||
Id = GenericFormOption.Address.ToString(),
|
||||
Name = "Provide your address",
|
||||
},
|
||||
{ } formId when formId == GenericFormOption.Email.ToString() => new FormData
|
||||
{
|
||||
Config = FormDataService.StaticFormEmail.ToString(),
|
||||
Config = JObject.FromObject(FormDataService.StaticFormEmail).ToString(),
|
||||
Id = GenericFormOption.Email.ToString(),
|
||||
Name = "Provide your email address",
|
||||
},
|
||||
@ -102,8 +96,4 @@ public class UIFormsController : Controller
|
||||
};
|
||||
return form;
|
||||
}
|
||||
|
||||
private bool IsValidRedirectUri(string? redirectUrl) =>
|
||||
!string.IsNullOrEmpty(redirectUrl) && Uri.TryCreate(redirectUrl, UriKind.RelativeOrAbsolute, out var uri) &&
|
||||
(Url.IsLocalUrl(redirectUrl) || uri.Host.Equals(Request.Host.Host));
|
||||
}
|
||||
|
@ -173,10 +173,10 @@ next:
|
||||
db.WalletObjectLinks.Add(new WalletObjectLinkData()
|
||||
{
|
||||
WalletId = tx.WalletDataId,
|
||||
BType = Data.WalletObjectData.Types.Tx,
|
||||
BId = tx.TransactionId,
|
||||
AType = Data.WalletObjectData.Types.Label,
|
||||
AId = labelId
|
||||
ChildType = Data.WalletObjectData.Types.Tx,
|
||||
ChildId = tx.TransactionId,
|
||||
ParentType = Data.WalletObjectData.Types.Label,
|
||||
ParentId = labelId
|
||||
});
|
||||
|
||||
if (label.Value is ReferenceLabel reflabel)
|
||||
@ -195,10 +195,10 @@ next:
|
||||
db.WalletObjectLinks.Add(new WalletObjectLinkData()
|
||||
{
|
||||
WalletId = tx.WalletDataId,
|
||||
BType = Data.WalletObjectData.Types.Tx,
|
||||
BId = tx.TransactionId,
|
||||
AType = reflabel.Type,
|
||||
AId = reflabel.Reference ?? String.Empty
|
||||
ChildType = Data.WalletObjectData.Types.Tx,
|
||||
ChildId = tx.TransactionId,
|
||||
ParentType = reflabel.Type,
|
||||
ParentId = reflabel.Reference ?? String.Empty
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -224,10 +224,10 @@ next:
|
||||
db.WalletObjectLinks.Add(new WalletObjectLinkData()
|
||||
{
|
||||
WalletId = tx.WalletDataId,
|
||||
BType = Data.WalletObjectData.Types.Tx,
|
||||
BId = tx.TransactionId,
|
||||
AType = "payout",
|
||||
AId = payout
|
||||
ChildType = Data.WalletObjectData.Types.Tx,
|
||||
ChildId = tx.TransactionId,
|
||||
ParentType = "payout",
|
||||
ParentId = payout
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -46,9 +46,9 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||
CustomCSSLink = blob.CustomCSSLink;
|
||||
EmbeddedCSS = blob.EmbeddedCSS;
|
||||
AllowCustomPaymentAmounts = blob.AllowCustomPaymentAmounts;
|
||||
FormResponse = blob.FormResponse is null
|
||||
FormResponse = string.IsNullOrEmpty(blob.FormResponse)
|
||||
? null
|
||||
: blob.FormResponse.ToObject<Dictionary<string, object>>();
|
||||
: JObject.Parse(blob.FormResponse).ToObject<Dictionary<string, object>>();
|
||||
}
|
||||
|
||||
[Display(Name = "Request customer data on checkout")]
|
||||
|
@ -98,7 +98,7 @@ namespace BTCPayServer.PaymentRequest
|
||||
CurrencyData = _currencies.GetCurrencyData(blob.Currency, true),
|
||||
LastUpdated = DateTime.UtcNow,
|
||||
FormId = blob.FormId,
|
||||
FormSubmitted = blob.FormResponse is not null,
|
||||
FormSubmitted = !string.IsNullOrEmpty(blob.FormResponse),
|
||||
AnyPendingInvoice = pendingInvoice != null,
|
||||
PendingInvoiceHasPayments = pendingInvoice != null &&
|
||||
pendingInvoice.ExceptionStatus != InvoiceExceptionStatus.None,
|
||||
|
@ -9,13 +9,11 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Forms;
|
||||
using BTCPayServer.ModelBinders;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||
@ -41,23 +39,19 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||
AppService appService,
|
||||
CurrencyNameTable currencies,
|
||||
StoreRepository storeRepository,
|
||||
UIInvoiceController invoiceController,
|
||||
FormComponentProviders formProviders)
|
||||
UIInvoiceController invoiceController)
|
||||
{
|
||||
_currencies = currencies;
|
||||
_appService = appService;
|
||||
_storeRepository = storeRepository;
|
||||
_invoiceController = invoiceController;
|
||||
FormProviders = formProviders;
|
||||
}
|
||||
|
||||
private readonly CurrencyNameTable _currencies;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly AppService _appService;
|
||||
private readonly UIInvoiceController _invoiceController;
|
||||
|
||||
public FormComponentProviders FormProviders { get; }
|
||||
|
||||
|
||||
[HttpGet("/")]
|
||||
[HttpGet("/apps/{appId}/pos/{viewType?}")]
|
||||
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
||||
@ -124,6 +118,8 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||
string notificationUrl,
|
||||
string redirectUrl,
|
||||
string choiceKey,
|
||||
string formId = null,
|
||||
string formData = null,
|
||||
string posData = null,
|
||||
RequiresRefundEmail requiresRefundEmail = RequiresRefundEmail.InheritFromStore,
|
||||
CancellationToken cancellationToken = default)
|
||||
@ -234,12 +230,9 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||
|
||||
default:
|
||||
// POST case: Handle form submit
|
||||
var formData = Form.Parse(Forms.UIFormsController.GetFormData(posFormId).Config);
|
||||
formData.ApplyValuesFromForm(this.Request.Form);
|
||||
|
||||
if (FormProviders.Validate(formData, ModelState))
|
||||
if (!string.IsNullOrEmpty(formData) && formId == posFormId)
|
||||
{
|
||||
formResponse = JObject.FromObject(formData.GetValues());
|
||||
formResponse = JObject.Parse(formData);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -254,12 +247,9 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||
{
|
||||
AspController = "UIForms",
|
||||
AspAction = "ViewPublicForm",
|
||||
RouteParameters =
|
||||
{
|
||||
{ "formId", posFormId }
|
||||
},
|
||||
FormParameters =
|
||||
{
|
||||
{ "formId", posFormId },
|
||||
{ "redirectUrl", Request.GetCurrentUrl() + query }
|
||||
}
|
||||
});
|
||||
|
@ -78,7 +78,7 @@ namespace BTCPayServer.Services
|
||||
|
||||
using var ctx = _ContextFactory.CreateContext();
|
||||
|
||||
// If we are using postgres, the `transactionIds.Contains(w.BId)` result in a long query like `ANY(@txId1, @txId2, @txId3, @txId4)`
|
||||
// If we are using postgres, the `transactionIds.Contains(w.ChildId)` result in a long query like `ANY(@txId1, @txId2, @txId3, @txId4)`
|
||||
// Such request isn't well optimized by postgres, and create different requests clogging up
|
||||
// pg_stat_statements output, making it impossible to analyze the performance impact of this query.
|
||||
// On top of this, the entity version is doing 2 left join to satisfy the Include queries, resulting in n*m row returned for each transaction.
|
||||
@ -106,9 +106,9 @@ namespace BTCPayServer.Services
|
||||
var query =
|
||||
$"SELECT wos.\"WalletId\", wos.\"Id\", wos.\"Type\", wos.\"Data\", wol.\"LinkData\", wol.\"Type2\", wol.\"Id2\"{includeNeighbourSelect} FROM ({selectWalletObjects}) wos " +
|
||||
$"LEFT JOIN LATERAL ( " +
|
||||
"SELECT \"AType\" AS \"Type2\", \"AId\" AS \"Id2\", \"Data\" AS \"LinkData\" FROM \"WalletObjectLinks\" WHERE \"WalletId\"=wos.\"WalletId\" AND \"BType\"=wos.\"Type\" AND \"BId\"=wos.\"Id\" " +
|
||||
"SELECT \"ParentType\" AS \"Type2\", \"ParentId\" AS \"Id2\", \"Data\" AS \"LinkData\" FROM \"WalletObjectLinks\" WHERE \"WalletId\"=wos.\"WalletId\" AND \"ChildType\"=wos.\"Type\" AND \"ChildId\"=wos.\"Id\" " +
|
||||
"UNION " +
|
||||
"SELECT \"BType\" AS \"Type2\", \"BId\" AS \"Id2\", \"Data\" AS \"LinkData\" FROM \"WalletObjectLinks\" WHERE \"WalletId\"=wos.\"WalletId\" AND \"AType\"=wos.\"Type\" AND \"AId\"=wos.\"Id\"" +
|
||||
"SELECT \"ChildType\" AS \"Type2\", \"ChildId\" AS \"Id2\", \"Data\" AS \"LinkData\" FROM \"WalletObjectLinks\" WHERE \"WalletId\"=wos.\"WalletId\" AND \"ParentType\"=wos.\"Type\" AND \"ParentId\"=wos.\"Id\"" +
|
||||
$" ) wol ON true " + includeNeighbourJoin;
|
||||
cmd.CommandText = query;
|
||||
if (queryObject.WalletId is not null)
|
||||
@ -177,21 +177,21 @@ namespace BTCPayServer.Services
|
||||
else
|
||||
{
|
||||
wosById.Add(id, wo);
|
||||
wo.Bs = new List<WalletObjectLinkData>();
|
||||
wo.ChildLinks = new List<WalletObjectLinkData>();
|
||||
}
|
||||
if (reader["Type2"] is not DBNull)
|
||||
{
|
||||
var l = new WalletObjectLinkData()
|
||||
{
|
||||
BType = (string)reader["Type2"],
|
||||
BId = (string)reader["Id2"],
|
||||
ChildType = (string)reader["Type2"],
|
||||
ChildId = (string)reader["Id2"],
|
||||
Data = reader["LinkData"] is DBNull ? null : (string)reader["LinkData"]
|
||||
};
|
||||
wo.Bs.Add(l);
|
||||
l.B = new WalletObjectData()
|
||||
wo.ChildLinks.Add(l);
|
||||
l.Child = new WalletObjectData()
|
||||
{
|
||||
Type = l.BType,
|
||||
Id = l.BId,
|
||||
Type = l.ChildType,
|
||||
Id = l.ChildId,
|
||||
Data = (!queryObject.IncludeNeighbours || reader["Data2"] is DBNull) ? null : (string)reader["Data2"]
|
||||
};
|
||||
}
|
||||
@ -215,8 +215,8 @@ namespace BTCPayServer.Services
|
||||
}
|
||||
if (queryObject.IncludeNeighbours)
|
||||
{
|
||||
q = q.Include(o => o.Bs).ThenInclude(o => o.B)
|
||||
.Include(o => o.As).ThenInclude(o => o.A);
|
||||
q = q.Include(o => o.ChildLinks).ThenInclude(o => o.Child)
|
||||
.Include(o => o.ParentLinks).ThenInclude(o => o.Parent);
|
||||
}
|
||||
q = q.AsNoTracking();
|
||||
|
||||
@ -299,10 +299,10 @@ namespace BTCPayServer.Services
|
||||
var l = new WalletObjectLinkData()
|
||||
{
|
||||
WalletId = a.WalletId.ToString(),
|
||||
AType = a.Type,
|
||||
AId = a.Id,
|
||||
BType = b.Type,
|
||||
BId = b.Id,
|
||||
ParentType = a.Type,
|
||||
ParentId = a.Id,
|
||||
ChildType = b.Type,
|
||||
ChildId = b.Id,
|
||||
Data = data?.ToString(Formatting.None)
|
||||
};
|
||||
ctx.WalletObjectLinks.Add(l);
|
||||
@ -345,10 +345,10 @@ namespace BTCPayServer.Services
|
||||
var l = new WalletObjectLinkData()
|
||||
{
|
||||
WalletId = a.WalletId.ToString(),
|
||||
AType = a.Type,
|
||||
AId = a.Id,
|
||||
BType = b.Type,
|
||||
BId = b.Id,
|
||||
ParentType = a.Type,
|
||||
ParentId = a.Id,
|
||||
ChildType = b.Type,
|
||||
ChildId = b.Id,
|
||||
Data = data?.ToString(Formatting.None)
|
||||
};
|
||||
var e = ctx.WalletObjectLinks.Add(l);
|
||||
@ -453,10 +453,10 @@ namespace BTCPayServer.Services
|
||||
ctx.WalletObjectLinks.Remove(new WalletObjectLinkData()
|
||||
{
|
||||
WalletId = a.WalletId.ToString(),
|
||||
AId = a.Id,
|
||||
AType = a.Type,
|
||||
BId = b.Id,
|
||||
BType = b.Type
|
||||
ParentId = a.Id,
|
||||
ParentType = a.Type,
|
||||
ChildId = b.Id,
|
||||
ChildType = b.Type
|
||||
});
|
||||
try
|
||||
{
|
||||
|
@ -1,19 +1,27 @@
|
||||
@using BTCPayServer.Abstractions.Form
|
||||
@using BTCPayServer.Abstractions.Form
|
||||
@using BTCPayServer.Forms
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@using Newtonsoft.Json.Linq
|
||||
@inject FormComponentProviders FormComponentProviders
|
||||
@inject FormComponentProvider FormComponentProvider
|
||||
@model BTCPayServer.Abstractions.Form.Field
|
||||
@if (!Model.Hidden)
|
||||
@{
|
||||
if (Model is not Fieldset fieldset)
|
||||
{
|
||||
fieldset = JObject.FromObject(Model).ToObject<Fieldset>();
|
||||
}
|
||||
}
|
||||
@if (!fieldset.Hidden)
|
||||
{
|
||||
<fieldset>
|
||||
<legend class="h3 mt-4 mb-3">@Model.Label</legend>
|
||||
@foreach (var field in Model.Fields)
|
||||
<legend class="h3 mt-4 mb-3">@fieldset.Label</legend>
|
||||
@foreach (var field in fieldset.Fields)
|
||||
{
|
||||
if (FormComponentProviders.TypeToComponentProvider.TryGetValue(field.Type, out var partial))
|
||||
{
|
||||
<partial name="@partial.View" for="@field"></partial>
|
||||
}
|
||||
var partial = FormComponentProvider.CanHandle(field);
|
||||
if (string.IsNullOrEmpty(partial))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
<partial name="@partial" for="@field"></partial>
|
||||
}
|
||||
</fieldset>
|
||||
}
|
||||
|
@ -1,34 +1,31 @@
|
||||
@using BTCPayServer.Abstractions.Form
|
||||
@using BTCPayServer.Abstractions.Form
|
||||
@using Newtonsoft.Json.Linq
|
||||
@model BTCPayServer.Abstractions.Form.Field
|
||||
@{
|
||||
var isInvalid = this.ViewContext.ModelState[Model.Name]?.ValidationState is Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid;
|
||||
var error = isInvalid ? this.ViewContext.ModelState[Model.Name].Errors[0].ErrorMessage : null;
|
||||
|
||||
if (Model is not HtmlInputField field)
|
||||
{
|
||||
field = JObject.FromObject(Model).ToObject<HtmlInputField>();
|
||||
}
|
||||
}
|
||||
<div class="form-group">
|
||||
@if (Model.Required)
|
||||
@if (field.Required)
|
||||
{
|
||||
<label class="form-label" for="@Model.Name" data-required>
|
||||
@Model.Label
|
||||
<label class="form-label" for="@field.Name" data-required>
|
||||
@field.Label
|
||||
</label>
|
||||
}
|
||||
else
|
||||
{
|
||||
<label class="form-label" for="@Model.Name">
|
||||
@Model.Label
|
||||
<label class="form-label" for="@field.Name">
|
||||
@field.Label
|
||||
</label>
|
||||
}
|
||||
|
||||
<input class="form-control @(Model.IsValid() ? "" : "is-invalid")" id="@Model.Name" type="@Model.Type" required="@Model.Required" name="@Model.Name" value="@Model.Value" aria-describedby="@("HelpText" + Model.Name)"/>
|
||||
@if(isInvalid)
|
||||
{
|
||||
<span class="text-danger">@error</span>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Model.HelpText))
|
||||
<input class="form-control @(field.IsValid() ? "" : "is-invalid")" id="@field.Name" type="@field.Type" required="@field.Required" name="@field.Name" value="@field.Value" aria-describedby="@("HelpText" + field.Name)"/>
|
||||
@if (!string.IsNullOrEmpty(field.HelpText))
|
||||
{
|
||||
<small id="@("HelpText" + Model.Name)" class="form-text text-muted">
|
||||
@Model.HelpText
|
||||
<small id="@("HelpText" + field.Name)" class="form-text text-muted">
|
||||
@field.HelpText
|
||||
</small>
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,7 @@
|
||||
{
|
||||
<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="requiresRefundEmail" value="@Model.RequiresRefundEmail.ToString()" />
|
||||
@{PayFormInputContent(item.BuyButtonText ?? Model.CustomButtonText, item.Price.Type, item.Price.Value, item.Price.Value);}
|
||||
</form>
|
||||
}
|
||||
|
@ -1,12 +1,14 @@
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@using BTCPayServer.Forms
|
||||
@model BTCPayServer.Abstractions.Form.Form
|
||||
@inject FormComponentProviders FormComponentProviders
|
||||
@inject FormComponentProvider FormComponentProvider
|
||||
|
||||
@foreach (var field in Model.Fields)
|
||||
{
|
||||
if (FormComponentProviders.TypeToComponentProvider.TryGetValue(field.Type, out var partial))
|
||||
var partial = FormComponentProvider.CanHandle(field);
|
||||
if (string.IsNullOrEmpty(partial))
|
||||
{
|
||||
<partial name="@partial.View" for="@field"></partial>
|
||||
continue;
|
||||
}
|
||||
<partial name="@partial" for="@field"></partial>
|
||||
}
|
||||
|
@ -33,7 +33,7 @@
|
||||
<input type="hidden" asp-for="RedirectUrl" value="@Model.RedirectUrl"/>
|
||||
}
|
||||
<partial name="_Form" model="@Model.Form"/>
|
||||
<input type="submit" class="btn btn-primary" name="command" value="Submit"/>
|
||||
<input type="submit" class="btn btn-primary" value="Submit"/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -416,7 +416,7 @@
|
||||
<h3 class="mb-3 mt-4">Webhooks</h3>
|
||||
<div class="table-responsive-xl">
|
||||
<table class="table table-hover table-responsive-md mb-5">
|
||||
<thead>
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>ID</th>
|
||||
@ -491,7 +491,7 @@
|
||||
<h3 class="mb-3 mt-4">Refunds</h3>
|
||||
<div class="table-responsive-xl">
|
||||
<table class="table table-hover table-responsive-md mb-5">
|
||||
<thead>
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th>Pull Payment</th>
|
||||
<th>Amount</th>
|
||||
@ -526,9 +526,9 @@
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
<h3 class="mb-0 mt-5">Events</h3>
|
||||
<table class="table table-hover mt-3 mb-4">
|
||||
<thead>
|
||||
<h3 class="mb-0">Events</h3>
|
||||
<table class="table table-hover">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Message</th>
|
||||
|
@ -477,7 +477,7 @@
|
||||
"nullable": true
|
||||
},
|
||||
"formResponse": {
|
||||
"type": "object",
|
||||
"type": "string",
|
||||
"description": "Form data response",
|
||||
"nullable": true
|
||||
}
|
||||
|
@ -34,16 +34,16 @@ public class FakeCustodian : ICustodian
|
||||
var fakeConfig = ParseConfig(config);
|
||||
|
||||
var form = new Form();
|
||||
var fieldset = Field.CreateFieldset();
|
||||
var fieldset = new Fieldset();
|
||||
|
||||
// Maybe a decimal type field would be better?
|
||||
var fakeBTCBalance = Field.Create("BTC Balance", "BTCBalance", fakeConfig?.BTCBalance.ToString(), true,
|
||||
var fakeBTCBalance = new HtmlInputField("BTC Balance", "BTCBalance", fakeConfig?.BTCBalance.ToString(), true,
|
||||
"Enter the amount of BTC you want to have.");
|
||||
var fakeLTCBalance = Field.Create("LTC Balance", "LTCBalance", fakeConfig?.LTCBalance.ToString(), true,
|
||||
var fakeLTCBalance = new HtmlInputField("LTC Balance", "LTCBalance", fakeConfig?.LTCBalance.ToString(), true,
|
||||
"Enter the amount of LTC you want to have.");
|
||||
var fakeEURBalance = Field.Create("EUR Balance", "EURBalance", fakeConfig?.EURBalance.ToString(), true,
|
||||
var fakeEURBalance = new HtmlInputField("EUR Balance", "EURBalance", fakeConfig?.EURBalance.ToString(), true,
|
||||
"Enter the amount of EUR you want to have.");
|
||||
var fakeUSDBalance = Field.Create("USD Balance", "USDBalance", fakeConfig?.USDBalance.ToString(), true,
|
||||
var fakeUSDBalance = new HtmlInputField("USD Balance", "USDBalance", fakeConfig?.USDBalance.ToString(), true,
|
||||
"Enter the amount of USD you want to have.");
|
||||
|
||||
fieldset.Label = "Your fake balances";
|
||||
|
Reference in New Issue
Block a user