# API Documentation — Payment Gateway QRIS OrderKuota

Payment Gateway QRIS — Unofficial OrderKuota API Client v1.0

---

## Base URL

Semua endpoint menggunakan prefix `/api`.

Base URL: `https://orkut.nickystore.my.id`

Contoh: `https://orkut.nickystore.my.id/api/payment/create`

Dokumentasi: `https://orkut.nickystore.my.id/docs`

---

## Autentikasi

Gunakan API Key via header `x-api-key` untuk semua endpoint.

API key didapat dari Dashboard → Akun → API Key.

---

## Endpoint Sesi

### GET /api/session
Mendapatkan informasi sesi user saat ini.

```json
{
  "userId": 123456,
  "username": "john",
  "role": "free",
  "api_key": "...",
  "payment_count": 3,
  "usage_limit": 10,
  "total_income": 150000
}
```

### POST /api/logout
Menghapus sesi user.

```json
{ "success": true }
```

### POST /api/user/api-key
Regenerate API key (key lama tidak berlaku).

```json
{ "success": true, "api_key": "new-api-key-here" }
```

---

## Panduan Integrasi Bot WhatsApp

Flow integrasi untuk bot WhatsApp atau aplikasi eksternal:

1. `POST /api/bot/payment/create` dengan header `x-api-key: YOUR_API_KEY` → dapat `id` + `qr_image_url`
2. Kirim QR image ke customer via WhatsApp
3. Tiap 5-10 detik `GET /api/bot/payment/:id` — cek status payment
4. Kalau status === `"succeed"` — kirim notifikasi "Pembayaran berhasil!"
5. Kalau status === `"cancelled"` — "Pembayaran dibatalkan"
6. Kalau masih status === `"pending"` — "Masih menunggu pembayaran"

### Contoh Kode (JavaScript)

```javascript
// Buat payment
const create = await fetch('https://orkut.nickystore.my.id/api/bot/payment/create', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_KEY' },
  body: JSON.stringify({ amount: 10000 })
});
const data = await create.json();
console.log(data.payment.qr_image_url); // Kirim ini ke customer

// Cek status (Loop tiap 5 detik)
setInterval(async () => {
  const res = await fetch(`https://orkut.nickystore.my.id/api/bot/payment/${paymentId}`, {
    headers: { 'x-api-key': 'YOUR_KEY' }
  });
  const d = await res.json();
  if (d.payment.status === 'succeed') {
    console.log('Pembayaran berhasil!');
    clearInterval(interval);
  }
}, 5000);
```

### Contoh dengan Python

```python
import requests, time

# Buat payment
r = requests.post('https://orkut.nickystore.my.id/api/bot/payment/create',
  json={'amount': 10000},
  headers={'x-api-key': 'YOUR_KEY'})
payment = r.json()['payment']
print(payment['qr_image_url']) # Kirim QR ke customer

# Polling status
while True:
    r = requests.get(f'https://orkut.nickystore.my.id/api/bot/payment/{payment["id"]}',
      headers={'x-api-key': 'YOUR_KEY'})
    status = r.json()['payment']['status']
    if status == 'succeed':
        print('Berhasil!')
        break
    elif status == 'cancelled':
        print('Dibatalkan')
        break
    time.sleep(5)
```

**Catatan:** Pastikan sudah menghubungkan akun OrderKuota di dashboard dan menyimpan QRIS string sebelum menggunakan payment endpoint. Limit gratis: 5x create payment.

### Alternatif: Pakai Webhook (Tanpa Polling)

Selain polling, bot bisa menerima notifikasi otomatis via webhook. Semua konfigurasi lewat API key saja — tidak perlu login dashboard.

```
GET    /api/bot/webhook/config   → lihat config
POST   /api/bot/webhook/config   → { url, events } → { success, secret }
DELETE /api/bot/webhook/config   → hapus config
POST   /api/bot/webhook/test     → kirim test ke URL config kamu
```

Contoh pasang webhook agar bot dapat `payment.success` otomatis:

```bash
curl -X POST "https://orkut.nickystore.my.id/api/bot/webhook/config" \
  -H "x-api-key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://botkamu.com/webhook","events":["payment.success"]}'
