openapi: 3.0.3
info:
  title: Social Media Guru Merchant Payments API
  version: "1.0.0"
  description: |
    Collect card, Capitec Pay and Instant EFT in South Africa. Your customer
    never loads PayFast on your domain — checkout opens a Social Media Guru popup.

    **All methods:** 6% + R2.00 excl. VAT, then 15% VAT on the fee. Minimum R50.00.

    Base URL: `https://www.socialmediaguru.co.za`

    1. `POST /api/v1/payments` from your **server** with `Authorization: Bearer sk_live_…`
    2. Pass `data.payment_url` to `SmgPay.open()` (`https://www.socialmediaguru.co.za/js/smg-pay.js`)
    3. Fulfil from the signed `payment.paid` webhook
  contact:
    name: Social Media Guru
    email: support@socialmediaguru.co.za
    url: https://www.socialmediaguru.co.za/developers
  license:
    name: MIT
    url: https://www.socialmediaguru.co.za/developers/LICENSE
servers:
  - url: https://www.socialmediaguru.co.za
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Payments
  - name: Balance
paths:
  /api/v1/payments:
    get:
      tags: [Payments]
      summary: List payments
      parameters:
        - in: query
          name: status
          schema: { type: string, enum: [pending, paid, cancelled, expired, failed] }
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 100, default: 25 }
      responses:
        "200":
          description: Payment list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Payment" }
        "401":
          description: Invalid API key
    post:
      tags: [Payments]
      summary: Create a payment
      description: |
        Call this from your **server**. Never put `sk_live_` in the browser.
        Then pass `payment_url` to `SmgPay.open()` on the client.

        Re-using `merchant_ref` returns the existing payment with `reused: true`.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreatePayment" }
            example:
              amount: 199.0
              merchant_ref: ORD-1001
              customer:
                name: Jane Doe
                email: jane@example.com
                phone: "0713743360"
              return_url: https://yourstore.co.za/order/1001/thanks
              cancel_url: https://yourstore.co.za/order/1001/cancel
      responses:
        "201":
          description: Payment created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Payment" }
                  reused: { type: boolean }
        "400":
          description: Validation error
        "409":
          description: Same amount charged too recently (anti-fraud)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error: { type: string }
                  code: { type: string, example: velocity_limit }
                  retry_after: { type: integer }
  /api/v1/payments/{id}:
    get:
      tags: [Payments]
      summary: Retrieve a payment
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
          description: Payment id, SMG-P- number, or your merchant_ref
      responses:
        "200":
          description: Payment
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Payment" }
        "404":
          description: Not found
    post:
      tags: [Payments]
      summary: Cancel a pending payment
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [action]
              properties:
                action: { type: string, enum: [cancel] }
      responses:
        "200":
          description: Cancelled
        "409":
          description: Not pending
  /api/v1/balance:
    get:
      tags: [Balance]
      summary: Available balance and totals
      responses:
        "200":
          description: Ledger snapshot
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      available: { type: number }
                      pending_withdrawal: { type: number }
                      paid_count: { type: integer }
                      paid_volume: { type: number }
                      fees_collected: { type: number }
                      net_earned: { type: number }
                      currency: { type: string, example: ZAR }
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: sk_live_…
  schemas:
    CreatePayment:
      type: object
      required: [amount, merchant_ref, customer]
      properties:
        amount:
          type: number
          minimum: 50
          maximum: 50000
        merchant_ref:
          type: string
          maxLength: 80
          description: Your unique order id (idempotent per merchant)
        description:
          type: string
          maxLength: 200
          description: Internal only — never sent to PayFast
        customer:
          type: object
          required: [name, email]
          properties:
            name: { type: string }
            email: { type: string, format: email }
            phone: { type: string }
        return_url:
          type: string
          format: uri
          description: We redirect the buyer here AFTER PayFast. Never given to PayFast.
        cancel_url: { type: string, format: uri }
    Payment:
      type: object
      properties:
        id: { type: string }
        number: { type: string, example: SMG-P-20260823-ABC12 }
        merchant_ref: { type: string }
        status: { type: string, enum: [pending, paid, cancelled, expired, failed] }
        currency: { type: string, example: ZAR }
        customer:
          type: object
          properties:
            name: { type: string }
            email: { type: string }
        amount: { type: number }
        amount_cents: { type: integer, example: 19900 }
        net: { type: number }
        net_cents: { type: integer }
        payment_url: { type: string, format: uri }
        js_sdk: { type: string, format: uri }
        token: { type: string }
        method: { type: string, nullable: true }
        fees:
          type: object
          description: card = 6%+R2+VAT; other = 6%+VAT
          properties:
            percent: { type: number, example: 6 }
            vat_percent: { type: number, example: 15 }
            card_fee_ex_vat: { type: number, example: 2 }
            other: { type: object }
            card: { type: object }
            settled: { type: object, nullable: true }
        expires_at: { type: string, format: date-time }
        paid_at: { type: string, format: date-time, nullable: true }
        created_at: { type: string, format: date-time }
