Skip to content

Custom shopping cart check API plugin not working for Angular Shop 9.3.1 #509

Closed as not planned

Description

@gitpm

Is this a regression?

Yes

Severity

🟠 Major – significantly affects usability

Please provide the branch(es) you discovered this issue in

v93

Environment Details

edge

Please provide a minimal set of steps to reproduce the issue

  1. Create a custom shopping cart check in the API according to the official documentation.
    see: https://github.com/OneIdentity/IdentityManager.ApiSdk/blob/v93/doc/05-composition-api-plugins.md

  2. Fix the custom shopping cart item check by adding an empty constructor to the Sample CartItemCheck.
    (Annotation if you don't fix it you will get an error during runtime)

Image

add: "public SampleCheck() { } " to the code

public class SampleCheck : ICartItemCheck    { 
        private readonly ICartItemCheckContext _context;  
        public SampleCheck() { }  '<-- add this line !!! 
        public SampleCheck(ICartItemCheckContext context) {  
           ...

=> result; no servor occurs during runtime

  1. Modify the SampleCheck- ProcessAsync-Task, in that way that the task always sets the Status to: CheckStatus.Error
internal async Task ProcessAsync(CancellationToken ct = default)        {
             Status = CheckStatus.Error; '<-- set Status to Error, independent from context
   }

public CheckStatus Status { get; set; } = CheckStatus.Pending;
  1. Add three products to the shopping cart
  2. Goto the shopping cart
  3. Perform a check to the shopping cart

Description

The modified SampleCheck: ICartItemCheck was modified to return always CheckStatus: Error;
Hence all(!!!) products should have the CheckStatus: Error

But in UI-Result only one Product has a CheckStatus: Error, where else the other two products are marked with CheckStatus: ok:

Image

Describe what you expected to happen versus what actually happened

As the SampleCheck always returns CheckStatus.Error in this case all three products should be marked to have an "Fehler" in the ui

Relevant logs or console output

Add a screenshot(s) if that helps illustrate the problem

No response

Suggested Fix

No response

Anything else?

The fact that the SampleCheck - class is instantiated without an context is an error in itself, because a SampleCheck - instance without a context cannot decide if the status is error or success and therefore it makes no sense. But exactly this happens. So I assume that is already the error. I assume if you find the codeline where the ICartItemCheck is instantiated without a context you found the bug.

Image

Before submitting...

  • I have searched for existing issues that match this one on github and on the community support page
  • I have included all necessary details to reproduce this issue

Activity

  1. asattler commented on Jul 13, 2026

    @asattler

    Hi Peter,

    thanks for raising this. I didn't notice, even though my project is live with 9.3.1 for 6 months now...
    I was able to verify your issue and I also found a possible solution.
    The misunderstanding (or better: wrong or imcomplete example in the SDK) is that it's not clear that the ICartCheck.CheckAsync is called for each item in the cart. Which means that with example code:

    public Task<IReadOnlyList<ICartItemCheck>> CheckAsync(ICartItemCheckContext context, CancellationToken ct = default)
        {
            // Set up the individual ICartItemCheck object(s) and return them.
            _check = new SampleCheck(context);
            return Task.FromResult<IReadOnlyList<ICartItemCheck>>(new[]
            {
                _check
            });
        }
    

    you're always returning an array with one check to proceed, which seems to overwrite the previous collection.
    So instead of doing this, create a list and maintain it.

    So my proposed solution is (DO NOT USE THIS APPROACH, SEE MY LARTER POST):

    public class SampleCartCheck : ICartCheck
    {
        private List<SampleCheck> _checks = [];
    
        // This method is called when a shopping cart must be checked.
        public Task<IReadOnlyList<ICartItemCheck>> CheckAsync(ICartItemCheckContext context, CancellationToken ct = default)
        {
            // Set up the individual ICartItemCheck object(s) and return them.
            _checks.Add(new SampleCheck(context));
            return Task.FromResult<IReadOnlyList<ICartItemCheck>>(_checks);
            
        }
    
        public Task ProcessAsync(CancellationToken ct = default)
        {
            List<Task> tasks = [];
            foreach(var check in _checks)
            {
                tasks.Add(check.ProcessAsync(ct));
            }
            return Task.WhenAll([.. tasks]);
        }
    }
    

    I did a short test and it seems to work.
    Feel free to review and use :)

    BR

    Andreas

  2. asattler commented on Jul 13, 2026

    @asattler

    My proposed solution doesn't seem to be the 100%. The web results now show multiple results per item...
    Seems like I need to invest a little more time. If you find a solution, please share with us @gitpm :)

  3. asattler commented on Jul 14, 2026

    @asattler

    So, I re-checked my code and the behavior I can monitor.
    CheckAsync is called for every item, ProcessAsync is called once per cart.
    This lead me to the following assumption:

    public class SampleCartCheck : ICartCheck
    {
        private List<SampleCheck> _checks = [];
    
        // This method is called for each item in a shopping cart.
        public Task<IReadOnlyList<ICartItemCheck>> CheckAsync(ICartItemCheckContext context, CancellationToken ct = default)
        {
            // Set up the individual ICartItemCheck object(s) and return them.
            var singlecheck = new SampleCheck(context);
            _checks.Add(singlecheck);
            return Task.FromResult<IReadOnlyList<ICartItemCheck>>([singlecheck]);
            
        }
    
        // this method is called once per cart to run the checks;
        public async Task ProcessAsync(CancellationToken ct = default)
        {
            foreach(var check in _checks)
            {
                await check.ProcessAsync(ct).ConfigureAwait(false);
            }
        }
    }
    

    I did a short test, and it seems to work as expected.

  4. gitpm commented on Jul 14, 2026

    @gitpm
    Author

    Hi Andreas,

    Thanks a lot for sharing your solution.

    It looks straightforward, and you're right. In my opinion, it makes perfect sense to create a separate ICartItemCheck for each ShoppingCartItem.

    I just created a simple cross-check test, and it seems to work in my project as well.

    Nevertheless, I noticed that the empty constructor of the ICartItemCheck class is still being invoked for some unknown reason. But as long as the result is correct, I don't really care why it happens.

    Image
  5. petermuehlhoefer commented on Aug 3, 2026

    @petermuehlhoefer

    Finally I solved the problem with this implementation:

    Models and Interfaces

    First of all I extended the ICartItemCheck by a ProcessCheckAsync-Methode in order to set the ICartItemCheck -Attributes within the instance of the ICartItemCheck-Interface-class.

    public interface ICccCartItemCheckHandler : ICartItemCheck
        {
            Task<CccCartItemCheckResult> ProcessCheckAsync(CancellationToken ct = default);
        }
     public sealed record CccCartItemCheckResult(
            CheckStatus Status,
            string? ResultText = null,
            object? Detail = null);
    }
    

    CartCheckProvider

    public class CccCartCheckProvider : ICartCheckProvider, IKnownTypeProvider
        {
            public Task<IReadOnlyList<ICartCheck>> GetCartChecksAsync(ICartCheckContext cart,
                CancellationToken ct = default)
            {
                return Task.FromResult<IReadOnlyList<ICartCheck>>
                    (
                      [new  CccCartCheck()]
                    );
            }
    
            // Konkreter ICartItemCheck-Typ muss bekannt sein, da Ergebnisse als JSON gespeichert werden  
            public IReadOnlyDictionary<Type, IReadOnlyList<Type>> GetTypes()
            {
                return new Dictionary<Type, IReadOnlyList<Type>>
                {
                    { typeof(ICartItemCheck),
                      [typeof(DuplicateShoppingCartItemInBasketCartItemCheck),  // example 1 of an ICartItemCheck (ICccCartItemCheckHandler)
                        typeof(DuplicateOrderCartItemCheck)] // example 2 of an ICartItemCheck (ICccCartItemCheckHandler)
                    }
                };
            }
        }
    

    CartCheck

    Then it goes straight forward:

        public class CccCartCheck : ICartCheck
        {
            private List<ICccCartItemCheckHandler> _cartItemChecks = new List<ICccCartItemCheckHandler>();
    
            public Task<IReadOnlyList<ICartItemCheck>> CheckAsync(ICartItemCheckContext context,
                CancellationToken ct = default)
            {
                List<ICccCartItemCheckHandler> multipleChecksOfTheShoppingCartItem = ShoppingCartItemCheckFactory.CreateShoppingCartItemChecks(context, ct);
    
                // Set up the individual ICartItemCheck object(s) and return them.
                _cartItemChecks.AddRange(multipleChecksOfTheShoppingCartItem);
                return Task.FromResult<IReadOnlyList<ICartItemCheck>>(multipleChecksOfTheShoppingCartItem);
    
            }
    
            public async Task ProcessAsync(CancellationToken ct = default)
            {
    
    #if DEBUG
                foreach (ICccCartItemCheckHandler cartItemCheck in _cartItemChecks)
                {
                    var cartItemCheckResult = await cartItemCheck.ProcessCheckAsync(ct);
                }
    #else
               await Task.WhenAll(_cartItemChecks.Select(check => check.ProcessCheckAsync(ct)));
    #endif
            }
        }
        }
    

    Example of an ICccCartItemCheckHandler provided by the ShoppingCartItemCheckFactory

        internal abstract class CccCartItemCheckBase: ICccCartItemCheckHandler
        {
            protected readonly ICartItemCheckContext _context;
    
            // Kopfdaten der Prüfung, die in der Ergebnisliste angezeigt werden.
            public abstract string Id { get; }
            public abstract string Title { get; }
    
    
            // Prüfergebnis der Prüfung, das in der Ergebnisliste angezeigt wird.
            public CheckStatus Status { get; set; } = CheckStatus.Pending;
            public string ResultText { get; set; }
            // Populate this property to append any serializable data to the result.
            public object Detail { get; set; }
    
    
            public CccCartItemCheckBase() { }
    
            public CccCartItemCheckBase(ICartItemCheckContext context)
            {
                _context = context;
            }
    
            public abstract Task<CccCartItemCheckResult> ProcessCheckAsync(CancellationToken ct = default);
    
            protected void SetResult(CccCartItemCheckResult result)
            {
                Status = result.Status;
                ResultText = result.ResultText;
                Detail = result.Detail;
            }
        }
    
        internal class DuplicateOrderCartItemCheck : CccCartItemCheckBase
        {
            // Kopfdaten der Prüfung, die in der Ergebnisliste angezeigt werden.
            public override string Id => "DuplicateOrderCartItemCheck";
            public override string Title => "Prüfe, ob eine identische Bestellung im Warensystem vorliegt.";
    
    
            public DuplicateOrderCartItemCheck() : base()
            {
            }
    
            public DuplicateOrderCartItemCheck(ICartItemCheckContext context) : base(context)
            { }
    
    
            public override async Task<CccCartItemCheckResult> ProcessCheckAsync(CancellationToken ct = default)
            {
                // Prüfe ob eine Bestellung mit den gleichen Parametern bereits existiert
                CccCartItemCheckResult duplicateOrderExists = await OrderCheckHandler.CheckIfOrderWithSameParametersAlreadyExists(_context, ct);
                var result = duplicateOrderExists ?? new CccCartItemCheckResult(CheckStatus.Success, "Keine identische Bestellung gefunden.");
                SetResult(result);
                return result;
         
    
  6. github-actions commented on Sep 2, 2026

    @github-actions

    This issue has not seen any action for a while! Closing for now, but it can be reopened at a later date.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions