> ## Documentation Index
> Fetch the complete documentation index at: https://docs.safefetch.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Stripe Refund

> Gate AI-initiated refunds behind a human approval step so no money moves without sign-off.

## The problem

An AI agent that calls Stripe directly can issue a refund the moment it decides to — no review, no record, no recourse. One hallucination or misread order ID and the money is gone.

```ts theme={null}
// ❌ Direct Stripe call — executes immediately, no audit trail
await stripe.refunds.create({ charge: "ch_3Qx9R2eZpVMlhbBV0Abc123", amount: 4999 });
```

## The SafeFetch solution

Wrap the Stripe call in `safeFetch()` with `approve: true`. The refund is held as `awaiting_approval` until a human clicks approve. Nothing hits Stripe until that happens.

## Full example

```ts theme={null}
import { safeFetch } from "safefetch";

const action = await safeFetch({
  url: "https://api.stripe.com/v1/refunds",
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
    "Content-Type": "application/x-www-form-urlencoded",
  },
  body: {
    charge: "ch_3Qx9R2eZpVMlhbBV0Abc123",
    amount: 4999, // $49.99 in cents
    reason: "requested_by_customer",
  },
  approve: true,
  // Optional: email a reviewer a magic-link approve/reject button
  notify: { email: "billing-team@yourcompany.com" },
  // Optional: notify your backend when the refund completes
  callback: "https://yourapp.com/webhooks/safefetch",
});

console.log(action.id);     // act_...
console.log(action.status); // "awaiting_approval"
```

The action is created immediately and returns with `status: "awaiting_approval"`. Stripe is not contacted yet.

## What happens next

<Steps>
  <Step title="Reviewer receives email">
    If you passed `notify.email`, SafeFetch sends an email with one-click **Approve** and **Reject** buttons. No login required — the links are signed magic links.
  </Step>

  <Step title="Reviewer approves">
    Clicking **Approve** moves the action to `pending` and dispatches the request to Stripe. The receipt page shows the full action details, including the Stripe response.
  </Step>

  <Step title="Your callback fires">
    If you passed a `callback` URL, SafeFetch sends a `POST` to it when the action reaches a terminal status (`completed` or `failed`). Check `action.status` and `action.response_code` to confirm the refund went through.
  </Step>
</Steps>

## Checking status programmatically

Poll or check status at any point using the action ID:

```ts theme={null}
const action = await safeFetch.get("act_Xk9mP2nQ4rT1vW8sY");

if (action.status === "awaiting_approval") {
  console.log("Still waiting for reviewer sign-off.");
} else if (action.status === "completed") {
  console.log("Refund issued. Stripe responded:", action.response_code);
  // action.response_body contains the full Stripe refund object
} else if (action.status === "failed") {
  console.log("Refund failed:", action.last_error);
}
```

## Approving via API

If you have your own approval UI instead of the magic-link email, approve programmatically:

```ts theme={null}
await safeFetch.approve("act_Xk9mP2nQ4rT1vW8sY");
```

Or cancel (reject) it:

```ts theme={null}
await safeFetch.cancel("act_Xk9mP2nQ4rT1vW8sY");
```

## Handling the callback

SafeFetch `POST`s to your callback URL when the action finishes. Verify the signature and act on the result:

```ts theme={null}
import type { Action } from "safefetch";

// In your webhook handler (e.g., Express, Hono):
app.post("/webhooks/safefetch", async (req, res) => {
  const action: Action = req.body;

  if (action.status === "completed" && action.response_code === 200) {
    // Stripe accepted the refund — update your records
    await db.orders.update({ id: action.body?.charge }, { refunded: true });
  } else if (action.status === "failed") {
    await alertTeam(`Refund failed for ${action.body?.charge}: ${action.last_error}`);
  }

  res.sendStatus(200);
});
```

## Receipt page

Every action has a receipt page in the dashboard showing the full audit trail: who approved it, when, what Stripe returned, and how long delivery took. Share the URL with your finance team as documentation.

```
https://api.safefetch.dev/dashboard/actions/act_Xk9mP2nQ4rT1vW8sY
```

<Note>
  `approve: true` cannot be combined with `sync: true`. Synchronous mode waits for immediate completion — that's incompatible with an asynchronous human decision.
</Note>
