Skip to content
Commerce block

Refund Request Form

Refunds are per line, not per order: a reason on each line that drives what is asked for next, and a running total itemising the restocking fee and the delivery rule.

348 lineslucide-reactAdded 13 Sept 2026
  • refund
  • returns
  • form
  • after-sale
  • commerce

What's included

  • components/refund-request-form.tsx
  • Needs lucide-react

Works with

  • React
  • Next.js
  • Tailwind CSS
  • TypeScript

npx hoverlab add refund-request-form

Or over MCP, from your editor's agent — no account needed.

License

Free to read, copy and install, for personal and non-commercial projects. Shipping it in client work or a paid product needs Pro ($79 once). The source lands in your repo and stops being ours — no attribution, nothing to upgrade.

Was this useful?

Start a page with this section — add more, order them, and leave with the page source.

Preview

Request a refund

Order ORD-88410 · delivered 2 September 2026. Pick the lines you want back — you do not have to return the whole order.

Meridian task chair

Graphite mesh · £420.00 each · 2 ordered

Halo task lamp

Brushed steel · £145.00 each · 1 ordered

Cable spine

Black, 1.2 m · £38.00 each · 4 ordered

One clear shot of the fault and one of the packaging. Claims without them are held until we ask for them, which adds days.

Refunds reach the original payment method in 3–5 working days once approved.

Rendered live in your current theme — this is the same component whose source is below, not a screenshot of it.

Source

components/refund-request-form.tsx
'use client'

/**
 * <RefundRequestForm> — asking for money back, line by line.
 *
 * The mistake almost every refund form makes is treating the order as the
 * unit. Customers refund *items*, and usually not all of them: two of the
 * four arrived damaged, the third was the wrong size, the fourth is fine. A
 * form with one reason box and one Submit forces that into a paragraph
 * someone in support then has to parse by hand.
 *
 * SO THE UNIT IS THE LINE ITEM
 *
 * Each line has its own checkbox, its own quantity, and its own reason,
 * because the reason drives the outcome: "arrived damaged" is refunded with
 * no return, "changed my mind" needs the item back, and the two cannot share
 * a code path. Selecting a reason therefore changes what the form asks for
 * next — a photo for damage, nothing for a change of mind.
 *
 * THE TOTAL IS COMPUTED IN FRONT OF THE CUSTOMER
 *
 * Refundable amount, delivery, and the restocking fee where one applies, all
 * itemised and updating as lines are ticked. A refund form that submits into
 * silence and mails a figure four days later generates a second contact every
 * time the figure surprises anyone.
 *
 * DELIVERY IS REFUNDED ONLY WHEN THE WHOLE ORDER GOES BACK. That is the rule
 * in most jurisdictions and every retailer, and it is the single most common
 * source of "you refunded me the wrong amount". It is stated in the summary as
 * a line, not in a footnote.
 *
 * ACCESSIBILITY: each line is a `<fieldset>` whose `<legend>` is the product
 * name, so a screen reader hears which item each reason select belongs to
 * rather than four identical "Reason" comboboxes. The running total is a
 * polite live region. The photo requirement is announced when it appears,
 * because a required field that materialises silently is a submit failure
 * waiting to happen.
 */

import * as React from 'react'
import { Camera, Info, ShieldQuestion } from 'lucide-react'

export interface RefundLine {
  id: string
  name: string
  variant: string
  unitPrice: number
  quantity: number
  /** Restocking fee percentage applied to a change-of-mind return. */
  restockPct?: number
}

export interface RefundRequestFormProps {
  orderRef?: string
  lines?: RefundLine[]
  deliveryPaid?: number
  currency?: string
  className?: string
}

const REASONS = [
  { id: 'damaged', label: 'Arrived damaged', needsPhoto: true, needsReturn: false },
  { id: 'faulty', label: 'Faulty in use', needsPhoto: true, needsReturn: true },
  { id: 'wrong', label: 'Wrong item sent', needsPhoto: false, needsReturn: true },
  { id: 'mind', label: 'Changed my mind', needsPhoto: false, needsReturn: true },
  { id: 'late', label: 'Arrived too late to be useful', needsPhoto: false, needsReturn: true },
] as const