```

Setelah itu, saat pembayaran sukses server akan POST ke URL kamu dengan header `X-Webhook-Signature` (HMAC-SHA256) dan `X-Webhook-Event`, lalu `data` berisi `{ id, amount, status: "succeed", balance_before, balance_after }`. Balas `200 OK` agar tidak di-retry. Verifikasi signature dengan secret yang didapat saat simpan config. Dokumentasi webhook lengkap: [Webhook Docs](/docs/webhook)

---

## Webhook

Sistem webhook mengirim notifikasi real-time ke URL kamu saat ada event pembayaran. Tidak perlu polling — server kami yang push data ke server kamu.

### Cara Kerja

1. Daftarkan URL webhook di Dashboard → Webhook
2. Pilih event yang ingin didengarkan
3. Simpan — kamu akan dapat secret key
4. Server kami akan mengirim POST request ke URL kamu saat event terjadi
5. Verifikasi signature untuk memastikan request benar dari kami

### Events Tersedia

| Event | Deskripsi |
|-------|-----------|
| `payment.created` | Pembayaran baru dibuat |
| `payment.success` | Pembayaran berhasil |
| `payment.cancelled` | Pembayaran dibatalkan |
| `payment.expired` | Pembayaran kedaluwarsa |
| `shop.created` | Pembelian role baru |
| `shop.success` | Role berhasil diaktifkan |
| `shop.expired` | Pembelian role kedaluwarsa |

### Payload

Setiap webhook dikirim dengan format:

```json
{
  "event": "payment.success",
  "timestamp": "2025-07-27T10:30:00.000Z",
  "data": {
    "id": "mqd123abc",
    "amount": 50000,
    "status": "succeed",
    "balance_before": 100000,
    "balance_after": 150000
  }
}
```

### Header

| Header | Deskripsi |
|--------|-----------|
| `Content-Type` | `application/json` |
| `X-Webhook-Event` | Nama event (contoh: `payment.success`) |
| `X-Webhook-Signature` | HMAC-SHA256 signature untuk verifikasi |
| `User-Agent` | `PaymetGateway-Webhook/1.0` |

### Verifikasi Signature

Untuk memastikan request benar dari server kami, verifikasi signature:

```javascript
const crypto = require('crypto');

function verifyWebhookSignature(secret, body, signature) {
  const expected = crypto.createHmac('sha256', secret).update(body).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
```

**Contoh di Express.js:**

```javascript
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const isValid = verifyWebhookSignature(YOUR_SECRET, req.body, signature);

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = JSON.parse(req.body);
  console.log('Event:', event.event);
  console.log('Data:', event.data);

  res.json({ received: true });
});
```

**Contoh di Python (Flask):**

```python
import hmac
import hashlib

