Hello,
I created a new custom gateway for the payments module and for the webhook function I needed an additional property to lookup the PaymentRequest based on the incoming payment reference id. For this I created an object extension and an EF extension mapping (because I need to look up the payment request record based on the extra property.
ObjectExtensionManager.Instance.Modules()
.ConfigurePayment(payment =>
{
payment.ConfigureEntity("PaymentRequest", paymentrequest =>
{
paymentrequest.AddOrUpdateProperty<string>( //property type: string
"PaymentReference", //property name
property =>
{
//validation rules
//property.Attributes.Add(new RequiredAttribute());
//property.Attributes.Add(new StringLengthAttribute(256) { MinimumLength = 5 });
//...other configurations for this property
}
);
});
});
ObjectExtensionManager.Instance
.MapEfCoreProperty<PaymentRequest, string>(
"PaymentReference",
(entityBuilder, propertyBuilder) =>
{
//propertyBuilder.HasMaxLength(128);
}
);
This is all working fine, but what I am missing is the additional column in the Payment Requests datatable in the GUI. I did some additional object extensions (on the IdentityUser) and there all the properties are visible in the datatable.
Am I missing something ? Is it otherwise possible to override the entire page (get the sourcecode of this page) ?
Regards,
Geert Veenstra
10 Answer(s)
-
0
What you did (Object Extension + EF Core mapping) only extends the entity and database schema. It does not automatically extend the Payment module’s MVC UI (datatable columns, create/edit forms, etc.).
The reason your
IdentityUserextension shows up in the UI is that Identity module’s UI is integrated with the “module entity extension” UI system (ABP Commercial feature). The Payment module’s “Payment Requests” UI does not automatically render arbitrary extension properties as extra columns.So you are not missing an EF mapping step. You need to extend/customize the MVC UI of the Payment module explicitly.
Overriding the entire page source:
- The Payment module’s MVC UI pages come from the module’s Razor Pages via the Virtual File System (embedded resources). Overriding them is possible, but it’s not exposed as a simple “download page source” feature.
- Practically, you either:
- add a UI extension point (if that page supports it), or
- copy the related Razor Page(s) from the module source code and override via VFS, or
- build your own page that lists payment requests including your new column.
Since this is a PRO/commercial module UI customization, and I can’t reliably fetch/verify the Payment module page source or the exact supported UI extension points right now (tooling access issue), the safest, verified next step is to contact ABP support so they can point you to the exact page/extension-point for “Payment Requests” in v10.0.3 MVC:
- https://abp.io/contact
References (verified):
- https://abp.io/docs/latest/Object-Extensions
- https://abp.io/docs/latest/Customizing-Application-Modules-Guide
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
You're not missing anything on the backend. The Object Extension +
MapEfCorePropertyyou wrote already pushesPaymentReferenceall the way through: the value is saved to the column, loaded back intoExtraProperties, and the AppService returns it as part of the DTO. If you callGET /api/payment-admin/requestsyou'll see the value in the JSON like this:{ "items": [ { "id": "...", "currency": "USD", "extraProperties": { "PaymentReference": "your-value" } } ] }The reason it doesn't show up in the Payment Requests table is purely a UI thing: that page's column list is hardcoded in
index.js, it does not iterate over object extensions the way Identity does. So you just need to add the column yourself via the Virtual File System.Pull the original
index.jsfrom the Payment module source (abp get-source Volo.Paymentor via ABP Suite / ABP Studio "Get Source Code" — the file is atVolo.Payment.Admin.Web/Pages/Payment/Requests/index.js), then drop a copy into your own MVC web project at the same relative pathPages/Payment/Requests/index.js, and add one entry tocolumnDefs:{ title: l("DisplayName:PaymentReference"), orderable: false, data: "extraProperties.PaymentReference" }Two things worth noting:
- The dot notation
extraProperties.PaymentReferenceis the same format ABP's own UI extension system uses (seeui-extensions.js), andextraPropertiesis camelCase while the dictionary key stays Pascal-case — that's the actual JSON shape, so the path isextraProperties.PaymentReference, notextraProperties.paymentReference. - Your physical
index.jsunderPages/Payment/Requests/overrides the module's embedded one through the VFS automatically, no extra registration needed.
We'll look into wiring the Payment Requests datatable into the UI extension system in a future version so this becomes automatic like Identity, but for now the override above is the way.
Thanks.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) - The dot notation
-
0
Hi,
Thank you for the additional info. I did not have time to reply, but placing the index.js in Pages/Payment/Requests is what I already did yesterday after the AI reply :-) I also did a replacement of the Index.cshtml file for the Products lookup page (which also has some additional information in my integration).
Downloading the Payment module source code was unfortunately not possible for me (I guess it is not part of Team licence). Too bad, since I would actually also like to filter on the extra property, any chance to share the part where the filter is setup?
Thanks.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
Glad the column part already works. For filtering by an extension property like
PaymentReferencethere's one extra catch worth knowing: the generated client proxy forpaymentRequestAdmin.getListhardcodes the query-string field list to the props ofPaymentRequestGetListInput(filter / creationDateMax / creationDateMin / paymentType / status / sorting / skipCount / maxResultCount). So if you just addinput.paymentReference = ...ingetFilter(), that field gets silently dropped on the way out.The pattern that works (verified end-to-end on a fresh ABP 10.4 MVC + SQL Server demo, with the same Object Extension you have):
- Push the extra value as an out-of-band query parameter from the datatable's ajax wrapper:
ajax: abp.libs.datatables.createAjax((input, ajaxParams) => { var pr = $("#inputPaymentReference").val(); if (pr) { ajaxParams = $.extend(true, {}, ajaxParams, { data: { PaymentReference: pr } }); } return service.getList(getFilter(input), ajaxParams); }),ajaxParams.dataends up appended to the URL on a GET, so the server sees?...&PaymentReference=xxx. No need to touch the client proxy.- Add the input to the filter form in your VFS-overridden
Index.cshtml:
<abp-column size-lg="_6" size-md="_12"> <label for="inputPaymentReference" class="form-label">Payment Reference</label> <input type="text" id="inputPaymentReference" class="form-control" /> </abp-column>The existing
$("#PaymentRequestFilterForm input,select").change(...)handler inindex.jswill pick it up and reload the table.- Override the AppService and read that query value directly, because the input DTO has no field for it. Since the EF mapping you wrote (
MapEfCoreProperty<PaymentRequest, string>("PaymentReference", ...)) creates a real column, you can filter at the SQL level withEF.Property<string>:
[Dependency(ReplaceServices = true)] [ExposeServices(typeof(IPaymentRequestAdminAppService))] public class MyPaymentRequestAdminAppService : PaymentRequestAdminAppService { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IRepository<PaymentRequest, Guid> _repo; public MyPaymentRequestAdminAppService( IPaymentRequestRepository paymentRequestRepository, IRepository<PaymentRequest, Guid> repo, IHttpContextAccessor httpContextAccessor) : base(paymentRequestRepository) { _repo = repo; _httpContextAccessor = httpContextAccessor; } public override async Task<PagedResultDto<PaymentRequestWithDetailsDto>> GetListAsync(PaymentRequestGetListInput input) { var pr = _httpContextAccessor.HttpContext?.Request.Query["PaymentReference"].ToString(); if (string.IsNullOrWhiteSpace(pr)) { return await base.GetListAsync(input); } var query = (await _repo.WithDetailsAsync()) .WhereIf(!input.Filter.IsNullOrWhiteSpace(), p => p.Currency.Contains(input.Filter) || p.Gateway.Contains(input.Filter) || p.ExternalSubscriptionId == input.Filter) .WhereIf(input.CreationDateMax != null, p => p.CreationTime <= input.CreationDateMax) .WhereIf(input.CreationDateMin != null, p => p.CreationTime >= input.CreationDateMin) .WhereIf(input.PaymentType != null, p => p.Products.Any(x => x.PaymentType == input.PaymentType)) .WhereIf(input.Status != null, p => p.State == input.Status) .Where(p => EF.Property<string>(p, "PaymentReference").Contains(pr)); var totalCount = await query.CountAsync(); var entities = await query .OrderBy(input.Sorting.IsNullOrEmpty() ? nameof(PaymentRequest.CreationTime) + " desc" : input.Sorting) .Skip(input.SkipCount).Take(input.MaxResultCount) .ToListAsync(); return new PagedResultDto<PaymentRequestWithDetailsDto>( totalCount, ObjectMapper.Map<List<PaymentRequest>, List<PaymentRequestWithDetailsDto>>(entities)); } }Re: the source download — yes, full module source is not part of the Team licence, and the assemblies are obfuscated so extracting embedded resources from the dll won't work either. For this case the only files you actually need to override are
Pages/Payment/Requests/index.js(andIndex.cshtml). Drop me an email at liming.ma@volosoft.com and I'll send those two files to you so you have a clean starting point.Thanks.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
Works perfectly! Thanks for your help again.
P.S. During the work I noticed an issue with the permissions: I have no need for the menu option "Payment plans" ( I need only the Payment Requests). So I tried to disable it in the Admin role, but if you disable the Payment Plan permission, then also the Payment Requests menu option is not visible anymore. Not really a big issue (for me), maybe it can be fixed in a future version.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
Great to hear the filter is working — happy it landed cleanly.
About the menu/permission behaviour: looking at the code,
Payment PlansandPayment Requestsare defined as independent top-level permissions (not parent-child), and each menu item requires only its own permission, so uncheckingPayment Plansalone shouldn't hidePayment Requests. Could you share the exact steps you took in the permission modal (which checkboxes you toggled, and whether you used the "Select all in this tab" checkbox at the top)? That'll help me reproduce it on my side and figure out what's actually going on.Thanks again for the detailed feedback.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
-
0
Hi,
Confirmed it's a bug in
Volo.Payment.Admin.Web.Menus.PaymentAdminMenuContributor— the.RequirePermissions(...)calls end up on the parent menu instead of the children, so removing one permission hides the whole group. Will be fixed in the next release.For now, drop this into your Web project:
public class FixedPaymentAdminMenuContributor : IMenuContributor { public async Task ConfigureMenuAsync(MenuConfigurationContext context) { if (context.Menu.Name != StandardMenus.Main) return; var l = context.GetLocalizer<PaymentResource>(); var paymentMenu = new ApplicationMenuItem( PaymentAdminMenus.GroupName, l["Menu:PaymentManagement"], icon: "fa fa-money-check"); context.Menu.GetAdministration().AddItem(paymentMenu); paymentMenu.AddItem(new ApplicationMenuItem( PaymentAdminMenus.Plans.PlansMenu, l["Menu:Plans"].Value, "/Payment/Plans", "fa fa-file-alt") .RequirePermissions(PaymentAdminPermissions.Plans.Default)); paymentMenu.AddItem(new ApplicationMenuItem( PaymentAdminMenus.PaymentRequests.PaymentRequestsMenu, l["Menu:PaymentRequests"].Value, "/Payment/Requests", "fa fa-exchange") .RequirePermissions(PaymentAdminPermissions.PaymentRequests.Default)); await Task.CompletedTask; } }Then in your
YourWebModule.ConfigureServices:Configure<AbpNavigationOptions>(options => { options.MenuContributors.RemoveAll(c => c is Volo.Payment.Admin.Web.Menus.PaymentAdminMenuContributor); options.MenuContributors.Add(new FixedPaymentAdminMenuContributor()); });Thanks.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
I implemented the fix and now only the Payment Requests menu option is indeed visible. Thank you for investigating and providing the fix!
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)