type ReasonId = (typeof REASONS)[number]['id']

const DEFAULT_LINES: RefundLine[] = [
  { id: 'l1', name: 'Meridian task chair', variant: 'Graphite mesh', unitPrice: 420, quantity: 2 },
  { id: 'l2', name: 'Halo task lamp', variant: 'Brushed steel', unitPrice: 145, quantity: 1, restockPct: 10 },
  { id: 'l3', name: 'Cable spine', variant: 'Black, 1.2 m', unitPrice: 38, quantity: 4, restockPct: 10 },
]

const INPUT_CLASS =
  'rounded-lg border border-border bg-background px-2.5 py-1.5 text-sm outline-none transition-colors focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-ring'

interface LineState {
  on: boolean
  quantity: number
  reason: ReasonId | ''
}

export function RefundRequestForm({
  orderRef = 'ORD-88410',
  lines = DEFAULT_LINES,
  deliveryPaid = 12,
  currency = '£',
  className = '',
}: RefundRequestFormProps) {
  const uid = React.useId()
  const [state, setState] = React.useState<Record<string, LineState>>(() =>
    Object.fromEntries(
      lines.map((line, i) => [
        line.id,
        { on: i === 0, quantity: i === 0 ? 1 : line.quantity, reason: i === 0 ? 'damaged' : '' },
      ]),
    ),
  )

  const chosen = lines.filter((line) => state[line.id]?.on)
  const goods = chosen.reduce(
    (sum, line) => sum + line.unitPrice * (state[line.id]?.quantity ?? 0),
    0,
  )
  const restocking = chosen.reduce((sum, line) => {
    const s = state[line.id]
    if (!s || s.reason !== 'mind' || !line.restockPct) return sum
    return sum + (line.unitPrice * s.quantity * line.restockPct) / 100
  }, 0)
  // Delivery comes back only when nothing is kept — the single most common
  // source of "you refunded me the wrong amount".
  const everythingReturned =
    chosen.length === lines.length &&
    lines.every((line) => (state[line.id]?.quantity ?? 0) === line.quantity)
  const delivery = everythingReturned ? deliveryPaid : 0
  const total = goods - restocking + delivery

  const needsPhoto = chosen.some(
    (line) => REASONS.find((r) => r.id === state[line.id]?.reason)?.needsPhoto,
  )
  const needsReturn = chosen.some(
    (line) => REASONS.find((r) => r.id === state[line.id]?.reason)?.needsReturn,
  )

  function money(value: number) {
    return `${currency}${value.toFixed(2)}`
  }

  return (
    <section className={`bg-background px-4 py-10 sm:px-6 ${className}`}>
      <form className="mx-auto max-w-3xl" onSubmit={(e) => e.preventDefault()}>
        <h2 className="text-xl font-bold tracking-tight">Request a refund</h2>
        <p className="mt-1 text-sm text-muted-foreground">
          Order {orderRef} · delivered 2 September 2026. Pick the lines you want
          back — you do not have to return the whole order.
        </p>

        <div className="mt-6 space-y-3">
          {lines.map((line) => {
            const s = state[line.id] ?? { on: false, quantity: line.quantity, reason: '' as const }
            const reason = REASONS.find((r) => r.id === s.reason)
            return (
              <fieldset
                key={line.id}
                className={`rounded-xl border p-4 transition-colors ${
                  s.on ? 'border-primary bg-primary/5' : 'border-border'
                }`}
              >
                <legend className="sr-only">{line.name}</legend>
                <div className="flex flex-wrap items-start gap-3">
                  <input
                    type="checkbox"
                    id={`${uid}-${line.id}-on`}
                    checked={s.on}
                    onChange={(e) =>
                      setState((v) => ({ ...v, [line.id]: { ...s, on: e.target.checked } }))
                    }
                    className="mt-1 h-4 w-4 shrink-0 rounded border-border accent-primary"
                  />
                  <div className="min-w-0 flex-1">
                    <label htmlFor={`${uid}-${line.id}-on`} className="text-sm font-medium">
                      {line.name}
                    </label>
                    <p className="text-xs text-muted-foreground">
                      {line.variant} · {money(line.unitPrice)} each · {line.quantity} ordered
                    </p>

                    {s.on ? (
                      <div className="mt-3 flex flex-wrap items-end gap-3">
                        <div>
                          <label
                            htmlFor={`${uid}-${line.id}-qty`}
                            className="block text-xs font-medium"
                          >
                            Quantity
                          </label>
                          <select
                            id={`${uid}-${line.id}-qty`}
                            value={s.quantity}
                            onChange={(e) =>
                              setState((v) => ({
                                ...v,
                                [line.id]: { ...s, quantity: Number(e.target.value) },
                              }))
                            }
                            className={`mt-1 ${INPUT_CLASS}`}
                          >
                            {Array.from({ length: line.quantity }, (_, i) => i + 1).map((n) => (
                              <option key={n} value={n}>
                                {n}
                              </option>
                            ))}
                          </select>
                        </div>

                        <div className="min-w-[12rem] flex-1">
                          <label
                            htmlFor={`${uid}-${line.id}-reason`}
                            className="block text-xs font-medium"
                          >
                            Reason for {line.name}
                          </label>
                          <select
                            id={`${uid}-${line.id}-reason`}
                            required
                            value={s.reason}
                            onChange={(e) =>
                              setState((v) => ({
                                ...v,
                                [line.id]: { ...s, reason: e.target.value as ReasonId },
                              }))
                            }
                            className={`mt-1 w-full ${INPUT_CLASS}`}
                          >
                            <option value="">Choose a reason…</option>
                            {REASONS.map((r) => (
                              <option key={r.id} value={r.id}>
                                {r.label}
                              </option>
                            ))}
                          </select>
                        </div>
                      </div>
                    ) : null}

                    {s.on && reason?.needsReturn && line.restockPct && s.reason === 'mind' ? (
                      <p className="mt-2 text-xs text-amber-700 dark:text-amber-400">
                        A {line.restockPct}% restocking fee applies to a change of
                        mind on this line.
                      </p>
                    ) : null}
                  </div>
                </div>
              </fieldset>
            )
          })}
        </div>

        {/* Announced when it appears — a required field that materialises
            silently is a submit failure waiting to happen. */}
        {needsPhoto ? (
          <div
            role="status"
            className="mt-4 rounded-xl border border-border bg-card p-4"
          >
            <label
              htmlFor={`${uid}-photos`}
              className="flex items-center gap-2 text-sm font-medium"
            >
              <Camera aria-hidden className="h-4 w-4 text-primary" />
              Photos of the damage — required
            </label>
            <p className="mt-1 text-xs text-muted-foreground">
              One clear shot of the fault and one of the packaging. Claims
              without them are held until we ask for them, which adds days.
            </p>
            <input
              id={`${uid}-photos`}
              type="file"
              multiple
              accept="image/*"
              required
              className="mt-2 block w-full text-xs file:me-3 file:rounded-lg file:border-0 file:bg-muted file:px-3 file:py-1.5 file:text-xs file:font-medium"
            />
          </div>
        ) : null}

        <div className="mt-6 grid gap-4 md:grid-cols-[minmax(0,1fr)_16rem]">
          <div className="min-w-0">
            <label htmlFor={`${uid}-notes`} className="block text-sm font-medium">
              Anything else we should know
              <span className="ms-2 text-xs font-normal text-muted-foreground">
                Optional
              </span>
            </label>
            <textarea
              id={`${uid}-notes`}
              rows={4}
              placeholder="The second chair had a cracked base plate out of the box."
              className={`mt-1.5 w-full ${INPUT_CLASS}`}
            />
          </div>

          <aside
            aria-live="polite"
            className="rounded-xl border border-border bg-card p-4 text-sm"
          >
            <h3 data-stress-ignore className="text-sm font-semibold">Estimated refund</h3>
            <dl className="mt-3 space-y-2">
              <div className="flex justify-between gap-3">
                <dt className="text-muted-foreground">Goods</dt>
                <dd>{money(goods)}</dd>
              </div>
              {restocking > 0 ? (
                <div className="flex justify-between gap-3">
                  <dt className="text-muted-foreground">Restocking fee</dt>
                  <dd className="text-destructive">−{money(restocking)}</dd>
                </div>
              ) : null}
              <div className="flex justify-between gap-3">
                <dt className="text-muted-foreground">Delivery</dt>
                <dd>
                  {delivery > 0 ? (
                    money(delivery)
                  ) : (
                    <span className="text-xs text-muted-foreground">
                      Not refunded — part of the order is kept
                    </span>
                  )}
                </dd>
              </div>
              <div className="flex justify-between gap-3 border-t border-border pt-2 font-semibold">
                <dt>Total</dt>
                <dd>{money(total)}</dd>
              </div>
            </dl>

            <p className="mt-3 flex gap-1.5 text-xs text-muted-foreground">
              <Info aria-hidden className="mt-0.5 h-3 w-3 shrink-0" />
              {needsReturn
                ? 'Some lines must come back before the refund is released. A prepaid label follows by email.'
                : 'Nothing needs returning. The refund is released once the photos are checked.'}
            </p>
          </aside>
        </div>

        <div className="mt-6 flex flex-wrap items-center justify-between gap-3">
          <p className="flex items-center gap-1.5 text-xs text-muted-foreground">
            <ShieldQuestion aria-hidden className="h-3.5 w-3.5" />
            Refunds reach the original payment method in 3–5 working days once
            approved.
          </p>
          <button
            type="submit"
            disabled={chosen.length === 0 || chosen.some((l) => !state[l.id]?.reason)}
            className="rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
          >
            Request {money(total)} back
          </button>
        </div>
      </form>
    </section>
  )
}