def verify_signature(secret, body, signature):
    expected = hmac.new(secret.encode(), body.encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Webhook-Signature')
    if not verify_signature(YOUR_SECRET, request.data.decode(), signature):
        return {'error': 'Invalid signature'}, 401

    event = request.json
    print(f"Event: {event['event']}")
    print(f"Data: {event['data']}")
    return {'received': True}
```

### Retry Logic

Jika server kamu return error (bukan 2xx), kami akan retry:

| Attempt | Delay | Keterangan |
|---------|-------|------------|
| 1 | 0 detik | Langsung kirim |
| 2 | 30 detik | Retry pertama |
| 3 | 2 menit | Retry kedua |
| Gagal | - | Log sebagai failed, stop |

### API Endpoints

#### GET /api/webhook/configs
Lihat semua konfigurasi webhook.

```json
{
  "success": true,
  "configs": [
    {
      "_id": "...",
      "userId": 123456,
      "url": "https://example.com/webhook",
      "secret": "...",
      "events": ["payment.success", "shop.success"],
      "active": true
    }
  ]
}
```

#### POST /api/webhook/config
Buat atau update konfigurasi webhook.

**Request:**
```json
{
  "url": "https://example.com/webhook",
  "events": ["payment.success", "payment.created"]
}
```

**Response:**
```json
{
  "success": true,
  "secret": "abc123..."
}
```

**Events yang tersedia:** `payment.created`, `payment.success`, `payment.cancelled`, `payment.expired`, `shop.created`, `shop.success`, `shop.expired`

#### DELETE /api/webhook/config/:id
Hapus konfigurasi webhook.

#### POST /api/webhook/config/toggle
Aktifkan/nonaktifkan webhook.

```json
{ "id": "...", "active": true }
```

#### POST /api/webhook/test/:id
Test kirim webhook test ke URL.

**Response:**
```json
{
  "success": true,
  "result": {
    "status": 200,
    "body": "OK",
    "ok": true
  }
}
```

#### GET /api/webhook/logs
Lihat riwayat pengiriman webhook (50 terakhir).

#### POST /api/webhook/test-local
Test kirim webhook ke receiver bawaan (`receive-test`) tanpa keluar server. Response berisi `receiptId`, `signature_valid`, dan `config_found`.

#### POST /api/webhook/receive-test
Receiver bawaan publik. Cocokkan signature HMAC-SHA256 dengan secret config pemilik, lalu simpan ke `webhook_receipts`. Bisa dipakai sebagai URL config agar event asli ikut tercatat.

#### GET /api/webhook/receipts
Lihat bukti webhook yang masuk ke receiver bawaan (test maupun event asli).

### Uji Coba Tanpa Server Sendiri

1. Buka halaman `https://orkut.nickystore.my.id/webhook`
2. Tekan tombol **"Kirim Test ke Receiver"** — hasil (payload + badge validasi signature) tampil di section **"Hasil Test Receiver"**
3. Atau pakai script lokal: `npm run webhook:test` (port 5543), set `WEBHOOK_SECRET` untuk verifikasi signature

> Dokumentasi webhook lengkap: [Webhook Docs](/docs/webhook)

### Contoh Implementasi Lengkap

**Node.js (Express):**

```javascript
const express = require('express');
const crypto = require('crypto');
const app = express();

const WEBHOOK_SECRET = 'secret-dari-dashboard';

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  // 1. Verifikasi signature
  const signature = req.headers['x-webhook-signature'];
  const expected = crypto.createHmac('sha256', WEBHOOK_SECRET).update(req.body).digest('hex');

  if (signature !== expected) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // 2. Parse payload
  const event = JSON.parse(req.body);

  // 3. Proses berdasarkan event
  switch (event.event) {
    case 'payment.created':
      console.log('Payment baru:', event.data.id);
      console.log('Nominal:', event.data.amount);
      break;

    case 'payment.success':
      console.log('Payment berhasil:', event.data.id);
      console.log('Saldo naik:', event.data.increase);
      // Kirim notifikasi ke customer, update database, dll
      break;

    case 'payment.cancelled':
      console.log('Payment dibatalkan:', event.data.id);
      break;

    case 'shop.success':
      console.log('Role diaktifkan:', event.data.role);
      console.log('Untuk user:', event.data.user_id);
      break;
  }

  res.json({ received: true });
});

app.listen(3000);
```

**Python (Flask):**

```python
from flask import Flask, request, jsonify
import hmac
import hashlib

app = Flask(__name__)
WEBHOOK_SECRET = 'secret-dari-dashboard'

@app.route('/webhook', methods=['POST'])
def webhook():
    # 1. Verifikasi signature
    signature = request.headers.get('X-Webhook-Signature', '')
    expected = hmac.new(WEBHOOK_SECRET.encode(), request.data, hashlib.sha256).hexdigest()

    if not hmac.compare_digest(signature, expected):
        return jsonify({'error': 'Invalid signature'}), 401

    # 2. Parse payload
    event = request.json

    # 3. Proses berdasarkan event
    if event['event'] == 'payment.created':
        print(f"Payment baru: {event['data']['id']}")
        print(f"Nominal: {event['data']['amount']}")

    elif event['event'] == 'payment.success':
        print(f"Payment berhasil: {event['data']['id']}")
        print(f"Saldo naik: {event['data']['increase']}")

    elif event['event'] == 'payment.cancelled':
        print(f"Payment dibatalkan: {event['data']['id']}")

    elif event['event'] == 'shop.success':
        print(f"Role diaktifkan: {event['data']['role']}")

    return jsonify({'received': True})

if __name__ == '__main__':
    app.run(port=3000)
```

---

## Contoh Implementasi API

Berikut contoh lengkap penggunaan endpoint utama menggunakan Python, JavaScript, dan cURL.

Base URL: `https://orkut.nickystore.my.id`

Gunakan API Key via header `x-api-key` untuk semua endpoint.

---

### 1. Cek Saldo Utama

Mendapatkan saldo utama akun OrderKuota.

**GET** `https://orkut.nickystore.my.id/api/orkut/qris`

| Parameter | Tipe | Deskripsi |
|-----------|------|-----------|
| — | — | Tidak ada parameter |
| **Auth** | | Header `x-api-key` |

Response:
```json
{
  "success": true,
  "qris_url": "https://...",
  "qris_name": "NAMA TOKO",
  "qris_balance": 500000,
  "balance": 100000,
  "qris_string": "..."
}
```

**Python:**
```python
import requests
url = "https://orkut.nickystore.my.id/api/orkut/qris"
headers = { "x-api-key": "API_KEY_ANDA" }
r = requests.get(url, headers=headers)
data = r.json()
print("Saldo Utama:", data.get("balance"))
print("Saldo QRIS:", data.get("qris_balance"))
```

**JavaScript (fetch):**
```javascript
const res = await fetch("https://orkut.nickystore.my.id/api/orkut/qris", {
  headers: { "x-api-key": "API_KEY_ANDA" }
});
const data = await res.json();
console.log("Saldo Utama:", data.balance);
console.log("Saldo QRIS:", data.qris_balance);
```

**cURL:**
```bash
curl -X GET "https://orkut.nickystore.my.id/api/orkut/qris" \
  -H "x-api-key: API_KEY_ANDA"
```

---

### 2. Cek Saldo QRIS

Mendapatkan saldo QRIS akun OrderKuota (endpoint sama dengan Saldo Utama, field berbeda).

**GET** `https://orkut.nickystore.my.id/api/orkut/qris`

| Parameter | Tipe | Deskripsi |
|-----------|------|-----------|
| — | — | Tidak ada parameter |
| **Auth** | | Header `x-api-key` |

Response:
```json
{
  "success": true,
  "qris_balance": 500000,
  "balance": 100000,
  ...
}
```

**Python:**
```python
import requests
url = "https://orkut.nickystore.my.id/api/orkut/qris"
headers = { "x-api-key": "API_KEY_ANDA" }
r = requests.get(url, headers=headers)
data = r.json()
print("Saldo QRIS:", data.get("qris_balance"))
```

**JavaScript (fetch):**
```javascript
const res = await fetch("https://orkut.nickystore.my.id/api/orkut/qris", {
  headers: { "x-api-key": "API_KEY_ANDA" }
});
const data = await res.json();
console.log("Saldo QRIS:", data.qris_balance);
```

**cURL:**
```bash
curl -X GET "https://orkut.nickystore.my.id/api/orkut/qris" \
  -H "x-api-key: API_KEY_ANDA"
```

---

### 3. Buat Payment QRIS

Membuat payment baru dan mendapatkan QRIS image untuk dibayar customer.

**POST** `https://orkut.nickystore.my.id/api/payment/create`

| Parameter | Tipe | Deskripsi |
|-----------|------|-----------|
| `amount` | number | Nominal pembayaran (bebas, min Rp 1, max Rp 10.000.000) |
| **Auth** | | Header `x-api-key` |

Request:
```json
{ "amount": 10000 }
```

Response:
```json
{
  "success": true,
  "payment": {
    "id": "pay_abc123",
    "amount": 10000,
    "status": "pending",
    "qr_image_url": "https://...",
    "balance_before": 500000
  }
}
```

**Python:**
```python
import requests
url = "https://orkut.nickystore.my.id/api/payment/create"
headers = { "x-api-key": "API_KEY_ANDA", "Content-Type": "application/json" }
body = { "amount": 10000 }
r = requests.post(url, json=body, headers=headers)
payment = r.json()["payment"]
print("Payment ID:", payment["id"])
print("QR Image:", payment["qr_image_url"])
```

**JavaScript (fetch):**
```javascript
const res = await fetch("https://orkut.nickystore.my.id/api/payment/create", {
  method: "POST",
  headers: { "Content-Type": "application/json", "x-api-key": "API_KEY_ANDA" },
  body: JSON.stringify({ amount: 10000 })
});
const data = await res.json();
console.log("Payment ID:", data.payment.id);
console.log("QR Image:", data.payment.qr_image_url);
```

**cURL:**
```bash
curl -X POST "https://orkut.nickystore.my.id/api/payment/create" \
  -H "x-api-key: API_KEY_ANDA" \
  -H "Content-Type: application/json" \
  -d '{"amount": 10000}'
```

> **⚠️ PENTING — cara payment otomatis jadi `succeed`:**
> Setelah customer membayar, status tidak berubah otomatis oleh server tunggal. Sistem memakai
> **attribution pool**: kenaikan saldo QRIS sejak baseline (diset saat payment dibuat) yang mengkredit
> payment. `setInterval` internal **tidak andal di serverless (Vercel)** — uang bisa masuk tapi payment
> tetap `pending`. **Wajib pasang scheduler eksternal** (mis. cron-job.org) yang memanggil endpoint
> `POST /api/cron/check-payments` tiap ~5 detik (12×/menit):
> ```
> Headers:
>   x-cron-key: <CRON_KEY atau BOT_API_KEY>
> Method: POST
> ```
> Selama belum ada scheduler, payment baru bisa dikredit lewat `POST /api/payment/check/:id` (cek manual).
> Riwayat transaksi QRIS kadang kosong (`point.success:false`) → deteksi per-transaksi tidak bisa
> diandalkan; pool adalah jalur utama kredit.

---

### 4. Withdraw QRIS ke Saldo Utama

Menarik saldo QRIS ke saldo utama akun OrderKuota.

**POST** `https://orkut.nickystore.my.id/api/orkut/withdraw`

| Parameter | Tipe | Deskripsi |
|-----------|------|-----------|
| `amount` | number | Nominal withdraw (kelipatan Rp 1.000) |
| **Auth** | | Header `x-api-key` |

Request:
```json
{ "amount": 50000 }
```

Response:
```json
{ "success": true, "data": { "results": { ... } } }
```

**Python:**
```python
import requests
url = "https://orkut.nickystore.my.id/api/orkut/withdraw"
headers = { "x-api-key": "API_KEY_ANDA", "Content-Type": "application/json" }
body = { "amount": 50000 }
r = requests.post(url, json=body, headers=headers)
print(r.json())
```

**JavaScript (fetch):**
```javascript
const res = await fetch("https://orkut.nickystore.my.id/api/orkut/withdraw", {
  method: "POST",
  headers: { "Content-Type": "application/json", "x-api-key": "API_KEY_ANDA" },
  body: JSON.stringify({ amount: 50000 })
});
const data = await res.json();
console.log(data);
```

**cURL:**
```bash
curl -X POST "https://orkut.nickystore.my.id/api/orkut/withdraw" \
  -H "x-api-key: API_KEY_ANDA" \
  -H "Content-Type: application/json" \
  -d '{"amount": 50000}'
```

---

### 5. Cek Status Pembayaran

Memeriksa status payment berdasarkan ID. Auto-check: jika masih pending, sistem akan cek saldo QRIS dulu sebelum return.

**POST** `https://orkut.nickystore.my.id/api/payment/check/:id`

| Parameter | Tipe | Deskripsi |
|-----------|------|-----------|
| `:id` | string (path) | ID payment yang didapat dari create payment |
| **Auth** | | Header `x-api-key` |

Response:
```json
{
  "success": true,
  "payment": {
    "id": "pay_abc123",
    "amount": 10000,
    "status": "succeed",
    "qr_image_url": "https://...",
    "balance_before": 500000,
    "balance_after": 510000
  },
  "increase": 10000
}
```

**Python:**
```python
import requests
payment_id = "pay_abc123"
url = f"https://orkut.nickystore.my.id/api/payment/check/{payment_id}"
headers = { "x-api-key": "API_KEY_ANDA", "Content-Type": "application/json" }
r = requests.post(url, headers=headers)
payment = r.json()["payment"]
print("Status:", payment["status"])
print("Nominal:", payment["amount"])
```

**JavaScript (fetch):**
```javascript
const paymentId = "pay_abc123";
const res = await fetch(`https://orkut.nickystore.my.id/api/payment/check/${paymentId}`, {
  method: "POST",
  headers: { "x-api-key": "API_KEY_ANDA", "Content-Type": "application/json" }
});
const data = await res.json();
console.log("Status:", data.payment.status);
```

**cURL:**
```bash
curl -X POST "https://orkut.nickystore.my.id/api/payment/check/pay_abc123" \
  -H "x-api-key: API_KEY_ANDA" \
  -H "Content-Type: application/json"
