openapi: 3.0.3
info:
  title: NovaTrek Payments Service
  description: |
    Manages payments, refunds, and billing for adventure bookings at NovaTrek Adventures.
    Handles payment processing, refund workflows, and financial reporting for all reservation transactions.
  version: 1.1.0
  contact:
    name: NovaTrek Platform Team
    email: platform@novatrek.example.com

servers:
  - url: https://api.novatrek.example.com/payments/v1
    description: Production
  - url: https://api-staging.novatrek.example.com/payments/v1
    description: Staging

security:
  - bearerAuth: []

tags:
  - name: Payments
    description: Payment processing and retrieval
  - name: Refunds
    description: Refund operations
  - name: Disputes
    description: Dispute lifecycle and escalation workflows
  - name: Reporting
    description: Financial reporting and summaries

paths:
  /payments:
    post:
      operationId: processPayment
      summary: Process a payment
      description: Initiates payment processing for a reservation. The payment is authorized and captured based on the selected method.
      tags: [Payments]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PaymentRequest'
      responses:
        '201':
          description: Payment initiated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Payment'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Duplicate payment for reservation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /payments/{payment_id}:
    get:
      operationId: getPayment
      summary: Retrieve payment details
      description: Returns full details of a specific payment including processor reference and status history.
      tags: [Payments]
      parameters:
        - $ref: '#/components/parameters/PaymentId'
      responses:
        '200':
          description: Payment details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Payment'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /payments/{payment_id}/refund:
    post:
      operationId: refundPayment
      summary: Initiate a refund
      description: |
        Creates a refund for the specified payment. Supports full or partial refunds.
        Partial refunds can be issued multiple times up to the original payment amount.
      tags: [Refunds]
      parameters:
        - $ref: '#/components/parameters/PaymentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefundRequest'
      responses:
        '201':
          description: Refund initiated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Refund'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: Refund amount exceeds remaining balance
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /guests/{guest_id}/payment-history:
    get:
      operationId: getGuestPaymentHistory
      summary: Retrieve payment history for a guest
      description: Returns paginated payment history for a specific guest, ordered by most recent first.
      tags: [Payments]
      parameters:
        - name: guest_id
          in: path
          required: true
          description: Unique guest identifier (from guest profile service)
          schema:
            type: string
            format: uuid
        - name: status
          in: query
          description: Filter by payment status
          schema:
            $ref: '#/components/schemas/PaymentStatus'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Offset'
      responses:
        '200':
          description: Paginated payment history
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentHistoryResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /disputes:
    post:
      operationId: createDispute
      summary: Create a refund dispute
      description: |
        Opens a dispute for a payment. The system evaluates the refund request against
        the configured refund policy and assigns an escalation tier (AUTO, AGENT, MANAGER).
        Auto-approved disputes are resolved immediately with a refund.
      tags: [Disputes]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DisputeRequest'
      responses:
        '201':
          description: Dispute created (may be auto-resolved if policy-eligible)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Dispute'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Payment not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: An active dispute already exists for this payment
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          $ref: '#/components/responses/InternalServerError'

    get:
      operationId: listDisputes
      summary: List disputes with optional filters
      tags: [Disputes]
      parameters:
        - name: status
          in: query
          description: Filter by dispute status
          schema:
            $ref: '#/components/schemas/DisputeStatus'
        - name: tier
          in: query
          description: Filter by escalation tier
          schema:
            $ref: '#/components/schemas/DisputeTier'
        - name: assigned_to
          in: query
          description: Filter by assigned agent or manager
          schema:
            type: string
        - name: guest_id
          in: query
          description: Filter by guest identifier
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Offset'
      responses:
        '200':
          description: Paginated dispute list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DisputeListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /disputes/{dispute_id}:
    get:
      operationId: getDispute
      summary: Retrieve dispute details
      tags: [Disputes]
      parameters:
        - $ref: '#/components/parameters/DisputeId'
      responses:
        '200':
          description: Dispute details with audit trail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Dispute'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

    patch:
      operationId: updateDispute
      summary: Update a dispute (assign, add notes, escalate)
      description: Supports field-level updates. Requires role matching the dispute tier.
      tags: [Disputes]
      parameters:
        - $ref: '#/components/parameters/DisputeId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DisputeUpdateRequest'
      responses:
        '200':
          description: Dispute updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Dispute'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Insufficient role for this dispute tier
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Concurrent modification (version mismatch)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /disputes/{dispute_id}/resolve:
    post:
      operationId: resolveDispute
      summary: Resolve a dispute with refund decision
      description: |
        Resolves the dispute with a decision (FULL_REFUND, PARTIAL_REFUND, DENIED).
        If approved, a refund record is created automatically. The resolver's role
        must match the dispute tier (AGENT or MANAGER).
      tags: [Disputes]
      parameters:
        - $ref: '#/components/parameters/DisputeId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DisputeResolution'
      responses:
        '200':
          description: Dispute resolved; refund created if approved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Dispute'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Resolver role does not match required tier
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: Dispute is not in a resolvable state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /chargebacks:
    post:
      operationId: ingestChargeback
      summary: Ingest a chargeback notification from payment processor
      description: |
        Creates a dispute record from an external chargeback notification.
        Chargebacks are auto-escalated to MANAGER tier.
      tags: [Disputes]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChargebackNotification'
      responses:
        '201':
          description: Chargeback dispute created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Dispute'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Chargeback dispute already exists for this processor reference
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /refund-policy/evaluate:
    post:
      operationId: evaluateRefundPolicy
      summary: Evaluate refund eligibility against policy (dry-run)
      description: |
        Returns the policy evaluation result without creating a dispute.
        Useful for showing guests their expected refund amount before submission.
      tags: [Disputes]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [payment_id, reason]
              properties:
                payment_id:
                  type: string
                  format: uuid
                reason:
                  $ref: '#/components/schemas/DisputeType'
                cancellation_date:
                  type: string
                  format: date-time
                  description: When the cancellation occurs (defaults to now)
      responses:
        '200':
          description: Policy evaluation result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyEvaluation'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /payments/daily-summary:
    get:
      operationId: getDailySummary
      summary: Get daily payment summary
      description: Returns an aggregated summary of payments processed on a given date, broken down by method and status.
      tags: [Reporting]
      parameters:
        - name: date
          in: query
          required: true
          description: The date for the summary (ISO 8601)
          schema:
            type: string
            format: date
            example: "2026-02-28"
        - name: currency
          in: query
          description: Filter by currency code
          schema:
            type: string
            example: USD
      responses:
        '200':
          description: Daily summary
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DailySummary'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  parameters:
    PaymentId:
      name: payment_id
      in: path
      required: true
      description: Unique payment identifier
      schema:
        type: string
        format: uuid
    DisputeId:
      name: dispute_id
      in: path
      required: true
      description: Unique dispute identifier
      schema:
        type: string
        format: uuid
    Limit:
      name: limit
      in: query
      description: Maximum number of results to return
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    Offset:
      name: offset
      in: query
      description: Number of results to skip for pagination
      schema:
        type: integer
        minimum: 0
        default: 0

  schemas:
    PaymentRequest:
      type: object
      required: [reservation_id, guest_id, amount, currency, method]
      properties:
        reservation_id:
          type: string
          format: uuid
          description: Reservation identifier (cross-ref svc-reservations)
        guest_id:
          type: string
          format: uuid
          description: Guest identifier
        amount:
          type: number
          format: double
          minimum: 0.01
          description: Payment amount
          example: 249.99
        currency:
          type: string
          pattern: '^[A-Z]{3}$'
          description: ISO 4217 currency code
          example: USD
        method:
          $ref: '#/components/schemas/PaymentMethod'
        payment_token:
          type: string
          description: Tokenized payment instrument from the payment vault
        idempotency_key:
          type: string
          description: Client-supplied key to prevent duplicate charges
          example: "ord-abc123-attempt1"

    Payment:
      type: object
      properties:
        id:
          type: string
          format: uuid
        reservation_id:
          type: string
          format: uuid
          description: Associated reservation (cross-ref svc-reservations)
        guest_id:
          type: string
          format: uuid
        amount:
          type: number
          format: double
          example: 249.99
        currency:
          type: string
          example: USD
        method:
          $ref: '#/components/schemas/PaymentMethod'
        status:
          $ref: '#/components/schemas/PaymentStatus'
        processor_reference:
          type: string
          description: External reference from the payment processor
          example: "ch_3Nk8aJ2eZvKYloR"
        refunded_amount:
          type: number
          format: double
          description: Total amount refunded against this payment
          default: 0
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    PaymentMethod:
      type: string
      enum:
        - CREDIT_CARD
        - DEBIT_CARD
        - BANK_TRANSFER
        - GIFT_CARD
        - PARTNER_VOUCHER

    PaymentStatus:
      type: string
      enum:
        - PENDING
        - AUTHORIZED
        - CAPTURED
        - FAILED
        - REFUNDED
        - PARTIALLY_REFUNDED

    RefundRequest:
      type: object
      required: [amount, reason]
      properties:
        amount:
          type: number
          format: double
          minimum: 0.01
          description: Amount to refund (partial or full)
        reason:
          type: string
          maxLength: 500
          description: Reason for the refund
          example: "Trip cancelled due to weather"
        initiated_by:
          type: string
          description: Operator or system that initiated the refund

    Refund:
      type: object
      properties:
        id:
          type: string
          format: uuid
        payment_id:
          type: string
          format: uuid
        amount:
          type: number
          format: double
        reason:
          type: string
        status:
          type: string
          enum: [PENDING, PROCESSED, FAILED]
        processor_reference:
          type: string
        initiated_by:
          type: string
        created_at:
          type: string
          format: date-time

    PaymentHistoryResponse:
      type: object
      properties:
        payments:
          type: array
          items:
            $ref: '#/components/schemas/Payment'
        total_count:
          type: integer
        limit:
          type: integer
        offset:
          type: integer

    DailySummary:
      type: object
      properties:
        date:
          type: string
          format: date
        total_transactions:
          type: integer
        total_captured:
          type: number
          format: double
        total_refunded:
          type: number
          format: double
        currency:
          type: string
        breakdown_by_method:
          type: array
          items:
            type: object
            properties:
              method:
                $ref: '#/components/schemas/PaymentMethod'
              count:
                type: integer
              total:
                type: number
                format: double

    ErrorResponse:
      type: object
      properties:
        error_code:
          type: string
          example: INVALID_REQUEST
        message:
          type: string
          example: The request body is invalid
        details:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
              issue:
                type: string

    DisputeType:
      type: string
      enum:
        - CANCELLATION
        - SERVICE_COMPLAINT
        - WEATHER
        - CHARGEBACK
        - OTHER

    DisputeStatus:
      type: string
      enum:
        - OPENED
        - UNDER_REVIEW
        - ESCALATED
        - RESOLVED
        - CLOSED

    DisputeTier:
      type: string
      enum:
        - AUTO
        - AGENT
        - MANAGER

    DisputeResolutionType:
      type: string
      enum:
        - FULL_REFUND
        - PARTIAL_REFUND
        - DENIED

    DisputeRequest:
      type: object
      required: [payment_id, guest_id, type, amount_requested]
      properties:
        payment_id:
          type: string
          format: uuid
          description: Payment to dispute
        guest_id:
          type: string
          format: uuid
          description: Guest requesting the refund
        type:
          $ref: '#/components/schemas/DisputeType'
        amount_requested:
          type: number
          format: double
          minimum: 0.01
          description: Refund amount requested
        reason:
          type: string
          maxLength: 1000
          description: Guest-supplied reason for the dispute
        cancellation_date:
          type: string
          format: date-time
          description: When the cancellation occurs (for policy evaluation)

    Dispute:
      type: object
      properties:
        id:
          type: string
          format: uuid
        payment_id:
          type: string
          format: uuid
        reservation_id:
          type: string
          format: uuid
        guest_id:
          type: string
          format: uuid
        type:
          $ref: '#/components/schemas/DisputeType'
        status:
          $ref: '#/components/schemas/DisputeStatus'
        tier:
          $ref: '#/components/schemas/DisputeTier'
        amount_requested:
          type: number
          format: double
        amount_approved:
          type: number
          format: double
          nullable: true
        resolution:
          $ref: '#/components/schemas/DisputeResolutionType'
        justification:
          type: string
          description: Structured reason for the resolution decision
        assigned_to:
          type: string
          nullable: true
          description: Agent or manager handling the dispute
        policy_evaluation:
          $ref: '#/components/schemas/PolicyEvaluation'
        _rev:
          type: integer
          description: Optimistic locking version
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        resolved_at:
          type: string
          format: date-time
          nullable: true

    DisputeUpdateRequest:
      type: object
      properties:
        assigned_to:
          type: string
          description: Assign to an agent or manager
        notes:
          type: string
          maxLength: 2000
          description: Internal notes on the dispute
        status:
          type: string
          enum: [UNDER_REVIEW, ESCALATED]
          description: Advance dispute status (forward-only)
        _rev:
          type: integer
          description: Required for optimistic locking

    DisputeResolution:
      type: object
      required: [decision, justification, resolver_id]
      properties:
        decision:
          $ref: '#/components/schemas/DisputeResolutionType'
        amount_approved:
          type: number
          format: double
          minimum: 0
          description: Required when decision is PARTIAL_REFUND
        justification:
          type: string
          maxLength: 2000
          description: Reason for the decision (required for audit)
        resolver_id:
          type: string
          description: Agent or manager resolving the dispute

    ChargebackNotification:
      type: object
      required: [processor_reference, amount, reason_code]
      properties:
        processor_reference:
          type: string
          description: External chargeback reference from payment processor
        payment_processor_reference:
          type: string
          description: Original payment processor reference (to match the payment)
        amount:
          type: number
          format: double
          description: Chargeback amount
        reason_code:
          type: string
          description: Processor-defined reason code
        reason_description:
          type: string
          description: Human-readable chargeback reason
        deadline:
          type: string
          format: date
          description: Response deadline from the processor

    PolicyEvaluation:
      type: object
      properties:
        eligible:
          type: boolean
          description: Whether the refund is policy-eligible
        refund_percentage:
          type: number
          format: double
          description: Recommended refund percentage per policy
        refund_amount:
          type: number
          format: double
          description: Calculated refund amount
        tier:
          $ref: '#/components/schemas/DisputeTier'
        policy_matched:
          type: string
          description: Name of the policy rule that matched
        evaluation_details:
          type: string
          description: Human-readable explanation of the policy evaluation

    DisputeListResponse:
      type: object
      properties:
        disputes:
          type: array
          items:
            $ref: '#/components/schemas/Dispute'
        total_count:
          type: integer
        limit:
          type: integer
        offset:
          type: integer

  responses:
    BadRequest:
      description: Invalid request parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: Missing or invalid authentication token
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    UnprocessableEntity:
      description: Request understood but cannot be processed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    InternalServerError:
      description: Unexpected server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
