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

> ## Agent Instructions
> Describe only the public 123hub merchant API documented on this site. The primary endpoint is POST /public/api/multihub/v1.
> Preserve API method names, field names, header names, and error codes exactly as documented. Do not invent endpoints or parameters.
> Treat every amount as an integer in minor units unless a page explicitly states otherwise.
> Never expose, request, or fabricate a merchant secret key. The SDK pages contain reference implementations, not official SDK packages.

# Refund requests

> Request idempotent full or partial refunds and follow their merchant-visible status

# Refund requests

The refund API starts and tracks monetary refunds owned by the payment service. It uses payment-row locking, cumulative partial-refund accounting, and a durable merchant `c_id` to prevent duplicate money movement.

<Info>
  **Prerequisites:** use `refunds.create` to request a refund, `refunds.read` to
  query it, and `refunds.comment` to add a merchant note. The referenced payment
  must belong to the same merchant and satisfy the API key's actual currency and
  service policy.
</Info>

## Methods

| Method                  | Scope             | Purpose                                          |
| ----------------------- | ----------------- | ------------------------------------------------ |
| `refund.request`        | `refunds.create`  | Request a full or partial monetary refund        |
| `refund.get`            | `refunds.read`    | Read one request by exactly one `c_id` or `h_id` |
| `refund.list`           | `refunds.read`    | List requests with cursor pagination             |
| `refund.summary`        | `refunds.read`    | Count requests by status                         |
| `refund.comment.create` | `refunds.comment` | Add a merchant-visible comment                   |
| `refund.comments.list`  | `refunds.read`    | List merchant-visible comments                   |

Refund statuses are `pending`, `approved`, `rejected`, and `processed`.

## Money contract

`amount` is a JSON string containing a positive integer in the payment currency's **minor units**. Do not send a JSON number, decimal point, sign, currency, or thousands separators.

* `"5000"` means 5,000 minor units.
* Omitting `amount` requests the complete refundable remainder calculated at execution time.
* Currency is resolved from the payment and cannot be overridden.
* The requested amount cannot exceed the current payment remainder after all processed partial refunds.
* The largest accepted value is `9223372036854775807`, the PostgreSQL signed `bigint` limit.

<Warning>
  Always keep refund money as integer strings. Values such as
  `"9007199254740993"` are valid but cannot be represented exactly by a
  JavaScript `number`; preserve the string through signing, storage, retries,
  webhook handling, and reconciliation.
</Warning>

## Request a refund

Use a stable, merchant-generated `params.identifiers.c_id`. It is the durable creation idempotency key.

<CodeGroup>
  ```bash cURL theme={null}
  BODY='{"method":"refund.request","params":{"identifiers":{"c_id":"refund-order-8401-1"},"payment":{"c_id":"order-8401"},"amount":"5000","reason":"Duplicate customer payment","notes":"Refund the second capture only"}}'
  SECRET='your_secret_key'
  HASH=$(printf '%s%s' "$BODY" "$SECRET" | openssl dgst -sha512 -r | awk '{print $1}')
  curl -X POST 'https://api.bafanglaicai88.com/public/api/multihub/v1' \
    -H 'Content-Type: application/json' -H 'X-Data-Application-Id: 1' \
    -H "X-Data-Hash: $HASH" -H "X-Data-Timestamp: $(date +%s)" \
    -H "X-Data-Nonce: $(uuidgen)" --data-binary "$BODY"
  ```

  ```python Python theme={null}
  import hashlib, json, time, uuid, requests

  payload = {"method": "refund.request", "params": {
      "identifiers": {"c_id": "refund-order-8401-1"},
      "payment": {"c_id": "order-8401"}, "amount": "5000",
      "reason": "Duplicate customer payment", "notes": "Refund the second capture only",
  }}
  body = json.dumps(payload, separators=(",", ":")); secret = "your_secret_key"
  response = requests.post("https://api.bafanglaicai88.com/public/api/multihub/v1", data=body, headers={
      "Content-Type": "application/json", "X-Data-Application-Id": "1",
      "X-Data-Hash": hashlib.sha512((body + secret).encode()).hexdigest(),
      "X-Data-Timestamp": str(int(time.time())), "X-Data-Nonce": str(uuid.uuid4()),
  }, timeout=30)
  print(response.json())
  ```

  ```javascript Node.js theme={null}
  import crypto from "node:crypto";

  const payload = {
    method: "refund.request",
    params: {
      identifiers: { c_id: "refund-order-8401-1" },
      payment: { c_id: "order-8401" },
      amount: "5000",
      reason: "Duplicate customer payment",
      notes: "Refund the second capture only",
    },
  };
  const body = JSON.stringify(payload);
  const secret = "your_secret_key";
  const response = await fetch("https://api.bafanglaicai88.com/public/api/multihub/v1", {
    method: "POST",
    body,
    headers: {
      "Content-Type": "application/json",
      "X-Data-Application-Id": "1",
      "X-Data-Hash": crypto
        .createHash("sha512")
        .update(body + secret)
        .digest("hex"),
      "X-Data-Timestamp": String(Math.floor(Date.now() / 1000)),
      "X-Data-Nonce": crypto.randomUUID(),
    },
  });
  console.log(await response.json());
  ```
