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

# Refunds Admin

> Administrative refund management, processing, and statistics

## Overview

Refunds Admin API provides admin control over the refund lifecycle. Includes viewing pending refund queues, force-processing refunds (bypassing store approval), marking externally processed refunds, viewing refund analytics, and bulk deletion. All functions require admin authorization.

**Location:** `convex/admins/refundsAdmin.ts`

***

## Get Pending Refunds

Lists return requests awaiting refund processing (status `picked_up`). Returns enriched data including customer name, store name, order total, and days waiting.

<ParamField path="paginationOpts" type="PaginationOptions" required>
  Pagination options (numItems, cursor)
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  const pending = await convex.query(api.admins.refundsAdmin.getPendingRefunds, {
    paginationOpts: { numItems: 20, cursor: null }
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  {
    "page": [
      {
        "_id": "rr123456789",
        "_creationTime": 1700000000000,
        "orderId": "o111",
        "storeId": "s222",
        "customerId": "c333",
        "status": "picked_up",
        "reason": "Item was damaged",
        "refundAmount": 85.50,
        "requestDate": 1700000000000,
        "approvedDate": 1700100000000,
        "pickedUpDate": 1700200000000,
        "customerName": "Ahmed Ali",
        "storeName": "Pizza Palace",
        "orderTotal": 125.50,
        "daysWaiting": 3
      }
    ],
    "continueCursor": "abc123",
    "isDone": true,
    "totalPending": 8
  }
  ```
</ResponseExample>

<Note>
  The `daysWaiting` is calculated from the pickup completion date (or creation time if pickup date is missing) to now. The `reason` is extracted from the first item in the return request. `totalPending` reflects the total count of all pending refunds, not just the current page.
</Note>

***

## Force Refund

Admin override to force-process a refund, bypassing the normal store approval workflow. Supports two modes: Stripe automated refund or manual marking.

<ParamField path="returnRequestId" type="Id<'returnRequests'>" required>
  Return request ID to process
</ParamField>

<ParamField path="adminNotes" type="string" required>
  Admin notes explaining the forced refund
</ParamField>

<ParamField path="processViaStripe" type="boolean" required>
  When `true`, schedules a Stripe refund via `processStripeRefundInternal`. When `false`, marks the refund as completed manually with a generated reference ID.
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  // Via Stripe
  const stripeResult = await convex.mutation(api.admins.refundsAdmin.forceRefund, {
    returnRequestId: "rr123456789",
    adminNotes: "Customer escalated to support, refund approved",
    processViaStripe: true
  });

  // Manual
  const manualResult = await convex.mutation(api.admins.refundsAdmin.forceRefund, {
    returnRequestId: "rr123456789",
    adminNotes: "Refund issued via bank transfer",
    processViaStripe: false
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Stripe Response theme={null}
  {
    "success": true,
    "message": "Stripe refund processing initiated"
  }
  ```

  ```json Manual Response theme={null}
  {
    "success": true,
    "message": "Marked as refunded manually"
  }
  ```
</ResponseExample>

<Note>
  When `processViaStripe` is `true`, the refund is scheduled asynchronously via `ctx.scheduler.runAfter` and the return request stays in `picked_up` status until Stripe confirms. When `false`, the status is immediately set to `refunded` with a reference ID formatted as `manual_{timestamp}_{adminUserId}`.
</Note>

***

## Mark Refund Processed

Marks a return request as refunded for refunds processed externally (bank transfer, cash, store credit, etc.). Creates an admin notification for audit tracking.

<ParamField path="returnRequestId" type="Id<'returnRequests'>" required>
  Return request ID to mark as processed
</ParamField>

<ParamField path="refundMethod" type="string" required>
  Method used for the refund, e.g. `bank_transfer`, `cash`, `store_credit`
</ParamField>

<ParamField path="externalRefundId" type="string">
  External reference number (e.g., bank transaction ID). If omitted, a generated ID is used.
</ParamField>

<ParamField path="adminNotes" type="string" required>
  Admin notes about the refund processing
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  await convex.mutation(api.admins.refundsAdmin.markRefundProcessed, {
    returnRequestId: "rr123456789",
    refundMethod: "bank_transfer",
    externalRefundId: "TXN-2026-03-001",
    adminNotes: "Refund sent via bank transfer to customer account"
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  null
  ```
</ResponseExample>

<Note>
  Throws an error if the return request is already in `refunded` status. A notification is created via `adminNotifications.createNotification` for audit tracking. If no `externalRefundId` is provided, a reference ID is generated as `external_{refundMethod}_{timestamp}`.
</Note>

***

## Get Refund Statistics

Returns refund analytics for the admin dashboard, including totals, amounts, and a breakdown by reason category.

<ParamField path="dateRange" type="object">
  Optional date range filter with `startDate` and `endDate` (timestamps in milliseconds)
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  // All-time stats
  const allTime = await convex.query(api.admins.refundsAdmin.getRefundStatistics, {});

  // Filtered by date range
  const filtered = await convex.query(api.admins.refundsAdmin.getRefundStatistics, {
    dateRange: {
      startDate: 1700000000000,
      endDate: 1702600000000
    }
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  {
    "totalRefunds": 85,
    "pendingRefunds": 8,
    "completedRefunds": 62,
    "totalRefundAmount": 7440.50,
    "averageRefundAmount": 120.01,
    "refundsByReason": {
      "damaged": 25,
      "wrong_item": 18,
      "not_as_described": 12,
      "changed_mind": 15,
      "other": 15
    }
  }
  ```
</ResponseExample>

<Note>
  Pending refunds are those with `picked_up` status. Completed refunds are those with `refunded` status. The `averageRefundAmount` is calculated only from completed refunds. Reason categorization is based on keyword matching in the return item reasons: "damaged", "wrong", "not as described", and "changed mind". Reasons not matching any category fall into `other`.
</Note>

***

## Bulk Delete Refunds

Permanently deletes multiple return request records. Processes in batches of 50 using internal mutations.

<ParamField path="refundIds" type="Id<'returnRequests'>[]" required>
  Array of return request IDs to delete
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  const result = await convex.action(api.admins.refundsAdmin.bulkDeleteRefunds, {
    refundIds: ["rr111", "rr222", "rr333"]
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  {
    "refundsDeleted": 3
  }
  ```
</ResponseExample>

<Note>
  This is a Convex action that delegates to internal mutations in batches of 50. Returns zero count if an empty array is passed. Silently skips already-deleted records.
</Note>
