Refund Approval

Alpha notice: This walkthrough mirrors the hosted demo. Workflow keys, node names, and demo statuses may change between demo releases.

This is the recommended first example for learning DBFlow end to end. It combines a real JSON workflow definition, Core runtime calls, host Filament resource action patterns, hooks, and Standard UI pages.

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.

Workflow key and constants

Item Value
Workflow key refund_dispute_approval
Model App\Models\RefundDispute
Constants class App\DBFlow\RefundDispute\RefundDisputeWorkflow

Key node keys from the demo:

  • support_lead_review
  • amount_gate (condition; routing via transitions[].condition)
  • risk_reviewer_review
  • end_approved

Amount condition: refund_amount >= 500 (RefundDisputeWorkflow::CONDITION_HIGH_AMOUNT).

Where the demo code lives

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
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
Integration test tests/Feature/RefundDisputeDbflowIntegrationTest.php

Model integration

RefundDispute uses HasWorkflow plus Workflowable, WorkflowContextInterface, and WorkflowRouteResolvable:

public function getWorkflowVariables(): array
{
    return [
        'refund_amount' => (float) $this->refund_amount,
        'risk_score' => (int) $this->risk_score,
        // ...
    ];
}

Variables feed transition conditions such as refund_amount >= 500.

Workflow definition source

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.

Register hooks

DBFlow::registerWorkflowHooks(
    app(WorkflowHooksRegistry::class),
    RefundDisputeWorkflow::KEY,
    RefundDisputeWorkflowHooks::class,
);

RefundDisputeWorkflowHooks::onApproved() sets host status to won. onRejected() sets lost. Investigation states (open, investigating, needs_evidence) remain host-owned until submit.

Start the workflow

From the Filament resource, Submit for resolution review calls:

DBFlow::start(
    RefundDisputeWorkflow::KEY,
    $record,
    Auth::user(),
);

Visibility requires investigating/needs-evidence status, DBFlow enabled, no running workflow, and record not closed.

Approve and reject tasks

Assignees work from My Tasks (/admin/dbflow/my-workflow-tasks). The table actions call MyWorkflowTaskActionRunner, which wraps:

DBFlow::approve($task, $actor, $comment);
DBFlow::reject($task, $actor, $comment, RejectStrategy::End);

Programmatically, resolve the pending task from the running instance:

$task = $dispute->runningWorkflowInstance(RefundDisputeWorkflow::KEY)
    ?->tasks()
    ->where('status', WorkflowTaskStatus::Pending)
    ->first();

Filament resource integration

RefundDisputeResource uses HasLifecycleActions for:

  • DBFlow submit action (submitThroughDbflow)
  • Host-only investigation buttons when DBFlow is off
  • Notifications on WorkflowAlreadyRunningException

RefundDisputeWorkflowPresenter adds workflow status, pending task counts, and links to My Tasks / instance detail on the resource view.

Timeline and instance detail

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.

Why start here

  • 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

Alpha caveats

  • Assignee user IDs in the seeder are environment-specific demo users
  • role assignee type is schema-only — use callback + registerAssigneeResolver() for dynamic approvers
  • permission assignee 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

What's next

Something wrong? Open an issue on GitHub