Before you paste

  • Styling is Tailwind utility classes on semantic tokens (bg-card, text-muted-foreground) — it inherits your theme instead of overriding it.
  • Install: npm i lucide-react
  • Every prop has a default, so it renders standalone before you wire it up.

Where it goes

Drop it at components/refund-request-form.tsx and import it where you need the section:

import { RefundRequestForm } from '@/components/refund-request-form'

Customize

3 of this block’s props are simple enough to drive from here. Change them and the block below re-renders — it is the same component whose source is above, not a mock of it. Everything else it accepts is in the table underneath.

Props

Read out of the component’s own type and signature, so this cannot drift from the source below. Every prop has a default — the component renders standalone before you pass it anything.

PropTypeDefault
orderRefstring'ORD-88410'
linesRefundLine[]DEFAULT_LINES
deliveryPaidnumber12
currencystring'£'
classNamestring''

Not using React?

The same block rendered once to markup, wrapped as a file your framework compiles. Tailwind classes are framework-agnostic, so the design transfers intact — the behaviour does not.

This block is interactive. The markup below is its initial state with the event handlers stripped — you will need to re-wire the behaviour in your framework.

refund-request-form.html
<!--
  Refund Request Form — markup from the Hoverlab catalog.

  This is the block rendered once to HTML and wrapped as a component
  file. It is not a port of the React source: the Tailwind classes carry
  the design, which is the part that took the work, and they are the same
  in every framework.

  This block is interactive in React and the handlers are NOT here.
  Buttons, toggles and menus render in their initial state and do
  nothing until you wire them up.
