openapi: 3.0.3
info:
  title: Kashu POS API
  description: >
    REST API for the Kashu POS System — fintech point of sale for Kashu Company
    Limited (Uganda). All endpoints return a consistent JSON envelope and are
    versioned under /api/v1.
  version: "1.0.0"
  contact:
    name: Kashu Company Limited
    email: Info@kashucompany.com
    url: https://kashucompany.com
servers:
  - url: http://localhost:8000/api/v1
    description: Local
  - url: https://api.kashucompany.com/api/v1
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Auth
  - name: Agent
  - name: Transactions
  - name: Airtime
  - name: Utilities
  - name: School Fees
  - name: Taxes
  - name: Mobile Money
  - name: Banking
  - name: Goods (POS)
  - name: Salary
  - name: Reports
  - name: Webhooks

paths:
  /auth/login:
    post:
      tags: [Auth]
      summary: Agent login (email or phone + password)
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [identifier, password]
              properties:
                identifier: { type: string, example: agent@kashucompany.com }
                password: { type: string, example: Demo@1234 }
                device_name: { type: string, example: web-pwa }
      responses:
        "200":
          description: Login successful (token returned)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/LoginResponse" }
        "422": { $ref: "#/components/responses/ValidationError" }

  /auth/verify-pin:
    post:
      tags: [Auth]
      summary: Verify a PIN for sensitive operations
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [pin]
              properties: { pin: { type: string, example: "1234" } }
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "422": { $ref: "#/components/responses/Error" }

  /auth/refresh:
    post: { tags: [Auth], summary: Refresh token, responses: { "200": { $ref: "#/components/responses/Success" } } }
  /auth/logout:
    post: { tags: [Auth], summary: Invalidate token, responses: { "200": { $ref: "#/components/responses/Success" } } }
  /auth/forgot-pin:
    post:
      tags: [Auth]
      summary: Request a PIN reset OTP
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, required: [identifier], properties: { identifier: { type: string } } }
      responses: { "200": { $ref: "#/components/responses/Success" } }
  /auth/reset-pin:
    post:
      tags: [Auth]
      summary: Reset PIN with OTP
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [identifier, otp, new_pin, new_pin_confirmation]
              properties:
                identifier: { type: string }
                otp: { type: string }
                new_pin: { type: string }
                new_pin_confirmation: { type: string }
      responses: { "200": { $ref: "#/components/responses/Success" } }

  /agent/profile:
    get: { tags: [Agent], summary: Get agent profile, responses: { "200": { $ref: "#/components/responses/Success" } } }
    put: { tags: [Agent], summary: Update profile, responses: { "200": { $ref: "#/components/responses/Success" } } }
  /agent/change-pin:
    post: { tags: [Agent], summary: Change PIN, responses: { "200": { $ref: "#/components/responses/Success" } } }
  /agent/change-password:
    post: { tags: [Agent], summary: Change password, responses: { "200": { $ref: "#/components/responses/Success" } } }
  /agent/dashboard:
    get: { tags: [Agent], summary: Dashboard summary, responses: { "200": { $ref: "#/components/responses/Success" } } }
  /agent/float:
    get: { tags: [Agent], summary: Float balance & history, responses: { "200": { $ref: "#/components/responses/Success" } } }
  /agent/commissions:
    get: { tags: [Agent], summary: Commission history & totals, responses: { "200": { $ref: "#/components/responses/Success" } } }
  /agent/notifications:
    get: { tags: [Agent], summary: List notifications, responses: { "200": { $ref: "#/components/responses/Success" } } }
  /agent/notifications/read:
    post: { tags: [Agent], summary: Mark notifications read, responses: { "200": { $ref: "#/components/responses/Success" } } }

  /transactions:
    get: { tags: [Transactions], summary: List transactions (filterable), responses: { "200": { $ref: "#/components/responses/Success" } } }
  /transactions/{ref}:
    get:
      tags: [Transactions]
      summary: Get a transaction
      parameters: [{ name: ref, in: path, required: true, schema: { type: string } }]
      responses: { "200": { $ref: "#/components/responses/Success" } }
  /transactions/{ref}/receipt:
    post:
      tags: [Transactions]
      summary: Generate receipt PDF (base64)
      parameters: [{ name: ref, in: path, required: true, schema: { type: string } }]
      responses: { "200": { $ref: "#/components/responses/Success" } }
  /transactions/{ref}/reverse:
    post:
      tags: [Transactions]
      summary: Reverse a successful transaction
      parameters: [{ name: ref, in: path, required: true, schema: { type: string } }]
      responses: { "200": { $ref: "#/components/responses/Success" } }

  /airtime/networks:
    get: { tags: [Airtime], summary: Networks & denominations, responses: { "200": { $ref: "#/components/responses/Success" } } }
  /airtime/buy:
    post:
      tags: [Airtime]
      summary: Purchase airtime
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [network, recipient_phone, amount]
              properties:
                network: { type: string, enum: [MTN, Airtel, Africell, Lycamobile, UTL] }
                recipient_phone: { type: string, example: "+256712345678" }
                amount: { type: number, example: 5000 }
                pin: { type: string, example: "1234" }
                idempotency_key: { type: string }
      responses:
        "201": { $ref: "#/components/responses/Success" }
        "422": { $ref: "#/components/responses/Error" }

  /utilities/providers:
    get: { tags: [Utilities], summary: List utility providers, responses: { "200": { $ref: "#/components/responses/Success" } } }
  /utilities/validate:
    post: { tags: [Utilities], summary: Validate account number, responses: { "200": { $ref: "#/components/responses/Success" } } }
  /utilities/pay:
    post:
      tags: [Utilities]
      summary: Pay a utility bill
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [provider, account_number, amount]
              properties:
                provider: { type: string, enum: [UMEME, NWSC, DStv, StarTimes, Azam, GOtv, Zuku, Liquid_Home, Roke_Telecom, Other] }
                account_number: { type: string }
                amount: { type: number }
                pin: { type: string }
      responses: { "201": { $ref: "#/components/responses/Success" } }

  /school-fees/schools:
    get: { tags: [School Fees], summary: Registered schools, responses: { "200": { $ref: "#/components/responses/Success" } } }
  /school-fees/lookup:
    post: { tags: [School Fees], summary: Look up student, responses: { "200": { $ref: "#/components/responses/Success" } } }
  /school-fees/pay:
    post: { tags: [School Fees], summary: Pay school fees, responses: { "201": { $ref: "#/components/responses/Success" } } }

  /taxes/lookup-prn:
    post: { tags: [Taxes], summary: Look up PRN from URA, responses: { "200": { $ref: "#/components/responses/Success" } } }
  /taxes/pay:
    post: { tags: [Taxes], summary: Pay tax, responses: { "201": { $ref: "#/components/responses/Success" } } }

  /mobile-money/mtn/send:
    post:
      tags: [Mobile Money]
      summary: MTN — send money
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [recipient_phone, amount]
              properties:
                recipient_phone: { type: string }
                amount: { type: number }
                pin: { type: string }
      responses: { "202": { $ref: "#/components/responses/Success" } }
  /mobile-money/mtn/withdraw:
    post: { tags: [Mobile Money], summary: MTN — withdraw (customer cash-out), responses: { "202": { $ref: "#/components/responses/Success" } } }
  /mobile-money/mpesa/stk-push:
    post: { tags: [Mobile Money], summary: M-Pesa STK push, responses: { "202": { $ref: "#/components/responses/Success" } } }
  /mobile-money/charges:
    get: { tags: [Mobile Money], summary: Charge schedule, responses: { "200": { $ref: "#/components/responses/Success" } } }

  /banking/banks:
    get: { tags: [Banking], summary: Supported banks, responses: { "200": { $ref: "#/components/responses/Success" } } }
  /banking/deposit:
    post: { tags: [Banking], summary: Cash deposit, responses: { "201": { $ref: "#/components/responses/Success" } } }
  /banking/withdraw:
    post: { tags: [Banking], summary: Cash withdrawal, responses: { "201": { $ref: "#/components/responses/Success" } } }

  /products:
    get: { tags: [Goods (POS)], summary: List products, responses: { "200": { $ref: "#/components/responses/Success" } } }
    post: { tags: [Goods (POS)], summary: Create product, responses: { "201": { $ref: "#/components/responses/Success" } } }
  /products/{id}:
    get:
      tags: [Goods (POS)]
      summary: Get product
      parameters: [{ name: id, in: path, required: true, schema: { type: integer } }]
      responses: { "200": { $ref: "#/components/responses/Success" } }
    put:
      tags: [Goods (POS)]
      summary: Update product
      parameters: [{ name: id, in: path, required: true, schema: { type: integer } }]
      responses: { "200": { $ref: "#/components/responses/Success" } }
    delete:
      tags: [Goods (POS)]
      summary: Delete product
      parameters: [{ name: id, in: path, required: true, schema: { type: integer } }]
      responses: { "200": { $ref: "#/components/responses/Success" } }
  /orders:
    post:
      tags: [Goods (POS)]
      summary: Create order / checkout
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [amount_paid, payment_method, items]
              properties:
                customer_name: { type: string }
                amount_paid: { type: number }
                payment_method: { type: string, enum: [cash, kashu_wallet, mobile_money, bank_card] }
                items:
                  type: array
                  items:
                    type: object
                    required: [product_name, unit_price, quantity]
                    properties:
                      product_id: { type: integer }
                      product_name: { type: string }
                      unit_price: { type: number }
                      quantity: { type: integer }
      responses: { "201": { $ref: "#/components/responses/Success" } }

  /salary/single:
    post: { tags: [Salary], summary: Single salary payment, responses: { "201": { $ref: "#/components/responses/Success" } } }
  /salary/bulk:
    post: { tags: [Salary], summary: Bulk salary batch (draft), responses: { "201": { $ref: "#/components/responses/Success" } } }
  /salary/batches/{id}/process:
    post:
      tags: [Salary]
      summary: Process a draft batch
      parameters: [{ name: id, in: path, required: true, schema: { type: integer } }]
      responses: { "202": { $ref: "#/components/responses/Success" } }

  /reports/summary:
    get: { tags: [Reports], summary: Summary metrics + series, responses: { "200": { $ref: "#/components/responses/Success" } } }
  /reports/export:
    post: { tags: [Reports], summary: Export transactions CSV, responses: { "200": { description: CSV file } } }

  /webhooks/mtn/callback:
    post: { tags: [Webhooks], summary: MTN callback, security: [], responses: { "200": { description: ack } } }
  /webhooks/airtel/callback:
    post: { tags: [Webhooks], summary: Airtel callback, security: [], responses: { "200": { description: ack } } }
  /webhooks/mpesa/callback:
    post: { tags: [Webhooks], summary: M-Pesa callback, security: [], responses: { "200": { description: ack } } }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Sanctum
  responses:
    Success:
      description: Successful response
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Envelope" }
    Error:
      description: Error
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorEnvelope" }
    ValidationError:
      description: Validation failed
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorEnvelope" }
  schemas:
    Envelope:
      type: object
      properties:
        success: { type: boolean, example: true }
        message: { type: string }
        data: { type: object }
        meta: { type: object }
    ErrorEnvelope:
      type: object
      properties:
        success: { type: boolean, example: false }
        message: { type: string }
        errors: { type: object }
        code: { type: string, example: VALIDATION_ERROR }
    LoginResponse:
      allOf:
        - $ref: "#/components/schemas/Envelope"
        - type: object
          properties:
            data:
              type: object
              properties:
                token: { type: string }
                token_type: { type: string, example: Bearer }
                expires_in: { type: integer, example: 28800 }
                agent: { type: object }
