KopiBot AI / Dokumentasi HTML
Semua Dokumen GitHub Repo Kembali ke Landing Page
☕ AI Agent Coffee Shop Commerce Platform

Platform AI untuk otomatisasi order, customer service, loyalty customer, Customer CRM, Customer Portal, dan manajemen multi cabang coffee shop.

🚀 Features

  • AI Chatbot Order Menu
  • WhatsApp / Telegram / Discord Integration
  • Multi Branch Management
  • AI Upselling & Promo Recommendation
  • Order via Website & Chat Apps
  • Variant Product & Topping Support
  • Loyalty Point, Customer CRM, dan Customer Portal
  • Multi Currency, Tax & Timezone
  • AI Customer Interaction Automation

💻 Tech Stack

PHP Native • MySQL • OpenAI • Anthropic
WhatsApp Gateway • REST API • LLM AI

☕ Suitable For

Coffee Shop • Cafe • Restaurant • Bakery • Beverage Store

Dibuat & Dikembangkan oleh

Kukuh TW

📧 Email: kukuhtw@gmail.com

📱 WhatsApp: wa.me/628129893706

🌄 Instagram: @kukuhtw

X/Twitter: @kukuhtw

Facebook: facebook.com/kukuhtw

LinkedIn: linkedin.com/in/kukuhtw

GitHub: github.com/kukuhtw/toko_kopi

🌐 Demo: botlelang.com/toko_kopi

© 2026 Kukuh TW. All rights reserved.

Markdown ke HTML

> ## ☕ AI Agent Coffee Shop Commerce Platform > Platform AI untuk otomatisasi order, customer service, loyalty customer, dan manajemen multi cabang coffee shop.

Plugin System — KopiBot AI

## ☕ AI Agent Coffee Shop Commerce Platform Platform AI untuk otomatisasi order, customer service, loyalty customer, dan manajemen multi cabang coffee shop. Dibuat & Dikembangkan oleh: Kukuh TW 📧 Email: kukuhtw@gmail.com 📱 WhatsApp: https://wa.me/628129893706 📷 Instagram: @kukuhtw 🐦 X/Twitter: @kukuhtw 👍 Facebook: https://www.facebook.com/kukuhtw 💼 LinkedIn: https://linkedin.com/in/kukuhtw 🌐 Demo: https://botlelang.com/toko_kopi © 2026 Kukuh TW. All rights reserved.

Daftar Isi

  1. Gambaran Umum
  2. Struktur Direktori
  3. Cara Membuat Plugin
  4. Hook Reference
  5. Interface Reference
  6. Database Schema
  7. Roadmap Implementasi

Gambaran Umum

KopiBot AI mendukung sistem plugin yang memungkinkan developer menambahkan fitur baru tanpa mengubah kode inti. Plugin bekerja melalui dua mekanisme:

Catatan Lisensi Plugin

KopiBot AI memakai model AGPL + commercial license.

Request → Kode Inti → HookManager::doAction/applyFilters → Plugin Callbacks → Response

Struktur Direktori

toko_kopi/
├── app/
│   └── Plugin/
│       ├── HookManager.php          ← engine action/filter
│       ├── PluginLoader.php         ← auto-discovery & bootstrap
│       ├── PluginInterface.php      ← kontrak wajib tiap plugin
│       ├── LlmProviderInterface.php ← kontrak LLM provider custom
│       └── ChannelInterface.php     ← kontrak channel baru
├── plugins/
│   ├── plugins.json                 ← daftar plugin aktif
│   └── {slug-plugin}/
│       ├── plugin.php               ← entry point (wajib)
│       ├── {ClassName}.php          ← implementasi plugin
│       └── assets/                  ← JS/CSS opsional
└── docs/
    └── plugin-system.md             ← dokumen ini

Cara Membuat Plugin

Langkah 1 — Buat folder plugin

plugins/nama-plugin-kamu/
├── plugin.php
└── NamaPlugin.php

Penamaan folder menggunakan kebab-case (contoh: midtrans-payment, loyalty-points).


Langkah 2 — Buat class plugin

