{
  "openapi": "3.1.0",
  "info": {
    "title": "MAXFI Payments API",
    "version": "2.0.0",
    "summary": "Universal payments, payouts and KYC API",
    "description": "One API for cards, wallets, BNPL, payouts and KYC. Hosted-checkout-first with first-class direct-API support. Idempotent, signed webhooks, deterministic sandbox.",
    "contact": {
      "name": "MAXFI Developer Support",
      "url": "https://dev.exezine.az/contact"
    },
    "license": {
      "name": "Commercial",
      "url": "https://dev.exezine.az/legal/terms"
    }
  },
  "servers": [
    {
      "url": "https://maxfi-api.exezine.az",
      "description": "Production (use sk_test_ keys for sandbox)"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Payments",
      "description": "Create, get, capture, refund, cancel, list payments"
    },
    {
      "name": "Checkout",
      "description": "Hosted checkout sessions"
    },
    {
      "name": "Payment Methods",
      "description": "Effective method catalog + non-card method submission"
    },
    {
      "name": "Widget",
      "description": "Embeddable JS SDK — session bootstrap + effective methods"
    },
    {
      "name": "Channels",
      "description": "Per-merchant payment channels (Adyen-style Stores) — bot, web, mobile, POS"
    },
    {
      "name": "Payouts",
      "description": "Send funds out — card, IBAN, SBP"
    },
    {
      "name": "Balance",
      "description": "Your balances per currency — available, pending, rolling reserve"
    },
    {
      "name": "Health",
      "description": "Public service health"
    }
  ],
  "paths": {
    "/v2/payments/checkout": {
      "post": {
        "tags": [
          "Checkout"
        ],
        "summary": "Create a checkout session",
        "operationId": "createCheckoutSession",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCheckoutRequest"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Session created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckoutSession"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v2/payments/checkout/{id}": {
      "get": {
        "tags": [
          "Checkout"
        ],
        "summary": "Get a checkout session",
        "operationId": "getCheckoutSession",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckoutSession"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/checkout/{sessionId}/methods": {
      "get": {
        "tags": [
          "Payment Methods"
        ],
        "summary": "Effective method catalog for a hosted checkout session",
        "description": "Returns the list of payment methods actually enabled for this session. This is the intersection of Merchant Connector Account (MCA) allowed methods, global Connector Method Capability (CMC), and per-merchant MCS overrides. Called by the MAXFI Checkout page on load. Fail-open to card-only if the catalog lookup errors — end customers always see at least one method.",
        "operationId": "getCheckoutMethods",
        "security": [],
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Effective methods",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EffectiveMethodsResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/checkout/{sessionId}/submit-method": {
      "post": {
        "tags": [
          "Payment Methods"
        ],
        "summary": "Submit a non-card payment method (SBP, SberPay, TPay, YooMoney, Apple/Google Pay, recurrent)",
        "description": "Called by the MAXFI Checkout page when the end customer picks any method other than `card`. Card payments continue to use the PCI SAQ A-EP `/checkout/:sessionId/pay` endpoint with the 2-phase 3DS flow. Response carries `next_action` telling the browser whether to render a QR code, open a deeplink, redirect, show an ACS iframe or poll. The checkout page polls `GET /checkout/:sessionId/status` until the session settles into a terminal state.",
        "operationId": "submitCheckoutMethod",
        "security": [],
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitMethodRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Method accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitMethodResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "409": {
            "description": "Session already in terminal state (succeeded/failed/cancelled)."
          }
        }
      }
    },
    "/widget/v1/session": {
      "post": {
        "tags": [
          "Widget"
        ],
        "summary": "Bootstrap a widget session and return a hosted checkout URL",
        "description": "Called by the Widget SDK (`Maxfi` class, loaded from `/widget/v1/maxfi.js`) when the user clicks the Pay button. Creates a regular checkout session and returns its URL so the widget can embed `/checkout/:sessionId` in an iframe. Accepts `initial_method` to pre-select a tab on the checkout page — handy when the widget renders its own method picker.",
        "operationId": "createWidgetSession",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWidgetSessionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Session created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WidgetSessionResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/widget/v1/methods": {
      "get": {
        "tags": [
          "Widget"
        ],
        "summary": "Effective method catalog for the authenticated merchant (widget context)",
        "description": "Returns the same shape as `GET /checkout/:sessionId/methods` but keyed by merchant (no session required). Fail-open to card-only on any lookup error.",
        "operationId": "getWidgetMethods",
        "responses": {
          "200": {
            "description": "Effective methods",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EffectiveMethodsResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/sbp/members": {
      "get": {
        "tags": [
          "Payment Methods"
        ],
        "summary": "Full list of SBP (Faster Payments System) member banks",
        "description": "Returns MAXFI's mirror of the public NSPK dictionary (qr.nspk.ru/proxyapp/c2bmembers.json). Includes every active SBP-connected bank with a 12-digit `member_id` (nspkId), localised names (en/ru/az), logo URL and iOS / Android deeplinks. Use this endpoint when you need to render your own bank picker before submitting an SBP payment, or when you want to validate a `member_id` server-side before calling MAXFI. No auth required — list is considered public information.",
        "operationId": "listSbpMembers",
        "security": [],
        "responses": {
          "200": {
            "description": "Active whitelist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SbpMembersList"
                }
              }
            }
          }
        }
      }
    },
    "/sbp/members/popular": {
      "get": {
        "tags": [
          "Payment Methods"
        ],
        "summary": "Top-N popular SBP member banks",
        "description": "Curated subset of `/sbp/members` sorted by `display_order`. MAXFI Ops maintains the popular list so hosted checkout can render a compact grid (top-12 banks by SBP traffic share) instead of the full 200-bank catalogue. Returns the same entry shape as `/sbp/members`.",
        "operationId": "listSbpMembersPopular",
        "security": [],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 12
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Popular banks",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SbpMembersList"
                }
              }
            }
          }
        }
      }
    },
    "/checkout/{sessionId}/sbp-members": {
      "get": {
        "tags": [
          "Payment Methods"
        ],
        "summary": "SBP member banks scoped to a hosted checkout session",
        "description": "Same list as `/sbp/members/popular` but tied to a running checkout session. Returns an empty array and `currency_supported: false` when the session currency is not RUB (SBP is a ruble-only rail). Used by the MAXFI hosted checkout page to render the bank picker grid inside the SBP tab.",
        "operationId": "getCheckoutSbpMembers",
        "security": [],
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 12
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Popular banks for this session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SbpMembersSessionList"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/widget/v1/sbp-members": {
      "get": {
        "tags": [
          "Widget"
        ],
        "summary": "SBP member banks for embedded Widget SDK",
        "description": "Authenticated with your publishable key. Returns the same popular-list shape as the public `/sbp/members/popular` endpoint, cached for 5 minutes on the CDN edge.",
        "operationId": "getWidgetSbpMembers",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 12
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Popular banks",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SbpMembersList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v2/payments": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Create a direct payment",
        "operationId": "createPayment",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePaymentRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      },
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "List payments",
        "operationId": "listPayments",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of payments"
          }
        }
      }
    },
    "/v2/payments/{id}": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "Get a payment",
        "operationId": "getPayment",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Payment"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v2/payments/{id}/capture": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Capture an authorisation",
        "operationId": "capturePayment",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Captured"
          }
        }
      }
    },
    "/v2/payments/{id}/refund": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Refund a payment (full or partial)",
        "operationId": "refundPayment",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RefundRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Refund issued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RefundResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v2/payments/{id}/cancel": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Cancel an authorisation or pending payment",
        "operationId": "cancelPayment",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled"
          }
        }
      }
    },
    "/v2/payouts": {
      "post": {
        "tags": [
          "Payouts"
        ],
        "summary": "Create a payout",
        "operationId": "createPayout",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePayoutRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Payout created"
          }
        }
      }
    },
    "/v1/balance": {
      "get": {
        "tags": [
          "Balance"
        ],
        "summary": "Get your balance",
        "description": "Per-currency balances of the authenticated merchant, in minor units (4900 = 49.00). `available` can be paid out, `pending` is waiting for settlement, `reserve` is the rolling reserve. An empty `balances` array means no ledger accounts yet — they are created on the first successful payment. If the ledger is temporarily unavailable the API answers **503 BALANCE_UNAVAILABLE** instead of zero balances: retry with backoff and never treat this response as an empty balance.",
        "operationId": "getBalance",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "merchant_id": {
                      "type": "string"
                    },
                    "balances": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "currency": {
                            "type": "string",
                            "example": "AZN"
                          },
                          "available": {
                            "type": "integer",
                            "example": 125000
                          },
                          "pending": {
                            "type": "integer",
                            "example": 4900
                          },
                          "reserve": {
                            "type": "integer",
                            "example": 0
                          }
                        }
                      }
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "description": "Balance is temporarily unavailable (ledger not reachable). Not a zero balance — retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "BALANCE_UNAVAILABLE",
                    "message": "Balance is temporarily unavailable. Please retry later.",
                    "request_id": "req_…"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/channels": {
      "get": {
        "tags": [
          "Channels"
        ],
        "summary": "List channels",
        "description": "Returns all channels of the authenticated merchant. By default soft-deleted channels are excluded.",
        "operationId": "listChannels",
        "parameters": [
          {
            "name": "include_deleted",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Channel"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Channels"
        ],
        "summary": "Create a channel",
        "description": "Slug (`code`) must be unique within your merchant. Returns the new Channel including the freshly minted `public_id` and `webhook_secret` (when set).",
        "operationId": "createChannel",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateChannelRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "$ref": "#/components/schemas/Channel"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "409": {
            "description": "Slug already taken for this merchant."
          }
        }
      }
    },
    "/v1/channels/{id}": {
      "get": {
        "tags": [
          "Channels"
        ],
        "summary": "Retrieve a channel",
        "description": "`id` accepts the UUID, the `ch_xxx` public id, or the merchant-defined slug.",
        "operationId": "getChannel",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "$ref": "#/components/schemas/Channel"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Channels"
        ],
        "summary": "Update a channel",
        "description": "Partial update — branding, webhook URL/secret/enabled, currencies, name. The `code` of the default channel is immutable.",
        "operationId": "updateChannel",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateChannelRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      },
      "delete": {
        "tags": [
          "Channels"
        ],
        "summary": "Soft-delete (default) or hard-delete (?hard=true)",
        "description": "Soft-delete is reversible — the row stays in DB and `restore` brings it back. Hard-delete is blocked when MCAs / API keys / transactions still reference the channel; the response is 409 with a `references` array enumerating the blockers. The default channel cannot be deleted at all.",
        "operationId": "deleteChannel",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "hard",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "409": {
            "description": "HAS_REFERENCES — channel still in use, see `references` array."
          }
        }
      }
    },
    "/v1/channels/{id}/promote-default": {
      "post": {
        "tags": [
          "Channels"
        ],
        "summary": "Make this channel the default",
        "description": "Demotes the previous default channel in the same call. The new default automatically inherits any unscoped requests.",
        "operationId": "promoteChannelDefault",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/channels/{id}/pause": {
      "post": {
        "tags": [
          "Channels"
        ],
        "summary": "Pause a channel",
        "description": "Temporarily hides the channel from routing without losing any history. The default channel cannot be paused.",
        "operationId": "pauseChannel",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/channels/{id}/resume": {
      "post": {
        "tags": [
          "Channels"
        ],
        "summary": "Resume a paused channel",
        "operationId": "resumeChannel",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/channels/{id}/restore": {
      "post": {
        "tags": [
          "Channels"
        ],
        "summary": "Restore a soft-deleted channel",
        "operationId": "restoreChannel",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v2/health": {
      "get": {
        "tags": [
          "Health"
        ],
        "summary": "Service health",
        "operationId": "health",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/auth/me": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "Authenticated session profile (now includes counterparty_role_codes)",
        "description": "Returns the merchant-portal session profile. Since v3.0 the response includes `counterparty_role_codes` — the list of roles this counterparty currently holds in CRM. Merchant Portal access requires `merchant` to be in the list. During the rollout window tokens without the claim are accepted with a warning; once `MERCHANT_ROLE_STRICT=true` is set on the BFF the gate becomes hard.",
        "operationId": "authMe",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "user_id",
                    "email",
                    "merchant_id",
                    "roles",
                    "counterparty_role_codes"
                  ],
                  "properties": {
                    "user_id": {
                      "type": "string",
                      "example": "kc_2A8z…"
                    },
                    "email": {
                      "type": "string",
                      "format": "email",
                      "example": "ops@acme.az"
                    },
                    "name": {
                      "type": "string"
                    },
                    "merchant_id": {
                      "type": "string",
                      "example": "merch_abc123"
                    },
                    "roles": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "example": [
                        "merchant"
                      ]
                    },
                    "counterparty_role_codes": {
                      "type": "array",
                      "description": "Roles this counterparty holds in CRM (Phase 11). Required values include \"merchant\" for portal access.",
                      "items": {
                        "type": "string",
                        "enum": [
                          "merchant",
                          "customer",
                          "vendor",
                          "service_provider",
                          "banking_partner",
                          "payment_processor",
                          "business_partner",
                          "regulator"
                        ]
                      },
                      "example": [
                        "merchant"
                      ]
                    },
                    "default_currency": {
                      "type": "string",
                      "enum": [
                        "AZN",
                        "USD",
                        "EUR",
                        "RUB"
                      ]
                    },
                    "currencies": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "language": {
                      "type": "string",
                      "enum": [
                        "en",
                        "ru",
                        "az"
                      ]
                    },
                    "is_live": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token does not carry counterparty_role_codes=merchant (strict mode)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "sk_live_… / sk_test_…"
      }
    },
    "responses": {
      "ValidationError": {
        "description": "Validation failed",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid Authorization header",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "No resource matches this id for the merchant",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "example": "VALIDATION_ERROR"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "CreateCheckoutRequest": {
        "type": "object",
        "required": [
          "amount",
          "currency"
        ],
        "properties": {
          "amount": {
            "type": "integer",
            "description": "Minor units (4900 = $49.00)"
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217",
            "example": "USD"
          },
          "description": {
            "type": "string",
            "maxLength": 500
          },
          "order_id": {
            "type": "string",
            "maxLength": 128
          },
          "language": {
            "type": "string",
            "enum": [
              "en",
              "ru",
              "az"
            ],
            "default": "en"
          },
          "customer": {
            "$ref": "#/components/schemas/Customer"
          },
          "success_url": {
            "type": "string",
            "format": "uri",
            "description": "Canonical location (top-level). Legacy: settings.success_url also accepted."
          },
          "cancel_url": {
            "type": "string",
            "format": "uri",
            "description": "Canonical location (top-level). Legacy: settings.cancel_url also accepted."
          },
          "settings": {
            "type": "object",
            "description": "Presentation overrides. Legacy fallback for success_url/cancel_url.",
            "properties": {
              "payment_methods": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PaymentMethodCode"
                },
                "description": "Whitelist of methods to expose on the checkout page. Subset of the effective catalog."
              },
              "button_text": {
                "type": "string"
              },
              "auto_return": {
                "type": "integer"
              },
              "success_url": {
                "type": "string",
                "format": "uri",
                "description": "Legacy — prefer top-level success_url."
              },
              "cancel_url": {
                "type": "string",
                "format": "uri",
                "description": "Legacy — prefer top-level cancel_url."
              }
            }
          },
          "webhook_url": {
            "type": "string",
            "format": "uri",
            "description": "Per-session override of the merchant-wide webhook URL."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "test_mode": {
            "type": "boolean"
          },
          "initial_method": {
            "$ref": "#/components/schemas/PaymentMethodCode",
            "description": "Pre-select a method tab on the hosted checkout page."
          },
          "channel_id": {
            "type": "string",
            "maxLength": 64,
            "description": "Channel to run this payment on. Accepts UUID, public_id (`ch_xxx`) or slug. Omit when the API key is channel-scoped (the channel is implicit) or to use the merchant default channel."
          }
        }
      },
      "CheckoutSession": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "cs_…"
          },
          "checkout_url": {
            "type": "string"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "amount": {
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "processing",
              "requires_action",
              "succeeded",
              "failed",
              "cancelled",
              "expired"
            ],
            "description": "Freshly created sessions are `open` and transition through processing/requires_action to terminal states."
          },
          "livemode": {
            "type": "boolean"
          },
          "selected_method_code": {
            "$ref": "#/components/schemas/PaymentMethodCode",
            "description": "Populated after the customer picks/submits a method."
          },
          "next_action": {
            "type": "object",
            "additionalProperties": true,
            "description": "Present when status=`requires_action`. Shape depends on the chosen method: `show_qr` (SBP), `redirect`, `three_ds_challenge` (card), `poll`."
          }
        }
      },
      "Customer": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "phone": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "country": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2
          }
        }
      },
      "CreatePaymentRequest": {
        "type": "object",
        "required": [
          "amount",
          "currency"
        ],
        "properties": {
          "amount": {
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "payment_method": {
            "type": "string",
            "enum": [
              "card",
              "token",
              "wallet"
            ],
            "default": "card"
          },
          "card": {
            "type": "object",
            "properties": {
              "number": {
                "type": "string"
              },
              "exp_month": {
                "type": "string"
              },
              "exp_year": {
                "type": "string",
                "description": "2-digit YY"
              },
              "cvc": {
                "type": "string"
              },
              "holder": {
                "type": "string"
              }
            }
          },
          "capture_method": {
            "type": "string",
            "enum": [
              "automatic",
              "manual"
            ],
            "default": "automatic"
          },
          "description": {
            "type": "string"
          },
          "order_id": {
            "type": "string"
          },
          "customer": {
            "$ref": "#/components/schemas/Customer"
          },
          "webhook_url": {
            "type": "string",
            "format": "uri"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "channel_id": {
            "type": "string",
            "maxLength": 64,
            "description": "Channel to run this payment on. Accepts UUID, public_id (`ch_xxx`) or slug. Omit when the API key is channel-scoped or to use the merchant default channel."
          }
        }
      },
      "Channel": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "UUID — internal"
          },
          "public_id": {
            "type": "string",
            "description": "Stable opaque id (ch_xxx). Use in URLs and stored references."
          },
          "code": {
            "type": "string",
            "description": "Slug, lowercase a-z 0-9 _ -. Unique within merchant. Use in code (`channel_id: \"steam_bot\"`)."
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "channel_type": {
            "type": "string",
            "enum": [
              "web",
              "bot",
              "mobile_app",
              "pos",
              "marketplace",
              "api",
              "invoice",
              "other"
            ]
          },
          "is_default": {
            "type": "boolean",
            "description": "Exactly one per merchant. Cannot be deleted."
          },
          "is_active": {
            "type": "boolean"
          },
          "paused_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "paused_reason": {
            "type": "string",
            "nullable": true
          },
          "deleted_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "logo_url": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "primary_color": {
            "type": "string",
            "nullable": true
          },
          "accent_color": {
            "type": "string",
            "nullable": true
          },
          "theme_mode": {
            "type": "string",
            "enum": [
              "light",
              "dark",
              "auto"
            ],
            "nullable": true
          },
          "custom_title": {
            "type": "string",
            "nullable": true
          },
          "custom_subtitle": {
            "type": "string",
            "nullable": true
          },
          "footer_text": {
            "type": "string",
            "nullable": true
          },
          "return_url": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "support_url": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "show_powered_by": {
            "type": "boolean"
          },
          "show_payment_icons": {
            "type": "boolean"
          },
          "enabled_languages": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "webhook_url": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "webhook_secret": {
            "type": "string",
            "nullable": true,
            "description": "HMAC-SHA256 key. Returned only on create/rotate; masked on list."
          },
          "webhook_events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Empty = subscribe to all."
          },
          "webhook_enabled": {
            "type": "boolean"
          },
          "default_currency": {
            "type": "string"
          },
          "allowed_currencies": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateChannelRequest": {
        "type": "object",
        "required": [
          "code",
          "name"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "Slug — lowercase a-z 0-9 _ -. Must be unique within your merchant."
          },
          "name": {
            "type": "string"
          },
          "channel_type": {
            "type": "string",
            "enum": [
              "web",
              "bot",
              "mobile_app",
              "pos",
              "marketplace",
              "api",
              "invoice",
              "other"
            ],
            "default": "web"
          },
          "description": {
            "type": "string"
          },
          "logo_url": {
            "type": "string",
            "format": "uri"
          },
          "primary_color": {
            "type": "string"
          },
          "return_url": {
            "type": "string",
            "format": "uri"
          },
          "support_url": {
            "type": "string",
            "format": "uri"
          },
          "webhook_url": {
            "type": "string",
            "format": "uri"
          },
          "webhook_secret": {
            "type": "string"
          },
          "webhook_enabled": {
            "type": "boolean",
            "default": false
          },
          "default_currency": {
            "type": "string"
          },
          "allowed_currencies": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Payment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "payment_id": {
            "type": "string"
          },
          "order_id": {
            "type": "string"
          },
          "checkout_session_id": {
            "type": "string"
          },
          "amount": {
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "payment_method": {
            "type": "string"
          },
          "source": {
            "type": "string"
          },
          "card_last4": {
            "type": "string"
          },
          "card_brand": {
            "type": "string"
          },
          "customer_email": {
            "type": "string"
          },
          "error_code": {
            "type": "string"
          },
          "error_message": {
            "type": "string"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "RefundRequest": {
        "type": "object",
        "properties": {
          "amount": {
            "type": "integer",
            "description": "Omit for full refund of the remaining balance."
          },
          "reason": {
            "type": "string",
            "maxLength": 500
          },
          "idempotency_key": {
            "type": "string",
            "maxLength": 128
          }
        }
      },
      "RefundResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "payment_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "refunded",
              "partially_refunded"
            ]
          },
          "amount_refunded": {
            "type": "integer"
          },
          "total_refunded": {
            "type": "integer"
          },
          "remaining": {
            "type": "integer"
          },
          "reason": {
            "type": "string"
          }
        }
      },
      "PaymentMethodCode": {
        "type": "string",
        "enum": [
          "card",
          "sbp",
          "sberpay",
          "tpay",
          "yoomoney",
          "recurrent",
          "apple_pay",
          "google_pay"
        ],
        "description": "Canonical MAXFI method code. `card` uses the PCI SAQ A-EP card form + 3DS flow. All other methods are submitted via `POST /checkout/:sessionId/submit-method`."
      },
      "MethodRenderMode": {
        "type": "string",
        "enum": [
          "inline_form",
          "qr_code",
          "redirect",
          "modal",
          "bottom_sheet",
          "deeplink"
        ],
        "description": "How the checkout page should render the method. QR = show QR for SBP, deeplink = open bank app via `bank.*://` URL on mobile, redirect = navigate away."
      },
      "EffectiveMerchantMethod": {
        "type": "object",
        "required": [
          "method_code",
          "display_order",
          "render_mode",
          "is_enabled"
        ],
        "properties": {
          "method_code": {
            "$ref": "#/components/schemas/PaymentMethodCode"
          },
          "display_order": {
            "type": "integer",
            "description": "Lower = shown first. Merchant-configurable via Merchant Portal."
          },
          "render_mode": {
            "$ref": "#/components/schemas/MethodRenderMode"
          },
          "is_enabled": {
            "type": "boolean",
            "description": "Always `true` in the effective list — disabled methods are filtered out."
          },
          "supports_recurring": {
            "type": "boolean"
          },
          "supports_refund": {
            "type": "boolean"
          },
          "min_amount_minor": {
            "type": "integer",
            "nullable": true,
            "description": "Per-method floor if the connector enforces one."
          },
          "max_amount_minor": {
            "type": "integer",
            "nullable": true
          },
          "allowed_currencies": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "ISO 4217 list, empty = any."
          }
        }
      },
      "EffectiveMethodsResponse": {
        "type": "object",
        "required": [
          "methods"
        ],
        "properties": {
          "methods": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EffectiveMerchantMethod"
            }
          },
          "fallback": {
            "type": "boolean",
            "description": "True if the catalog lookup failed and we degraded to card-only."
          }
        }
      },
      "SubmitMethodRequest": {
        "type": "object",
        "required": [
          "method_code"
        ],
        "properties": {
          "method_code": {
            "$ref": "#/components/schemas/PaymentMethodCode"
          },
          "phone": {
            "type": "string",
            "description": "SberPay / TPay: customer phone (E.164)."
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "member_id": {
            "type": "string",
            "description": "SBP: optional NSPK member id for bank pre-selection."
          },
          "wallet_id": {
            "type": "string",
            "description": "YooMoney: optional wallet id."
          },
          "saved_payment_method_id": {
            "type": "string",
            "description": "recurrent: id of a saved token."
          },
          "payment_token": {
            "type": "string",
            "description": "apple_pay / google_pay: tokenised payload."
          },
          "payment_data": {
            "type": "object",
            "additionalProperties": true,
            "description": "apple_pay / google_pay: raw wallet payload (passed through)."
          }
        }
      },
      "NextActionSbp": {
        "type": "object",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "show_qr"
          },
          "qr_image_url": {
            "type": "string",
            "nullable": true,
            "description": "Data URL or HTTPS image ready to render."
          },
          "qr_raw_payload": {
            "type": "string",
            "nullable": true,
            "description": "Raw NSPK payload — render via local QR lib when image is missing."
          },
          "deeplink": {
            "type": "string",
            "nullable": true,
            "description": "Mobile deeplink to open the user's bank app."
          }
        }
      },
      "NextActionRedirect": {
        "type": "object",
        "required": [
          "type",
          "redirect_url"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "redirect"
          },
          "redirect_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "NextActionThreeDS": {
        "type": "object",
        "required": [
          "type",
          "acs_url",
          "creq"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "three_ds_challenge"
          },
          "acs_url": {
            "type": "string",
            "format": "uri"
          },
          "creq": {
            "type": "string"
          }
        }
      },
      "NextActionPoll": {
        "type": "object",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "poll"
          },
          "hint": {
            "type": "string",
            "description": "Human-readable reason, e.g. \"awaiting_payment\"."
          }
        }
      },
      "SubmitMethodResponse": {
        "type": "object",
        "required": [
          "session_id",
          "status"
        ],
        "properties": {
          "session_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "processing",
              "requires_action",
              "succeeded",
              "failed"
            ]
          },
          "next_action": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/NextActionSbp"
              },
              {
                "$ref": "#/components/schemas/NextActionRedirect"
              },
              {
                "$ref": "#/components/schemas/NextActionThreeDS"
              },
              {
                "$ref": "#/components/schemas/NextActionPoll"
              }
            ],
            "nullable": true,
            "description": "What the browser must do next. Absent when status is already terminal."
          },
          "error": {
            "$ref": "#/components/schemas/Error"
          }
        }
      },
      "CreateWidgetSessionRequest": {
        "type": "object",
        "required": [
          "amount",
          "currency"
        ],
        "properties": {
          "amount": {
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "order_id": {
            "type": "string"
          },
          "customer": {
            "$ref": "#/components/schemas/Customer"
          },
          "initial_method": {
            "$ref": "#/components/schemas/PaymentMethodCode"
          },
          "language": {
            "type": "string",
            "enum": [
              "en",
              "ru",
              "az"
            ]
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "WidgetSessionResponse": {
        "type": "object",
        "required": [
          "session_id",
          "checkout_url",
          "expires_at"
        ],
        "properties": {
          "session_id": {
            "type": "string"
          },
          "checkout_url": {
            "type": "string",
            "format": "uri"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SbpMember": {
        "type": "object",
        "required": [
          "member_id",
          "name",
          "is_popular",
          "display_order"
        ],
        "description": "A single SBP (Система Быстрых Платежей) participant bank, as published by НСПК (qr.nspk.ru/proxyapp/c2bmembers.json). The `member_id` is the stable 12-digit nspkId accepted by all SBP acquirers (Bank131, YooKassa, Tinkoff…). Use it as `extra.memberId` on `POST /checkout/:sessionId/submit-method` for the `sbp` method.",
        "properties": {
          "member_id": {
            "type": "string",
            "description": "12-digit NSPK participant id (e.g. `100000000111` = Сбербанк).",
            "example": "100000000111"
          },
          "name": {
            "type": "object",
            "required": [
              "en",
              "ru",
              "az"
            ],
            "description": "Localised bank name — use whichever locale matches the checkout page.",
            "properties": {
              "en": {
                "type": "string",
                "example": "Sberbank"
              },
              "ru": {
                "type": "string",
                "example": "Сбербанк"
              },
              "az": {
                "type": "string",
                "example": "Sberbank"
              }
            }
          },
          "logo_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "NSPK-hosted logo, safe to embed client-side. `null` when the CDN entry is missing."
          },
          "deeplink_ios": {
            "type": "string",
            "nullable": true,
            "description": "Universal Link to open the bank app on iOS (e.g. `bank100000000111://`). Prefer over generic SBP deeplink when known."
          },
          "deeplink_android": {
            "type": "string",
            "nullable": true,
            "description": "Android intent/Universal Link to open the bank app."
          },
          "is_popular": {
            "type": "boolean",
            "description": "Curated \"most popular\" set shown by default on the hosted checkout grid."
          },
          "display_order": {
            "type": "integer",
            "description": "Lower = shown first. Popular banks have stable ordering across refreshes."
          }
        }
      },
      "SbpMembersList": {
        "type": "object",
        "required": [
          "data",
          "count"
        ],
        "description": "Whitelist response for SBP bank picker endpoints. `data` is always deduplicated by `member_id` and ordered by `display_order` then `name.ru`.",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SbpMember"
            }
          },
          "count": {
            "type": "integer",
            "description": "Number of entries in `data` — matches `data.length`."
          }
        }
      },
      "SbpMembersSessionList": {
        "type": "object",
        "required": [
          "data",
          "count",
          "currency_supported"
        ],
        "description": "Session-scoped variant of SbpMembersList. Returns an empty `data` array with `currency_supported=false` when the checkout session currency is not RUB (SBP is a rouble-only rail), so the UI can hide the picker without a second round-trip.",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SbpMember"
            }
          },
          "count": {
            "type": "integer"
          },
          "currency_supported": {
            "type": "boolean",
            "description": "`true` if the session currency is RUB and the picker should be rendered."
          }
        }
      }
    }
  }
}