{
  "openapi": "3.1.0",
  "info": {
    "title": "StaliPay API",
    "version": "2.3.7",
    "description": "StaliPay Direct — cổng thanh toán độc lập, kết nối trực tiếp ngân hàng (MB Bank). Nhận giao dịch BDSD, khớp đơn, gửi thông báo có chữ ký HMAC. Không qua trung gian đối soát (SePay, Casso…)."
  },
  "servers": [{ "url": "https://pay.stali.vn" }],
  "paths": {
    "/api/v1/events/ingest": {
      "post": {
        "operationId": "ingestEvent",
        "summary": "Gửi một giao dịch thanh toán",
        "description": "Ingest single payment event from shop or connector.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": { "type": "string" },
            "description": "Khóa chống trùng (TTL 24h) — tùy chọn"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/IngestEvent" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Kết quả khớp đơn",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/IngestResult" }
              }
            }
          },
          "401": { "description": "Khóa API không hợp lệ" },
          "429": { "description": "Vượt giới hạn tốc độ" }
        }
      }
    },
    "/api/v1/events/batch": {
      "post": {
        "operationId": "batchIngestEvents",
        "summary": "Gửi hàng loạt (tối đa 100 giao dịch)",
        "description": "Batch ingest — max 100 events per request.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": { "type": "string" },
            "description": "Dùng tiền tố batch: để chống trùng cả lô"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["events"],
                "properties": {
                  "events": {
                    "type": "array",
                    "maxItems": 100,
                    "items": { "$ref": "#/components/schemas/IngestEvent" }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Kết quả từng giao dịch trong lô" }
        }
      }
    },
    "/api/v1/payments/lookup": {
      "get": {
        "operationId": "lookupPayment",
        "summary": "Tra cứu đơn theo mã",
        "description": "Reconcile payment by order code.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          {
            "name": "order_code",
            "in": "query",
            "required": true,
            "schema": { "type": "string" },
            "description": "Mã đơn trong nội dung chuyển khoản"
          }
        ],
        "responses": {
          "200": { "description": "Trạng thái thanh toán và metadata" },
          "404": { "description": "Không tìm thấy giao dịch khớp" }
        }
      }
    },
    "/api/v1/connector/push": {
      "post": {
        "summary": "Đẩy giao dịch BDSD (connector chung)",
        "description": "Generic BDSD push — dùng khi tích hợp adapter tùy chỉnh.",
        "security": [{ "bearerAuth": [] }],
        "responses": {
          "200": { "description": "Đã nhận và xử lý" }
        }
      }
    },
    "/api/v1/connector/direct/health": {
      "get": {
        "operationId": "directHealth",
        "summary": "Sức khỏe StaliPay Direct",
        "description": "Trạng thái kết nối MB Bank, adapter, lần poll cuối.",
        "responses": {
          "200": {
            "description": "Trạng thái connector, sandbox_mode, poll lag"
          }
        }
      }
    },
    "/api/v1/webhooks/dry-run": {
      "post": {
        "summary": "Thử chữ ký thông báo (không gửi HTTP)",
        "description": "Dry-run webhook HMAC — verify signing without delivery.",
        "security": [{ "bearerAuth": [] }],
        "responses": {
          "200": { "description": "Chữ ký và payload mẫu" }
        }
      }
    },
    "/api/v1/webhooks/replay": {
      "post": {
        "summary": "Gửi lại một thông báo",
        "description": "Replay single webhook delivery by ID.",
        "security": [{ "bearerAuth": [] }],
        "responses": {
          "200": { "description": "Đã xếp hàng gửi lại" }
        }
      }
    },
    "/api/v1/webhooks/replay/batch": {
      "post": {
        "summary": "Gửi lại hàng loạt thông báo",
        "description": "Batch replay failed/pending deliveries.",
        "security": [{ "bearerAuth": [] }],
        "responses": {
          "200": { "description": "Số lượng đã xếp hàng" }
        }
      }
    },
    "/api/v1/webhooks/dispatch": {
      "post": {
        "summary": "Kích hoạt gửi thông báo nội bộ",
        "description": "Internal dispatch worker trigger.",
        "security": [{ "bearerAuth": [] }],
        "responses": {
          "200": { "description": "Worker đã nhận lệnh" }
        }
      }
    },
    "/api/proxy/webhook": {
      "post": {
        "summary": "Proxy thông báo đến (CNAME đã xác minh)",
        "description": "Inbound webhook proxy for verified custom domain."
      }
    },
    "/api/v1/webhooks/sink": {
      "post": {
        "summary": "Sink thử chữ ký thông báo",
        "description": "Test webhook sink — demo signature verification."
      }
    },
    "/api/v1/onboarding/register": {
      "post": {
        "summary": "Đăng ký merchant tự phục vụ",
        "description": "Self-service registration — trả về khóa thử sp_test_*.",
        "responses": {
          "201": { "description": "Tài khoản và khóa API thử" }
        }
      }
    },
    "/api/health": {
      "get": {
        "summary": "Kiểm tra sức khỏe",
        "description": "Liveness probe — DB, workers."
      }
    },
    "/api/status/summary": {
      "get": {
        "summary": "Tóm tắt trạng thái (nhẹ)",
        "description": "Lightweight status for dashboards — cache 15s."
      }
    },
    "/api/metrics": {
      "get": {
        "summary": "Chỉ số Prometheus",
        "description": "Prometheus metrics including SLA gauges."
      }
    },
    "/api/v1/connector/{provider}": {
      "get": {
        "summary": "Connector legacy (đã ngừng)",
        "description": "SePay, Casso và các connector trung gian trả HTTP 410 Gone. Chuyển sang StaliPay Direct.",
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "enum": ["sepay", "casso"] }
          }
        ],
        "responses": {
          "410": {
            "description": "connector_retired — dùng StaliPay Direct thay thế"
          }
        }
      },
      "post": {
        "summary": "Connector legacy (đã ngừng)",
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "enum": ["sepay", "casso"] }
          }
        ],
        "responses": {
          "410": { "description": "connector_retired" }
        }
      }
    },
    "/api/enterprise-inquiry": {
      "post": {
        "summary": "Liên hệ gói Doanh nghiệp",
        "description": "Gửi yêu cầu tư vấn Enterprise — rate limit 5/15 phút/IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/EnterpriseInquiry" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Đã ghi nhận — trả reference ENT-*",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": { "type": "boolean" },
                    "reference": { "type": "string", "example": "ENT-MF8K2A" }
                  }
                }
              }
            }
          },
          "400": { "description": "missing_fields hoặc invalid_phone" },
          "429": { "description": "rate_limited" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Khóa API sp_live_* (chính thức) hoặc sp_test_* (thử nghiệm)"
      }
    },
    "schemas": {
      "IngestEvent": {
        "type": "object",
        "required": ["bank_id", "account_number", "amount_vnd"],
        "properties": {
          "bank_id": { "type": "string", "example": "MB", "description": "Mã ngân hàng" },
          "account_number": { "type": "string", "description": "Số tài khoản nhận tiền" },
          "external_id": { "type": "string", "description": "ID giao dịch phía ngân hàng" },
          "amount_vnd": { "type": "integer", "description": "Số tiền VND" },
          "transfer_content": { "type": "string", "description": "Nội dung chuyển khoản (mã đơn)" }
        }
      },
      "IngestResult": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "event_id": { "type": "string" },
          "status": { "enum": ["matched", "unmatched"], "description": "Đã khớp đơn hay chưa" },
          "order_code": { "type": "string", "nullable": true },
          "delivery_id": { "type": "string", "nullable": true },
          "duplicate": { "type": "boolean", "description": "Giao dịch trùng (idempotency)" }
        }
      },
      "WebhookPayload": {
        "type": "object",
        "properties": {
          "event": { "type": "string", "example": "payment.matched" },
          "id": { "type": "string" },
          "data": {
            "type": "object",
            "properties": {
              "amount_vnd": { "type": "integer" },
              "order_code": { "type": "string" },
              "transfer_content": { "type": "string" },
              "bank_id": { "type": "string" },
              "bank_account": { "type": "string" },
              "reference": { "type": "string" }
            }
          }
        }
      },
      "EnterpriseInquiry": {
        "type": "object",
        "required": ["shop_name", "email"],
        "properties": {
          "shop_name": { "type": "string", "maxLength": 120 },
          "email": { "type": "string", "format": "email" },
          "phone": { "type": "string", "description": "SĐT VN (+84 hoặc 0)" },
          "monthly_volume": { "type": "string", "maxLength": 64 },
          "note": { "type": "string", "maxLength": 1000 },
          "website": { "type": "string", "description": "Honeypot — để trống" }
        }
      }
    }
  }
}
