Generated from: “Document how a refund moves between support, finance, and the warehouse, and who owns each step.”
View the Markdown
```meta
title: Refund handoffs
subtitle: How a refund case moves between Support, Finance, and the Warehouse, and which team owns each step.
tag: DRAFT
```
A refund touches three teams and three systems. Support owns the customer and the case, the Warehouse owns the physical return, and Finance owns the money. A case stalls when a team does not know it is their turn. Every handoff below names the artifact that passes across and the clock that starts when it does.
```callout
tone: note
title: Assumptions
body: "The request did not name the tools, the policy window, or the payment path. This document assumes Support works in Zendesk, the Warehouse in ShipHero, and Finance in NetSuite. It assumes a 30-day return window from delivery and refunds to the original payment method only. Exchanges and store credit are out of scope."
```
## Who owns each step
Each step sits in exactly one lane. The lane that holds the step owns the outcome and the clock; the next lane does nothing until the handoff artifact exists. Dashed links are notifications with no work attached.
```swimlane
id: refund-lanes
title: Refund case from request to payout
lanes: [Customer, Support, Warehouse, Finance]
phases:
- { label: Intake, from: 1, to: 3 }
- { label: Return, from: 4, to: 7 }
- { label: Payout, from: 8, to: 10 }
steps:
- { id: request, col: 1, lane: Customer, kind: start, label: Request refund }
- { id: policy, col: 2, lane: Support, kind: decision, label: Within policy? }
- { id: rma, col: 3, lane: Support, label: Issue RMA and return label, note: SLA 1 business day }
- { id: decline, col: 3, lane: Customer, kind: end, label: Request declined }
- { id: ship, col: 4, lane: Customer, kind: wait, label: Ship item back, note: 14 days to post }
- { id: receive, col: 5, lane: Warehouse, label: Receive and scan RMA }
- { id: inspect, col: 6, lane: Warehouse, kind: decision, label: Condition as declared? }
- { id: restock, col: 7, lane: Warehouse, label: Restock and mark verified, note: SLA 2 business days from receipt, accent: true }
- { id: amount, col: 8, lane: Finance, label: Set refund amount }
- { id: post, col: 9, lane: Finance, label: Post refund to payment provider, note: SLA 1 business day, accent: true }
- { id: notify, col: 10, lane: Support, label: Notify customer and close case }
- { id: paid, col: 10, lane: Customer, kind: end, label: Refund received }
links:
- request -> policy: case opened
- policy -> rma: "yes"
- policy -x-> decline: "no, with reason"
- rma -> ship: label emailed
- ship -> receive: parcel arrives
- receive -> inspect
- inspect -> restock: "yes"
- inspect -x-> amount: "no, restocking fee"
- restock -> amount: verified
- amount -> post
- post --> notify: provider confirmation
- notify --> paid
```
Support never touches the amount and Finance never touches the customer. That split keeps one team from both deciding a refund and paying it, which is the control the auditors asked for.
## The states a case passes through
The case state is the single field all three teams read. Only the owning team moves the case out of a state; the transitions below are the full set.
```state
id: refund-states
title: Refund case lifecycle
dir: LR
states:
- { id: s0, col: 1, row: 2, kind: start }
- { id: requested, col: 2, row: 2, kind: wait, name: REQUESTED }
- { id: rejected, col: 3, row: 3, kind: terminal, name: REJECTED }
- { id: rma, col: 3, row: 2, kind: active, name: RMA_ISSUED }
- { id: received, col: 4, row: 2, kind: active, name: RECEIVED }
- { id: expired, col: 4, row: 3, kind: terminal, name: EXPIRED }
- { id: verified, col: 5, row: 2, kind: active, name: VERIFIED }
- { id: refunded, col: 6, row: 2, kind: terminal, name: REFUNDED }
transitions:
- { from: s0, to: requested, event: customer submits form }
- { from: requested, to: rma, event: Support approves, guard: within 30 days of delivery }
- { from: requested, to: rejected, event: Support declines }
- { from: requested, to: verified, event: Support approves, guard: no physical return needed }
- { from: rma, to: received, event: Warehouse scans RMA }
- { from: rma, to: expired, event: 14 days without a scan }
- { from: received, to: verified, event: Warehouse completes inspection }
- { from: verified, to: refunded, event: Finance posts refund }
```
`EXPIRED` is the only state a timer sets. Support reopens an expired case by issuing a new RMA, which returns it to `RMA_ISSUED` with a fresh 14-day clock.
## What the Warehouse does with a return that does not match
Most returns arrive intact and match the RMA. The three exceptions below are the ones that used to bounce between teams by email; each now has one owner and one exit.
```flow
id: inspection-exceptions
title: Inspection outcomes at the receiving dock
dir: LR
nodes:
- { id: arrive, col: 1, row: 1, kind: start, label: Parcel arrives }
- { id: match, col: 2, row: 1, kind: decision, label: RMA scan matches an open case? }
- { id: quarantine, col: 2, row: 2, kind: process, label: Hold in quarantine bay }
- { id: support, col: 3, row: 2, kind: process, label: Support identifies the order within 3 days }
- { id: condition, col: 3, row: 1, kind: decision, label: Condition as declared? }
- { id: restock, col: 4, row: 1, kind: process, label: Restock, mark case VERIFIED }
- { id: fee, col: 4, row: 2, kind: process, label: Photograph, mark VERIFIED with a 20% restocking fee }
- { id: finance, col: 5, row: 1, kind: end, label: Finance sets the amount }
- { id: dispose, col: 4, row: 3, kind: end, label: Dispose after 30 days unclaimed }
edges:
- arrive -> match
- match -> condition: "yes"
- match -x-> quarantine: "no"
- quarantine -> support: photo and label posted to Zendesk
- support -> condition: order found
- support -x-> dispose: no order found
- condition -> restock: "yes"
- condition -x-> fee: "no"
- restock -> finance
- fee -> finance
```
The Warehouse never decides the refund amount. It records the condition and the fee flag, and Finance turns those into a number. A disputed fee is argued with Finance, not with the dock.
## Handoffs, clocks, and systems of record
A handoff is complete when the artifact in the third column exists in the system in the last column. The SLA counts business days from that moment.
```table
id: handoff-contract
title: One row per step, one owner per row
columns: [Step, Owner, Handoff artifact produced, SLA, System of record]
rows:
- [Open case and check policy, Support, "Case in REQUESTED with order ID and reason", 1 business day, Zendesk]
- [Issue RMA and return label, Support, "RMA number and prepaid label on the case", 1 business day, Zendesk]
- [Ship item back, Customer, Carrier scan on the label, 14 calendar days, Carrier tracking]
- [Receive and scan RMA, Warehouse, Case in RECEIVED, Same day as delivery, ShipHero]
- [Inspect and restock, Warehouse, "Case in VERIFIED, condition note, fee flag", 2 business days, ShipHero]
- [Set refund amount, Finance, "Credit memo with amount and fee line", 1 business day, NetSuite]
- [Post refund, Finance, Provider transaction ID on the credit memo, 1 business day, NetSuite]
- [Notify customer and close, Support, "Case in REFUNDED, closing macro sent", 1 business day, Zendesk]
note: "The end-to-end target is 10 business days from request to provider confirmation, excluding the customer's shipping time."
```
When a step misses its SLA, the case owner for that row is paged, not the next team. The next team has no artifact yet and cannot act; chasing them only adds a second conversation to the one that is already late.