Znode 9 quote edits: invalidate shipping and totals correctly
Model shipping and totals as derived state, then invalidate them in the correct order after quote lines or delivery inputs change.
In this guide
The removed line that still affects freight
A buyer removes a bulky item from a quote. The line disappears immediately, but shipping still reflects the original shipment. Refreshing the page sometimes fixes it. That inconsistency suggests more than an incorrect number: the application has multiple representations of the quote and no clear rule for invalidating derived state.
Start by listing what changed and what depends on it. Lines influence package composition, weights, restrictions, discounts, shipping methods, tax inputs, and totals. An edit can make a previously selected shipping method invalid even when its old price still looks plausible.
Create a dependency map
Separate authoritative inputs from calculated results. The saved line set, authorized account, destination, and chosen service constraints are inputs. Shipment estimates and totals are derived. A boolean such as 'shipping calculated' is meaningful only for the exact inputs used to produce that calculation.
Trace where the customized Znode 9 flow holds these values: persistence, cart models, quote models, session, and caches. Do not assume removing one session key invalidates every representation. Equally, avoid clearing an entire session when only one quote's calculation is stale. Scope invalidation to the affected quote and its dependent state.
Keep calculation state tied to its inputs
QuoteCalculationState.cs clears the calculated amounts when quote inputs change and accepts a result only for the matching revision. The lock makes operations atomic within that one instance. Invalidate after reloading the current quote lines, then carry the returned revision through shipping and total calculation.
A web farm needs the same revision comparison at its shared persistence boundary. Scope that state by quote identity rather than allocating unrelated instances for concurrent requests. In Znode 9, apply the guard around your custom quote controller or agent's existing calculation path and retain the complete custom cart model.
Quotes/QuoteCalculationState.cs csharp
using System;
namespace Gcg.DeveloperExamples.Quotes
{
// Owned state for one quote; share the instance only through its owner.
public sealed class QuoteCalculationState
{
private readonly object gate = new object();
private long revision;
private decimal? shipping;
private decimal? total;
public long InvalidateAfterInputChange()
{
lock (gate)
{
revision = checked(revision + 1);
shipping = null; total = null;
return revision;
}
}
public bool TryApply(long calculatedRevision, decimal shippingAmount,
decimal totalAmount)
{
if (shippingAmount < 0 || totalAmount < 0)
throw new ArgumentOutOfRangeException();
lock (gate)
{
if (calculatedRevision != revision) return false;
shipping = shippingAmount; total = totalAmount;
return true;
}
}
public Tuple<long, decimal?, decimal?> Read()
{
lock (gate) return Tuple.Create(revision, shipping, total);
}
}
}
// Call InvalidateAfterInputChange after reloading authoritative quote lines.
// Carry its revision with the calculation and pass it to TryApply.
// Persisted quotes need a database/API conditional write across processes.
Do not manufacture a successful estimate
If the shipping provider fails after an edit, the old estimate is no longer a valid fallback unless a documented policy explicitly allows it for unchanged inputs. Return an unresolved calculation state and preserve the buyer's quote. Showing zero freight can accidentally turn an outage into a financial commitment.
Likewise, do not reset all custom fees simply because the carrier amount is stale. Identify which fees depend on lines, destination, account, or the selected service. Recalculate each through its owning rule. Keep each discount and fee formula under its own documented commercial rule.
Test the quote beyond the immediate screen
Use a synthetic quote with two products whose shipment characteristics differ. Remove one line, change quantity, change destination, and switch service. After each action inspect the response, persisted quote, and a fresh session. The displayed method, calculation status, and total should agree with the current inputs.
Introduce a provider timeout, a stale session snapshot, and two overlapping edits. Confirm that an older calculation cannot overwrite a newer quote revision. Check conversion from quote to order as a separate journey: a correct quote screen does not prove that the final order consumes the same validated calculation.
- Verify line additions and removals separately.
- Exercise a method that becomes unavailable after an edit.
- Preserve custom model data during recalculation.
Make invalidation part of the rule
A useful implementation names the reason for invalidation and the inputs it affects. That makes future changes safer. When a new packaging rule or account fee is introduced, its dependencies can be added deliberately instead of relying on an unrelated controller to clear a cache.
The central question is whether every displayed calculation belongs to the quote the user is currently editing. Once that relationship is explicit, session behavior, provider errors, and concurrent edits can be handled consistently. The fix becomes a maintainable workflow rather than a sequence of resets that happens to work after a refresh.
References and further reading
Independent guidance from GCG. Znode is a trademark of its owner. Examples use fictional data and are not official platform documentation. Suggest a correction.