Checkout Shipping Method Step Skips Validation on Custom Field
A B2B store adds a custom purchase_order_number field to the shipping step via a UI component override in Acme/Checkout/view/frontend/web/js/view/shipping-mixin.js. The field is required when the customer's company has requires_po = true. The mixin adds a ko.observable and a validate method, but checkout proceeds to payment without the field when the customer selects a shipping method that triggers shippingMethod re-render. The mixin currently hooks onShippingMethodChange but not the step's validate chain. The store uses the default checkout.steps layout. QA can reproduce only when switching shipping methods after filling the field.
1. Why does validation pass despite the missing required field?
Read the worked answer
The checkout step validates via the validate method on the step component, which iterates registered validator observables. The mixin adds a ko.observable and a validate method on the shipping view model, but it does not register the observable in the step's validator list, and it does not override the step's validate to include the custom field. When the shipping method changes, the shipping view model re-renders and the custom field's binding context is recreated, so any transient validation state is lost. Checkout's validate chain calls shippingStep.validate() which delegates to the shipping method's validate - the mixin's method is never invoked. Fix: in the mixin, override validate to call the original and then check the custom observable, and register the field with additionalValidators or push it into the step's validator array. Also bind the field to a stable observable that survives re-render (store it on the parent step, not the child).
2. How do you confirm the fix covers the method-switch case without breaking the default flow?
Read the worked answer
Add a ko.computed that reads requires_po and the field value, and call additionalValidators.registerValidator with a function returning false when required and empty. In the mixin, wrap the original validate with return originalValidate() && customValidate(). Test matrix: (1) company requires PO, field empty, switch shipping method → blocked; (2) field filled, switch method → proceeds; (3) company doesn't require PO → field ignored; (4) default guest checkout → unaffected. Use the checkout's stepNavigator to confirm the step doesn't advance on failure, and check the console for the validator's error message.