openapi: 3.0.3
info:
  title: Atlas SoftPOS Commerce API
  version: 1.0.0
  description: |
    POS-facing API for payment terminal integration. Supports orchestrated (simple) and
    granular transaction modes with real-time event streaming via WebSocket.

    ## Transaction Paths

    This API serves two connectivity models:

    - **Local Connected** -- POS communicates with the terminal device over local network (IP) or USB.
      Use the `http://terminal:8443` base URL.
    - **Cloud** -- POS communicates through Atlas cloud infrastructure, which routes to the terminal.
      Use the `https://api.atlas-softpos.com` base URL.

    A third path, **Local Embedded** (direct Kotlin SDK integration), is native and not covered by
    this REST/WebSocket specification.

    ## Operation Modes

    ### Orchestrated Mode (Simple)

    Use the `/v1/transaction/sale`, `/v1/transaction/refund`, and similar endpoints. The terminal
    handles the full transaction lifecycle internally. The request blocks until a final result is
    returned. Suitable for integrations that do not need to interact with an external acquirer
    host mid-transaction.

    ### Granular Mode

    Use `/v1/transaction/initiate` and `/v1/transaction/complete` for POS-side acquirer routing.
    The POS initiates a transaction, receives chip data (EMV tags, encrypted PIN block), sends
    that data to its own acquirer, and then completes the transaction with the issuer response.
    This enables multi-acquirer routing, store-and-forward, and custom authorization logic.

    ## WebSocket Event Streaming

    For real-time transaction events, connect via WebSocket:

    ```
    ws://terminal:8443/v1/session      (local)
    wss://api.atlas-softpos.com/v1/session  (cloud)
    ```

    ### Protocol

    - Send JSON messages with an `action` field to initiate operations.
    - Receive `TransactionEvent` JSON messages as the transaction progresses.
    - Events are a discriminated union keyed on the `event` field.

    ### WebSocket Actions

    | Action        | Description                                | Payload fields                                             |
    |---------------|--------------------------------------------|------------------------------------------------------------|
    | `sale`        | Start a sale                               | `amount`, `currency`, `entryMode?`, `referenceId?`, `tipAmount?`, `metadata?` |
    | `pre_auth`    | Start a pre-authorization                  | `amount`, `currency`, `entryMode?`, `referenceId?`, `metadata?`              |
    | `card_read`   | Read card data (tokenize / loyalty)        | `entryMode?`, `referenceId?`                               |
    | `complete`    | Complete online auth (granular mode)       | `transactionId`, `responseCode`, `authorizationCode`, `issuerAuthData?`, `issuerScripts71?`, `issuerScripts72?` |
    | `display`     | Show content on terminal screen            | `lines`, `duration?`, `clearPrevious?`                     |
    | `input`       | Request input from user on terminal        | `prompt`, `inputType`, `options?`, `minLength?`, `maxLength?`, `timeoutMs?` |
    | `abort`       | Abort the current in-progress transaction  | `transactionId`                                            |

    ### Event Types

    | Event                | When                                           | Key fields                                                 |
    |----------------------|------------------------------------------------|------------------------------------------------------------|
    | `waiting_for_card`   | Terminal is ready, waiting for tap/insert/swipe | `transactionId`, `message`                                 |
    | `card_detected`      | Card presented, reading data                   | `transactionId`, `entryMode`, `cardScheme?`                |
    | `processing`         | Transaction processing underway                | `transactionId`, `message`                                 |
    | `pin_required`       | Terminal is prompting cardholder for PIN        | `transactionId`                                            |
    | `online_authorization`| Chip data ready for POS-side online auth        | `transactionId`, `chipData`                                |
    | `result`             | Final transaction result                       | Full `TransactionResponse` payload                         |
    | `error`              | An error occurred                              | `transactionId?`, `errorCode`, `message`                   |
    | `cancelled`          | Transaction was cancelled or aborted           | `transactionId`, `reason`                                  |

    ### Example: Orchestrated Sale via WebSocket

    ```json
    // POS sends:
    { "action": "sale", "amount": 2500, "currency": "NZD" }

    // Terminal sends events in sequence:
    { "event": "waiting_for_card", "transactionId": "txn_01J...", "message": "Present card" }
    { "event": "card_detected", "transactionId": "txn_01J...", "entryMode": "CONTACTLESS", "scheme": "VISA", "maskedPan": "****1234" }
    { "event": "processing", "transactionId": "txn_01J...", "message": "Processing" }
    { "event": "result", "response": { "transactionId": "txn_01J...", "status": "APPROVED", "amount": 2500, "currency": "NZD", ... } }
    ```

    ### Example: Granular Mode via WebSocket

    ```json
    // POS sends:
    { "action": "sale", "amount": 5000, "currency": "NZD", "granular": true }

    // Terminal sends chip data for POS-side routing:
    { "event": "waiting_for_card", "transactionId": "txn_01J...", "message": "Present card" }
    { "event": "card_detected", "transactionId": "txn_01J...", "entryMode": "CONTACTLESS", "scheme": "MASTERCARD", "maskedPan": "****5678" }
    { "event": "online_authorization", "transactionId": "txn_01J...", "chipData": { "emvTags": { "9F26": "AABB...", "9F27": "80", ... }, "encryptedPinBlock": null, "pinKsn": null } }

    // POS routes to acquirer, then completes:
    { "action": "complete", "transactionId": "txn_01J...", "responseCode": "00", "authorizationCode": "123456", "issuerAuthData": "3030" }

    // Terminal sends final result:
    { "event": "result", "response": { "transactionId": "txn_01J...", "status": "APPROVED", ... } }
    ```

  contact:
    name: Atlas SoftPOS Engineering
    email: engineering@atlas-softpos.com
  license:
    name: Proprietary

servers:
  - url: http://terminal:8443
    description: Local terminal device on local network (IP/USB)
  - url: https://api.atlas-softpos.com
    description: Cloud-hosted API

security:
  - apiKey: []
  - bearerAuth: []

tags:
  - name: Device
    description: Terminal device health and status
  - name: Transactions
    description: Financial transaction operations (orchestrated mode)
  - name: Granular
    description: Granular transaction mode for POS-side acquirer routing
  - name: Transaction Management
    description: Query and manage in-progress or completed transactions
  - name: Reconciliation
    description: Transaction totals and end-of-day settlement
  - name: Device Interaction
    description: Display, input, and print operations on the terminal