-->
<section class="bg-background px-4 py-10 sm:px-6 ">
  <form class="mx-auto max-w-3xl">
    <h2 class="text-xl font-bold tracking-tight">Request a refund</h2>
    <p class="mt-1 text-sm text-muted-foreground">Order ORD-88410 · delivered 2 September 2026. Pick the lines you want back — you do not have to return the whole order.</p>
    <div class="mt-6 space-y-3">
      <fieldset class="rounded-xl border p-4 transition-colors border-primary bg-primary/5">
        <legend class="sr-only">Meridian task chair</legend>
        <div class="flex flex-wrap items-start gap-3">
          <input type="checkbox" id="_R_0_-l1-on" class="mt-1 h-4 w-4 shrink-0 rounded border-border accent-primary" checked="" />
          <div class="min-w-0 flex-1">
            <label for="_R_0_-l1-on" class="text-sm font-medium">Meridian task chair</label>
            <p class="text-xs text-muted-foreground">Graphite mesh · £420.00 each · 2 ordered</p>
            <div class="mt-3 flex flex-wrap items-end gap-3">
              <div>
                <label for="_R_0_-l1-qty" class="block text-xs font-medium">Quantity</label>
                <select id="_R_0_-l1-qty" class="mt-1 rounded-lg border border-border bg-background px-2.5 py-1.5 text-sm outline-none transition-colors focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-ring">
                  <option value="1" selected="">1</option>
                  <option value="2">2</option>
                </select>
              </div>
              <div class="min-w-[12rem] flex-1">
                <label for="_R_0_-l1-reason" class="block text-xs font-medium">Reason for Meridian task chair</label>
                <select id="_R_0_-l1-reason" required="" class="mt-1 w-full rounded-lg border border-border bg-background px-2.5 py-1.5 text-sm outline-none transition-colors focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-ring">
                  <option value="">Choose a reason…</option>
                  <option value="damaged" selected="">Arrived damaged</option>
                  <option value="faulty">Faulty in use</option>
                  <option value="wrong">Wrong item sent</option>
                  <option value="mind">Changed my mind</option>
                  <option value="late">Arrived too late to be useful</option>
                </select>
              </div>
            </div>
          </div>
        </div>
      </fieldset>
      <fieldset class="rounded-xl border p-4 transition-colors border-border">
        <legend class="sr-only">Halo task lamp</legend>
        <div class="flex flex-wrap items-start gap-3">
          <input type="checkbox" id="_R_0_-l2-on" class="mt-1 h-4 w-4 shrink-0 rounded border-border accent-primary" />
          <div class="min-w-0 flex-1">
            <label for="_R_0_-l2-on" class="text-sm font-medium">Halo task lamp</label>
            <p class="text-xs text-muted-foreground">Brushed steel · £145.00 each · 1 ordered</p>
          </div>
        </div>
      </fieldset>
      <fieldset class="rounded-xl border p-4 transition-colors border-border">
        <legend class="sr-only">Cable spine</legend>
        <div class="flex flex-wrap items-start gap-3">
          <input type="checkbox" id="_R_0_-l3-on" class="mt-1 h-4 w-4 shrink-0 rounded border-border accent-primary" />
          <div class="min-w-0 flex-1">
            <label for="_R_0_-l3-on" class="text-sm font-medium">Cable spine</label>
            <p class="text-xs text-muted-foreground">Black, 1.2 m · £38.00 each · 4 ordered</p>
          </div>
        </div>
      </fieldset>
    </div>
    <div role="status" class="mt-4 rounded-xl border border-border bg-card p-4">
      <label for="_R_0_-photos" class="flex items-center gap-2 text-sm font-medium"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-camera h-4 w-4 text-primary" aria-hidden="true"><path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"></path><circle cx="12" cy="13" r="3"></circle></svg>Photos of the damage — required</label>
      <p class="mt-1 text-xs text-muted-foreground">One clear shot of the fault and one of the packaging. Claims without them are held until we ask for them, which adds days.</p>
      <input id="_R_0_-photos" type="file" multiple="" accept="image/*" required="" class="mt-2 block w-full text-xs file:me-3 file:rounded-lg file:border-0 file:bg-muted file:px-3 file:py-1.5 file:text-xs file:font-medium" />
    </div>
    <div class="mt-6 grid gap-4 md:grid-cols-[minmax(0,1fr)_16rem]">
      <div class="min-w-0">
        <label for="_R_0_-notes" class="block text-sm font-medium">Anything else we should know<span class="ms-2 text-xs font-normal text-muted-foreground">Optional</span></label>
        <textarea id="_R_0_-notes" rows="4" placeholder="The second chair had a cracked base plate out of the box." class="mt-1.5 w-full rounded-lg border border-border bg-background px-2.5 py-1.5 text-sm outline-none transition-colors focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-ring"></textarea>
      </div>
      <aside aria-live="polite" class="rounded-xl border border-border bg-card p-4 text-sm">
        <h3 data-stress-ignore="true" class="text-sm font-semibold">Estimated refund</h3>
        <dl class="mt-3 space-y-2">
          <div class="flex justify-between gap-3">
            <dt class="text-muted-foreground">Goods</dt>
            <dd>£420.00</dd>
          </div>
          <div class="flex justify-between gap-3">
            <dt class="text-muted-foreground">Delivery</dt>
            <dd>
              <span class="text-xs text-muted-foreground">Not refunded — part of the order is kept</span>
            </dd>
          </div>
          <div class="flex justify-between gap-3 border-t border-border pt-2 font-semibold">
            <dt>Total</dt>
            <dd>£420.00</dd>
          </div>
        </dl>
        <p class="mt-3 flex gap-1.5 text-xs text-muted-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-info mt-0.5 h-3 w-3 shrink-0" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><path d="M12 16v-4"></path><path d="M12 8h.01"></path></svg>Nothing needs returning. The refund is released once the photos are checked.</p>
      </aside>
    </div>
    <div class="mt-6 flex flex-wrap items-center justify-between gap-3">
      <p class="flex items-center gap-1.5 text-xs text-muted-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-shield-question-mark h-3.5 w-3.5" aria-hidden="true"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"></path><path d="M9.1 9a3 3 0 0 1 5.82 1c0 2-3 3-3 3"></path><path d="M12 17h.01"></path></svg>Refunds reach the original payment method in 3–5 working days once approved.</p>
      <button type="submit" class="rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background">Request £420.00 back</button>
    </div>
  </form>
