openapi: 3.0.3
info:
  title: Payment Processing API
  version: 1.0.0
security:
- bearerAuth: []
paths:
  /api/v1/payments:
    post:
      summary: Submit an atomic JOD internal transfer
      security:
      - bearerAuth: []
      description: Requires payments:write scope and source debit authority. Canonically
        identical retries return the original result. Unknown/duplicate fields and
        XML DTD/entities are forbidden.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Payment'
            example:
              paymentId: d92192df-f79a-4553-8120-5660d2eb1294
              fromAccount: ACC-1001
              toAccount: ACC-2002
              amount: '125.750'
              date: '2026-10-28T13:30:00+03:00'
              purpose: Invoice INV-2048
              notes: October services
          application/xml:
            schema:
              $ref: '#/components/schemas/Payment'
            example: <?xml version="1.0"?><payment><paymentId>d92192df-f79a-4553-8120-5660d2eb1294</paymentId><fromAccount>ACC-1001</fromAccount><toAccount>ACC-2002</toAccount><amount>125.750</amount><date>2026-10-28T13:30:00+03:00</date><purpose>Invoice
              INV-2048</purpose><notes>October services</notes></payment>
      responses:
        '201':
          description: First completed transfer
          headers:
            Location:
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Receipt'
            application/xml:
              schema:
                $ref: '#/components/schemas/Receipt'
        '200':
          description: Idempotent replay of completed transfer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Receipt'
            application/xml:
              schema:
                $ref: '#/components/schemas/Receipt'
        '400': &id002
          description: MALFORMED_MESSAGE or VALIDATION_FAILED
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
            application/xml:
              schema:
                $ref: '#/components/schemas/Error'
        '401': &id003
          description: UNAUTHENTICATED
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
            application/xml:
              schema:
                $ref: '#/components/schemas/Error'
        '403': &id004
          description: FORBIDDEN
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
            application/xml:
              schema:
                $ref: '#/components/schemas/Error'
        '404': &id005
          description: NOT_FOUND or ACCOUNT_NOT_FOUND
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
            application/xml:
              schema:
                $ref: '#/components/schemas/Error'
        '406': &id006
          description: NOT_ACCEPTABLE
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
            application/xml:
              schema:
                $ref: '#/components/schemas/Error'
        '409': &id007
          description: ACCOUNT_NOT_ACTIVE, INSUFFICIENT_FUNDS, IDEMPOTENCY_CONFLICT
            or PAYMENT_LIMIT_EXCEEDED
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
            application/xml:
              schema:
                $ref: '#/components/schemas/Error'
        '413': &id008
          description: MESSAGE_TOO_LARGE
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
            application/xml:
              schema:
                $ref: '#/components/schemas/Error'
        '415': &id009
          description: UNSUPPORTED_MEDIA_TYPE
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
            application/xml:
              schema:
                $ref: '#/components/schemas/Error'
        '429': &id010
          description: RATE_LIMITED
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
            application/xml:
              schema:
                $ref: '#/components/schemas/Error'
          headers: &id001
            Retry-After:
              schema:
                type: integer
              description: Seconds before retrying the same payment ID
        '500': &id011
          description: INTERNAL_ERROR
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
            application/xml:
              schema:
                $ref: '#/components/schemas/Error'
        '503': &id012
          description: TEMPORARILY_UNAVAILABLE
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
            application/xml:
              schema:
                $ref: '#/components/schemas/Error'
          headers: *id001
  /api/v1/payments/{paymentId}:
    get:
      summary: Retrieve an authorized stored payment
      parameters:
      - in: path
        name: paymentId
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Stored outcome, including definitive REJECTED status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Receipt'
            application/xml:
              schema:
                $ref: '#/components/schemas/Receipt'
        '400': *id002
        '401': *id003
        '403': *id004
        '404': *id005
        '406': *id006
        '409': *id007
        '413': *id008
        '415': *id009
        '429': *id010
        '500': *id011
        '503': *id012
  /api/v1/accounts/{accountId}/balance:
    get:
      summary: Authorized persisted account balance
      parameters:
      - in: path
        name: accountId
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Available JOD balance
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Balance'
            application/xml:
              schema:
                $ref: '#/components/schemas/Balance'
        '400': *id002
        '401': *id003
        '403': *id004
        '404': *id005
        '406': *id006
        '409': *id007
        '413': *id008
        '415': *id009
        '429': *id010
        '500': *id011
        '503': *id012
  /api/v1/admin/reconciliation:
    get:
      summary: Operator reconciliation snapshot; requires payments:admin
      responses:
        '200':
          description: Consistency flag, issue identifiers and report timestamp
        '400': *id002
        '401': *id003
        '403': *id004
        '404': *id005
        '406': *id006
        '409': *id007
        '413': *id008
        '415': *id009
        '429': *id010
        '500': *id011
        '503': *id012
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  schemas:
    Payment:
      type: object
      additionalProperties: false
      required:
      - paymentId
      - fromAccount
      - toAccount
      - amount
      - date
      - purpose
      xml:
        name: payment
      properties:
        paymentId:
          type: string
          format: uuid
        fromAccount:
          type: string
          minLength: 1
          maxLength: 100
        toAccount:
          type: string
          minLength: 1
          maxLength: 100
        amount:
          oneOf:
          - type: string
            pattern: ^[0-9]+(\.[0-9]{1,3})?$
          - type: number
            minimum: 0.001
            maximum: 10000
            multipleOf: 0.001
        date:
          type: string
          format: date-time
        purpose:
          type: string
          minLength: 1
          maxLength: 255
        notes:
          type: string
          maxLength: 1000
    Receipt:
      type: object
      xml:
        name: response
      properties:
        paymentId:
          type: string
        status:
          type: string
          enum:
          - COMPLETED
          - REJECTED
        fromAccount:
          type: string
        toAccount:
          type: string
        amount:
          type: string
          pattern: ^[0-9]+\.[0-9]{3}$
        currency:
          type: string
        processedAt:
          type: string
        code:
          type: string
    Error:
      type: object
      required:
      - status
      - code
      - message
      - issues
      - correlationId
      - timestamp
      xml:
        name: response
      properties:
        paymentId:
          type: string
          format: uuid
        status:
          type: string
          enum:
          - ERROR
          - REJECTED
        code:
          type: string
        message:
          type: string
        correlationId:
          type: string
          format: uuid
        timestamp:
          type: string
          format: date-time
        issues:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
              code:
                type: string
              message:
                type: string
    Balance:
      type: object
      properties:
        accountId:
          type: string
        availableBalance:
          type: string
        currency:
          type: string
