Stack guide
Laravel and Stripe Connect: refunds beyond Cashier’s comfort zone
Laravel Cashier manages subscriptions elegantly; destination-charge refunds sit mostly outside its remit, leaving teams hand-rolling the path where Connect flags live. Hand-rolled is fine — Laravel’s queue and cache primitives make the guardrails straightforward. Descriptive note: Cashier is named here as context, not criticism; its scope simply ends where marketplace refund plumbing begins.
The manual refund action
A dedicated action class carrying both flags, executed through queued jobs for anything user-initiated:
StripeRefund::create([
'charge' => $chargeId,
'reverse_transfer' => true,
'refund_application_fee' => true,
], ['idempotency_key' => "refund-{$orderId}-{$attempt}"]);Cache-based dedupe before hitting Stripe
Laravel cache atomic locks deduplicate concurrent submissions cheaply: acquire lock per order-intent, check completion marker, proceed or return early. Complements Stripe-side idempotency — cache handles same-second collisions, keys handle cross-hour retries.
Horizon visibility for clawback jobs
Failed reversal jobs must surface loudly, not silently exhaust retries. Horizon dashboards plus failure notifications make money-moving failures first-class operational events — the alternative is discovering them in month-end reconciliation, weeks late.
Common questions
Does Cashier pass reverse_transfer through?
SDK-level pass-through exists; the guardrails around it remain yours regardless of package choice.
Does this require a FeeGuard integration?
No — the page stands alone as stack guidance. FeeGuard observes your event stream externally rather than embedding in it.