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_review
  • amount_gate (condition; routing via transitions[].condition)
  • risk_reviewer_review
  • end_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

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
Something wrong? Open an issue on GitHub