</section>
  • This is rendered HTML, not a translation of the React source. The Tailwind classes carry the design and work in any framework.
  • It is one frame: the component in its initial state, with no props applied beyond the defaults.
  • This block is interactive in React — toggles, menus or form state. None of that survives here; the markup is the closed/default state and the handlers are gone. Re-wire them in your own framework.
  • Requires Tailwind, and the design tokens the classes reference (bg-card, text-muted-foreground, and so on). The template ZIPs ship a globals.css that defines them.

What each framework gets across the whole catalog — effects convert properly; this rung is markup.

For AI

The component, its props, the design tokens it expects and the command that installs it — as one prompt. Paste it into Claude, Cursor, v0 or ChatGPT and what they build around it will match the rest of the catalog instead of inventing its own system.

See the prompt

8 more blocks in After-Sale Service

All of them free to read, copy and install — no account, no locked tiles, no watermarked preview. The whole catalog is open, and so are the API and the CLI.

Browse After-Sale Service

Shipping one commercially

Copying the code is free. Putting it in client work or a paid product is what Pro is for — the licence, not the access.

  • A commercial licence for everything in the catalog
  • Unlimited bundle exports, in Vue, Svelte and Tailwind
  • One payment — no subscription, nothing to renew
Pro — $79 once

More After-Sale Service blocks

