openapi: 3.0.0
info:
  title: Amabills - Whoosh Core API
  version: 1.1.0
  description: >
    The Whoosh Innovations Pty. Core API provides electricity vending and
    traffic fine services for South African municipalities.
servers:
  - url: https://api-test.amabills.co.za/api
    description: Testing
  - url: https://api.amabills.co.za/api
    description: Production
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-KEY
  schemas:
    SuccessEnvelopeBoolean:
      type: object
      description: Standard response envelope used by Electricity endpoints.
      properties:
        success:
          example: true
          type: boolean
          description: >-
            `true` - action successful, result is in `data`. `false` - error
            occurred, details are in `error` and `errorNo`.
        data:
          description: Result payload. `null` when `success` is `false`.
          nullable: true
        error:
          type: string
          nullable: true
          example: 999 - Internal server error
          description: Human-readable error message. `null` when `success` is `true`.
        errorNo:
          type: integer
          nullable: true
          example: 999
          description: Error code. `null` when `success` is `true`.
    Municipality:
      type: string
      enum:
        - tshwane_prepaid
        - zeerust_prepaid
        - mogale_prepaid
        - ngwathe_prepaid
        - mogale_postpaid
        - tshwane_postpaid
      description: >-
        Municipality identifier. Municipalities ending in `_prepaid` support
        token vending;  Municipalities ending in `_postpaid` supports postpaid
        bill payments.
    BillMunicipality:
      type: string
      enum:
        - mogale_postpaid
        - tshwane_postpaid
      description: Postpaid municipal account issuer. Accepted by the Bills endpoints only.
    ElectricityCustomer:
      type: object
      description: Details of the meter owner returned by ConfirmCustomer.
      properties:
        id:
          type: string
          description: Meter number (same as `meter_number`).
          example: '142567423303'
        meter_number:
          type: string
          description: Meter number.
          example: '142567423303'
        meter_source:
          $ref: '#/components/schemas/Municipality'
        name:
          type: string
          description: Surname of the meter owner.
          example: TSHITAMBA
        phone_number:
          type: string
          nullable: true
          description: >-
            Phone number of the meter owner. `null` if not provided by the
            municipality.
        email:
          type: string
          format: email
          nullable: true
          description: >-
            Email of the meter owner. `null` if not provided by the
            municipality.
        provider_specific:
          type: object
          description: Municipality-specific metadata. Shape varies per provider.
          properties:
            token:
              type: string
              description: Printable slip that can be presented to the customer.
            name:
              type: string
            firstName:
              type: string
            pocName:
              type: string
            minLimit:
              type: integer
            maxLimit:
              type: integer
            tarrifIndex:
              type: integer
            sgc:
              type: integer
            krn:
              type: integer
            keyExpiryNumber:
              type: integer
            tokenTechnology:
              type: integer
            units:
              type: number
    ConfirmCustomerResult:
      type: object
      properties:
        exists:
          type: boolean
          description: >-
            `true` if the meter number was validated and can be used to vend
            electricity or pay bills.
        municipality:
          $ref: '#/components/schemas/Municipality'
        customer:
          $ref: '#/components/schemas/ElectricityCustomer'
    PurchaseElectricityResult:
      type: object
      properties:
        name:
          type: string
          description: Full name of the person linked to the meter.
          example: MR. A J KWINDA
        amount:
          type: number
          description: Amount transacted.
          example: 50
        meter:
          type: string
          description: Meter number used for the transaction.
          example: '81114926223'
        date:
          type: string
          description: 'Date-time string. Format: "DD mmm YYYY hh:mm:ss AM/PM".'
          example: 22 Mar 2026 3:24:11 PM
        timestamp:
          type: integer
          format: int64
          description: Transaction date-time as a UNIX timestamp in milliseconds.
          example: 1774193051166
        token:
          type: string
          nullable: true
          description: >-
            **Prepaid only.** The vended token including the full printable
            receipt. `null` for postpaid municipalities.
        tokenCount:
          type: integer
          nullable: true
          description: Total number of new tokens generated. `null` for postpaid.
          example: 1
        units:
          type: number
          nullable: true
          description: Total electricity units vended. `null` for postpaid.
          example: 2.9
        receipt:
          type: string
          nullable: true
          description: >-
            **Postpaid only.** URL with the purchase receipt proving bill
            offset. `null` for prepaid municipalities.
        municipality:
          $ref: '#/components/schemas/Municipality'
        mode:
          type: integer
          description: '`0` = prepaid token vend, `1` = postpaid bill payment.'
          example: 0
        details:
          type: object
          description: Low-level transaction details from the municipality gateway.
          properties:
            balance:
              type: number
            code:
              type: integer
            date:
              type: string
            desc:
              type: string
            message:
              type: string
            refno:
              type: string
            time:
              type: string
            trDetails:
              type: object
              properties:
                amt:
                  type: number
                date:
                  type: string
                'no':
                  type: string
                status:
                  type: string
                time:
                  type: string
                vat:
                  type: number
            voucher:
              type: object
              properties:
                amounts:
                  type: array
                  items:
                    type: object
                    properties:
                      desc:
                        type: string
                      amt:
                        type: number
                      outstanding:
                        type: number
                elecamt:
                  type: number
                message:
                  type: string
                meterno:
                  type: string
                name:
                  type: string
                receiptdesc:
                  type: string
                receiptno:
                  type: string
                receiver:
                  type: string
                token:
                  type: array
                  items:
                    type: object
                    properties:
                      token:
                        type: string
                      info:
                        type: string
                      type:
                        type: string
                units:
                  type: number
                vatno:
                  type: integer
        meterDetails:
          type: object
          properties:
            msno:
              type: string
            sgc:
              type: integer
            krn:
              type: integer
            ti:
              type: integer
    BalanceService:
      type: object
      properties:
        balance:
          type: number
          description: Current balance for this service.
        type:
          type: string
          description: Service type name (e.g. `prepaid-electricity`).
        service_specification:
          type: string
          description: Municipality identifier for this service slot.
        nTransactions:
          type: integer
          description: Total number of transactions recorded against this service.
        receipts:
          type: array
          nullable: true
          description: >-
            Transactions within the requested period. Only present when
            `printReceipts` is `true`.
          items:
            $ref: '#/components/schemas/VasReceipt'
    VasReceipt:
      type: object
      properties:
        transactionId:
          type: string
        type:
          type: string
          enum:
            - topup
            - deposited
        timestamp:
          type: integer
          format: int64
        amount:
          type: number
        meterNumber:
          type: string
          nullable: true
        receipt:
          type: string
          nullable: true
    ReceiptsConfiguration:
      type: object
      required:
        - from
      properties:
        from:
          type: integer
          format: int64
          description: UNIX timestamp (ms) – start of the period.
        to:
          type: integer
          format: int64
          nullable: true
          description: UNIX timestamp (ms) – end of the period. Defaults to now if omitted.
    BalanceResponse:
      type: object
      properties:
        balance:
          type: number
          description: Total balance across all consumed prepaid VAS services.
        payments:
          type: object
          properties:
            lastPaymentValue:
              type: number
            nPayments:
              type: integer
            paymentsTotal:
              type: number
        services:
          type: array
          description: Balance breakdown per prepaid service.
          items:
            $ref: '#/components/schemas/BalanceService'
        timestamp:
          type: integer
          format: int64
    ErrorMessage:
      type: object
      description: >-
        Unified error type returned in the `error` field of all Traffic Fine
        responses when `success` is `true`.
    ErrorMessagePayment:
      type: object
      description: >-
        Unified error type returned in the `error` field of all Traffic Fine
        responses when `success` is `1`.
      properties:
        code:
          type: integer
          description: >-
            Error code.

            | Code | Meaning | |------|---------| | 1 | Authentication failed |
            | 2 | Service unavailable – try again; contact support if it
            persists | | 3 | Service error – contact support | | 5 | Invalid ID
            | | 6 | ID must be numeric | | 7 | ID number check digit must be
            valid | | 8 | The identified infringer does not exist | | 9 | No
            infringement found for the identification type or number | | 10 |
            The check digit of the infringement notice number must be valid | |
            11 | The infringement notice number must be payable at a registering
            authority | | 12 | The infringement notice number does not exist | |
            13 | The infringement notice number does not exist | | 14 | The
            infringement notice number may not be in status 10 (court case) | |
            15 | Elected to be tried in court with a successful court
            application status | | 16 | Transaction not allowed – nomination of
            driver is pending | | 17 | Partially paid fine must be settled at a
            registering authority or DLTS | | 18 | The infringement may not be
            in status 13 (partially paid) | | 23 | Validation error |
          example: 9
        message:
          type: string
          description: Well-explained error message.
          example: No infringement found for the provided ID number.
        validationMessage:
          type: array
          nullable: true
          description: >-
            Descriptive list of validation error messages. Present when `code`
            is `23`.
          items:
            type: string
    IssAuthority:
      type: object
      properties:
        code:
          type: string
          description: Authority code.
          example: '4065'
        description:
          type: string
          description: Authority name.
          example: GP Dept of Community Safety.
    InfringementLocation:
      type: object
      properties:
        cityTown:
          type: string
          description: City or town where the infringement took place.
          example: JOHANNESBURG
        issAuthority:
          $ref: '#/components/schemas/IssAuthority'
        streetName:
          type: string
          description: Street on which the infringement took place.
          example: N1
        suburb:
          type: string
          description: Suburb in which the infringement was recorded.
          example: JOHANNESBURG
    InfringementCharge:
      type: object
      properties:
        classification:
          type: string
          description: Fine classification.
          example: Operated a vehicle without displaying...
        demeritPoints:
          type: integer
          description: Demerit points tied to this infringement.
          example: 0
        description:
          type: string
          description: Detailed description of the charge.
          example: >-
            Operated a vehicle without displaying the licence disc of said
            vehicle
    InfringementDetail:
      type: object
      properties:
        vehicleLicenseNumber:
          type: string
          description: Licence number of the vehicle that infringed.
          example: JR47HJGP
        noticeNumber:
          type: string
          description: Notice number tied to the infringement.
          example: '0100110373510309'
        charge:
          $ref: '#/components/schemas/InfringementCharge'
        date:
          type: string
          description: Date on which the infringement took place (YYYY-MM-DD).
          example: '2023-01-22'
        time:
          type: string
          description: Time at which the infringement took place (hh:mm:ss).
          example: '10:38:00'
        location:
          $ref: '#/components/schemas/InfringementLocation'
        netFineAmnt:
          type: number
          description: Net fine amount for this infringement.
          example: 560
        vendorFeeAmnt:
          type: number
          description: Vendor processing fee for this infringement.
          example: 14
        grossAmnt:
          type: number
          description: Total amount to charge the customer (net + vendor fee).
          example: 574
        isPaid:
          type: boolean
          description: Whether this infringement has already been paid.
          example: false
    QueryByIdNumberResult:
      type: object
      properties:
        idNumber:
          type: string
          description: The ID number used in the query.
          example: '9710226013089'
        hasFinesToPay:
          type: boolean
          description: Whether there are outstanding fines for this ID.
          example: true
        netFinesAmnt:
          type: number
          description: Total net amount for all outstanding fines.
          example: 560
        vendorFeeAmnt:
          type: number
          description: Total vendor processing fee for all outstanding fines.
          example: 14
        grossAmnt:
          type: number
          description: Total amount to charge the customer across all outstanding fines.
          example: 574
        infringements:
          type: array
          description: List of infringements tied to the ID number.
          items:
            $ref: '#/components/schemas/InfringementDetail'
    QueryByNoticeNumberResult:
      type: object
      properties:
        noticeNumber:
          type: string
          description: The notice number used in the query.
          example: '0100110373510309'
        hasFinesToPay:
          type: boolean
          description: Whether there are outstanding fines for this notice number.
          example: true
        netFinesAmnt:
          type: number
          description: Total net amount for all outstanding fines.
          example: 560
        vendorFeeAmnt:
          type: number
          description: Total vendor processing fee.
          example: 14
        grossAmnt:
          type: number
          description: Total amount to charge the customer.
          example: 574
        infringements:
          type: array
          description: Infringements tied to the notice number.
          items:
            $ref: '#/components/schemas/InfringementDetail'
    MarkFineAsPaidCommand:
      type: object
      required:
        - noticeNumber
        - amount
        - currency
      properties:
        noticeNumber:
          type: string
          description: Notice number of the fine being marked as paid.
          example: '0100110373510309'
        amount:
          type: number
          description: Gross amount paid for this fine (net fine + vendor fee).
          example: 574
        currency:
          type: string
          description: Currency code.
          example: ZAR
    PaidFine:
      type: object
      properties:
        noticeNumber:
          type: string
          description: Notice number of the paid fine.
          example: '240990007600759'
        invoiceNumber:
          type: string
          description: AARTO invoice number.
          example: 47280000005V
        receiptNumber:
          type: string
          description: AARTO receipt number.
          example: 4728000000N1
        netFinesAmntPaid:
          type: number
          description: Net fine amount attributed to this fine.
          example: 1600
        vendorFeeAmntPaid:
          type: number
          description: Vendor fee attributed to this fine.
          example: 14
        grossAmntPaid:
          type: number
          description: Total amount paid for this fine.
          example: 1614
    MarkTrafficFinesAsPaidResult:
      type: object
      properties:
        orderId:
          type: string
          description: Whoosh order ID referencing the complete transaction.
          example: b89a1717-5266-43c7-93cc-7e51c0fe01fb
        grossAmntPaid:
          type: number
          description: Total gross amount paid across all fines in this transaction.
          example: 1614
        netFinesAmntPaid:
          type: number
          description: Total net fine amount paid.
          example: 1600
        vendorFeeAmntPaid:
          type: number
          description: Total vendor fee paid.
          example: 14
        merchantFeePaid:
          type: number
          description: Merchant fee charged for this transaction.
          example: 7.5
        finesPaid:
          type: array
          description: Breakdown of each fine paid including AARTO receipt details.
          items:
            $ref: '#/components/schemas/PaidFine'
    ValidatePaymentResult:
      type: object
      properties:
        amntToPay:
          type: number
          description: Correct gross amount that should be charged for the selected fines.
          example: 574
        difference:
          type: number
          description: >-
            Difference between the proposed amount and the correct amount. `0`
            means the proposed amount is correct. If the proposed amount is less
            than the amtToPay then this value will be negative
          example: 0
        isCorrectAmount:
          type: boolean
          description: >-
            `true` if the proposed amount matches the expected gross amount for
            the selected fines.
          example: true
    TransactionDto:
      type: object
      description: A single MPGS transaction returned in the end-of-day ledger.
      properties:
        paymentReference:
          type: string
          description: Payment reference for the transaction.
          example: ORD-20240401-001
        transactionStatus:
          type: string
          description: MPGS transaction status.
          example: SUCCESS
        transactionAmount:
          type: number
          description: Transaction amount.
          example: 150
        transactionCurrency:
          type: string
          description: ISO 4217 currency code.
          example: ZAR
        transactionTime:
          type: string
          format: date-time
          description: ISO timestamp of when the transaction occurred.
          example: '2024-04-01T14:32:00+02:00'
    VerificationErrorDto:
      type: object
      description: A reconciliation or verification error.
      properties:
        message:
          type: string
          description: Human-readable error details.
          example: Transaction amount mismatch detected.
    EndOfDayLedgerResponse:
      type: object
      description: Response body for the End of Day Ledger endpoint.
      properties:
        success:
          type: boolean
          description: '`true` when the request was processed successfully.'
          example: true
        merchantId:
          type: string
          description: Merchant identifier derived from the API key.
          example: MERCH-001
        timezone:
          type: string
          description: Always `Africa/Johannesburg`.
          example: Africa/Johannesburg
        businessDate:
          type: string
          format: date
          description: The reconciled business date (YYYY-MM-DD).
          example: '2024-04-01'
        periodStart:
          type: string
          format: date-time
          description: Start of the business day as an ISO timestamp.
          example: '2024-04-01T00:00:00+02:00'
        periodEnd:
          type: string
          format: date-time
          description: End of the business day as an ISO timestamp.
          example: '2024-04-01T23:59:59+02:00'
        generatedOn:
          type: string
          format: date-time
          description: Timestamp when this response was generated.
          example: '2024-04-02T06:05:00+02:00'
        transactionCount:
          type: integer
          description: Total number of transactions returned.
          example: 42
        totalAmount:
          type: number
          description: Sum of all transaction amounts.
          example: 8750
        verificationErrorCount:
          type: integer
          description: Number of verification errors encountered.
          example: 0
        transactions:
          type: array
          description: List of MPGS transactions for the business day.
          items:
            $ref: '#/components/schemas/TransactionDto'
        verificationErrors:
          type: array
          description: List of verification errors, if any.
          items:
            $ref: '#/components/schemas/VerificationErrorDto'
    OrderPaymentStatusResponse:
      type: object
      description: Response body for the Order Payment Status endpoint.
      properties:
        success:
          type: boolean
          description: '`true` when the request was processed successfully.'
          example: true
        merchantId:
          type: string
          description: Merchant identifier derived from the API key.
          example: MERCH-001
        orderId:
          type: string
          description: The order ID that was checked.
          example: ORD-20240401-001
        checkedOn:
          type: string
          format: date-time
          description: Timestamp when the status verification was performed.
          example: '2024-04-02T06:10:00+02:00'
        paymentStatus:
          type: string
          description: Overall payment status for the order.
          enum:
            - PAID
            - PENDING
            - FAILED
            - NO_ATTEMPT
            - VERIFICATION_ERROR
          example: PAID
        paymentReference:
          type: string
          description: Primary transaction reference (same as `orderId`).
          example: ORD-20240401-001
        transactionStatus:
          type: string
          description: Raw MPGS transaction status.
          example: SUCCESS
        transactionAmount:
          type: number
          description: Transaction amount.
          example: 150
        transactionCurrency:
          type: string
          description: ISO 4217 currency code.
          example: ZAR
        transactionTime:
          type: string
          format: date-time
          description: Timestamp of the transaction.
          example: '2024-04-01T14:32:00+02:00'
        verificationErrors:
          type: array
          description: Verification errors, if any.
          items:
            $ref: '#/components/schemas/VerificationErrorDto'
    OrderPaymentStatusErrorResponse:
      type: object
      description: >-
        Error response body for the Order Payment Status endpoint (400, 403, 404
        and 500). Same shape as `OrderPaymentStatusResponse` plus `id` and
        `referrer`, which are always empty strings on error.
      properties:
        success:
          type: boolean
          example: false
        id:
          type: string
          example: ''
        referrer:
          type: string
          example: ''
        merchantId:
          type: string
        orderId:
          type: string
        checkedOn:
          type: string
          format: date-time
        paymentStatus:
          type: string
          enum:
            - VERIFICATION_ERROR
          example: VERIFICATION_ERROR
        paymentReference:
          type: string
          example: ''
        transactionStatus:
          type: string
          example: UNKNOWN
        transactionAmount:
          type: number
          example: 0
        transactionCurrency:
          type: string
          example: ZAR
        transactionTime:
          type: string
          format: date-time
        verificationErrors:
          type: array
          description: Always contains exactly one entry describing the failure.
          items:
            $ref: '#/components/schemas/VerificationErrorDto'
    LineItemDto:
      type: object
      description: An individual product or service line item within an order.
      required:
        - id
        - productId
        - name
        - unitPrice
        - currency
        - quantity
      properties:
        id:
          type: string
          description: Line item ID (internal reference).
          example: MOG-123456
        productId:
          type: string
          description: Product or service ID.
          example: municipal-bill
        name:
          type: string
          description: Display name of the item shown on checkout and receipts.
          example: Municipal Bill Payment
        description:
          type: string
          nullable: true
          description: Optional item description shown on checkout and receipts.
          example: Municipal bill payment for MOG-123456 of amount 1200
        unitPrice:
          type: number
          description: Price per unit.
          example: 1200
        currency:
          type: string
          description: ISO 4217 currency code. Must match the order currency.
          enum:
            - ZAR
          example: ZAR
        quantity:
          type: number
          description: Number of units purchased.
          example: 1
        lineTotal:
          type: number
          nullable: true
          description: Total for this line item (`unitPrice × quantity`).
          example: 1200
        vat:
          type: number
          nullable: true
          description: VAT percentage applied to this line item.
          example: 0
        vatAmount:
          type: number
          nullable: true
          description: VAT amount for this line item.
          example: 0
        discount:
          type: array
          nullable: true
          description: Discounts applied to this line item.
          items:
            type: number
        imageUrls:
          type: array
          nullable: true
          description: Product image URLs for UI display.
          items:
            type: string
            format: uri
        descriptions:
          type: array
          nullable: true
          description: Additional descriptions for UI display.
          items:
            type: string
    OrderDto:
      type: object
      description: >-
        Commercial transaction details for the order being submitted for
        payment.
      required:
        - id
        - date
        - orderDescription
        - orderTotal
        - currency
        - products
      properties:
        id:
          type: string
          description: >-
            Vendor's internal order ID. Maps to `order.reference` in the Whoosh
            Payment Gateway.
          example: TRF-20240215-001
        refferer:
          type: string
          nullable: true
          description: Original order reference for reference mapping.
          example: TRF-20240215-001
        date:
          type: string
          format: date-time
          description: Order creation timestamp.
          example: '2024-02-15T10:30:00Z'
        dueDate:
          type: string
          format: date-time
          nullable: true
          description: Optional due date for the order.
          example: '2024-02-15T10:30:00Z'
        orderDescription:
          type: string
          description: Description shown on checkout and receipts.
          example: Municipal Bill Payment
        orderTotal:
          type: number
          description: >-
            Total payable amount. Maps to `order.amount` in the Whoosh Payment
            Gateway.
          example: 2450
        vatTotal:
          type: number
          nullable: true
          description: VAT portion of the order total.
          example: 0
        currency:
          type: string
          description: ISO 4217 transaction currency.
          enum:
            - ZAR
          example: ZAR
        products:
          type: array
          description: >-
            Order line items used for receipts, reporting, and customer
            transparency.
          items:
            $ref: '#/components/schemas/LineItemDto'
    PutOrderCommand:
      type: object
      description: >-
        Request body for creating an order and generating a Whoosh Payment
        Gateway hosted checkout URL.
      required:
        - merchantId
        - order
        - redirectSuccess
        - redirectCancel
      properties:
        merchantId:
          type: string
          description: >-
            Identifies the merchant creating the order. Use `staging` for the
            test environment. A production `merchantId` will be issued before
            go-live.
          example: staging
        order:
          $ref: '#/components/schemas/OrderDto'
        redirectSuccess:
          type: string
          format: uri
          description: >-
            URL the customer is redirected to after a successful payment. Maps
            to `successUrl` in the Whoosh Payment Gateway.
          example: https://placeholder.com
        redirectCancel:
          type: string
          format: uri
          description: >-
            URL the customer is redirected to after cancellation or failure.
            Maps to `cancelUrl` in the Whoosh Payment Gateway.
          example: https://placeholder.com
        platform:
          type: string
          nullable: true
          description: Source platform identifier used for reconciliation reporting.
          example: MOG
        metadata:
          type: object
          nullable: true
          description: >-
            Arbitrary key-value pairs stored by Whoosh for reporting. Not
            forwarded to the Whoosh Payment Gateway.
          additionalProperties: true
          example:
            orgId: TEST-ORG-001
            userId: JABS78t8ABK
    PutOrderResponse:
      type: object
      description: Response returned after successfully creating an order.
      properties:
        orderId:
          type: string
          description: Internal Whoosh order ID.
          example: whoosh-order-abc123
        referredId:
          type: string
          description: Vendor's original order ID (echoed back).
          example: TRF-20240215-001
        paymentPageUrl:
          type: string
          format: uri
          description: >-
            Whoosh Payment Gateway hosted checkout URL to redirect the customer
            to for payment.
          example: https://payments.whoosh.example.com/pay/abc123
        successCallback:
          type: string
          format: uri
          description: Final success redirect URL.
          example: https://placeholder.com
        failCallback:
          type: string
          format: uri
          description: Final failure redirect URL.
          example: https://placeholder.com