```

---

### 6. Withdraw QRIS via Bot (API Key)

Menarik saldo QRIS ke saldo utama menggunakan API Key (untuk bot WhatsApp / aplikasi eksternal).

**POST** `https://orkut.nickystore.my.id/api/bot/withdraw`

| Parameter | Tipe | Deskripsi |
|-----------|------|-----------|
| `amount` | number | Nominal withdraw (kelipatan Rp 1.000) |
| **Auth** | | Header `x-api-key` |

Request:
```json
{ "amount": 50000 }
```

Response:
```json
{ "success": true, "data": { "results": { ... } } }
```

**Python:**
```python
import requests
url = "https://orkut.nickystore.my.id/api/bot/withdraw"
headers = { "x-api-key": "API_KEY_ANDA", "Content-Type": "application/json" }
body = { "amount": 50000 }
r = requests.post(url, json=body, headers=headers)
print(r.json())
```

**JavaScript (fetch):**
```javascript
const res = await fetch("https://orkut.nickystore.my.id/api/bot/withdraw", {
  method: "POST",
  headers: { "Content-Type": "application/json", "x-api-key": "API_KEY_ANDA" },
  body: JSON.stringify({ amount: 50000 })
});
const data = await res.json();
console.log(data);
```

**cURL:**
```bash
curl -X POST "https://orkut.nickystore.my.id/api/bot/withdraw" \
  -H "x-api-key: API_KEY_ANDA" \
  -H "Content-Type: application/json" \
  -d '{"amount": 50000}'
```

---

**Tips:** Gunakan API Key via header `x-api-key` untuk semua endpoint. Semua nominal dalam Rupiah (integer).

---

© 2026 Paymet Gateway — Unofficial OrderKuota API Client. [Beranda](/) - [Dashboard](/dashboard) - [Kembali ke atas](#)