</CodeGroup>

Example result:

```json theme={null}
{
  "success": true,
  "result": {
    "identifiers": {
      "c_id": "refund-order-8401-1",
      "h_id": "40776f5f-970c-439d-a936-92917802d0c0"
    },
    "payment": { "h_id": "3c3708bc-78d6-4bcb-b626-43dc9c05d0d6" },
    "status": "pending",
    "amount": "5000",
    "processed_amount": null,
    "currency": "INR",
    "reason": "Duplicate customer payment",
    "notes": "Refund the second capture only",
    "resolved_at": null,
    "created_at": "2026-07-15T10:00:00.000Z",
    "updated_at": "2026-07-15T10:00:00.000Z"
  },
  "request_id": "7a0bc676-1706-4f4d-be82-991a56340c8e",
  "processing_time": 29
}
```

## Safe retries and partial refunds

If `refund.request` times out, retry the exact body with the same `c_id`, a fresh timestamp, and a fresh nonce. A committed request returns the same public refund result. Reusing that `c_id` with another amount, reason, notes, or payment returns code `1013`.

Only one active `pending` or `approved` request is allowed for a payment. After a partial request becomes `processed`, you may create another request with a new `c_id`; the available amount is calculated from the current cumulative remainder under a database lock.

Do not create a new `c_id` while the status of a timed-out request is unknown. First query:

```json theme={null}
{
  "method": "refund.get",
  "params": { "identifiers": { "c_id": "refund-order-8401-1" } }
}
```

## List and summarize

`refund.list` accepts `statuses`, one payment reference, `created_from`, `created_to`, `limit`, and an opaque `cursor`:

```json theme={null}
{
  "method": "refund.list",
  "params": {
    "statuses": ["pending", "approved"],
    "payment": { "c_id": "order-8401" },
    "limit": 25
  }
}
```

The response contains `items` and `next_cursor`, never a full `total`. Use `refund.summary` with optional ISO 8601 `created_from`/`created_to` to obtain `by_status` counts.

## Refund comments

Refund comments are a separate merchant-visible conversation store. Adding a comment does not create or link a support ticket and does not approve or process the refund.

```json theme={null}
{
  "method": "refund.comment.create",
  "operation_id": "refund-order-8401-1-comment-1",
  "params": {
    "refund": { "c_id": "refund-order-8401-1" },
    "body": "The customer confirmed the destination account."
  }
}
```

Use `refund.comments.list` with the refund identifier, optional `limit`, and optional `cursor`. Returned comments contain a public comment `h_id`, `author` (`merchant` or `support`), body, and timestamps.

## Error handling

|    Code | HTTP | Meaning                                                   | Recovery                                                      |
| ------: | :--: | --------------------------------------------------------- | ------------------------------------------------------------- |
| `11001` |  404 | Refund or merchant-owned payment is not visible           | Verify the identifier and credential                          |
| `11002` |  409 | Payment status is not refundable                          | Refresh payment status; do not retry unchanged                |
| `11003` |  400 | Amount is zero, malformed, or above `9223372036854775807` | Send a positive digit-string within the signed `bigint` range |
| `11004` |  409 | Amount exceeds the locked current remainder               | Query current refund history and submit a valid new operation |
| `11005` |  409 | A `pending` or `approved` request already exists          | Query and finish the active request                           |
|  `1013` |  409 | Existing `c_id` has another canonical payload             | Recover the original request; never overwrite its meaning     |
|  `2002` |  503 | Refund dependency is unavailable                          | Retry with the same `c_id` and exact payload after backoff    |

## Best practices

* Generate refund `c_id` values on your server before the first attempt and persist them with the order.
* Keep amounts as decimal digit strings at database, queue, and JSON boundaries, including values above JavaScript's safe-integer limit.
* Reconcile on both refund `h_id` and payment `h_id`; never use internal numeric IDs.
* Treat `processed_amount` as an observed result, not permission to calculate a new refund without querying current state.
