openapi: "3.0.3"

info:
  title: Rounin Merchant API
  version: "1.0"
  description: |
    Server-to-server API for merchants. All endpoints require HMAC-SHA256 authentication.

    **Base URL:** `https://api.rounin.io/api/v1`

    ## Authentication

    Every request must carry four headers:

    | Header | Description |
    |--------|-------------|
    | `X-Api-Key` | Public API key |
    | `X-Timestamp` | Unix seconds. Rejected if outside ±60 s of server time |
    | `X-Nonce` | Unique random string per request. Replays rejected for 5 min |
    | `X-Signature` | `HMAC-SHA256(secret_key, body_bytes)` as hex. For GET requests — sign the query string |

servers:
  - url: https://api.rounin.io/api/v1
    description: Production

security:
  - MerchantHMAC: []

components:
  securitySchemes:
    MerchantHMAC:
      type: apiKey
      in: header
      name: X-Api-Key
      description: |
        Additionally requires X-Signature, X-Timestamp, X-Nonce headers.
        See full auth docs in MERCHANT_API.md.

  schemas:
    SuccessEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object

    ErrorEnvelope:
      type: object
      required: [success, error]
      properties:
        success:
          type: boolean
          example: false
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              example: ORDER_NOT_FOUND
            message:
              type: string
              example: No such order for your account

    PaymentType:
      type: object
      properties:
        code:
          type: string
          example: CARD_SBER
        display_name:
          type: string
          example: Карта Сбербанк
        method:
          type: string
          enum: [card, sbp, qr]
          example: card
        bank_code:
          type: string
          example: SBER
        bank_name:
          type: string
          example: Сбербанк
        commission_percent:
          type: string
          example: "1.50"

    PaymentDetails:
      type: object
      properties:
        bank_code:
          type: string
          example: SBER
        bank_name:
          type: string
          example: Сбербанк
        card_last4:
          type: string
          example: "4242"
          description: Present for card payments
        holder_name:
          type: string
          example: IVAN IVANOV
          description: Present for card payments
        phone:
          type: string
          example: "+79001234567"
          description: Present for SBP payments
        qr_code_url:
          type: string
          description: Present for QR payments

    Order:
      type: object
      properties:
        order_id:
          type: string
          format: uuid
          example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
        merchant_order_id:
          type: string
          example: my-order-001
        status:
          type: string
          enum: [pending, processing, completed, cancelled, disputed]
          example: pending
        amount_rub:
          type: string
          example: "5001.00"
          description: Final amount (with unique suffix)
        amount_rub_base:
          type: string
          example: "5000.00"
          description: Original requested amount
        payment_type_code:
          type: string
          example: CARD_SBER
        client_identifier:
          type: string
          example: user_42
        expires_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        completed_at:
          type: string
          format: date-time
          nullable: true
        cancelled_reason:
          type: string
          nullable: true
        paid_amount_rub:
          type: string
          nullable: true
          description: Actual amount received per SMS confirmation
        payment_details:
          $ref: "#/components/schemas/PaymentDetails"