paths:
  # ──────────────────────────────────────────────
  # Device
  # ──────────────────────────────────────────────
  /v1/device/health:
    get:
      operationId: getDeviceHealth
      summary: Terminal health check
      description: |
        Returns the current health and connectivity status of the terminal device.
        Analogous to Adyen's Login call -- use this to verify the terminal is reachable
        and ready to process transactions before initiating a sale.
      tags:
        - Device
      responses:
        "200":
          description: Terminal is healthy and reachable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeviceStatus"
              example:
                connected: true
                terminalId: "TID-00012345"
                serialNumber: "SN-9876543210"
                firmwareVersion: "1.4.2"
                batteryLevel: 87
                connectivity:
                  method: WIFI
                  signalStrength: -42
                  networkName: "StoreNetwork-5G"
                lastTransactionTime: "2026-03-28T09:14:22Z"
                uptime: 86400
        "503":
          description: Terminal is not ready or unreachable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ──────────────────────────────────────────────
  # Financial Transactions (Orchestrated)
  # ──────────────────────────────────────────────
  /v1/transaction/sale:
    post:
      operationId: createSale
      summary: Start a sale
      description: |
        Initiate a sale transaction in orchestrated mode. The terminal handles the full
        lifecycle (card read, online authorization, completion). This call blocks until the
        transaction reaches a terminal state (APPROVED, DECLINED, CANCELLED, or ERROR).
      tags:
        - Transactions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TransactionRequest"
            example:
              type: SALE
              amount: 2500
              currency: "NZD"
              entryMode: AUTO
              referenceId: "POS-INV-20260328-001"
              tipAmount: 0
              metadata:
                cashier: "Jane"
                register: "REG-02"
      responses:
        "200":
          description: Transaction completed (check `status` for outcome)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionResponse"
              example:
                transactionId: "txn_01JQXYZ123456"
                status: APPROVED
                type: SALE
                amount: 2500
                currency: "NZD"
                tipAmount: 0
                authorizationCode: "A12345"
                responseCode: "00"
                cardScheme: VISA
                maskedPan: "************1234"
                entryMode: CONTACTLESS
                aid: "A0000000031010"
                applicationLabel: "Visa Credit"
                cryptogramType: TC
                tvr: "0000000000"
                cvmResult: NO_CVM
                referenceId: "POS-INV-20260328-001"
                rrn: "262803141422"
                timestamp: "2026-03-28T09:14:22Z"
                receiptData:
                  merchantName: "Atlas Coffee"
                  merchantId: "MID123456789"
                  terminalId: "TID-00012345"
                  transactionId: "txn_01JQXYZ123456"
                  dateTime: "2026-03-28T09:14:22Z"
                  cardScheme: VISA
                  maskedPan: "************1234"
                  applicationLabel: "Visa Credit"
                  aid: "A0000000031010"
                  authorizationCode: "A12345"
                  amount: 2500
                  tipAmount: 0
                  totalAmount: 2500
                  entryMode: CONTACTLESS
                  cvmPerformed: NO_CVM
                  approvalStatus: APPROVED
                metadata:
                  cashier: "Jane"
                  register: "REG-02"
        "400":
          description: Invalid request (missing fields, invalid amount, etc.)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Another transaction is already in progress
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: Terminal not ready
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/transaction/pre-auth:
    post:
      operationId: createPreAuth
      summary: Start a pre-authorization
      description: |
        Initiate a pre-authorization to reserve funds on the cardholder's account.
        The pre-auth must later be completed (captured) or voided. Blocks until result.
      tags:
        - Transactions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TransactionRequest"
            example:
              type: PRE_AUTH
              amount: 15000
              currency: "NZD"
              referenceId: "HOTEL-CHK-9012"
      responses:
        "200":
          description: Pre-authorization completed (check `status` for outcome)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "409":
          $ref: "#/components/responses/Conflict"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /v1/transaction/pre-auth/complete:
    post:
      operationId: completePreAuth
      summary: Complete a pre-authorization
      description: |
        Capture/complete a previously approved pre-authorization. The final amount may
        differ from the original pre-auth amount (partial capture). Requires the
        `originalTransactionId` of the pre-auth.
      tags:
        - Transactions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TransactionRequest"
            example:
              type: PRE_AUTH_COMPLETE
              amount: 13750
              currency: "NZD"
              originalTransactionId: "txn_01JQXYZ789012"
              referenceId: "HOTEL-CHK-9012"
      responses:
        "200":
          description: Pre-auth completion result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          description: Original pre-auth transaction not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          $ref: "#/components/responses/Conflict"

  /v1/transaction/refund:
    post:
      operationId: createRefund
      summary: Process a refund
      description: |
        Process a refund against a previously completed sale. Requires `originalTransactionId`.
        The refund amount may be less than or equal to the original sale amount (partial refund).
      tags:
        - Transactions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TransactionRequest"
            example:
              type: REFUND
              amount: 2500
              currency: "NZD"
              originalTransactionId: "txn_01JQXYZ123456"
              referenceId: "POS-RFD-20260328-001"
      responses:
        "200":
          description: Refund result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          description: Original transaction not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          $ref: "#/components/responses/Conflict"

  /v1/transaction/void:
    post:
      operationId: createVoid
      summary: Void a transaction
      description: |
        Void a previously completed transaction. The transaction must not yet be settled.
        Requires `originalTransactionId`. The full amount of the original transaction is reversed.
      tags:
        - Transactions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TransactionRequest"
            example:
              type: VOID
              amount: 2500
              currency: "NZD"
              originalTransactionId: "txn_01JQXYZ123456"
              referenceId: "POS-VOID-20260328-001"
      responses:
        "200":
          description: Void result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          description: Original transaction not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          $ref: "#/components/responses/Conflict"

  /v1/transaction/card-read:
    post:
      operationId: createCardRead
      summary: Read card data only
      description: |
        Read card data without performing a financial transaction. Useful for tokenization,
        loyalty lookups, or customer identification. Returns masked PAN, card scheme, and
        optionally a token depending on terminal configuration.
      tags:
        - Transactions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TransactionRequest"
            example:
              type: CARD_READ
              amount: 0
              currency: "NZD"
              entryMode: CONTACTLESS
              referenceId: "LOYALTY-SCAN-001"
      responses:
        "200":
          description: Card read result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "409":
          $ref: "#/components/responses/Conflict"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /v1/transaction/balance-inquiry:
    post:
      operationId: createBalanceInquiry
      summary: Check card balance
      description: |
        Query the available balance on a card. Typically used for prepaid or gift cards.
        Requires card presentation at the terminal.
      tags:
        - Transactions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TransactionRequest"
            example:
              type: BALANCE_INQUIRY
              amount: 0
              currency: "NZD"
              entryMode: AUTO
      responses:
        "200":
          description: Balance inquiry result (balance returned in `amount` field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "409":
          $ref: "#/components/responses/Conflict"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  # ──────────────────────────────────────────────
  # Granular Mode
  # ──────────────────────────────────────────────
  /v1/transaction/initiate:
    post:
      operationId: initiateTransaction
      summary: Initiate transaction (granular mode)
      description: |
        Start a transaction in granular mode. The terminal reads the card and returns EMV
        chip data (tags, encrypted PIN block) for the POS to route to its own acquirer.
        The response status will be `PENDING_ONLINE`, indicating the POS must now perform
        online authorization and call `/v1/transaction/complete` with the issuer response.

        This enables:
        - Multi-acquirer routing based on card scheme, BIN, or business rules
        - Store-and-forward for offline-capable environments
        - Custom authorization logic at the POS layer
      tags:
        - Granular
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TransactionRequest"
            example:
              type: SALE
              amount: 5000
              currency: "NZD"
              entryMode: AUTO
              referenceId: "POS-GRAN-20260328-001"
      responses:
        "200":
          description: |
            Card data returned. Status is `PENDING_ONLINE`. The `chipData` field contains
            EMV tags and optional encrypted PIN block for acquirer authorization.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionResponse"
              example:
                transactionId: "txn_01JQXYZ345678"
                status: PENDING_ONLINE
                type: SALE
                amount: 5000
                currency: "NZD"
                cardScheme: MASTERCARD
                maskedPan: "************5678"
                entryMode: CONTACTLESS
                aid: "A0000000041010"
                applicationLabel: "Mastercard"
                cryptogramType: ARQC
                chipData:
                  emvTags:
                    "9F26": "AABBCCDD11223344"
                    "9F27": "80"
                    "9F10": "0110A00003240000000000000000000000FF"
                    "9F37": "12345678"
                    "9F36": "0042"
                    "9C": "00"
                    "9F02": "000000005000"
                    "5F2A": "0554"
                    "9A": "260328"
                    "9F1A": "0554"
                  encryptedPinBlock: null
                  pinKsn: null
                  pinBlockFormat: null
                referenceId: "POS-GRAN-20260328-001"
                timestamp: "2026-03-28T09:20:05Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "409":
          $ref: "#/components/responses/Conflict"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /v1/transaction/complete:
    post:
      operationId: completeTransaction
      summary: Complete online authorization (granular mode)
      description: |
        Complete a transaction that is in `PENDING_ONLINE` status by providing the issuer's
        authorization response. The terminal will perform second-generate AC processing
        and return the final transaction result.

        If the issuer declined, pass the appropriate `responseCode` (e.g., `"05"`) and the
        terminal will handle the decline flow.
      tags:
        - Granular
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OnlineAuthResponse"
            example:
              transactionId: "txn_01JQXYZ345678"
              responseCode: "00"
              authorizationCode: "B67890"
              issuerAuthData: "3030"
              issuerScripts71: null
              issuerScripts72: null
      responses:
        "200":
          description: Transaction completed (check `status` for final outcome)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionResponse"
              example:
                transactionId: "txn_01JQXYZ345678"
                status: APPROVED
                type: SALE
                amount: 5000
                currency: "NZD"
                authorizationCode: "B67890"
                responseCode: "00"
                cardScheme: MASTERCARD
                maskedPan: "************5678"
                entryMode: CONTACTLESS
                aid: "A0000000041010"
                applicationLabel: "Mastercard"
                cryptogramType: TC
                tvr: "0000000000"
                cvmResult: NO_CVM
                referenceId: "POS-GRAN-20260328-001"
                rrn: "262803142005"
                timestamp: "2026-03-28T09:20:08Z"
                receiptData:
                  merchantName: "Atlas Coffee"
                  merchantId: "MID123456789"
                  terminalId: "TID-00012345"
                  transactionId: "txn_01JQXYZ345678"
                  dateTime: "2026-03-28T09:20:08Z"
                  cardScheme: MASTERCARD
                  maskedPan: "************5678"
                  applicationLabel: "Mastercard"
                  aid: "A0000000041010"
                  authorizationCode: "B67890"
                  amount: 5000
                  tipAmount: 0
                  totalAmount: 5000
                  entryMode: CONTACTLESS
                  cvmPerformed: NO_CVM
                  approvalStatus: APPROVED
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          description: Transaction not found or not in PENDING_ONLINE state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          $ref: "#/components/responses/Conflict"

  # ──────────────────────────────────────────────
  # Transaction Management
  # ──────────────────────────────────────────────
  /v1/transactions:
    get:
      operationId: listTransactions
      summary: List transactions
      description: |
        Search and filter transactions across the terminal. Cursor-paginated,
        filterable by date range, status, type, card scheme, and reference ID.
        Ordered by timestamp descending (newest first).
      tags:
        - Transaction Management
      parameters:
        - name: cursor
          in: query
          schema:
            type: string
          description: Opaque pagination cursor from a previous response
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: from
          in: query
          schema:
            type: string
            format: date-time
          description: Include transactions on or after this timestamp
        - name: to
          in: query
          schema:
            type: string
            format: date-time
          description: Include transactions before this timestamp
        - name: status
          in: query
          schema:
            type: string
            enum: [APPROVED, DECLINED, CANCELLED, ERROR, REVERSED, PENDING_ONLINE, PENDING_COMPLETION]
          description: Filter by transaction status
        - name: type
          in: query
          schema:
            type: string
            enum: [SALE, PRE_AUTH, PRE_AUTH_COMPLETE, REFUND, VOID, CARD_READ, BALANCE_INQUIRY]
          description: Filter by transaction type
        - name: cardScheme
          in: query
          schema:
            type: string
            enum: [VISA, MASTERCARD, AMEX, DISCOVER, JCB, UNIONPAY, EFTPOS, OTHER]
          description: Filter by card scheme
        - name: referenceId
          in: query
          schema:
            type: string
          description: Exact match on POS-assigned reference ID
      responses:
        "200":
          description: Paginated transaction list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/TransactionResponse"
                  hasMore:
                    type: boolean
                  nextCursor:
                    type: string
                    nullable: true
                  totalCount:
                    type: integer
                    description: Total matching transactions (may be approximate for large result sets)

  /v1/transaction/{transactionId}:
    get:
      operationId: getTransaction
      summary: Get transaction by ID
      description: |
        Retrieve the current state and details of a transaction by its unique identifier.
        Can be used to poll for status or retrieve receipt data after completion.
      tags:
        - Transaction Management
      parameters:
        - name: transactionId
          in: path
          required: true
          description: Unique transaction identifier
          schema:
            type: string
          example: "txn_01JQXYZ123456"
      responses:
        "200":
          description: Transaction details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionResponse"
        "404":
          description: Transaction not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/transaction/{transactionId}/abort:
    post:
      operationId: abortTransaction
      summary: Abort in-progress transaction
      description: |
        Abort a transaction that is currently in progress. If the transaction has already
        reached a terminal state (APPROVED, DECLINED, etc.), this call returns an error.
        Use void instead for completed transactions.
      tags:
        - Transaction Management
      parameters:
        - name: transactionId
          in: path
          required: true
          description: Unique transaction identifier
          schema:
            type: string
          example: "txn_01JQXYZ123456"
      responses:
        "200":
          description: Transaction aborted successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionResponse"
        "404":
          description: Transaction not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Transaction is not in an abortable state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ──────────────────────────────────────────────
  # Reconciliation
  # ──────────────────────────────────────────────
  /v1/totals:
    get:
      operationId: getTransactionTotals
      summary: Get running transaction totals
      description: |
        Retrieve running transaction totals for the current settlement period.
        Optionally filter by currency. Useful for end-of-day balancing before
        performing reconciliation.
      tags:
        - Reconciliation
      parameters:
        - name: currency
          in: query
          required: false
          description: Filter totals by ISO 4217 currency code
          schema:
            type: string
            pattern: "^[A-Z]{3}$"
          example: "NZD"
      responses:
        "200":
          description: Current transaction totals
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionTotals"
              example:
                saleCount: 47
                saleAmount: 328750
                refundCount: 2
                refundAmount: 5000
                voidCount: 1
                voidAmount: 1200
                preAuthCount: 3
                preAuthAmount: 45000
                currency: "NZD"

  /v1/reconciliation:
    post:
      operationId: performReconciliation
      summary: Settle and close day
      description: |
        Perform end-of-day reconciliation. When `closeDay` is true, the terminal settles
        all pending transactions, resets running totals, and returns a summary of the
        settlement period.
      tags:
        - Reconciliation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReconciliationRequest"
            example:
              closeDay: true
      responses:
        "200":
          description: Reconciliation result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReconciliationResponse"
              example:
                status: SUCCESS
                totals:
                  saleCount: 47
                  saleAmount: 328750
                  refundCount: 2
                  refundAmount: 5000
                  voidCount: 1
                  voidAmount: 1200
                  preAuthCount: 3
                  preAuthAmount: 45000
                  currency: "NZD"
                timestamp: "2026-03-28T23:00:00Z"
        "409":
          description: Reconciliation already in progress
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  # ──────────────────────────────────────────────
  # Settlement History
  # ──────────────────────────────────────────────
  /v1/settlements:
    get:
      operationId: listSettlements
      summary: List settlement batches
      description: Return historical settlement batches, cursor-paginated, newest first.
      tags:
        - Reconciliation
      parameters:
        - name: cursor
          in: query
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
        - name: from
          in: query
          schema:
            type: string
            format: date-time
        - name: to
          in: query
          schema:
            type: string
            format: date-time
        - name: currency
          in: query
          schema:
            type: string
            pattern: "^[A-Z]{3}$"
      responses:
        "200":
          description: Paginated list of settlement batches
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/SettlementBatch"
                  hasMore:
                    type: boolean
                  nextCursor:
                    type: string
                    nullable: true

  /v1/settlements/{settlementId}:
    get:
      operationId: getSettlement
      summary: Get a settlement batch
      description: Retrieve details of a single settlement batch.
      tags:
        - Reconciliation
      parameters:
        - name: settlementId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Settlement batch details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementBatch"
        "404":
          description: Settlement not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/settlements/{settlementId}/transactions:
    get:
      operationId: listSettlementTransactions
      summary: List transactions in a settlement batch
      description: Cursor-paginated list of transactions that rolled into this settlement.
      tags:
        - Reconciliation
      parameters:
        - name: settlementId
          in: path
          required: true
          schema:
            type: string
        - name: cursor
          in: query
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: type
          in: query
          schema:
            type: string
            enum: [SALE, REFUND, VOID, PRE_AUTH_COMPLETE]
      responses:
        "200":
          description: Paginated transaction list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/TransactionResponse"
                  hasMore:
                    type: boolean
                  nextCursor:
                    type: string
                    nullable: true
        "404":
          description: Settlement not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/settlements/{settlementId}/export:
    get:
      operationId: exportSettlement
      summary: Export a settlement batch
      description: Stream the full transaction listing as CSV or JSONL.
      tags:
        - Reconciliation
      parameters:
        - name: settlementId
          in: path
          required: true
          schema:
            type: string
        - name: format
          in: query
          required: true
          schema:
            type: string
            enum: [csv, jsonl]
      responses:
        "200":
          description: Streamed export file
          content:
            text/csv:
              schema:
                type: string
            application/jsonl:
              schema:
                type: string
        "404":
          description: Settlement not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ──────────────────────────────────────────────
  # WebSocket Session
  # ──────────────────────────────────────────────
  /v1/session:
    get:
      operationId: openSession
      summary: Open a WebSocket session
      description: |
        Upgrade to WebSocket for real-time transaction event streaming.
        Send JSON action messages (sale, pre_auth, card_read, complete,
        display, input, abort) and receive TransactionEvent messages.
      tags:
        - WebSocket Stream
      responses:
        "101":
          description: Switching Protocols — WebSocket upgrade successful

  # ──────────────────────────────────────────────
  # Device Interaction
  # ──────────────────────────────────────────────
  /v1/device/display:
    post:
      operationId: displayOnTerminal
      summary: Show text on terminal screen
      description: |
        Display text or content on the terminal screen. Can be used for idle-screen
        branding, transaction prompts, or status messages. The terminal clears the
        display after `duration` milliseconds, or keeps it until the next display
        call or transaction.
      tags:
        - Device Interaction
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DisplayRequest"
            example:
              lines:
                - text: "Welcome to Atlas Coffee"
                  alignment: CENTER
                  fontSize: LARGE
                  bold: true
                - text: "Tap to pay"
                  alignment: CENTER
                  fontSize: MEDIUM
                  bold: false
              duration: 10000
              clearPrevious: true
      responses:
        "200":
          description: Display updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                example:
                  success: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /v1/device/input:
    post:
      operationId: requestInput
      summary: Request input from terminal
      description: |
        Request input from the cardholder or operator via the terminal screen.
        Supports text entry, numeric entry, email, phone, selection lists,
        yes/no prompts, and star ratings. Blocks until the user responds or
        the timeout is reached.
      tags:
        - Device Interaction
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InputRequest"
            example:
              prompt: "Enter your email for receipt"
              inputType: EMAIL
              timeoutMs: 30000
      responses:
        "200":
          description: Input received (or cancelled/timed out)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InputResponse"
              example:
                value: "customer@example.com"
                cancelled: false
                timedOut: false
        "400":
          $ref: "#/components/responses/BadRequest"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /v1/device/print:
    post:
      operationId: printOnTerminal
      summary: Print on terminal printer
      description: |
        Send a print job to the terminal's built-in printer (if available).
        Supports text lines with formatting options. Set `cutPaper` to true
        to cut the paper after printing.
      tags:
        - Device Interaction
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PrintRequest"
            example:
              lines:
                - text: "Atlas Coffee"
                  alignment: CENTER
                  bold: true
                  fontSize: LARGE
                - text: "================================"
                  alignment: CENTER
                  bold: false
                  fontSize: NORMAL
                - text: "Flat White x1         $5.00"
                  alignment: LEFT
                  bold: false
                  fontSize: NORMAL
                - text: "================================"
                  alignment: CENTER
                  bold: false
                  fontSize: NORMAL
                - text: "TOTAL                 $5.00"
                  alignment: LEFT
                  bold: true
                  fontSize: NORMAL
                - text: "Thank you!"
                  alignment: CENTER
                  bold: false
                  fontSize: NORMAL
              cutPaper: true
      responses:
        "200":
          description: Print job accepted
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                example:
                  success: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "503":
          description: Printer not available or terminal unreachable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

# ════════════════════════════════════════════════
# Components
# ════════════════════════════════════════════════
components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-Api-Key
      description: API key for authenticating POS-to-terminal requests (local and cloud)
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT bearer token for cloud API authentication

  responses:
    BadRequest:
      description: Invalid request parameters
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Conflict:
      description: Another transaction is already in progress on this terminal
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    ServiceUnavailable:
      description: Terminal is not ready or unreachable
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

  schemas:
    # ──────────────────────────────────────────
    # Transaction Request / Response
    # ──────────────────────────────────────────
    TransactionRequest:
      type: object
      required:
        - type
        - amount
        - currency
      properties:
        type:
          type: string
          description: Transaction type
          enum:
            - SALE
            - PRE_AUTH
            - PRE_AUTH_COMPLETE
            - REFUND
            - VOID
            - CARD_READ
            - BALANCE_INQUIRY
        amount:
          type: integer
          minimum: 0
          description: |
            Transaction amount in minor units (e.g., cents). For a $25.00 NZD transaction,
            send `2500`. Use `0` for card-read and balance-inquiry operations.
          example: 2500
        currency:
          type: string
          pattern: "^[A-Z]{3}$"
          description: ISO 4217 currency code
          example: "NZD"
        cashbackAmount:
          type: integer
          minimum: 0
          description: Cashback amount in minor units (sale transactions only)
          example: 0
        entryMode:
          type: string
          description: |
            Card entry mode preference. `AUTO` lets the terminal accept any method.
          enum:
            - CONTACTLESS
            - CONTACT
            - AUTO
          default: AUTO
        referenceId:
          type: string
          maxLength: 64
          description: POS-assigned reference identifier for this transaction
          example: "POS-INV-20260328-001"
        originalTransactionId:
          type: string
          description: |
            Required for refund, void, and pre-auth completion. The `transactionId` of
            the original transaction being referenced.
          example: "txn_01JQXYZ123456"
        tipAmount:
          type: integer
          minimum: 0
          description: Tip amount in minor units
          example: 0
        metadata:
          type: object
          additionalProperties:
            type: string
          description: |
            Arbitrary key-value metadata to attach to the transaction. Returned in
            the response and available in reconciliation exports. Max 20 keys,
            max 256 characters per value.
          example:
            cashier: "Jane"
            register: "REG-02"

    TransactionResponse:
      type: object
      required:
        - transactionId
        - status
        - type
        - amount
        - currency
        - timestamp
      properties:
        transactionId:
          type: string
          description: Unique identifier for this transaction
          example: "txn_01JQXYZ123456"
        status:
          $ref: "#/components/schemas/TransactionStatus"
        type:
          type: string
          enum:
            - SALE
            - PRE_AUTH
            - PRE_AUTH_COMPLETE
            - REFUND
            - VOID
            - CARD_READ
            - BALANCE_INQUIRY
          description: Transaction type
        amount:
          type: integer
          description: Transaction amount in minor units
          example: 2500
        currency:
          type: string
          description: ISO 4217 currency code
          example: "NZD"
        tipAmount:
          type: integer
          description: Tip amount in minor units
          example: 0
        authorizationCode:
          type: string
          description: Authorization code from the issuer
          example: "A12345"
        responseCode:
          type: string
          description: |
            ISO 8583 response code. `"00"` = approved. See issuer documentation for
            full code table.
          example: "00"
        cardScheme:
          type: string
          description: Card scheme / network
          enum:
            - VISA
            - MASTERCARD
            - AMEX
            - DISCOVER
            - JCB
            - UNIONPAY
            - EFTPOS
            - OTHER
          example: "VISA"
        maskedPan:
          type: string
          description: Masked primary account number (first 6 and last 4 visible, rest masked)
          example: "************1234"
        entryMode:
          type: string
          description: Actual card entry mode used
          enum:
            - CONTACTLESS
            - CONTACT
            - MAGSTRIPE
            - MANUAL
          example: "CONTACTLESS"
        aid:
          type: string
          description: EMV Application Identifier (AID) hex string
          example: "A0000000031010"
        applicationLabel:
          type: string
          description: EMV application label (human-readable card application name)
          example: "Visa Credit"
        cryptogramType:
          type: string
          description: |
            Type of cryptogram generated by the card.
            - `TC` -- Transaction Certificate (offline approved)
            - `ARQC` -- Authorization Request Cryptogram (online auth required)
            - `AAC` -- Application Authentication Cryptogram (declined)
          enum:
            - TC
            - ARQC
            - AAC
        tvr:
          type: string
          description: Terminal Verification Results (10-character hex string)
          example: "0000000000"
        cvmResult:
          type: string
          description: Cardholder Verification Method used
          enum:
            - PIN
            - SIGNATURE
            - CDCVM
            - NO_CVM
            - FAILED
          example: "NO_CVM"
        referenceId:
          type: string
          description: POS-assigned reference identifier (echoed from request)
          example: "POS-INV-20260328-001"
        rrn:
          type: string
          description: Retrieval Reference Number
          example: "262803141422"
        timestamp:
          type: string
          format: date-time
          description: Transaction timestamp (ISO 8601)
          example: "2026-03-28T09:14:22Z"
        receiptData:
          $ref: "#/components/schemas/ReceiptData"
        metadata:
          type: object
          additionalProperties:
            type: string
          description: Metadata echoed from the request
        chipData:
          $ref: "#/components/schemas/ChipData"

    TransactionStatus:
      type: string
      description: |
        Transaction status:
        - `APPROVED` -- Transaction approved by issuer
        - `DECLINED` -- Transaction declined by issuer or card
        - `CANCELLED` -- Transaction cancelled by cardholder or POS
        - `ERROR` -- A processing error occurred
        - `REVERSED` -- Transaction was reversed (void completed)
        - `PENDING_ONLINE` -- Granular mode: awaiting POS-side online authorization
        - `PENDING_COMPLETION` -- Pre-auth awaiting capture/completion
      enum:
        - APPROVED
        - DECLINED
        - CANCELLED
        - ERROR
        - REVERSED
        - PENDING_ONLINE
        - PENDING_COMPLETION

    # ──────────────────────────────────────────
    # Chip Data (Granular Mode)
    # ──────────────────────────────────────────
    ChipData:
      type: object
      description: |
        EMV chip data returned during granular mode for POS-side acquirer routing.
        Contains the EMV tags needed to construct an authorization request, plus
        optional encrypted PIN data.
      properties:
        emvTags:
          type: object
          additionalProperties:
            type: string
          description: |
            Map of EMV tag hex identifiers to hex-encoded values. Common tags include:
            - `9F26` -- Application Cryptogram
            - `9F27` -- Cryptogram Information Data
            - `9F10` -- Issuer Application Data
            - `9F37` -- Unpredictable Number
            - `9F36` -- Application Transaction Counter
            - `9C` -- Transaction Type
            - `9F02` -- Amount Authorized
            - `5F2A` -- Transaction Currency Code
            - `9A` -- Transaction Date
            - `9F1A` -- Terminal Country Code
          example:
            "9F26": "AABBCCDD11223344"
            "9F27": "80"
            "9F10": "0110A00003240000000000000000000000FF"
            "9F37": "12345678"
            "9F36": "0042"
        encryptedPinBlock:
          type: string
          nullable: true
          description: Encrypted PIN block (hex), or null if PIN was not entered
          example: null
        pinKsn:
          type: string
          nullable: true
          description: Key Serial Number for the PIN encryption key (hex)
          example: null
        pinBlockFormat:
          type: string
          nullable: true
          description: PIN block format identifier
          enum:
            - ISO_0
            - ISO_1
            - ISO_3
            - ISO_4
          example: null

    OnlineAuthResponse:
      type: object
      required:
        - transactionId
        - responseCode
      properties:
        transactionId:
          type: string
          description: The transaction ID from the initiate response
          example: "txn_01JQXYZ345678"
        responseCode:
          type: string
          description: |
            ISO 8583 response code from the issuer. `"00"` = approved.
            Any other code is treated as declined.
          example: "00"
        authorizationCode:
          type: string
          description: Authorization code from the issuer (required when approved)
          example: "B67890"
        issuerAuthData:
          type: string
          nullable: true
          description: Issuer Authentication Data (EMV tag 91), hex-encoded
          example: "3030"
        issuerScripts71:
          type: string
          nullable: true
          description: Issuer Script Template 1 (EMV tag 71), hex-encoded
          example: null
        issuerScripts72:
          type: string
          nullable: true
          description: Issuer Script Template 2 (EMV tag 72), hex-encoded
          example: null

    # ──────────────────────────────────────────
    # Transaction Events (WebSocket)
    # ──────────────────────────────────────────
    TransactionEvent:
      description: |
        Discriminated union of WebSocket event messages. The `event` field determines
        the event type and which additional fields are present.
      oneOf:
        - $ref: "#/components/schemas/WaitingForCardEvent"
        - $ref: "#/components/schemas/CardDetectedEvent"
        - $ref: "#/components/schemas/ProcessingEvent"
        - $ref: "#/components/schemas/PinRequiredEvent"
        - $ref: "#/components/schemas/OnlineAuthorizationEvent"
        - $ref: "#/components/schemas/ResultEvent"
        - $ref: "#/components/schemas/ErrorEvent"
        - $ref: "#/components/schemas/CancelledEvent"
      discriminator:
        propertyName: event
        mapping:
          waiting_for_card: "#/components/schemas/WaitingForCardEvent"
          card_detected: "#/components/schemas/CardDetectedEvent"
          processing: "#/components/schemas/ProcessingEvent"
          pin_required: "#/components/schemas/PinRequiredEvent"
          online_authorization: "#/components/schemas/OnlineAuthorizationEvent"
          result: "#/components/schemas/ResultEvent"
          error: "#/components/schemas/ErrorEvent"
          cancelled: "#/components/schemas/CancelledEvent"

    WaitingForCardEvent:
      type: object
      required:
        - event
        - transactionId
        - message
      properties:
        event:
          type: string
          enum: [waiting_for_card]
        transactionId:
          type: string
          example: "txn_01JQXYZ123456"
        message:
          type: string
          description: Human-readable prompt (e.g., "Present card")
          example: "Present card"

    CardDetectedEvent:
      type: object
      required:
        - event
        - scheme
        - maskedPan
        - entryMode
      properties:
        event:
          type: string
          enum: [card_detected]
        transactionId:
          type: string
          nullable: true
          example: "txn_01JQXYZ123456"
        scheme:
          type: string
          description: Detected card scheme
          example: "VISA"
        maskedPan:
          type: string
          description: Masked PAN (last 4 digits visible)
          example: "****1234"
        entryMode:
          type: string
          enum:
            - CONTACTLESS
            - CONTACT
            - AUTO
          example: "CONTACTLESS"

    ProcessingEvent:
      type: object
      required:
        - event
        - transactionId
        - message
      properties:
        event:
          type: string
          enum: [processing]
        transactionId:
          type: string
          example: "txn_01JQXYZ123456"
        message:
          type: string
          description: Processing stage description
          example: "Authorizing"

    PinRequiredEvent:
      type: object
      required:
        - event
        - transactionId
      properties:
        event:
          type: string
          enum: [pin_required]
        transactionId:
          type: string
          example: "txn_01JQXYZ123456"

    OnlineAuthorizationEvent:
      type: object
      required:
        - event
        - transactionId
        - chipData
      properties:
        event:
          type: string
          enum: [online_authorization]
        transactionId:
          type: string
          example: "txn_01JQXYZ345678"
        chipData:
          $ref: "#/components/schemas/ChipData"

    ResultEvent:
      type: object
      description: Final transaction result delivered via WebSocket. The full TransactionResponse is nested under the `response` field.
      required:
        - event
        - response
      properties:
        event:
          type: string
          enum: [result]
        response:
          $ref: "#/components/schemas/TransactionResponse"

    ErrorEvent:
      type: object
      required:
        - event
        - errorCode
        - message
      properties:
        event:
          type: string
          enum: [error]
        transactionId:
          type: string
          nullable: true
          description: Transaction ID (null if error occurred before transaction was created)
          example: "txn_01JQXYZ123456"
        errorCode:
          type: string
          description: Machine-readable error code
          enum:
            - TERMINAL_BUSY
            - CARD_READ_ERROR
            - COMMUNICATION_ERROR
            - TIMEOUT
            - INTERNAL_ERROR
            - UNSUPPORTED_CARD
            - INVALID_REQUEST
          example: "CARD_READ_ERROR"
        message:
          type: string
          description: Human-readable error description
          example: "Failed to read card -- please try again"

    CancelledEvent:
      type: object
      required:
        - event
        - transactionId
        - reason
      properties:
        event:
          type: string
          enum: [cancelled]
        transactionId:
          type: string
          example: "txn_01JQXYZ123456"
        reason:
          type: string
          description: Reason for cancellation
          enum:
            - USER_CANCELLED
            - POS_ABORTED
            - TIMEOUT
            - CARD_REMOVED
          example: "USER_CANCELLED"

    # ──────────────────────────────────────────
    # Device Interaction
    # ──────────────────────────────────────────
    DisplayRequest:
      type: object
      required:
        - lines
      properties:
        lines:
          type: array
          items:
            $ref: "#/components/schemas/DisplayLine"
          minItems: 1
          maxItems: 10
          description: Lines of text to display on the terminal screen
        duration:
          type: integer
          minimum: 0
          description: |
            Duration in milliseconds to show the display. 0 or omitted means
            the display persists until replaced by another display call or transaction.
          example: 10000
        clearPrevious:
          type: boolean
          default: true
          description: Clear the screen before displaying new content

    DisplayLine:
      type: object
      required:
        - text
      properties:
        text:
          type: string
          maxLength: 40
          description: Text content for this line
          example: "Welcome to Atlas Coffee"
        alignment:
          type: string
          enum:
            - LEFT
            - CENTER
            - RIGHT
          default: LEFT
          description: Text alignment
        fontSize:
          type: string
          enum:
            - SMALL
            - NORMAL
            - MEDIUM
            - LARGE
          default: NORMAL
          description: Font size
        bold:
          type: boolean
          default: false
          description: Whether to render text in bold

    InputRequest:
      type: object
      required:
        - prompt
        - inputType
      properties:
        prompt:
          type: string
          maxLength: 100
          description: Prompt text displayed to the user
          example: "Enter your email for receipt"
        inputType:
          type: string
          description: Type of input to request
          enum:
            - TEXT
            - NUMERIC
            - EMAIL
            - PHONE
            - SELECTION
            - YES_NO
            - RATING
        options:
          type: array
          items:
            type: string
          description: |
            Options for SELECTION input type. Each string is a selectable option
            displayed on screen.
          example: ["Dine In", "Takeaway", "Delivery"]
        minLength:
          type: integer
          minimum: 0
          description: Minimum input length (for TEXT, NUMERIC, EMAIL, PHONE types)
          example: 1
        maxLength:
          type: integer
          minimum: 1
          description: Maximum input length (for TEXT, NUMERIC, EMAIL, PHONE types)
          example: 100
        timeoutMs:
          type: integer
          minimum: 1000
          maximum: 120000
          description: >
            Timeout in milliseconds. If the user does not respond within this
            time, the request returns with `timedOut: true`.
          default: 30000
          example: 30000

    InputResponse:
      type: object
      required:
        - cancelled
        - timedOut
      properties:
        value:
          type: string
          nullable: true
          description: |
            The user's input value. Null if cancelled or timed out.
            For YES_NO, returns `"YES"` or `"NO"`.
            For RATING, returns the numeric rating as a string (e.g., `"4"`).
            For SELECTION, returns the selected option text.
          example: "customer@example.com"
        cancelled:
          type: boolean
          description: Whether the user explicitly cancelled the input
          example: false
        timedOut:
          type: boolean
          description: Whether the input timed out without a response
          example: false

    PrintRequest:
      type: object
      required:
        - lines
      properties:
        lines:
          type: array
          items:
            $ref: "#/components/schemas/PrintLine"
          minItems: 1
          maxItems: 100
          description: Lines to print
        cutPaper:
          type: boolean
          default: false
          description: Whether to cut the paper after printing

    PrintLine:
      type: object
      required:
        - text
      properties:
        text:
          type: string
          maxLength: 48
          description: Text content for this print line
          example: "Atlas Coffee"
        alignment:
          type: string
          enum:
            - LEFT
            - CENTER
            - RIGHT
          default: LEFT
          description: Text alignment
        bold:
          type: boolean
          default: false
          description: Whether to render text in bold
        fontSize:
          type: string
          enum:
            - SMALL
            - NORMAL
            - LARGE
          default: NORMAL
          description: Font size

    # ──────────────────────────────────────────
    # Receipt Data
    # ──────────────────────────────────────────
    ReceiptData:
      type: object
      description: Structured receipt data suitable for rendering or printing a customer/merchant receipt
      properties:
        merchantName:
          type: string
          description: Merchant trading name
          example: "Atlas Coffee"
        merchantId:
          type: string
          description: Merchant identifier (MID)
          example: "MID123456789"
        terminalId:
          type: string
          description: Terminal identifier (TID)
          example: "TID-00012345"
        transactionId:
          type: string
          description: Transaction identifier
          example: "txn_01JQXYZ123456"
        dateTime:
          type: string
          format: date-time
          description: Transaction date and time (ISO 8601)
          example: "2026-03-28T09:14:22Z"
        cardScheme:
          type: string
          description: Card scheme name
          example: "VISA"
        maskedPan:
          type: string
          description: Masked PAN for receipt display
          example: "************1234"
        applicationLabel:
          type: string
          description: EMV application label
          example: "Visa Credit"
        aid:
          type: string
          description: EMV Application Identifier
          example: "A0000000031010"
        authorizationCode:
          type: string
          description: Issuer authorization code
          example: "A12345"
        amount:
          type: integer
          description: Transaction amount in minor units
          example: 2500
        tipAmount:
          type: integer
          description: Tip amount in minor units
          example: 0
        totalAmount:
          type: integer
          description: Total amount (amount + tip) in minor units
          example: 2500
        entryMode:
          type: string
          description: Card entry mode
          example: "CONTACTLESS"
        cvmPerformed:
          type: string
          description: Cardholder verification method performed
          enum:
            - PIN
            - SIGNATURE
            - CDCVM
            - NO_CVM
            - FAILED
          example: "NO_CVM"
        approvalStatus:
          type: string
          description: Human-readable approval status for receipt
          enum:
            - APPROVED
            - DECLINED
          example: "APPROVED"

    # ──────────────────────────────────────────
    # Reconciliation
    # ──────────────────────────────────────────
    ReconciliationRequest:
      type: object
      required:
        - closeDay
      properties:
        closeDay:
          type: boolean
          description: |
            When true, settles all pending transactions and resets running totals.
            When false, returns current totals without settling (dry run).
          example: true

    ReconciliationResponse:
      type: object
      required:
        - status
        - totals
        - timestamp
      properties:
        status:
          type: string
          description: |
            Reconciliation outcome:
            - `SUCCESS` -- All transactions settled successfully
            - `FAILED` -- Settlement failed (totals not reset)
            - `PARTIAL` -- Some transactions settled, others failed (check totals)
          enum:
            - SUCCESS
            - FAILED
            - PARTIAL
        totals:
          $ref: "#/components/schemas/TransactionTotals"
        timestamp:
          type: string
          format: date-time
          description: Reconciliation timestamp
          example: "2026-03-28T23:00:00Z"

    TransactionTotals:
      type: object
      required:
        - saleCount
        - saleAmount
        - refundCount
        - refundAmount
        - voidCount
        - voidAmount
        - preAuthCount
        - preAuthAmount
        - currency
      properties:
        saleCount:
          type: integer
          minimum: 0
          description: Number of completed sale transactions
          example: 47
        saleAmount:
          type: integer
          minimum: 0
          description: Total sale amount in minor units
          example: 328750
        refundCount:
          type: integer
          minimum: 0
          description: Number of completed refund transactions
          example: 2
        refundAmount:
          type: integer
          minimum: 0
          description: Total refund amount in minor units
          example: 5000
        voidCount:
          type: integer
          minimum: 0
          description: Number of voided transactions
          example: 1
        voidAmount:
          type: integer
          minimum: 0
          description: Total voided amount in minor units
          example: 1200
        preAuthCount:
          type: integer
          minimum: 0
          description: Number of pre-authorization transactions
          example: 3
        preAuthAmount:
          type: integer
          minimum: 0
          description: Total pre-auth amount in minor units
          example: 45000
        currency:
          type: string
          pattern: "^[A-Z]{3}$"
          description: ISO 4217 currency code for these totals
          example: "NZD"

    # ──────────────────────────────────────────
    # Device Status
    # ──────────────────────────────────────────
    DeviceStatus:
      type: object
      required:
        - connected
        - terminalId
      properties:
        connected:
          type: boolean
          description: Whether the terminal is connected and operational
          example: true
        terminalId:
          type: string
          description: Terminal identifier (TID)
          example: "TID-00012345"
        serialNumber:
          type: string
          description: Device serial number
          example: "SN-9876543210"
        firmwareVersion:
          type: string
          description: Current firmware version
          example: "1.4.2"
        batteryLevel:
          type: integer
          minimum: 0
          maximum: 100
          nullable: true
          description: Battery level percentage (null for mains-powered terminals)
          example: 87
        connectivity:
          $ref: "#/components/schemas/ConnectivityInfo"
        lastTransactionTime:
          type: string
          format: date-time
          nullable: true
          description: Timestamp of the last completed transaction (null if none)
          example: "2026-03-28T09:14:22Z"
        uptime:
          type: integer
          minimum: 0
          description: Terminal uptime in seconds since last boot
          example: 86400

    ConnectivityInfo:
      type: object
      required:
        - method
      properties:
        method:
          type: string
          description: Network connectivity method
          enum:
            - WIFI
            - CELLULAR
            - ETHERNET
            - USB
          example: "WIFI"
        signalStrength:
          type: integer
          nullable: true
          description: Signal strength in dBm (null for wired connections)
          example: -42
        networkName:
          type: string
          nullable: true
          description: Network SSID or name (null for cellular/USB)
          example: "StoreNetwork-5G"

    # ──────────────────────────────────────────
    # Settlement Batch
    # ──────────────────────────────────────────
    SettlementBatch:
      type: object
      required:
        - id
        - closedAt
        - status
        - currency
        - netAmount
        - merchantId
        - terminalId
      properties:
        id:
          type: string
          description: Settlement batch identifier
          example: "stl_01JQDAY00042"
        closedAt:
          type: string
          format: date-time
          description: Timestamp when the batch was closed
          example: "2026-04-08T23:00:00Z"
        status:
          type: string
          enum: [PENDING, SETTLED, FAILED, PARTIAL]
          description: Settlement batch status
        currency:
          type: string
          pattern: "^[A-Z]{3}$"
          example: "NZD"
        saleCount:
          type: integer
        saleAmount:
          type: integer
        refundCount:
          type: integer
        refundAmount:
          type: integer
        voidCount:
          type: integer
        voidAmount:
          type: integer
        netAmount:
          type: integer
          description: Net settled amount (sales - refunds - voids) in minor units
          example: 322550
        acquirerBatchRef:
          type: string
          description: Acquirer-side batch reference for downstream reconciliation
          example: "BATCH-20260408-0042"
        fundingDate:
          type: string
          format: date-time
          description: Expected date funds will be credited to the merchant account
          example: "2026-04-10T00:00:00Z"
        merchantId:
          type: string
          example: "MID123456789"
        terminalId:
          type: string
          example: "TID-00012345"

    # ──────────────────────────────────────────
    # Error
    # ──────────────────────────────────────────
    ErrorResponse:
      type: object
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: Machine-readable error code
          example: "INVALID_REQUEST"
        message:
          type: string
          description: Human-readable error description
          example: "The 'amount' field must be a positive integer"
        details:
          type: object
          additionalProperties: true
          description: Additional error context (field-level validation errors, etc.)
