Decide, then dointermediate
Keep the rules in a function that takes data and returns a decision, and the effects in a thin layer that carries it out — then testing the rules needs no mocks, no clock and no network.
To test whether a refund is owed you currently need a mailer and a ledger.
The decision is pure and testable alone; the effects happen only in the shell.
Published in full, before you start — every point is one of these and there is nothing else. Each one runs your code; it is not a search for keywords.
Keep the rules in a function that takes data and returns a decision, and the effects in a thin layer that carries it out — then testing the rules needs no mocks, no clock and no network.