{
  "openapi": "3.1.0",
  "info": {
    "title": "ACAPAY Payment Gateway API",
    "version": "1.1.0",
    "description": "QRIS dinamis per-project (Bearer prj_sec_xxx), ledger ganda, payout manual 1x24jam + fee 3k, bank account 1x immutable, broadcast, avatar via Catbox (hash di admin settings). Base /api/v1. Envelope {success, data/error}. Docs: /docs"
  },
  "servers": [{ "url": "https://pay.xiamostore.xyz", "description": "Production" }],
  "security": [{ "bearerAuth": [] }, { "cookieAuth": [] }],
  "components": {
    "securitySchemes": {
      "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "acp_sec_/prj_sec_" },
      "cookieAuth": { "type": "apiKey", "in": "cookie", "name": "acapay_session" }
    },
    "schemas": {
      "Success": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object" } } },
      "Error": { "type": "object", "properties": { "success": { "type": "boolean", "example": false }, "error": { "type": "object", "properties": { "code": { "type": "string", "example": "VALIDATION_ERROR" }, "message": { "type": "string" } } } } }
    }
  },
  "paths": {
    "/api/v1/auth/register": { "post": { "summary": "Register merchant", "security": [], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["name", "email", "password", "business_name"], "properties": { "name": { "type": "string", "minLength": 2 }, "email": { "type": "string", "format": "email" }, "password": { "type": "string", "minLength": 6 }, "business_name": { "type": "string", "minLength": 2 }, "whatsapp": { "type": "string" }, "store_url": { "type": "string" }, "purpose": { "type": "string" }, "role": { "type": "string", "enum": ["merchant", "user", "admin"], "default": "merchant" } } } } }, "responses": { "201": { "description": "Created + Set-Cookie (2d)" }, "400": { "description": "VALIDATION_ERROR/EMAIL_ALREADY_EXISTS" } } } },
    "/api/v1/auth/login": { "post": { "summary": "Login (session 2d, redirect if already logged)", "security": [], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["email", "password"], "properties": { "email": { "type": "string", "format": "email" }, "password": { "type": "string" }, "rememberMe": { "type": "boolean" } } } } }, "responses": { "200": { "description": "OK + Set-Cookie acapay_session (2d)" } } } },
    "/api/v1/auth/me": {
      "get": { "summary": "Get current user (avatar_url, merchant, balance)", "responses": { "200": { "description": "User+merchant+balance" }, "401": { "description": "UNAUTHORIZED" } } },
      "patch": { "summary": "Update profile (name, avatar_url)", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string", "minLength": 2 }, "avatar_url": { "type": "string", "format": "uri", "nullable": true } } } } }, "responses": { "200": { "description": "Updated" } } }
    },
    "/api/v1/auth/avatar": { "post": { "summary": "Upload avatar via Catbox (hash from admin settings DB)", "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": { "type": "object", "required": ["file"], "properties": { "file": { "type": "string", "format": "binary" } } } } }, "responses": { "200": { "description": "url" } } } },
    "/api/v1/auth/logout": { "post": { "summary": "Logout", "security": [], "responses": { "200": { "description": "Cookie cleared" } } } },
    "/api/v1/bank-account": {
      "get": { "summary": "Get rekening (1 per user, immutable)", "responses": { "200": { "description": "BankAccount or null" } } },
      "post": { "summary": "Create rekening (1x only, tidak dapat diedit/hapus)", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["bank_name", "account_number", "account_name"], "properties": { "bank_name": { "type": "string" }, "account_number": { "type": "string" }, "account_name": { "type": "string" } } } } }, "responses": { "201": { "description": "Created" }, "400": { "description": "ALREADY_EXISTS" } } }
    },
    "/api/v1/projects": {
      "get": { "summary": "List projects (max 5, with total_transaksi)", "responses": { "200": { "description": "Project[]" } } },
      "post": { "summary": "Create project (max 5)", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["name"], "properties": { "name": { "type": "string", "minLength": 2 }, "mode": { "type": "string", "enum": ["sandbox", "live"], "default": "sandbox" }, "fee_by_merchant": { "type": "boolean", "default": true }, "webhook_url": { "type": "string", "format": "uri", "nullable": true } } } } }, "responses": { "201": { "description": "prj_sec_xxx" }, "400": { "description": "PROJECT_LIMIT" } } }
    },
    "/api/v1/projects/{id}": {
      "get": { "summary": "Detail project", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "Project" } } },
      "patch": { "summary": "Update project (Aktif/Mode/Fee By Merchant/Webhook)", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string" }, "is_active": { "type": "boolean" }, "mode": { "type": "string", "enum": ["sandbox", "live"] }, "fee_by_merchant": { "type": "boolean" }, "webhook_url": { "type": "string", "format": "uri", "nullable": true } } } } }, "responses": { "200": { "description": "Updated" } } },
      "post": { "summary": "Regenerate project API key", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "new prj_sec" } } },
      "delete": { "summary": "Delete project", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "Deleted" } } }
    },
    "/api/v1/broadcasts": { "get": { "summary": "List active broadcasts (merchant overview card)", "security": [], "responses": { "200": { "description": "Broadcast[]" } } } },
    "/api/v1/admin/broadcasts": {
      "get": { "summary": "Admin list all broadcasts", "responses": { "200": { "description": "Broadcast[]" } } },
      "post": { "summary": "Admin create broadcast (title, description HTML bold/italic, image_url)", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["title", "description"], "properties": { "title": { "type": "string" }, "description": { "type": "string", "description": "HTML rich text" }, "image_url": { "type": "string", "format": "uri", "nullable": true }, "is_active": { "type": "boolean" } } } } }, "responses": { "201": { "description": "Broadcast" } } }
    },
    "/api/v1/admin/broadcasts/{id}": {
      "patch": { "summary": "Admin update broadcast", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "Updated" } } },
      "delete": { "summary": "Admin delete broadcast", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "Deleted" } } }
    },
    "/api/v1/invoices": {
      "post": { "summary": "Create invoice (wajib project via Bearer prj_sec_xxx atau project_id, fee ikut project, external_id custom)", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["amount", "description", "customer"], "properties": { "amount": { "type": "integer", "minimum": 1 }, "description": { "type": "string", "maxLength": 255 }, "customer": { "type": "object", "required": ["name", "email"], "properties": { "name": { "type": "string" }, "email": { "type": "string", "format": "email" }, "phone": { "type": "string", "nullable": true } } }, "callback_url": { "type": "string", "format": "uri", "nullable": true }, "fee_bearer": { "type": "string", "enum": ["merchant", "customer"] }, "external_id": { "type": "string" }, "project_id": { "type": "string" } } } } }, "responses": { "201": { "description": "Invoice+transaction" } } },
      "get": { "summary": "List invoices", "responses": { "200": { "description": "Invoice[]" } } }
    },
    "/api/v1/invoices/{id}": { "get": { "summary": "Get invoice (support custom external_id)", "security": [], "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "invoice+transaction+merchant" }, "404": { "description": "INVOICE_NOT_FOUND" } } } },
    "/api/v1/transactions": { "get": { "summary": "List transactions", "responses": { "200": { "description": "Transaction[]" } } } },
    "/api/v1/transactions/{id}": { "get": { "summary": "Get transaction", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "Transaction" }, "403": { "description": "FORBIDDEN" } } } },
    "/api/v1/balance": { "get": { "summary": "Get balance+ledger", "responses": { "200": { "description": "balance+ledger" } } } },
    "/api/v1/qris": { "get": { "summary": "Generate QRIS (Bearer prj_sec_xxx disarankan agar terikat merchant)", "parameters": [{ "name": "amount", "in": "query", "required": true, "schema": { "type": "integer", "minimum": 1 } }], "responses": { "200": { "description": "image_data_url (+merchant_id if Bearer)" }, "503": { "description": "QRIS_STATIC_CODE missing" } } } },
    "/api/v1/payouts": {
      "post": { "summary": "Request payout (wajib punya rekening 1x immutable, fee 3k)", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["amount"], "properties": { "amount": { "type": "integer", "minimum": 1 } } } } }, "responses": { "201": { "description": "Payout pending (uses stored bank account)" } } },
      "get": { "summary": "List payouts", "responses": { "200": { "description": "Payout[]" } } }
    },
    "/api/v1/payouts/{id}": { "get": { "summary": "Get payout", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "Payout" } } } },
    "/api/v1/merchant/api-keys": {
      "get": { "summary": "Get merchant keys (legacy)", "responses": { "200": { "description": "merchant keys" } } },
      "post": { "summary": "Regenerate secret (session only)", "responses": { "200": { "description": "new secret_key" } } }
    },
    "/api/v1/merchant/webhooks": {
      "get": { "summary": "Get webhook config+logs", "responses": { "200": { "description": "webhook" } } },
      "post": { "summary": "Update webhook", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "webhook_url": { "type": "string", "format": "uri", "nullable": true }, "business_name": { "type": "string" } } } } }, "responses": { "200": { "description": "updated" } } }
    },
    "/api/v1/admin/stats": { "get": { "summary": "Admin stats" } },
    "/api/v1/admin/transactions": { "get": { "summary": "Admin all transactions" } },
    "/api/v1/admin/payouts/{id}/approve": { "post": { "summary": "Approve payout", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }] } },
    "/api/v1/admin/payouts/{id}/complete": { "post": { "summary": "Complete payout", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["transfer_reference"], "properties": { "transfer_reference": { "type": "string" }, "admin_note": { "type": "string" } } } } } } },
    "/api/v1/admin/payouts/{id}/reject": { "post": { "summary": "Reject payout" } },
    "/api/v1/admin/payouts/{id}/fail": { "post": { "summary": "Fail payout" } },
    "/api/v1/admin/unmatched-payments": { "get": { "summary": "List unmatched" } },
    "/api/v1/admin/unmatched-payments/{id}/match": { "post": { "summary": "Manual match", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["transaction_id"], "properties": { "transaction_id": { "type": "string" }, "notes": { "type": "string" } } } } } } },
    "/api/v1/admin/users": { "get": { "summary": "List users safe (with avatar_url)" } },
    "/api/v1/admin/merchants": { "get": { "summary": "List merchants" } },
    "/api/v1/admin/audit-logs": { "get": { "summary": "Audit logs" } },
    "/api/v1/admin/settings": { "get": { "summary": "Get settings (catbox_user_hash, payout_fee 3k, qris)" }, "post": { "summary": "Update settings (catbox_user_hash from admin pengaturan DB)" } },
    "/api/v1/admin/webhooks": { "get": { "summary": "All webhook logs" } },
    "/api/webhooks/payhook": {
      "get": { "summary": "PayHook health", "security": [] },
      "post": { "summary": "PayHook incoming", "security": [], "description": "HMAC X-Payhook-Signature, token Bearer. Async 202." }
    }
  }
}