paths:
  /merchant/payment-types:
    get:
      operationId: listPaymentTypes
      summary: List available payment types
      description: Returns payment types enabled for your account with your commission rate.
      parameters:
        - name: X-Timestamp
          in: header
          required: true
          schema: { type: string }
        - name: X-Nonce
          in: header
          required: true
          schema: { type: string }
        - name: X-Signature
          in: header
          required: true
          schema: { type: string }
          description: HMAC-SHA256 of empty string (no body/query)
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          items:
                            type: array
                            items:
                              $ref: "#/components/schemas/PaymentType"
        "401":
          description: Auth failure
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"

  /merchant/orders:
    post:
      operationId: createOrder
      summary: Create a payment order
      description: |
        Creates a new order. The platform assigns a trader and returns payment details.

        **Idempotency:** if `merchant_order_id` already exists, the existing order is returned (HTTP 200).
      parameters:
        - name: X-Timestamp
          in: header
          required: true
          schema: { type: string }
        - name: X-Nonce
          in: header
          required: true
          schema: { type: string }
        - name: X-Signature
          in: header
          required: true
          schema: { type: string }
          description: HMAC-SHA256 of raw request body bytes
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [merchant_order_id, amount_rub, payment_type_code]
              properties:
                merchant_order_id:
                  type: string
                  example: my-order-001
                  description: Your unique order ID (used for idempotency)
                amount_rub:
                  type: string
                  example: "5000.00"
                  description: Amount in RUB. Max 500000.00, up to 2 decimal places
                payment_type_code:
                  type: string
                  example: CARD_SBER
                  description: Code from GET /merchant/payment-types
                ttl_seconds:
                  type: integer
                  minimum: 1
                  maximum: 3600
                  default: 900
                  example: 600
                client_identifier:
                  type: string
                  example: user_42
                webhook_url:
                  type: string
                  format: uri
                  example: https://yoursite.com/hooks/payment
                callback_data:
                  type: object
                  description: Arbitrary JSON passed back in webhooks
      responses:
        "201":
          description: Order created
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/Order"
        "200":
          description: Idempotent — order already existed
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/Order"
        "400":
          description: Bad request (BAD_BODY, MISSING_FIELDS, BAD_AMOUNT)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Auth failure
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "422":
          description: |
            Business rule violation:
            - `PAYMENT_TYPE_INACTIVE` — type not found or no commission
            - `NO_AVAILABLE_TRADERS` — no traders on shift
            - `INSUFFICIENT_BALANCE` — balance too low
            - `TRADER_BALANCE_RACE` — concurrent conflict, retry
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limit exceeded
          headers:
            Retry-After:
              schema:
                type: integer

  /merchant/orders/{order_id}:
    get:
      operationId: getOrder
      summary: Get order by ID
      parameters:
        - name: order_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: X-Timestamp
          in: header
          required: true
          schema: { type: string }
        - name: X-Nonce
          in: header
          required: true
          schema: { type: string }
        - name: X-Signature
          in: header
          required: true
          schema: { type: string }
          description: "HMAC-SHA256 of query string: `order_id=<uuid>`"
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/Order"
        "401":
          description: Auth failure
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: ORDER_NOT_FOUND
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"

  /merchant/orders/{order_id}/cancel:
    post:
      operationId: cancelOrder
      summary: Cancel a pending order
      description: Cancels a `pending` order and refunds your balance.
      parameters:
        - name: order_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: X-Timestamp
          in: header
          required: true
          schema: { type: string }
        - name: X-Nonce
          in: header
          required: true
          schema: { type: string }
        - name: X-Signature
          in: header
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                reason:
                  type: string
                  example: customer_abandoned
                  description: Optional. Defaults to "merchant_cancelled"
      responses:
        "200":
          description: Cancelled successfully
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/Order"
        "401":
          description: Auth failure
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: ORDER_NOT_FOUND
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "422":
          description: ORDER_NOT_CANCELLABLE — order is not in pending status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"

  /merchant/appeals:
    post:
      operationId: createAppeal
      summary: Open a dispute on a completed order
      parameters:
        - name: X-Timestamp
          in: header
          required: true
          schema: { type: string }
        - name: X-Nonce
          in: header
          required: true
          schema: { type: string }
        - name: X-Signature
          in: header
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [order_id, reason]
              properties:
                order_id:
                  type: string
                  format: uuid
                reason:
                  type: string
                  example: payment_not_received
                comment:
                  type: string
                  example: Funds did not arrive to our account
      responses:
        "201":
          description: Appeal created
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          appeal_id:
                            type: integer
                          order_id:
                            type: string
                            format: uuid
                          status:
                            type: string
                            example: open
                          reason:
                            type: string
                          created_at:
                            type: string
                            format: date-time
        "401":
          description: Auth failure
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: ORDER_NOT_FOUND
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "422":
          description: Order not in appellable state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
