Refund Approval
Alpha notice: This walkthrough mirrors the hosted demo. Workflow keys, node names, and demo statuses may change between demo releases.
What you will build
A refund dispute moves from investigation into a formal resolution approval. High refund amounts require an extra reviewer.
Investigation → Submit for resolution review
→ Support Lead Review → Amount Gate
→ [refund_amount >= 500] Risk Reviewer Review → Approved
→ [default] → Approved
Terminal workflow rejection maps the dispute to a lost outcome through hooks. Approval maps to won.
Business object
| Item | Value |
|---|---|
| Model | App\Models\RefundDispute |
| Workflow key | refund_dispute_approval |
| Constants class | App\DBFlow\RefundDispute\RefundDisputeWorkflow |
RefundDispute uses HasWorkflow plus Workflowable, WorkflowContextInterface, and WorkflowRouteResolvable. Transition conditions read refund_amount and risk_score from getWorkflowVariables():
public function getWorkflowVariables(): array
{
return [
'refund_amount' => (float) $this->refund_amount,
'risk_score' => (int) $this->risk_score,
// ...
];
}
Demo source paths:
| Area | Path |
|---|---|
| Model | app/Models/RefundDispute.php |
| Workflow constants | app/DBFlow/RefundDispute/RefundDisputeWorkflow.php |
| Hooks | app/DBFlow/RefundDispute/RefundDisputeWorkflowHooks.php |
| Definition seeder | database/seeders/Demo/RefundDisputeWorkflowSeeder.php |
| Integration test | tests/Feature/RefundDisputeDbflowIntegrationTest.php |
Workflow
Key node keys:
support_lead_reviewamount_gate(condition; routing viatransitions[].condition)risk_reviewer_reviewend_approved
Amount condition: refund_amount >= 500 (RefundDisputeWorkflow::CONDITION_HIGH_AMOUNT).
The demo builds a JSON definition in RefundDisputeWorkflowSeeder using WorkflowBuilderNodeFactory, validates with WorkflowDefinitionValidator, then publishes via CreateWorkflowDraft and PublishWorkflowDraft. You can also express the same graph through a WorkflowDefinitionProvider and SyncWorkflowDefinitions — see Code-defined Workflows.
RefundDisputeWorkflowHooks::onApproved() sets host status to won. onRejected() sets lost. Investigation states (open, investigating, needs_evidence) remain host-owned until submit.
Why this matters
- Single clear workflow key and amount branch
- Extensive demo tests (
RefundDisputeAmountBranchingTest,RefundDisputeDbflowIntegrationTest) - Shows separation between host investigation lifecycle and DBFlow resolution approval
- Covers hooks, presenters, and Filament actions without ERP complexity
Minimal code
Register hooks:
DBFlow::registerWorkflowHooks(
app(WorkflowHooksRegistry::class),
RefundDisputeWorkflow::KEY,
RefundDisputeWorkflowHooks::class,
);
Start from a business action:
DBFlow::start(
RefundDisputeWorkflow::KEY,
$record,
Auth::user(),
);
Approve or reject the pending task:
use DbflowLabs\Core\DBFlow;
use DbflowLabs\Core\Enums\WorkflowTaskStatus;
use DbflowLabs\Core\Enums\RejectStrategy;
$task = $dispute->runningWorkflowInstance(RefundDisputeWorkflow::KEY)
?->tasks()
->where('status', WorkflowTaskStatus::Pending)
->first();
if ($task) {
DBFlow::approve($task, $actor, $comment);
// DBFlow::reject($task, $actor, $comment, RejectStrategy::End);
}
Filament integration
| Area | Path |
|---|---|
| Resource actions | app/Filament/Resources/RefundDisputeResource/Concerns/HasLifecycleActions.php |
| Workflow presenter | app/Filament/Resources/RefundDisputeResource/Support/RefundDisputeWorkflowPresenter.php |
| Instance page override | app/DBFlow/Filament/Pages/DemoViewWorkflowInstance.php |
RefundDisputeResource uses HasLifecycleActions for:
- DBFlow submit action (
submitThroughDbflow) - Host-only investigation buttons when DBFlow is off
- Notifications on
WorkflowAlreadyRunningException
Assignees work from My Tasks (/admin/dbflow/my-workflow-tasks). Table actions delegate to MyWorkflowTaskActionRunner, which calls Core's ApproveTask and RejectTask actions — the same runtime entrypoints as DBFlow::approve() and DBFlow::reject().
RefundDisputeWorkflowPresenter adds workflow status, pending task counts, and links to My Tasks / instance detail on the resource view.
Open Workflow Instances → select the running instance, or follow the link from the resource presenter. ViewWorkflowInstance (demo subclass DemoViewWorkflowInstance) renders the timeline via WorkflowInstanceTimelinePresenter. See Workflow Timeline.
What to try next
- Filament Resource Actions → — general action patterns
- Purchase Request Approval → — second demo scenario
- Approve and Reject → — runtime API reference
Alpha caveats:
- Assignee user IDs in the seeder are environment-specific demo users
roleassignee type is schema-only — usecallback+registerAssigneeResolver()for dynamic approverspermissionassignee type is a resolver key alias, not Spatie Permission — see Assignee resolution- Pro visual editing is separate; this example uses published JSON from seeders or sync
- Re-run sync or demo seeders after resetting the database to restore published definitions