security:
  - ApiKeyAuth: []
tags:
  - name: Electricity
    description: >-
      Prepaid token vending for Tshwane, Mogale, Zeerust, and Ngwathe
      municipalities. `Purchase` and `ConfirmCustomer` also still accept
      postpaid meter numbers for backwards compatibility — new integrations
      should use the `Bills` endpoints for postpaid municipal accounts instead.
  - name: Bills
    description: >-
      Postpaid municipal bill payments for Tshwane and Mogale accounts,
      identified by account number rather than meter number.
  - name: VAS
    description: Value-added services (airtime, data, etc.).
  - name: Traffic Fines
    description: >-
      Traffic fine lookups, payment validation, and payment marking via the
      AARTO system.
  - name: Whoosh Payments
    description: >-
      Order creation and Whoosh Payment Gateway checkout initiation. Use these
      endpoints to create an order on the Whoosh platform and obtain a payment
      URL to redirect customers for secure card capture.

      There also reconciliation and payment status verification endpoints. Use
      these to retrieve end-of-day transaction ledgers and verify the payment
      status of individual orders.
paths:
  /v2/Electricity/ConfirmCustomer:
    post:
      operationId: confirmCustomer
      summary: Confirm Customer
      description: >-
        Verifies that a meter number exists and is eligible for electricity
        vending or bill payment. Call this before purchasing to avoid higher
        latency — if you vend with an unknown meter number the system runs an
        implicit lookup first.
      tags:
        - Electricity
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - meterNumber
              properties:
                meterNumber:
                  type: string
                  description: Meter number to confirm.
                  example: '142567423303'
                municipality:
                  $ref: '#/components/schemas/Municipality'
                  description: >-
                    Municipality the meter belongs to. If omitted the system
                    multi-casts to all known municipalities (slower).
                amount:
                  type: number
                  description: Intended purchase amount (used for credit pre-checking).
                method:
                  type: string
                  enum:
                    - cash
                    - card
                  description: Intended payment method.
            example:
              meterNumber: '0171001182'
              municipality: tshwane_prepaid
      responses:
        '200':
          description: Customer confirmation response.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelopeBoolean'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/ConfirmCustomerResult'
              example:
                success: true
                data:
                  exists: true
                  municipality: tshwane_prepaid
                  customer:
                    id: '142567423303'
                    meter_number: '142567423303'
                    meter_source: tshwane_prepaid
                    name: TSHITAMBA
                    phone_number: null
                    email: null
                    provider_specific:
                      token: |-
                        Mr. MT TSHITAMBA
                        72 MONOKANE CRESCENT
                        LOTUS GARDENS X02
                        ...
                      name: TSHITAMBA
                      firstName: MT
                      pocName: '002303736'
                      minLimit: 3000
                      maxLimit: 0
                      tarrifIndex: -1
                      sgc: 225
                      krn: 1
                      keyExpiryNumber: 255
                      tokenTechnology: 2
                      units: 0
        '401':
          description: Unauthorized – missing or invalid API key.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 401-4 - Unauthorized. No API key set
              example:
                success: false
                error: 401-4 - Unauthorized. No API key set
        '500':
          description: >-
            Internal server error. The exact message may vary; treat any
            non-2xx, non-401 response as a generic failure.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 999 - Internal server error
              example:
                success: false
                error: 999 - Internal server error
  /v2/Electricity/Purchase:
    post:
      operationId: purchaseElectricity
      summary: Purchase / Vend Token / Pay Bills
      description: >-
        Vends an electricity token (prepaid) or marks a payment against a
        postpaid running balance.


        - **Prepaid** (`tshwane_prepaid`, `mogale_prepaid`, `zeerust_prepaid`,
        `ngwathe_prepaid`):
          returns a token string the customer enters into their meter.

        - **Postpaid** (`mogale_postpaid`, `tshwane_postpaid`): returns a
        receipt URL proving bill offset.


        Use the `idempotency` field to safely retry failed requests without risk
        of duplicate vends.

        > **Postpaid bill payments:** if you already know you're paying a
        postpaid municipal account, prefer the dedicated `Bills/Purchase` and
        `Bills/ConfirmCustomer` endpoints — they identify the account by
        `accountNumber` rather than `meterNumber` and drop fields that only
        apply to prepaid vending.
      tags:
        - Electricity
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - meterNumber
                - amount
                - method
                - municipality
              properties:
                meterNumber:
                  type: string
                  example: '04235758721'
                municipality:
                  $ref: '#/components/schemas/Municipality'
                amount:
                  type: number
                  example: 400
                method:
                  type: string
                  enum:
                    - cash
                    - card
                  example: cash
                idempotency:
                  type: string
                  description: >-
                    Unique string to prevent duplicate vends. A second request
                    with the same value will not result in a second transaction.
                  example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
            example:
              meterNumber: '04235758721'
              municipality: zeerust_prepaid
              amount: 400
              method: cash
              idempotency: a1b2c3d4-e5f6-7890-abcd-ef1234567890
      responses:
        '200':
          description: Purchase response.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelopeBoolean'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/PurchaseElectricityResult'
              example:
                success: true
                data:
                  name: MR. A J KWINDA
                  amount: 50
                  meter: '81114926223'
                  date: 22 Mar 2026 3:24:11 PM
                  timestamp: 1774193051166
                  token: |2-
                            City of Tshwane
                            ---------------
                    Receipt No.: 109707/29
                    ...
                  tokenCount: 1
                  units: 2.9
                  receipt: null
                  municipality: tshwane_prepaid
                  mode: 0
                  details:
                    balance: 1
                    code: 0
                    date: 22 Mar 2026
                    desc: OK
                    message: '-'
                    refno: 7128f265-dc3f-4666-a700-161d50037283
                    time: 3:24:11 PM
                    trDetails:
                      amt: 50
                      date: 22 Mar 2026
                      'no': 109707/871224
                      status: OK
                      time: 3:24:11 PM
                      vat: 6.52
                    voucher:
                      amounts:
                        - desc: Arrears
                          amt: 0
                          outstanding: 0
                        - desc: VAT
                          amt: 6.52
                      elecamt: 43.48
                      meterno: 07/07/07064470565
                      name: MR. A J KWINDA
                      receiptdesc: '-'
                      receiptno: 109707/871224
                      receiver: City of TSHWANE
                      token:
                        - token: 1529 6469 6769 5938 000
                          info: Electricity Credit(2.9)
                          type: CRED
                      units: 2.9
                      vatno: 4000142267
                  meterDetails:
                    msno: 07/07/07064470565
                    sgc: 225
                    krn: 2
                    ti: 1
        '401':
          description: Unauthorized – missing or invalid API key.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 401-4 - Unauthorized. No API key set
              example:
                success: false
                error: 401-4 - Unauthorized. No API key set
        '500':
          description: >-
            Internal server error. The exact message may vary; treat any
            non-2xx, non-401 response as a generic failure.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 999 - Internal server error
              example:
                success: false
                error: 999 - Internal server error
  /v2/Bills/ConfirmCustomer:
    post:
      operationId: confirmBillCustomer
      summary: Confirm Customer
      description: >-
        Verifies that a postpaid municipal account number exists and returns its
        current running balance. Call this before `Purchase` to avoid higher
        latency — if you pay a bill for an unknown account number the system
        runs an implicit lookup first.
      tags:
        - Bills
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - accountNumber
              properties:
                accountNumber:
                  type: string
                  description: Account number to confirm.
                  example: '04235758721'
                municipality:
                  $ref: '#/components/schemas/BillMunicipality'
                  description: >-
                    Municipality the account belongs to. If omitted the system
                    multi-casts to all known bill municipalities (slower).
                amount:
                  type: number
                  description: Intended payment amount (used for credit pre-checking).
                method:
                  type: string
                  enum:
                    - cash
                    - card
                  description: Intended payment method.
            example:
              accountNumber: '04235758721'
              municipality: mogale_postpaid
      responses:
        '200':
          description: Customer confirmation response.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelopeBoolean'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/ConfirmCustomerResult'
              example:
                success: true
                data:
                  exists: true
                  municipality: mogale_postpaid
                  customer:
                    id: '04235758721'
                    account_number: '04235758721'
                    account_source: mogale_postpaid
                    name: MR. A J KWINDA
                    balance: 1850
                    last_update: 22 Mar 2026 3:24:11 PM
                    last_update_timestamp: 1774193051166
                    address: 72 MONOKANE CRESCENT, LOTUS GARDENS X02
        '401':
          description: Unauthorized – missing or invalid API key.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 401-4 - Unauthorized. No API key set
              example:
                success: false
                error: 401-4 - Unauthorized. No API key set
        '500':
          description: >-
            Internal server error. The exact message may vary; treat any
            non-2xx, non-401 response as a generic failure.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 999 - Internal server error
              example:
                success: false
                error: 999 - Internal server error
  /v2/Bills/Purchase:
    post:
      operationId: purchaseBill
      summary: Purchase / Pay Bill
      description: >-
        Pays down a postpaid municipal account's running balance. Unlike
        `Electricity/Purchase`, no token or kWh amount is returned — only the
        account's remaining balance.

        The account is identified by `accountNumber` rather than `meterNumber`,
        and the request carries none of the prepaid/POS-only fields (`arrears`,
        `sgc`, `tarrifIndex`, `krn`) accepted by `Electricity/Purchase`.

        Use the `idempotency` field to safely retry failed requests without risk
        of duplicate payments.
      tags:
        - Bills
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - accountNumber
                - amount
                - method
                - municipality
                - idempotency
              properties:
                accountNumber:
                  type: string
                  description: Account number of the postpaid bill to pay.
                  example: '04235758721'
                municipality:
                  $ref: '#/components/schemas/BillMunicipality'
                amount:
                  type: number
                  example: 400
                method:
                  type: string
                  enum:
                    - cash
                    - card
                  example: cash
                idempotency:
                  type: string
                  description: >-
                    Unique string to prevent duplicate payments. A second
                    request with the same value will not result in a second
                    transaction.
                  example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
            example:
              accountNumber: '04235758721'
              municipality: mogale_postpaid
              amount: 400
              method: cash
              idempotency: a1b2c3d4-e5f6-7890-abcd-ef1234567890
      responses:
        '200':
          description: Bill payment response.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelopeBoolean'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/PurchaseElectricityResult'
              example:
                success: true
                data:
                  name: MR. A J KWINDA
                  amount: 400
                  account_number: '04235758721'
                  date: 22 Mar 2026 3:24:11 PM
                  timestamp: 1774193051166
                  balance: 1850
                  address: 72 MONOKANE CRESCENT, LOTUS GARDENS X02
                  municipality: mogale_postpaid
        '401':
          description: Unauthorized – missing or invalid API key.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 401-4 - Unauthorized. No API key set
              example:
                success: false
                error: 401-4 - Unauthorized. No API key set
        '500':
          description: >-
            Internal server error. The exact message may vary; treat any
            non-2xx, non-401 response as a generic failure.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 999 - Internal server error
              example:
                success: false
                error: 999 - Internal server error
  /v1/Merchant/CheckBalance:
    post:
      operationId: checkBalance
      summary: Check Balance
      description: >-
        Returns the authenticated merchant's current credit balance across all
        consumed prepaid VAS services. Optionally includes a transaction
        breakdown for a specified period.
      tags:
        - Electricity
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                printReceipts:
                  type: boolean
                  default: false
                  description: >-
                    If `true`, include topup and vend receipts per service for
                    the period defined in `receipts`.
                receipts:
                  $ref: '#/components/schemas/ReceiptsConfiguration'
                  description: Required when `printReceipts` is `true`.
            example:
              printReceipts: false
      responses:
        '200':
          description: Balance response.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelopeBoolean'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/BalanceResponse'
              example:
                success: true
                data:
                  balance: -10908
                  payments:
                    lastPaymentValue: 0
                    nPayments: 0
                    paymentsTotal: 0
                  services:
                    - balance: 7668
                      nTransactions: 149
                      type: prepaid-electricity
                      service_specification: tshwane_prepaid
                    - balance: 944
                      nTransactions: 40
                      type: prepaid-electricity
                      service_specification: mogale_prepaid
                    - balance: 1910
                      nTransactions: 16
                      type: prepaid-electricity
                      service_specification: zeerust_prepaid
                    - balance: 40
                      nTransactions: 2
                      type: prepaid-electricity
                      service_specification: eskom_prepaid
                  timestamp: 1774193070098
        '401':
          description: Unauthorized – missing or invalid API key.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 401-4 - Unauthorized. No API key set
              example:
                success: false
                error: 401-4 - Unauthorized. No API key set
        '500':
          description: >-
            Internal server error. The exact message may vary; treat any
            non-2xx, non-401 response as a generic failure.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 999 - Internal server error
              example:
                success: false
                error: 999 - Internal server error
  /v1/VAS/Confirm:
    post:
      operationId: confirmVas
      summary: Confirm VAS
      tags:
        - VAS
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                productID:
                  type: string
                amount:
                  type: string
                reference:
                  type: string
            example:
              productID: dde67d01-73ff-4f20-acb2-212e1b22ffcd
              amount: '500'
              reference: '[10,20,30,36,1,4,5,8]'
      responses:
        '401':
          description: Unauthorized – missing or invalid API key.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 401-4 - Unauthorized. No API key set
              example:
                success: false
                error: 401-4 - Unauthorized. No API key set
        '500':
          description: >-
            Internal server error. The exact message may vary; treat any
            non-2xx, non-401 response as a generic failure.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 999 - Internal server error
              example:
                success: false
                error: 999 - Internal server error
  /v1/VAS/Purchase:
    post:
      operationId: purchaseVas
      summary: Purchase VAS
      tags:
        - VAS
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                productID:
                  type: string
                amount:
                  type: string
                reference:
                  type: string
            example:
              productID: dde67d01-73ff-4f20-acb2-212e1b22ffcd
              amount: '500'
              reference: '[10,20,30,36,1,4,5,8]'
      responses:
        '401':
          description: Unauthorized – missing or invalid API key.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 401-4 - Unauthorized. No API key set
              example:
                success: false
                error: 401-4 - Unauthorized. No API key set
        '500':
          description: >-
            Internal server error. The exact message may vary; treat any
            non-2xx, non-401 response as a generic failure.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 999 - Internal server error
              example:
                success: false
                error: 999 - Internal server error
  /v1/TrafficFine/ViewByIDnumber:
    post:
      operationId: viewTrafficFineByID
      summary: Query by ID Number
      description: >-
        Returns all recorded traffic fines for a given South African national ID
        number or Foreign ID number.
      tags:
        - Traffic Fines
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - idNumber
              properties:
                idNumber:
                  type: string
                  description: South African national ID number or Foreign ID number.
                  example: '9710226013089'
                type:
                  type: string
                  enum:
                    - local
                    - foreign
                  description: >-
                    Optional parameter to determine whether the ID you are
                    searching with is local or foreing. Please note that foreign
                    ID is not the same as a passport number. If left empty it
                    will default to `local`
                  example: foreign
            example:
              idNumber: '9710226013089'
              type: local
      responses:
        '200':
          description: Traffic fine query by ID response.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    description: '`true` = success, `false` = error.'
                  idNumber:
                    nullable: true
                    type: string
                    description: >-
                      The ID number used in the query. `null` if the type was
                      foreign
                  foreignId:
                    nullable: true
                    type: string
                    description: >-
                      The foreign ID number that was used in the query. `null`
                      if the type was local or type was empty
                  result:
                    nullable: true
                    type: object
                    description: Query result. `null` when `success` is `false`.
                    allOf:
                      - $ref: '#/components/schemas/QueryByIdNumberResult'
                  error:
                    nullable: true
                    type: object
                    description: Error details. `null` when `success` is `true`.
                    allOf:
                      - $ref: '#/components/schemas/ErrorMessage'
              example:
                success: true
                idNumber: '9710226013089'
                result:
                  infringements:
                    - vehicleLicenseNumber: JR47HJGP
                      charge:
                        classification: Failed to license a vehicle with the ...
                        demeritPoints: 1
                        description: >-
                          Failed to license a vehicle in accordance with Chapter
                          III Part I of Road Traffic Regulations 2000 with the
                          appropriate registering authority
                      date: '2023-01-22'
                      time: '10:38:00'
                      noticeNumber: '0100110373510309'
                      location:
                        cityTown: JOHANNESBURG
                        issAuthority:
                          code: '4065'
                          description: GP Dept of Community Safety.
                        streetName: N1
                        suburb: JOHANNESBURG
                      netFineAmnt: 560
                      vendorFeeAmnt: 14
                      grossAmnt: 574
                      isPaid: false
                  hasFinesToPay: true
                  netFinesAmnt: 560
                  vendorFeeAmnt: 14
                  grossAmnt: 574
                  idNumber: '9710226013089'
        '401':
          description: Unauthorized – missing or invalid API key.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 401-4 - Unauthorized. No API key set
              example:
                success: false
                error: 401-4 - Unauthorized. No API key set
        '500':
          description: Internal server error.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  idNumber:
                    type: string
                  error:
                    type: object
                    allOf:
                      - $ref: '#/components/schemas/ErrorMessage'
              example:
                success: false
                idNumber: '9710226013089'
                error:
                  code: 999
                  message: 999 - Internal server error
                  validationMessages:
                    - 999 - Internal server error
  /v1/TrafficFine/ViewByBusinessRegistration:
    post:
      operationId: viewTrafficFineByBRN
      summary: Query by Business Registration Number
      description: >-
        Returns all recorded traffic fines for a given business registration
        number (BRN).
      tags:
        - Traffic Fines
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - brn
              properties:
                idNumber:
                  type: string
                  description: Business registration number.
                  example: '0402555070058'
            example:
              brn: F141235290013
      responses:
        '200':
          description: Traffic fine query by BRN response.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  brn:
                    type: string
                  result:
                    nullable: true
                    type: object
                    description: Query result. `null` when `success` is `false`.
                    allOf:
                      - $ref: '#/components/schemas/QueryByIdNumberResult'
                  error:
                    nullable: true
                    type: object
                    description: Error details. `null` when `success` is `true`.
                    allOf:
                      - $ref: '#/components/schemas/ErrorMessage'
        '401':
          description: Unauthorized - missing or invalid API key.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 401-4 - Unauthorized. No API key set
              example:
                success: false
                error: 401-4 - Unauthorized. No API key set
        '500':
          description: Internal server error.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  brn:
                    type: string
                  error:
                    type: object
                    allOf:
                      - $ref: '#/components/schemas/ErrorMessage'
              example:
                success: false
                brn: 2019/123456/07
                error:
                  code: 999
                  message: 999 - Internal server error
                  validationMessages:
                    - 999 - Internal server error
  /v1/TrafficFine/ViewByNoticeNumber:
    post:
      operationId: viewTrafficFineByNoticeNumber
      summary: Query by Notice Number
      description: >-
        Returns the details of a specific traffic fine by its AARTO notice
        number.
      tags:
        - Traffic Fines
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - noticeNumber
              properties:
                noticeNumber:
                  type: string
                  description: AARTO notice number of the traffic fine.
                  example: '0100110373510309'
            example:
              noticeNumber: '0100110373510309'
      responses:
        '200':
          description: Traffic fine query by notice number response.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  noticeNumber:
                    type: string
                    description: The notice number used in the query.
                  result:
                    nullable: true
                    type: object
                    description: Query result. `null` when `success` is `false`.
                    allOf:
                      - $ref: '#/components/schemas/QueryByNoticeNumberResult'
                  error:
                    type: object
                    nullable: true
                    allOf:
                      - $ref: '#/components/schemas/ErrorMessage'
              example:
                success: true
                noticeNumber: '0100110373510309'
                result:
                  infringements:
                    - vehicleLicenseNumber: JR47HJGP
                      charge:
                        classification: Operated a vehicle without displaying...
                        demeritPoints: 0
                        description: >-
                          Operated a vehicle without displaying the licence disc
                          of said vehicle
                      date: '2023-01-22'
                      time: '10:38:00'
                      noticeNumber: '0100110373510309'
                      location:
                        cityTown: JOHANNESBURG
                        issAuthority:
                          code: '4065'
                          description: GP Dept of Community Safety.
                        streetName: N1
                        suburb: JOHANNESBURG
                      netFineAmnt: 560
                      vendorFeeAmnt: 14
                      grossAmnt: 574
                      isPaid: false
                  hasFinesToPay: true
                  netFinesAmnt: 560
                  vendorFeeAmnt: 14
                  grossAmnt: 574
                  noticeNumber: '0100110373510309'
        '401':
          description: Unauthorized - missing or invalid API key.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 401-4 - Unauthorized. No API key set
              example:
                success: false
                error: 401-4 - Unauthorized. No API key set
        '500':
          description: Internal server error.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  noticeNumber:
                    type: string
                  error:
                    type: object
                    allOf:
                      - $ref: '#/components/schemas/ErrorMessage'
              example:
                success: false
                noticeNumber: '0100110373510309'
                error:
                  code: 999
                  message: 999 - Internal server error
                  validationMessages:
                    - 999 - Internal server error
  /v1/TrafficFine/ValidateMarkTrafficFinesAsPaid:
    post:
      operationId: validateMarkTrafficFinesAsPaid
      summary: Validate Traffic Fine Payment
      description: >-
        Validates that the proposed payment amount matches the expected gross
        amount for the selected fines. Call this before `MarkTrafficFineAsPaid`
        to confirm the correct amount.
      tags:
        - Traffic Fines
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - paymentReference
                - paymentType
                - finesPaid
              properties:
                paymentReference:
                  type: string
                  description: Merchant-side reference for this order.
                  example: myreference
                paymentType:
                  type: string
                  enum:
                    - CARD
                    - EFT
                  example: CARD
                finesPaid:
                  type: array
                  items:
                    $ref: '#/components/schemas/MarkFineAsPaidCommand'
            example:
              paymentReference: myreference
              paymentType: CARD
              finesPaid:
                - noticeNumber: '0100110373510309'
                  amount: 574
                  currency: ZAR
      responses:
        '200':
          description: Payment validation response.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: integer
                    enum:
                      - 0
                      - 1
                  amntProposed:
                    type: number
                    description: The amount that was proposed for validation.
                  reference:
                    type: string
                    description: The `paymentReference` echoed back.
                  result:
                    type: object
                    nullable: true
                    description: Query result. `null` when `success` is `0`.
                    allOf:
                      - $ref: '#/components/schemas/ValidatePaymentResult'
                  error:
                    type: object
                    nullable: true
                    allOf:
                      - $ref: '#/components/schemas/ErrorMessagePayment'
              example:
                success: 1
                amntProposed: 574
                reference: myreference
                result:
                  amntToPay: 574
                  difference: 0
                  isCorrectAmount: true
        '401':
          description: Unauthorized - missing or invalid API key.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 401-4 - Unauthorized. No API key set
              example:
                success: false
                error: 401-4 - Unauthorized. No API key set
        '500':
          description: Internal server error.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: integer
                    enum:
                      - 0
                  amntProposed:
                    type: number
                  reference:
                    type: string
                  error:
                    type: object
                    allOf:
                      - $ref: '#/components/schemas/ErrorMessagePayment'
              example:
                success: 0
                amntProposed: 0
                reference: myreference
                error:
                  code: 999
                  message: 999 - Internal server error
                  validationMessages:
                    - 999 - Internal server error
  /v1/TrafficFine/MarkTrafficFineAsPaid:
    post:
      operationId: markTrafficFineAsPaid
      summary: Mark Traffic Fines as Paid
      description: >-
        Marks one or more traffic fines as paid on behalf of a customer. The
        merchant declares that they received the gross amount (net fine + vendor
        fee) from the customer and wishes to clear the fines with AARTO.


        Calling this endpoint will result in a Whoosh invoice for the gross
        transaction value of all fines marked paid, issued at the end of each
        billing cycle.


        > **Recommended:** Call `ValidateMarkTrafficFinesAsPaid` first to
        confirm the correct amount before calling this endpoint.
      tags:
        - Traffic Fines
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - paymentReference
                - paymentType
                - finesPaid
              properties:
                paymentReference:
                  type: string
                  description: Merchant-side reference to tie the result to an order.
                  example: myreference
                paymentType:
                  type: string
                  enum:
                    - CARD
                    - EFT
                  description: How the transaction was paid.
                  example: CARD
                finesPaid:
                  type: array
                  description: List of fines paid to the merchant to be cleared with AARTO.
                  items:
                    $ref: '#/components/schemas/MarkFineAsPaidCommand'
            example:
              paymentReference: myreference
              paymentType: CARD
              finesPaid:
                - noticeNumber: '0240060000000458'
                  amount: 1614
                  currency: ZAR
      responses:
        '200':
          description: Mark as paid response.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: integer
                    enum:
                      - 0
                      - 1
                  reference:
                    type: string
                    description: The `paymentReference` echoed back.
                  result:
                    nullable: true
                    type: object
                    description: Transaction result. `null` when `success` is `0`.
                    allOf:
                      - $ref: '#/components/schemas/MarkTrafficFinesAsPaidResult'
                  error:
                    nullable: true
                    type: object
                    description: Error details. `null` when `success` is `1`.
                    allOf:
                      - $ref: '#/components/schemas/ErrorMessage'
              example:
                success: 1
                reference: myreference
                result:
                  orderId: b89a1717-5266-43c7-93cc-7e51c0fe01fb
                  grossAmntPaid: 1614
                  netFinesAmntPaid: 1600
                  vendorFeeAmntPaid: 14
                  merchantFeePaid: 7.5
                  finesPaid:
                    - invoiceNumber: 47280000005V
                      netFinesAmntPaid: 1600
                      noticeNumber: '240990007600759'
                      receiptNumber: 4728000000N1
                      vendorFeeAmntPaid: 14
                      grossAmntPaid: 1614
        '401':
          description: Unauthorized – missing or invalid API key.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 401-4 - Unauthorized. No API key set
              example:
                success: false
                error: 401-4 - Unauthorized. No API key set
        '500':
          description: Internal server error.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: integer
                    enum:
                      - 0
                  reference:
                    type: string
                  error:
                    type: object
                    allOf:
                      - $ref: '#/components/schemas/ErrorMessage'
              example:
                success: 0
                reference: myreference
                error:
                  code: 999
                  message: 999 - Internal server error
                  validationMessages:
                    - 999 - Internal server error
  /v2/putOrder:
    post:
      operationId: putOrderV2
      summary: Create Order
      description: >-
        Creates an order on the Whoosh platform and returns a Whoosh Payment
        Gateway hosted checkout URL to redirect the customer to for secure card
        payment.


        Whoosh orchestrates order creation and reconciliation, and the Whoosh
        Payment Gateway manages card capture, authorization, and payment
        security.


        > **Staging environment:** Always use `merchantId: staging` when calling
        the test base URL. A production `merchantId` will be issued before
        go-live.
      tags:
        - Whoosh Payments
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PutOrderCommand'
            example:
              merchantId: staging
              order:
                id: TRF-20240215-001
                refferer: TRF-20240215-001
                date: '2024-02-15T10:30:00Z'
                dueDate: '2024-02-15T10:30:00Z'
                orderDescription: Municipal Bill Payment
                orderTotal: 2450
                vatTotal: 0
                currency: ZAR
                products:
                  - id: MOG-123456
                    productId: municipal-bill
                    name: Municipal Bill Payment
                    description: Municipal bill payment for MOG-123456 of amount 1200
                    unitPrice: 1200
                    lineTotal: 1200
                    vatAmount: 0
                    currency: ZAR
                    quantity: 1
                  - id: MOG-654321
                    productId: municipal-bill
                    name: Municipal Bill Payment
                    description: Municipal bill payment for MOG-654321 of amount 1250
                    unitPrice: 1250
                    lineTotal: 1250
                    vatAmount: 0
                    currency: ZAR
                    quantity: 1
              redirectSuccess: https://placeholder.com
              redirectCancel: https://placeholder.com
              platform: MOG
              metadata:
                orgId: TEST-ORG-001
                userId: JABS78t8ABK
      responses:
        '200':
          description: >-
            Order created successfully. Redirect the customer to
            `paymentPageUrl`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PutOrderResponse'
              example:
                orderId: whoosh-order-abc123
                referredId: TRF-20240215-001
                paymentPageUrl: https://payments.whoosh.example.com/pay/abc123
                successCallback: https://placeholder.com
                failCallback: https://placeholder.com
        '400':
          description: >-
            Bad request – order validation failed. Returns a `{ status, message,
            data }` wrapper. `data` is normally an empty object, since the
            underlying error's `message` is not enumerable and is not preserved
            on serialization.
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: integer
                    example: 400
                  message:
                    type: string
                    description: >-
                      One of: "Account is not a merchant account" (merchant
                      exists but is not of merchant type); "Incomplete order
                      details! Amount and description are required and cannot be
                      empty" (missing/empty `order.orderTotal` or
                      `order.orderDescription`); "Order with ID {referrer} has
                      already been registered on merchant {merchantId}" (an
                      order with the same `refferer`/`id` was already put for
                      this merchant).
                  data:
                    type: object
                    description: >-
                      Always an empty object in practice, since the underlying
                      `Error`'s `message`/`stack` are non-enumerable and are
                      dropped on JSON serialization.
                    example: {}
              examples:
                notAMerchantAccount:
                  summary: Account is not a merchant account
                  value:
                    status: 400
                    message: Account is not a merchant account
                    data: {}
                incompleteOrderDetails:
                  summary: Missing amount or description
                  value:
                    status: 400
                    message: >-
                      Incomplete order details! Amount and description are
                      required and cannot be empty
                    data: {}
                duplicateOrder:
                  summary: Order already registered for this merchant
                  value:
                    status: 400
                    message: >-
                      Order with ID TRF-20240215-001 has already been registered
                      on merchant staging
                    data: {}
        '401':
          description: Unauthorized – missing or invalid API key.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 401-4 - Unauthorized. No API key set
              example:
                success: false
                error: 401-4 - Unauthorized. No API key set
        '500':
          description: >-
            Internal server error – for example when no merchant exists for the
            given `merchantId`. This case is not caught by the handler's own
            error handling, so the exact body is not guaranteed; treat any
            non-2xx, non-400/401 response as a generic failure.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 999 - Internal server error
              example:
                success: false
                error: 999 - Internal server error
  /v2/whooshEndOfDayLedger:
    post:
      operationId: whooshEndOfDayLedger
      summary: End of Day Ledger
      description: >-
        Returns all MPGS transactions for the previous South Africa business
        day, including reconciliation metadata and any verification errors.


        No request body is required. The merchant is identified automatically
        from the `x-api-key` header.
      tags:
        - Whoosh Payments
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: Successfully retrieved previous business day transactions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EndOfDayLedgerResponse'
              example:
                success: true
                merchantId: MERCH-001
                timezone: Africa/Johannesburg
                businessDate: '2024-04-01'
                periodStart: '2024-04-01T00:00:00+02:00'
                periodEnd: '2024-04-01T23:59:59+02:00'
                generatedOn: '2024-04-02T06:05:00+02:00'
                transactionCount: 2
                totalAmount: 300
                verificationErrorCount: 0
                transactions:
                  - paymentReference: ORD-20240401-001
                    transactionStatus: SUCCESS
                    transactionAmount: 150
                    transactionCurrency: ZAR
                    transactionTime: '2024-04-01T10:15:00+02:00'
                  - paymentReference: ORD-20240401-002
                    transactionStatus: SUCCESS
                    transactionAmount: 150
                    transactionCurrency: ZAR
                    transactionTime: '2024-04-01T14:32:00+02:00'
                verificationErrors: []
        '400':
          description: Missing `merchantId` in request body.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EndOfDayLedgerResponse'
              example:
                success: false
                merchantId: ''
                timezone: Africa/Johannesburg
                businessDate: '2024-04-01'
                periodStart: '2024-04-01T00:00:00+02:00'
                periodEnd: '2024-04-01T23:59:59+02:00'
                generatedOn: '2024-04-02T06:05:00+02:00'
                transactionCount: 0
                totalAmount: 0
                verificationErrorCount: 1
                transactions: []
                verificationErrors:
                  - message: Missing merchantId in request body.
        '401':
          description: Unauthorized – missing or invalid API key.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 401-4 - Unauthorized. No API key set
              example:
                success: false
                error: 401-4 - Unauthorized. No API key set
        '403':
          description: >-
            The `merchantId` resolved from the API key does not match, and is
            not a registered sub-merchant.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EndOfDayLedgerResponse'
              example:
                success: false
                merchantId: MERCH-001
                timezone: Africa/Johannesburg
                businessDate: '2024-04-01'
                periodStart: '2024-04-01T00:00:00+02:00'
                periodEnd: '2024-04-01T23:59:59+02:00'
                generatedOn: '2024-04-02T06:05:00+02:00'
                transactionCount: 0
                totalAmount: 0
                verificationErrorCount: 1
                transactions: []
                verificationErrors:
                  - message: >-
                      Provided merchantId does not match the authenticated API
                      key and is not a registered sub-merchant.
        '404':
          description: No MPGS channel configured for this merchant.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EndOfDayLedgerResponse'
              example:
                success: false
                merchantId: MERCH-001
                timezone: Africa/Johannesburg
                businessDate: '2024-04-01'
                periodStart: '2024-04-01T00:00:00+02:00'
                periodEnd: '2024-04-01T23:59:59+02:00'
                generatedOn: '2024-04-02T06:05:00+02:00'
                transactionCount: 0
                totalAmount: 0
                verificationErrorCount: 1
                transactions: []
                verificationErrors:
                  - message: No MPGS channel configured for merchant MERCH-001
        '500':
          description: >-
            Internal server error. The exact message may vary; treat any
            non-2xx, non-401 response as a generic failure.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 999 - Internal server error
              example:
                success: false
                error: 999 - Internal server error
  /v2/whooshCheckOrderPaymentStatus:
    post:
      operationId: whooshCheckOrderPaymentStatus
      summary: Order Payment Status
      description: >-
        Verifies the payment status of a specific order by checking its
        associated MPGS payment sessions.
      tags:
        - Whoosh Payments
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - orderId
              properties:
                orderId:
                  type: string
                  description: The order ID to verify payment status for.
                  example: ORD-20240401-001
            example:
              orderId: ORD-20240401-001
      responses:
        '200':
          description: Payment status successfully retrieved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderPaymentStatusResponse'
              example:
                success: true
                merchantId: MERCH-001
                orderId: ORD-20240401-001
                checkedOn: '2024-04-02T06:10:00+02:00'
                paymentStatus: PAID
                paymentReference: ORD-20240401-001
                transactionStatus: SUCCESS
                transactionAmount: 150
                transactionCurrency: ZAR
                transactionTime: '2024-04-01T14:32:00+02:00'
                verificationErrors: []
        '400':
          description: Missing or empty `merchantId` or `orderId`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderPaymentStatusErrorResponse'
              example:
                success: false
                id: ''
                referrer: ''
                merchantId: ''
                orderId: ''
                checkedOn: '2024-04-02T06:10:00+02:00'
                paymentStatus: VERIFICATION_ERROR
                paymentReference: ''
                transactionStatus: UNKNOWN
                transactionAmount: 0
                transactionCurrency: ZAR
                transactionTime: '2024-04-02T06:10:00+02:00'
                verificationErrors:
                  - message: Missing merchantId or orderId in request body.
        '401':
          description: Unauthorized – missing or invalid API key.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 401-4 - Unauthorized. No API key set
              example:
                success: false
                error: 401-4 - Unauthorized. No API key set
        '403':
          description: >-
            The `merchantId` in the request does not match the authenticated API
            key and is not a registered sub-merchant.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderPaymentStatusErrorResponse'
              example:
                success: false
                id: ''
                referrer: ''
                merchantId: MERCH-001
                orderId: ORD-20240401-001
                checkedOn: '2024-04-02T06:10:00+02:00'
                paymentStatus: VERIFICATION_ERROR
                paymentReference: ''
                transactionStatus: UNKNOWN
                transactionAmount: 0
                transactionCurrency: ZAR
                transactionTime: '2024-04-02T06:10:00+02:00'
                verificationErrors:
                  - message: >-
                      Provided merchantId does not match the authenticated API
                      key and is not a registered sub-merchant.
        '404':
          description: >-
            No MPGS channel configured for the merchant, or no order found for
            the given `orderId`/merchant.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderPaymentStatusErrorResponse'
              example:
                success: false
                id: ''
                referrer: ''
                merchantId: MERCH-001
                orderId: ORD-20240401-001
                checkedOn: '2024-04-02T06:10:00+02:00'
                paymentStatus: VERIFICATION_ERROR
                paymentReference: ''
                transactionStatus: UNKNOWN
                transactionAmount: 0
                transactionCurrency: ZAR
                transactionTime: '2024-04-02T06:10:00+02:00'
                verificationErrors:
                  - message: No order ORD-20240401-001 found for merchant MERCH-001.
        '500':
          description: >-
            Internal server error. The exact message may vary; treat any
            non-2xx, non-401 response as a generic failure.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: 999 - Internal server error
              example:
                success: false
                error: 999 - Internal server error
