Open Closed

Payment module object extensions #10655


User avatar
0
geertveenstra created

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

Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

10 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • 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 IdentityUser extension 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:
        1. add a UI extension point (if that page supports it), or
        2. copy the related Razor Page(s) from the module source code and override via VFS, or
        3. 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)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    You're not missing anything on the backend. The Object Extension + MapEfCoreProperty you wrote already pushes PaymentReference all the way through: the value is saved to the column, loaded back into ExtraProperties, and the AppService returns it as part of the DTO. If you call GET /api/payment-admin/requests you'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.js from the Payment module source (abp get-source Volo.Payment or via ABP Suite / ABP Studio "Get Source Code" — the file is at Volo.Payment.Admin.Web/Pages/Payment/Requests/index.js), then drop a copy into your own MVC web project at the same relative path Pages/Payment/Requests/index.js, and add one entry to columnDefs:

    {
        title: l("DisplayName:PaymentReference"),
        orderable: false,
        data: "extraProperties.PaymentReference"
    }
    

    Two things worth noting:

    • The dot notation extraProperties.PaymentReference is the same format ABP's own UI extension system uses (see ui-extensions.js), and extraProperties is camelCase while the dictionary key stays Pascal-case — that's the actual JSON shape, so the path is extraProperties.PaymentReference, not extraProperties.paymentReference.
    • Your physical index.js under Pages/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)
  • User Avatar
    0
    geertveenstra created

    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)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    Glad the column part already works. For filtering by an extension property like PaymentReference there's one extra catch worth knowing: the generated client proxy for paymentRequestAdmin.getList hardcodes the query-string field list to the props of PaymentRequestGetListInput (filter / creationDateMax / creationDateMin / paymentType / status / sorting / skipCount / maxResultCount). So if you just add input.paymentReference = ... in getFilter(), 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):

    1. 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.data ends up appended to the URL on a GET, so the server sees ?...&PaymentReference=xxx. No need to touch the client proxy.

    1. 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 in index.js will pick it up and reload the table.

    1. 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 with EF.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 (and Index.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)
  • User Avatar
    0
    geertveenstra created

    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)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    Great to hear the filter is working — happy it landed cleanly.

    About the menu/permission behaviour: looking at the code, Payment Plans and Payment Requests are defined as independent top-level permissions (not parent-child), and each menu item requires only its own permission, so unchecking Payment Plans alone shouldn't hide Payment 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)
  • User Avatar
    0
    geertveenstra created

    Here both are activated in the role (and both visible):

    Here only Payment Requests is activated (but none are visible):

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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)
  • User Avatar
    0
    geertveenstra created

    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)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Great 👍

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on September 28, 2026, 11:44
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.