<?php
// plugins/nama-plugin-kamu/NamaPlugin.php

use App\Plugin\PluginInterface;

class NamaPlugin implements PluginInterface
{
    public function getName(): string    { return 'Nama Plugin Kamu'; }
    public function getVersion(): string { return '1.0.0'; }
    public function getAuthor(): string  { return 'Nama Developer'; }

    public function register(): void
    {
        // Daftarkan hook di sini
        \App\Plugin\HookManager::addAction('order.created', [$this, 'onOrderCreated']);
        \App\Plugin\HookManager::addFilter('cart.total',    [$this, 'tambahBiaya']);
    }

    public function onOrderCreated(array $order): void
    {
        // Lakukan sesuatu saat order baru masuk
        // Contoh: kirim notifikasi ke sistem eksternal
    }

    public function tambahBiaya(float $total, array $cart): float
    {
        return $total + 1000; // tambah biaya admin Rp1.000
    }
}

Langkah 3 — Buat entry point plugin

<?php
// plugins/nama-plugin-kamu/plugin.php

require_once __DIR__ . '/NamaPlugin.php';

return [
    'class'       => NamaPlugin::class,
    'name'        => 'Nama Plugin Kamu',
    'version'     => '1.0.0',
    'author'      => 'Nama Developer',
    'description' => 'Deskripsi singkat fungsi plugin ini.',
    'requires'    => '1.0.0', // versi minimum KopiBot
];

Langkah 4 — Aktifkan plugin

Edit plugins/plugins.json:

{
    "nama-plugin-kamu": { "active": true },
    "plugin-lain":      { "active": false }
}

Plugin langsung aktif pada request berikutnya — tidak perlu restart server.


Hook Reference

Chat & AI

Hook Type Parameter Keterangan
chat.message_received action $message, $branchId Pesan baru masuk dari channel manapun
chat.before_ai filter $messages[], $branchId Sebelum dikirim ke LLM — bisa modifikasi prompt
chat.after_ai filter $response, $branchId Setelah LLM merespons — bisa modifikasi teks
chat.intent_detected action $intent, $message Setelah intent terdeteksi
llm.providers filter $providers[] Daftar provider AI — tambah provider baru di sini
skills.registered filter $skills[] Daftar skill chatbot — tambah skill baru di sini

Order Lifecycle

Hook Type Parameter Keterangan
order.before_create filter $orderData[] Sebelum order disimpan ke DB
order.created action $order[] Order baru berhasil dibuat
order.status_changed action $order[], $oldStatus, $newStatus Status order berubah
order.payment_updated action $order[], $paymentStatus Status pembayaran berubah
order.completed action $order[] Order selesai

Cart & Checkout

Hook Type Parameter Keterangan
cart.item_added action $item[], $sessionId Item ditambahkan ke keranjang
cart.total filter $total, $cart[] Total harga — tambah biaya/diskon custom
cart.before_checkout filter $cartData[] Validasi sebelum checkout
checkout.data filter $data[] Data checkout — bisa tambah field custom

Dashboard & UI

Hook Type Parameter Keterangan
dashboard.nav_items filter $items[], $role Tambah item menu sidebar
dashboard.branch_widgets filter $widgets[] Widget di dashboard cabang
dashboard.super_widgets filter $widgets[] Widget di dashboard super admin
settings.sections filter $sections[], $branchId Tambah section di halaman settings
settings.saved action $branchId, $settings[] Pengaturan cabang disimpan

Channel

Hook Type Parameter Keterangan
channel.registered filter $channels[] Tambah channel baru (implementasi ChannelInterface)
channel.message_sent action $recipient, $message, $channel Setelah pesan dikirim ke customer

Interface Reference

PluginInterface

interface PluginInterface {
    public function getName(): string;
    public function getVersion(): string;
    public function getAuthor(): string;
    public function register(): void;  // ← daftarkan semua hook di sini
}

LlmProviderInterface

Implementasikan ini untuk menambah AI provider baru (Gemini, Groq, Mistral, dll.):

