The endgame of this problem always turns into some sort of “log of events” with loosely coupled subscribers.
A single state machine suffers from a combinatorial explosion of states as it has to handle every corner case, combinations of every scenario, etc…
What if a single shopping basket contains both a digital good and a physically shipped one? What if some items are shipped separately and/or delayed? Etc…
Instead the business rules are encoded into smaller state machines that listen to events on the log and pay attention only to relevant events. This avoids much of the complexity and allows the OOP types to remain relatively clean and stable over time.
Now the “digital goods” shipping handler can simply listen to events where “delivery=authorized” and “type=digital”, allowing it to ignore how the payment was authorised (or just store credit!) and ignore anything with physical shipping constraints.
It then writes an event that marks that line item in the shopping cart as “delivered”, allowing partial cancellations later, etc…