{
  "openapi": "3.0.3",
  "info": {
    "title": "Poko Payment Gateway API",
    "version": "2.0.0",
    "description": "Dokumentasi Lengkap RESTful API, Webhook IPN, & Spesifikasi Integrasi QRIS Dinamis Poko Payment Gateway (Dual-Gateway GoPay & DANA).",
    "termsOfService": "https://pokopayment.biz.id/docs",
    "contact": {
      "name": "Poko Customer Support",
      "url": "https://t.me/pokosoft",
      "email": "support@pokopayment.biz.id"
    },
    "community": {
      "group": "https://t.me/PokoPaymentNotif",
      "bot": "https://t.me/pokopaymentbot"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://pokopayment.biz.id/docs"
    }
  },
  "servers": [
    {
      "url": "https://pokopayment.biz.id",
      "description": "Production Gateway Server"
    }
  ],
  "externalDocs": {
    "description": "Mini App Dokumentasi Interaktif & Panduan Integrasi",
    "url": "https://pokopayment.biz.id/docs"
  },
  "security": [
    {
      "BearerAuth": []
    },
    {
      "ApiKeyAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API Key (mch_live_...)",
        "description": "Kirimkan API Key toko Anda melalui header `Authorization: Bearer <API_KEY>`. Format ini adalah format resmi yang direkomendasikan."
      },
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "Format alternatif melalui header `X-Api-Key: <API_KEY>`."
      }
    },
    "schemas": {
      "CreateQrisRequest": {
        "type": "object",
        "required": [
          "amount"
        ],
        "properties": {
          "amount": {
            "type": "integer",
            "minimum": 100,
            "maximum": 100000000,
            "example": 25000,
            "description": "Nominal tagihan pembayaran dalam satuan Rupiah (IDR). Sistem otomatis menambahkan kode unik genap (+2, +4, ...) agar mutasi tidak pernah ambigu."
          },
          "description": {
            "type": "string",
            "maxLength": 25,
            "example": "INV-2026-001",
            "description": "Catatan singkat atau nomor invoice toko (maks. 25 karakter)."
          },
          "customerName": {
            "type": "string",
            "maxLength": 25,
            "example": "Budi Santoso",
            "description": "Nama pelanggan pemesan (maks. 25 karakter)."
          },
          "redirectUrl": {
            "type": "string",
            "format": "uri",
            "example": "https://tokosaya.com/sukses",
            "description": "URL halaman redirect website Anda setelah pembeli selesai membayar di halaman web QRIS."
          },
          "webhookUrl": {
            "type": "string",
            "format": "uri",
            "example": "https://tokosaya.com/api/payment-callback",
            "description": "URL endpoint webhook server Anda (IPN) jika ingin melakukan override khusus per transaksi."
          }
        }
      },
      "CreateQrisResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "data": {
            "type": "object",
            "properties": {
              "qrisId": {
                "type": "string",
                "example": "q7x2m9a1",
                "description": "ID unik transaksi di sistem gateway"
              },
              "trxId": {
                "type": "string",
                "example": "TG-A8F2K9",
                "description": "Kode invoice referensi"
              },
              "amount": {
                "type": "integer",
                "example": 25002,
                "description": "Total nominal yang harus dibayar pembeli (termasuk kode unik genap)"
              },
              "baseAmount": {
                "type": "integer",
                "example": 25000,
                "description": "Nominal asli tagihan sebelum ditambah kode unik"
              },
              "uniqueCode": {
                "type": "integer",
                "example": 2,
                "description": "Nilai kode unik genap yang ditambahkan sistem (+2, +4, ...)"
              },
              "qrString": {
                "type": "string",
                "example": "00020101021226...",
                "description": "String payload payload standar EMVCo CRC16 QRIS"
              },
              "qrImageUrl": {
                "type": "string",
                "example": "https://pokopayment.biz.id/qr/q7x2m9a1?format=raw",
                "description": "URL gambar langsung barcode QRIS (image/png)"
              },
              "checkoutUrl": {
                "type": "string",
                "example": "https://pokopayment.biz.id/qr/q7x2m9a1",
                "description": "Halaman checkout web interaktif pelanggan"
              },
              "status": {
                "type": "string",
                "enum": [
                  "PENDING",
                  "PAID",
                  "EXPIRED"
                ],
                "example": "PENDING"
              },
              "expiresAt": {
                "type": "string",
                "format": "date-time",
                "example": "2026-10-26T10:05:00.000Z"
              }
            }
          }
        }
      },
      "CheckPaymentRequest": {
        "type": "object",
        "properties": {
          "amount": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100000000,
            "example": 25002,
            "description": "Nominal yang dicari pada mutasi pembayaran."
          },
          "startTime": {
            "type": "integer",
            "example": 1761450000,
            "description": "Unix timestamp awal pencarian transaksi."
          },
          "trx_id": {
            "type": "string",
            "example": "TRX-A8F2K9",
            "description": "Opsional: batasi pencarian ke satu invoice."
          }
        }
      },
      "CheckPaymentResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "paid": {
            "type": "boolean",
            "example": true
          },
          "status": {
            "type": "string",
            "enum": [
              "PAID",
              "PENDING",
              "EXPIRED"
            ],
            "example": "PAID"
          },
          "data": {
            "type": "object",
            "properties": {
              "qrisId": {
                "type": "string",
                "example": "q7x2m9a1"
              },
              "trxId": {
                "type": "string",
                "example": "TG-A8F2K9"
              },
              "amount": {
                "type": "integer",
                "example": 25002
              },
              "status": {
                "type": "string",
                "example": "PAID"
              },
              "paidAt": {
                "type": "string",
                "format": "date-time",
                "example": "2026-10-26T10:01:15.000Z"
              },
              "paymentSource": {
                "type": "string",
                "enum": [
                  "gopay",
                  "dana"
                ],
                "example": "gopay"
              }
            }
          }
        }
      },
      "MerchantProfileResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "merchant": {
            "type": "object",
            "properties": {
              "telegramId": {
                "type": "string",
                "example": "123456789"
              },
              "merchantName": {
                "type": "string",
                "example": "TOKO MAJU JAYA"
              },
              "nmid": {
                "type": "string",
                "example": "ID1020030040050"
              },
              "qrisConfigured": {
                "type": "boolean",
                "example": true
              },
              "gobizConnected": {
                "type": "boolean",
                "example": true
              },
              "danaConnected": {
                "type": "boolean",
                "example": true
              },
              "subscriptionActive": {
                "type": "boolean",
                "example": true
              },
              "remainingDays": {
                "type": "integer",
                "example": 29
              },
              "webhookUrl": {
                "type": "string",
                "example": "https://tokosaya.com/webhook"
              }
            }
          }
        }
      },
      "WebhookPayload": {
        "type": "object",
        "description": "Payload yang dikirimkan gateway via HTTP POST ke Webhook URL server toko Anda saat pembayaran lunas.",
        "required": [
          "event",
          "qrisId",
          "trxId",
          "amount",
          "status",
          "signature"
        ],
        "properties": {
          "event": {
            "type": "string",
            "example": "payment.success"
          },
          "qrisId": {
            "type": "string",
            "example": "q7x2m9a1"
          },
          "trxId": {
            "type": "string",
            "example": "TG-A8F2K9"
          },
          "amount": {
            "type": "integer",
            "example": 25002,
            "description": "Total bayar lunas"
          },
          "baseAmount": {
            "type": "integer",
            "example": 25000,
            "description": "Nominal asli"
          },
          "uniqueCode": {
            "type": "integer",
            "example": 2,
            "description": "Kode unik genap"
          },
          "merchantId": {
            "type": "string",
            "example": "123456789"
          },
          "customerName": {
            "type": "string",
            "example": "Budi Santoso"
          },
          "description": {
            "type": "string",
            "example": "INV-2026-001"
          },
          "status": {
            "type": "string",
            "example": "PAID"
          },
          "paidAt": {
            "type": "string",
            "format": "date-time",
            "example": "2026-10-26T10:01:15.000Z"
          },
          "source": {
            "type": "string",
            "enum": [
              "gopay",
              "dana"
            ],
            "example": "gopay"
          },
          "signature": {
            "type": "string",
            "example": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
            "description": "HMAC-SHA256 signature payload untuk verifikasi keaslian pengirim (kunci rahasia: API Key Anda)."
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": false
          },
          "error": {
            "type": "string",
            "example": "Nominal harus berupa angka antara Rp 100 s/d Rp 100.000.000."
          },
          "code": {
            "type": "string",
            "example": "INVALID_AMOUNT"
          }
        }
      }
    }
  },
  "paths": {
    "/create-qris": {
      "post": {
        "summary": "Buat Tagihan QRIS Dinamis Instan",
        "description": "Membuat QRIS Dinamis pas-nominal berstandar EMVCo CRC16 dengan kode unik genap otomatis untuk memastikan mutasi real-time unik tanpa bentrok.",
        "operationId": "createQris",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateQrisRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tagihan QRIS berhasil dibuat",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateQrisResponse"
                }
              }
            }
          },
          "400": {
            "description": "Parameter permintaan tidak valid (misal: nominal < 100 atau > 100jt, atau QRIS statis belum diatur di bot)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "API Key tidak valid atau tidak disertakan pada header",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Langganan toko belum aktif atau kedaluwarsa",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit terlampaui (maksimal 60 request / menit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/check-payment": {
      "post": {
        "summary": "Cek Status Pembayaran Real-Time",
        "description": "Mengecek apakah tagihan QRIS tertentu sudah dibayar atau belum. Mengembalikan data mutasi bank/e-wallet jika sudah lunas.",
        "operationId": "checkPayment",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckPaymentRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Status pembayaran berhasil diperiksa",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckPaymentResponse"
                }
              }
            }
          },
          "400": {
            "description": "qrisId atau trxId tidak disertakan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "API Key tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Data QRIS tidak ditemukan atau telah kedaluwarsa",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/merchant-profile": {
      "get": {
        "summary": "Profil & Status Toko Merchant",
        "description": "Mengambil informasi status integrasi merchant, sisa hari langganan, dan saluran pembayaran aktif.",
        "operationId": "getMerchantProfile",
        "responses": {
          "200": {
            "description": "Data profil berhasil diambil",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MerchantProfileResponse"
                }
              }
            }
          },
          "401": {
            "description": "API Key tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/token-status": {
      "get": {
        "summary": "Status Token & Sesi Saluran",
        "description": "Mengecek validitas token sesi GoBiz & DANA toko.",
        "operationId": "getTokenStatus",
        "responses": {
          "200": {
            "description": "Status token berhasil didapatkan"
          }
        }
      }
    },
    "/qr/{id}": {
      "get": {
        "summary": "Halaman Checkout Web & Raw QRIS Image",
        "description": "Halaman web interaktif pembeli untuk scan QRIS. Dilengkapi fitur Auto-Burn Single-Use QRIS (QR hangus otomatis jika sudah lunas atau kedaluwarsa). Gunakan query `?format=raw` untuk mengunduh stream file gambar PNG barcode langsung.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID QRIS (qrisId)"
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "raw"
              ]
            },
            "description": "Isi dengan `raw` untuk merender gambar PNG barcode langsung (Content-Type: image/png)."
          }
        ],
        "responses": {
          "200": {
            "description": "Halaman HTML checkout interaktif atau Binary PNG image"
          },
          "410": {
            "description": "QRIS sudah lunas atau kedaluwarsa (Auto-burn protection mengembalikan 410 Gone saat request format raw)"
          }
        }
      }
    },
    "/api/qr-status/{id}": {
      "get": {
        "summary": "Polling Status Publik QRIS",
        "description": "Endpoint publik ringan untuk polling AJAX dari halaman checkout pembeli.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID QRIS (qrisId)"
          }
        ],
        "responses": {
          "200": {
            "description": "Status terkini QRIS"
          }
        }
      }
    },
    "/health": {
      "get": {
        "summary": "Status Kesehatan Gateway",
        "description": "Health-check endpoint untuk monitoring uptime dan status koneksi Supabase serta background poller.",
        "responses": {
          "200": {
            "description": "Gateway beroperasi normal"
          }
        }
      }
    }
  },
  "webhooks_documentation": {
    "summary": "Webhook Instant Payment Notification (IPN)",
    "description": "Ketika pembayaran pelanggan terdeteksi lunas (< 2 detik via callback push atau poller), gateway otomatis mengirim HTTP POST request ke Webhook URL Anda.",
    "method": "POST",
    "recommended_handling": [
      "1. Kembalikan HTTP Status 200 OK sesegera mungkin (maksimal 5 detik).",
      "2. Verifikasi HMAC-SHA256 signature pada header X-Poko-Signature atau property signature di payload.",
      "3. Cek apakah transaksi sudah pernah diproses di database Anda untuk mencegah duplikasi (idempotensi)."
    ],
    "signature_verification_snippet_node": "const crypto = require('crypto');\nfunction verifyWebhook(body, secretApiKey) {\n    const { signature, ...data } = body;\n    const expected = crypto.createHmac('sha256', secretApiKey).update(JSON.stringify(data)).digest('hex');\n    return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));\n}",
    "signature_verification_snippet_php": "<?php\n$raw = file_get_contents('php://input');\n$data = json_decode($raw, true);\n$sig = $data['signature'] ?? '';\nunset($data['signature']);\n$expected = hash_hmac('sha256', json_encode($data), $apiKey);\nif (hash_equals($expected, $sig)) {\n    // Pembayaran valid & otentik\n}"
  },
  "features": [
    {
      "name": "Dual-Gateway GoPay & DANA",
      "description": "Dukungan ganda untuk GoBiz (GoPay Merchant) dan DANA Bisnis/Personal dalam satu gateway terpadu."
    },
    {
      "name": "Auto-Burn Single-Use QRIS",
      "description": "QRIS dinamis langsung hangus seketika setelah dibayar atau kedaluwarsa. Barcode dicabut dan endpoint gambar mengembalikan HTTP 410 Gone untuk mencegah transfer ganda."
    },
    {
      "name": "Sequential Even Unique Code",
      "description": "Penambahan kode unik bilangan genap berurutan (+2, +4, +6, ...) per merchant agar pembayaran teridentifikasi secara deterministik tanpa ambigu."
    },
    {
      "name": "Sub-2 Second Detection & Callback Push",
      "description": "Deteksi pembayaran ultra cepat langsung dari callback bank sebelum fallback polling berkala (60 detik)."
    },
    {
      "name": "Proteksi SSRF & Timing-Safe Security",
      "description": "URL Webhook divalidasi ketat terhadap IP privat/cloud metadata (AWS, GCP, DigitalOcean), dan validasi secret menggunakan timingSafeEqual."
    }
  ]
}