You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Custom shopping cart check API plugin not working for Angular Shop 9.3.1 #509
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)
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
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;
Add three products to the shopping cart
Goto the shopping cart
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:
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.
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
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 :)
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 :)
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.
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.
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;
Activity
asattler commentedon Jul 13, 2026
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:
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):
I did a short test and it seems to work.
Feel free to review and use :)
BR
Andreas
asattler commentedon Jul 13, 2026
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 :)
asattler commentedon Jul 14, 2026
So, I re-checked my code and the behavior I can monitor.
CheckAsyncis called for every item,ProcessAsyncis called once per cart.This lead me to the following assumption:
I did a short test, and it seems to work as expected.
gitpm commentedon Jul 14, 2026
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.
petermuehlhoefer commentedon Aug 3, 2026
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.
CartCheckProvider
CartCheck
Then it goes straight forward:
Example of an ICccCartItemCheckHandler provided by the ShoppingCartItemCheckFactory
github-actions commentedon Sep 2, 2026
This issue has not seen any action for a while! Closing for now, but it can be reopened at a later date.