When is it appropriate to return a workflow error?

For example, say an activity returns back a failure that isn’t retryable (ex. user does not have enough funds to make a purchase). We could either 1) run compensating actions, set the state of the purchase to FAILED, and succeed the workflow because its done or 2) roll compensating actions + send and error to the workflow.
Aare there pros/cons to how that should be handled? Are failed workflows easier to rerun?

Failing the workflow execution has some benefits for your use case, for example:

a) Ability to filter workflows based on their execution status. In both web ui as well as via tctl/client apis it would be easier to filter/search for workflows with failed status to find “FAILED” purchases rather than maybe having to query all completed ones or record the purchase decision in custom search attributes.

b) Temporal only allows one workflow execution with a particular workflow id to be executing at a time. SDKs do provide different workflow id reuse policy settings such as:

WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE (allow an execution with workflowid x if previous one completed with any status)
WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE (do not allow re-use of this workflow id regardless of the previous exec status with that workflow id)

and the one helpful for this use case:
WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE_FAILED_ONLY (allow re-use of this workflow id only if previous exec terminated, was cancelled, timed out, or failed).

This id reuse policy could help add a level of protection for you where you might want to re-run purchase orders for only the ones that previously failed, omitting successfully completed ones.