View category
Open the full page for this block

Warranty Coverage Panel

A verdict for this serial number today, with exclusions given equal weight to cover and an extension offered only while it can still be bought.

After-Sale Service259 lines1 dep
Open the full page for this block

Refund Status Tracker

Where the money is and who is holding it - four stages, each naming the responsible party, including the bank stage every where-is-my-refund contact comes from.

After-Sale Service241 lines1 dep
Open the full page for this block

Refunds Overview Table

The operator queue, sorted oldest-open-first because recency is the wrong default, with blocked as a first-class status that names its blocker.

After-Sale Service277 lines1 dep
Open the full page for this block

Return or Exchange Picker

Refund, exchange or credit decided per line, with live stock on the variants and the price difference shown before the goods are posted rather than after.

After-Sale Service331 lines1 dep
Open the full page for this block

Return Label and Instructions

The screen between approved and posted: three drop-off methods with the thing that actually blocks each one, the packing rules that get returns refused, and a date rather than a duration.

After-Sale Service285 lines1 dep
Open the full page for this block

Service Repair Request

A repair is not a return - a symptom picker that filters the service routes, warranty resolved inside the form, and an estimate with its uncertainty stated.

After-Sale Service310 lines1 dep
Open the full page for this block

Review History

The reviews you wrote and the ones you owe, with editing that is honest about being marked as edited and moderation that names the rule it applied.

After-Sale Service291 lines1 dep
Open the full page for this block

Gift Card Balance Check

A balance checker built for the two ways it goes wrong - an empty card and an expired one - answering with a transaction history rather than a bare zero.

After-Sale Service254 lines1 dep