openapi: 3.1.0
info:
  title: Northline Pay Merchant API
  version: "1.4.0"
  description: |
    Create payments on behalf of buyers, capture, refund, and list them.
    All amounts are expressed in the payment currency.
    Authenticate with your secret API key as a bearer token.
  contact:
    name: Northline Pay API team
    email: api@northlinepay.example
servers:
  - url: https://api.sandbox.northlinepay.example/v1
    description: Sandbox
  - url: https://api.northlinepay.example/v1
    description: Production

security:
  - apiKey: []

paths:
  /payments:
    post:
      operationId: createPayment
      summary: Create a payment
      description: |
        Creates a payment for a buyer. With `capture_method: automatic` the
        payment is authorized and captured in one call. With `manual`, call
        the capture endpoint later.
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePaymentRequest"
      responses:
        "201":
          description: Payment created
          headers:
            Northline-Request-Id:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Payment"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          description: The payment method was declined
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    get:
      operationId: listPayments
      summary: List payments
      description: Returns the merchant's payments, most recent first.
      parameters:
        - name: limit
          in: query
          description: Number of payments to return.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: status
          in: query
          schema:
            $ref: "#/components/schemas/PaymentStatus"
      responses:
        "200":
          description: A page of payments
          content:
            application/json:
              schema:
                type: object
                required: [object, data, has_more]
                properties:
                  object:
                    type: string
                    const: list
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Payment"
                  has_more:
                    type: boolean
        "401":
          $ref: "#/components/responses/Unauthorized"

  /payments/{payment_id}:
    get:
      operationId: getPayment
      summary: Retrieve a payment
      parameters:
        - $ref: "#/components/parameters/PaymentId"
      responses:
        "200":
          description: The payment
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Payment"
        "404":
          $ref: "#/components/responses/NotFound"

  /payments/{payment_id}/capture:
    post:
      operationId: capturePayment
      summary: Capture an authorized payment
      parameters:
        - $ref: "#/components/parameters/PaymentId"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                amount:
                  type: number
                  description: Amount to capture. Defaults to the authorized amount.
      responses:
        "200":
          description: The captured payment
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Payment"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: The payment is not in a capturable state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /refunds:
    post:
      operationId: createRefund
      summary: Refund a captured payment
      description: Refunds all or part of a captured payment.
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [payment_id]
              properties:
                payment_id:
                  type: string
                amount:
                  type: number
                  description: Amount to refund. Defaults to the full captured amount.
                reason:
                  type: string
                  enum: [requested_by_customer, duplicate, fraudulent]
      responses:
        "201":
          description: Refund created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Refund"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"

  /refunds/{refund_id}:
    get:
      operationId: getRefund
      summary: Retrieve a refund
      parameters:
        - name: refund_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: The refund
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Refund"
        "404":
          $ref: "#/components/responses/NotFound"

webhooks:
  payment.authorized:
    post:
      summary: A payment was authorized
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEvent"
      responses:
        "200":
          description: Acknowledge with any 2xx status
  payment.captured:
    post:
      summary: A payment was captured
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEvent"
      responses:
        "200":
          description: Acknowledge with any 2xx status
  payment.failed:
    post:
      summary: A payment failed or was declined
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEvent"
      responses:
        "200":
          description: Acknowledge with any 2xx status
  refund.succeeded:
    post:
      summary: A refund was completed
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEvent"
      responses:
        "200":
          description: Acknowledge with any 2xx status

components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: Your secret key, `sk_test_...` in sandbox and `sk_live_...` in production.

  parameters:
    PaymentId:
      name: payment_id
      in: path
      required: true
      schema:
        type: string
        pattern: "^pay_[A-Za-z0-9]{16}$"
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: A unique key you generate to safely retry the request.
      schema:
        type: string
        maxLength: 64

  headers:
    RequestId:
      description: Identifier of this request, for support tickets.
      schema:
        type: string

  responses:
    BadRequest:
      description: The request was invalid
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Unauthorized:
      description: The API key is missing or invalid
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    NotFound:
      description: No such resource for this merchant
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"

  schemas:
    PaymentStatus:
      type: string
      enum: [pending, authorized, captured, failed, canceled]

    CreatePaymentRequest:
      type: object
      required: [amount, currency, payment_method]
      properties:
        amount:
          type: number
          description: The payment amount.
        currency:
          type: string
          description: Three-letter currency code.
        capture_method:
          type: string
          enum: [automatic, manual]
          default: automatic
        payment_method:
          $ref: "#/components/schemas/PaymentMethodInput"
        customer:
          type: object
          properties:
            email:
              type: string
              format: email
        description:
          type: string
          maxLength: 200
        split:
          type: array
          description: How the amount is distributed across seller accounts.
          items:
            type: object
            required: [account_id, amount]
            properties:
              account_id:
                type: string
              amount:
                type: number
        metadata:
          type: object
          additionalProperties:
            type: string

    PaymentMethodInput:
      type: object
      required: [type, token]
      properties:
        type:
          type: string
          enum: [card, sepa_debit, interac]
        token:
          type: string
          description: A payment method token created with Northline.js.

    Payment:
      type: object
      required: [id, object, status, amount, currency, capture_method, payment_method, created_at]
      properties:
        id:
          type: string
        object:
          type: string
          const: payment
        status:
          $ref: "#/components/schemas/PaymentStatus"
        amount:
          type: number
        currency:
          type: string
        capture_method:
          type: string
          enum: [automatic, manual]
        payment_method:
          type: object
          properties:
            type:
              type: string
            card:
              type: object
              properties:
                brand:
                  type: string
                last4:
                  type: string
                exp_month:
                  type: integer
                exp_year:
                  type: integer
        customer:
          type: object
          properties:
            email:
              type: string
        split:
          type: array
          items:
            type: object
            properties:
              account_id:
                type: string
              amount:
                type: number
        failure_code:
          type: string
        failure_message:
          type: string
        metadata:
          type: object
          additionalProperties:
            type: string
        created_at:
          type: string
          format: date-time

    Refund:
      type: object
      required: [id, object, payment_id, amount, currency, status, created_at]
      properties:
        id:
          type: string
        object:
          type: string
          const: refund
        payment_id:
          type: string
        amount:
          type: number
        currency:
          type: string
        status:
          type: string
          enum: [pending, succeeded, failed]
        reason:
          type: string
        created_at:
          type: string
          format: date-time

    WebhookEvent:
      type: object
      required: [id, type, created_at, data]
      properties:
        id:
          type: string
        type:
          type: string
        created_at:
          type: string
          format: date-time
        data:
          type: object
          description: The payment or refund resource, as returned by the API.

    Error:
      type: object
      required: [type, code, message]
      properties:
        type:
          type: string
          enum: [invalid_request_error, authentication_error, card_error, api_error]
        code:
          type: string
          description: |
            One of: invalid_request, authentication_failed, payment_not_found,
            payment_not_capturable, card_declined, rate_limited.
        message:
          type: string
        param:
          type: string
          description: The request field the error refers to, when applicable.
