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

# Refund a payment

> Refund all or part of a prior SUCCESS collection (merchant → customer).
Credits the original payer's SIM and debits the merchant float. No
commission is taken — the merchant keeps absorbing the original fee.
Omit `amount` for a full refund of the remaining refundable.
Emits a `payment.refunded` webhook.




## OpenAPI

````yaml /openapi/sandpay.yaml post /v1/payments/{id}/refund
openapi: 3.1.0
info:
  title: SandPay API
  version: 0.5.3
  description: |
    Official public API for the SandPay Mobile Money sandbox. Simulate MTN,
    Orange, Moov, and Airtel payment flows across African markets without
    touching real money rails. The API is deterministic, low-latency, and
    designed to mirror the production behaviour of each operator so client
    integrations can be exercised end-to-end before going live.
  contact:
    name: SandPay Support
    url: https://docs.sandpay.dev
    email: support@sandpay.dev
  license:
    name: MIT
    identifier: MIT
servers:
  - url: https://api.sandpay.dev
    description: Production
  - url: https://api.sandbox.sandpay.dev
    description: Sandbox
security:
  - bearerAuth: []
tags:
  - name: Payments
    description: Create, retrieve, list, and refund simulated Mobile Money payments.
  - name: Disbursements
    description: Free-form payouts (merchant → an msisdn), not linked to a collection.
  - name: Meta
    description: API version + capability discovery for keeping integrations current.
  - name: Health
    description: Service health probe.
paths:
  /v1/payments/{id}/refund:
    post:
      tags:
        - Payments
      summary: Refund a payment
      description: |
        Refund all or part of a prior SUCCESS collection (merchant → customer).
        Credits the original payer's SIM and debits the merchant float. No
        commission is taken — the merchant keeps absorbing the original fee.
        Omit `amount` for a full refund of the remaining refundable.
        Emits a `payment.refunded` webhook.
      operationId: refundPayment
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            pattern: ^TX_[A-Z0-9]+$
          description: Transaction id of the original collection, e.g. `TX_8K3M9F`.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefundInput'
      responses:
        '200':
          description: Refund created (status SUCCESS or INSUFFICIENT_FUNDS).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Refund'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Original payment not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: |
            Refund not allowed — original is not a SUCCESS collection, is fully
            refunded, or the requested amount exceeds the refundable remaining.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  schemas:
    RefundInput:
      type: object
      properties:
        amount:
          type: integer
          minimum: 1
          description: |
            Amount in minor units to refund. Omit for a full refund of the
            remaining refundable (original amount − prior refunds).
          example: 500
        reference:
          type: string
          minLength: 1
          maxLength: 80
          description: Optional idempotency reference. Auto-generated when omitted.
        description:
          type: string
          maxLength: 200
          description: Optional free-form description (max 200 chars).
    Refund:
      type: object
      required:
        - tx_id
        - type
        - status
        - amount
        - parent_tx_id
      properties:
        tx_id:
          type: string
          example: TX_RF01A2B3
          description: Transaction id of the refund.
        type:
          type: string
          enum:
            - refund
        status:
          $ref: '#/components/schemas/PaymentStatus'
        amount:
          type: integer
          minimum: 1
          description: Amount refunded (minor units).
          example: 500
        parent_tx_id:
          type: string
          description: The original collection this refund reverses.
          example: TX_8K3M9F
        refunded_total:
          type: number
          description: >-
            Total successfully refunded against the parent, including this
            refund.
          example: 500
        refundable_remaining:
          type: number
          description: Refundable amount still remaining after this refund.
          example: 0
        merchant_balance_after:
          type: number
          description: Merchant float after the refund.
        recipient_balance_after:
          type: number
          nullable: true
          description: Recipient SIM balance after the credit, or null.
        idempotent:
          type: boolean
          description: True when this refund already existed (idempotent replay).
    ErrorResponse:
      type: object
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: >-
            Machine-readable error code (e.g. `invalid_body`, `unauthorized`,
            `quota_exceeded`).
          example: quota_exceeded
        message:
          type: string
          description: Human-readable description of what went wrong.
          example: Monthly simulation quota exceeded.
        detail:
          type: object
          nullable: true
          description: Optional structured detail, e.g. field validation errors.
          additionalProperties: true
    PaymentStatus:
      type: string
      enum:
        - SUCCESS
        - PIN_INVALID
        - INSUFFICIENT_FUNDS
        - TIMEOUT
        - ACCOUNT_BLOCKED
        - USER_CANCELLED
        - UNKNOWN_MSISDN
        - LIMIT_EXCEEDED
        - SERVICE_UNAVAILABLE
        - DUPLICATE_REFERENCE
        - PENDING
      description: Final settlement status.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: API key in format `sp_sk_test_...` or `sp_sk_live_...`.

````