interface LlmProviderInterface {
    public function getName(): string;   // 'gemini', 'groq', dll.
    public function chat(array $messages, array $options = []): string;
    public function estimateCost(int $promptTokens, int $completionTokens): float;
    public function isAvailable(): bool;
}

Kemudian daftarkan via filter:

HookManager::addFilter('llm.providers', function(array $providers): array {
    $providers['gemini'] = new GeminiProvider();
    return $providers;
});

ChannelInterface

Implementasikan ini untuk menambah channel baru (LINE, Slack, dll.):

interface ChannelInterface {
    public function getName(): string;
    public function handleWebhook(array $payload, int $branchId): void;
    public function sendMessage(string $recipient, string $message, array $options = []): bool;
    public function isAvailable(int $branchId): bool;
}

Database Schema

Untuk plugin yang perlu menyimpan konfigurasi:

-- Tabel manajemen plugin (global)
CREATE TABLE plugins (
    id         INT AUTO_INCREMENT PRIMARY KEY,
    slug       VARCHAR(100) UNIQUE NOT NULL,
    name       VARCHAR(200) NOT NULL,
    version    VARCHAR(20),
    is_active  TINYINT(1) DEFAULT 1,
    settings   JSON,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Pengaturan plugin per cabang
CREATE TABLE plugin_branch_settings (
    plugin_slug VARCHAR(100)  NOT NULL,
    branch_id   INT           NOT NULL,
    setting_key VARCHAR(100)  NOT NULL,
    setting_val TEXT,
    PRIMARY KEY (plugin_slug, branch_id, setting_key)
);

Plugin bisa menggunakan tabel ini untuk menyimpan API key, konfigurasi, dll. per cabang.


Roadmap Implementasi

Fase Komponen Status
1 HookManager, PluginInterface, PluginLoader ✅ Done
2 Hook di order lifecycle (OrderModel) ✅ Done
3 Hook di chat/AI flow (ChatbotEngine, cart endpoints) ✅ Done
4 LlmProviderInterface — filter llm.providers di ChatbotEngine ✅ Done
5 ChannelInterface + ChannelRouter + generic webhook endpoint ✅ Done
6 Plugin manager UI di dashboard (super/plugins.php) ✅ Done

Contoh Plugin Lengkap

Lihat plugins/example-plugin/ untuk contoh plugin minimal yang bisa dijadikan template. Untuk contoh skill chatbot yang siap di-clone, lihat plugins/example-skill-plugin/.

Template ini juga mudah ditemukan dari dashboard super admin di super/plugins.php:


Checklist Currency-Safe

Saat menambah fitur baru yang menampilkan harga, nominal promo, revenue, atau total order:

branch/local memakai kode cabang seperti AUD, USD, IDR global memakai label eksplisit seperti Global (IDR)

Helper yang bisa dipakai:

Audit cepat yang bisa dijalankan:

php scripts/audit-currency-hardcode.php

Script default memeriksa code yang dieksekusi di app/, public/, dan plugins/. Kalau mau ikut memeriksa dokumentasi dan contoh, jalankan:

php scripts/audit-currency-hardcode.php --include-docs

Script ini mencari marker seperti Rp atau label (Rp) agar hardcode currency baru lebih cepat ketahuan.


Menambah Channel Baru via Plugin

Implementasikan ChannelInterface dan daftarkan via filter channel.registered:

use App\Plugin\{PluginInterface, HookManager, ChannelInterface};

class LineChannelProvider implements ChannelInterface
{
    public function getName(): string { return 'line'; }

    public function verifyWebhook(array $headers, string $rawBody): bool
    {
        $signature = $headers['X-Line-Signature'] ?? '';
        $secret    = 'your-channel-secret';
        return hash_equals(base64_encode(hash_hmac('sha256', $rawBody, $secret, true)), $signature);
    }

    public function parseMessage(array $payload): ?array
    {
        $event = $payload['events'][0] ?? null;
        if (!$event || $event['type'] !== 'message') return null;
        return [
            'from'    => $event['source']['userId'],
            'message' => $event['message']['text'] ?? '',
        ];
    }

    public function sendMessage(string $recipient, string $message, array $options = []): bool
    {
        // Panggil LINE Messaging API
        return true;
    }

    public function isAvailable(int $branchId): bool { return true; }
}

class LinePlugin implements PluginInterface
{
    public function getName(): string    { return 'LINE Messenger'; }
    public function getVersion(): string { return '1.0.0'; }
    public function getAuthor(): string  { return 'Developer'; }

    public function register(): void
    {
        HookManager::addFilter('channel.registered', function(array $channels): array {
            $channels['line'] = new LineChannelProvider();
            return $channels;
        });
    }
}

Webhook URL: POST /api/channel/webhook.php?channel=line&branch={id}

Catatan desain:

satu bot per cabang, atau satu bot host untuk semua cabang aktif.

Hook channel.message_sent otomatis difire setelah pesan dikirim di semua channel yang aktif, termasuk channel plugin seperti Telegram, WA, LINE, atau Discord.


Menambah Skill Chatbot via Plugin

Skill adalah unit kemampuan chatbot — menangani satu jenis intent dan mengembalikan reply. Plugin bisa mendaftarkan skill baru via filter skills.registered tanpa mengubah kode inti.

Mulai sekarang, registrasi skill disarankan memakai helper SkillRegistry agar developer tidak perlu array_splice() manual.

SkillInterface

namespace App\Skills;

interface SkillInterface
{
    /**
     * @param  array $context {branch_id, branch, customer, conversation, cart, intent,
     *                         message, language, currency, ppn_rate, conv_context, ...}
     * @return array {reply, state, action_result, conv_context}
     */
    public function handle(array $context): array;

    public function canHandle(string $intent): bool;
}

Contoh — Skill Reservasi Meja

<?php
// plugins/reservasi-meja/ReservasiSkill.php

use App\Skills\SkillInterface;

class ReservasiSkill implements SkillInterface
{
    public function canHandle(string $intent): bool
    {
        return $intent === 'reservasi_meja';
    }

    public function handle(array $context): array
    {
        $lang  = $context['language'];
        $reply = $lang === 'id'
            ? "Untuk reservasi meja, silakan hubungi kami di 0812-xxxx-xxxx atau isi form di website kami."
            : "To reserve a table, please contact us at 0812-xxxx-xxxx or fill in the form on our website.";

        return [
            'reply'         => $reply,
            'state'         => 'idle',
            'action_result' => null,
            'conv_context'  => $context['conv_context'],
        ];
    }
}
<?php
// plugins/reservasi-meja/ReservasiPlugin.php

use App\Plugin\{PluginInterface, HookManager};
use App\Skills\SkillRegistry;
use App\Services\IntentPatternRegistry;

class ReservasiPlugin implements PluginInterface
{
    public function getName(): string    { return 'Reservasi Meja'; }
    public function getVersion(): string { return '1.0.0'; }
    public function getAuthor(): string  { return 'Developer'; }

    public function register(): void
    {
        HookManager::addFilter('skills.registered', [$this, 'registerSkill']);
        HookManager::addFilter('intent.patterns',   [$this, 'registerIntentPatterns']);
    }

    public function registerSkill(array $skills): array
    {
        return SkillRegistry::register($skills, new ReservasiSkill(), 60);
    }

    public function registerIntentPatterns(array $patterns): array
    {
        return IntentPatternRegistry::extend($patterns, 'reservasi_meja', [
            'reservasi', 'booking meja', 'book table', 'reserve table', 'pesan meja',
        ]);
    }
}
<?php
// plugins/reservasi-meja/plugin.php

require_once __DIR__ . '/ReservasiSkill.php';
require_once __DIR__ . '/ReservasiPlugin.php';

return [
    'class'       => ReservasiPlugin::class,
    'name'        => 'Reservasi Meja',
    'version'     => '1.0.0',
    'author'      => 'Developer',
    'description' => 'Menambah kemampuan chatbot untuk menjawab pertanyaan reservasi meja.',
    'requires'    => '1.0.0',
];

Catatan Penting

Helper yang Disediakan

use App\Skills\SkillRegistry;
use App\Services\IntentPatternRegistry;