openapi: 3.0.3
info:
  title: LeefiPay API
  version: 1.0.0
  description: |
    Multi-tenant M-Pesa collect REST API.
    Authenticate with Sanctum Bearer tokens from POST /api/v1/auth/token.
servers:
  - url: /api/v1
    description: Current stable
security:
  - bearerAuth: []
paths:
  /health:
    get:
      security: []
      summary: Health check
      responses:
        '200':
          description: OK
  /auth/token:
    post:
      security: []
      summary: Issue API token
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password, device_name]
              properties:
                email: { type: string, format: email }
                password: { type: string }
                device_name: { type: string }
                abilities:
                  type: array
                  items: { type: string }
      responses:
        '201':
          description: Token issued
  /auth/me:
    get:
      summary: Current user
      responses:
        '200':
          description: User + tenant
  /customers:
    get:
      summary: List customers
      parameters:
        - in: query
          name: search
          schema: { type: string }
        - in: query
          name: per_page
          schema: { type: integer }
      responses:
        '200':
          description: Paginated customers
    post:
      summary: Create customer
      responses:
        '201':
          description: Created
  /invoices:
    get:
      summary: List invoices
      responses:
        '200':
          description: Paginated invoices
    post:
      summary: Create invoice
      responses:
        '201':
          description: Created
  /invoices/{id}/pay:
    post:
      summary: Pay invoice (stk|cash)
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: integer }
      responses:
        '201':
          description: Payment initiated
  /payments/stk:
    post:
      summary: Initiate STK Push
      parameters:
        - in: header
          name: Idempotency-Key
          schema: { type: string }
      responses:
        '201':
          description: Processing
  /payments/cash:
    post:
      summary: Record cash payment
      responses:
        '201':
          description: Successful
  /payments/manual:
    post:
      summary: Start manual M-Pesa payment
      responses:
        '201':
          description: Pending verification
  /webhooks/mpesa:
    post:
      security: []
      summary: Safaricom STK callback
      responses:
        '200':
          description: Accepted
  /webhooks/sms/delivery:
    post:
      security: []
      summary: SMS delivery report
      responses:
        '200':
          description: Processed
  /reports/overview:
    get:
      summary: BI overview KPIs
      parameters:
        - in: query
          name: period
          schema: { type: string, enum: [today, yesterday, week, month, year, custom] }
      responses:
        '200':
          description: Overview
  /integrations/test:
    post:
      summary: Test integration credentials
      description: Validates token, tenant/merchant status, and returns payment channels.
      responses:
        '200':
          description: Connection successful
        '403':
          description: Merchant inactive or API disabled
  /integrations/woocommerce/orders:
    post:
      summary: Create invoice + payment URL from a WooCommerce order
      description: |
        Requires invoices:write. Optional initiate_stk also requires payments:write.
        Amounts are integer KES. Same order_id is idempotent per tenant.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [order_id, line_items]
              properties:
                order_id: { type: string }
                site_url: { type: string, format: uri }
                currency: { type: string }
                customer:
                  type: object
                  properties:
                    name: { type: string }
                    email: { type: string, format: email }
                    phone: { type: string }
                    company: { type: string }
                    address: { type: string }
                line_items:
                  type: array
                  minItems: 1
                  items:
                    type: object
                    required: [name, unit_price]
                    properties:
                      name: { type: string }
                      quantity: { type: number }
                      unit_price: { type: integer }
                      tax_rate: { type: number }
                      discount_amount: { type: integer }
                shipping_total: { type: integer }
                sale_discount: { type: integer }
                initiate_stk: { type: boolean }
                phone: { type: string }
                payment_channel_id: { type: integer }
                success_url: { type: string, format: uri }
                cancel_url: { type: string, format: uri }
                failure_url: { type: string, format: uri }
                pending_url: { type: string, format: uri }
                webhook_url: { type: string, format: uri }
                webhook_secret: { type: string }
      responses:
        '201':
          description: Invoice created
        '200':
          description: Existing invoice returned
  /integrations/woocommerce/orders/{invoice}:
    get:
      summary: WooCommerce-linked invoice status
      parameters:
        - in: path
          name: invoice
          required: true
          schema: { type: integer }
      responses:
        '200':
          description: Status
  /integrations/woocommerce/orders/{invoice}/stk:
    post:
      summary: Initiate STK for unpaid WooCommerce invoice balance
      parameters:
        - in: path
          name: invoice
          required: true
          schema: { type: integer }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                phone: { type: string }
                amount: { type: integer }
                payment_channel_id: { type: integer }
                idempotency_key: { type: string }
      responses:
        '201':
          description: STK initiated
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Sanctum
  schemas:
    Envelope:
      type: object
      properties:
        success: { type: boolean }
        message: { type: string }
        data: {}
        meta: { type: object }
        errors: { type: object }
