Files
chatwoot-ai-agent/fastadmin/chathub
GreatQiu 91104e58cf v1.8: FastAdmin chathub-addon — register/plan/payment/member-center + 5 channel bindings
- New fastadmin/chathub/ (11 files, 204K): user-facing FastAdmin ThinkPHP 5 addon
- _markOrderPaid() now calls _provisionAsync() on empty embed_code (closes 'paid but no code' gap)
- New reprovision() action — user-initiated resource rebuild
- payReturn() smart redirect: 3 branches (just_paid / provisioning / pending / fallback)
- status badge updated with 'provisioning' state (blue)
- _initialize() whitelist expanded: reprovision (user) + payNotify/payReturn (public webhook)
- 5 chathub_* tables (tenant/log/order/channel_account/gateway_log) + MIGRATIONS.md

Bugfixes during E2E:
- payNotify HTTP 500: tenant.status ENUM missing 'provisioning' value (DBA migration)
- payNotify HTTP 500: chathub_log.status='received' (not in ENUM) — changed to 'success'
- TP5 method signature: function reprovision(\$ids) does not read query string — use \$this->request->param('ids')
2026-06-05 14:20:00 +08:00
..

FastAdmin ChatHub Addon

PHP frontend for the Chatwoot AI multi-tenant SaaS system, packaged as a FastAdmin addon (ThinkPHP 5).

What this is

A self-contained FastAdmin plugin that provides the user-facing side of the platform: registration, plan selection, payment (Alipay/WeChat), member center, and channel credential management. All HTML is inlined in the controller (no view/ templates, no FastAdmin render engine) — see "Architecture" below for why.

The service-side lives in this same monorepo, one level up: gateway/ (in-process Python library), chatwoot_ws_agent.py (WebSocket agent), and provision_server.py (HTTP provisioning API). The addon talks to those via HTTP only.

Architecture

┌──────────────────────────────────────────────────────────────────┐
│                 FastAdmin ChatHub Addon (this dir)                │
│                                                                  │
│   public/  →  register / landing / my / channelAuth / payNotify   │
│   ┌──────────────────────────────────────────────────────────┐    │
│   │  controller/Index.php (2108 lines)                       │    │
│   │   • All HTML inlined as PHP heredoc                      │    │
│   │   • POST to chathub-provision over HTTP/JSON             │    │
│   │   • _initialize() whitelists public + user actions       │    │
│   │   • Idempotency-Key for safe webhook retries             │    │
│   └──────────────────────────────────────────────────────────┘    │
│                                                                  │
│   model/ChathubTenant.php  →  TP5 model with JSON getters         │
│   config.php               →  17 admin-fillable config fields    │
│   install.sql              →  5 tables (tenant/log/order/...)     │
└──────────────────────────────────┬───────────────────────────────┘
                                   │  HTTP/JSON
                                   ▼
              ┌──────────────────────────────────────┐
              │   chathub-provision (Python service) │
              │   :5566 /provision /suspend ...      │
              └──────────────────────────────────────┘

Why no view/ directory?

FastAdmin's template engine uses ThinkPHP's view() / fetch() with ThinkTemplate syntax. For this addon, we deliberately use pure echo + PHP heredoc strings inside the controller because:

  1. Single-file deploys — the entire UI for my() and register() is in controller/Index.php, no template files to keep in sync.
  2. Dynamic flash messages — the FLASH_MESSAGE placeholder is replaced via str_replace at the end of my() based on query string params (?just_paid=1 / ?provisioning=1 / ?pending=1 / ?error=...).
  3. No template-engine dependency — works on any ThinkPHP 5 install without think-template package.

If you need to customize the UI, edit the heredoc strings inside controller/Index.php directly. Search for register() and my() for the two main render blocks.

Routes (URL → controller action)

URL Action Auth Purpose
/addons/chathub/index/landing landing public Landing page
/addons/chathub/index/register register public Registration + plan selection
/addons/chathub/index/login login public Login form
/addons/chathub/index/doLogin doLogin public Login submit
/addons/chathub/index/logout logout public Logout
/addons/chathub/index/my my user (session) Member center (tenant list)
/addons/chathub/index/channelList channelList user Choose a channel to bind
/addons/chathub/index/channelAuth channelAuth user OAuth redirect (5 platforms)
/addons/chathub/index/channelCallback channelCallback user OAuth callback
/addons/chathub/index/reprovision reprovision user Manually retry provisioning
/addons/chathub/index/payAlipay payAlipay user Start Alipay payment
/addons/chathub/index/payWechat payWechat user Start WeChat payment
/addons/chathub/index/payNotify payNotify public (webhook) Alipay/WeChat async callback
/addons/chathub/index/payReturn payReturn public (webhook) Alipay/WeChat sync return

Install

1. Copy addon directory

cp -r fastadmin/chathub /www/sites/<your-site>/index/addons/

2. Run SQL

The 5 tables are defined in install.sql (uses __PREFIX__ placeholder for FastAdmin table prefix). Run via phpMyAdmin or mysql:

mysql -u <user> -p <dbname> < install.sql

The script will substitute __PREFIX__ with fa_ (or whatever your prefix config is) — FastAdmin's db()->execute() does this automatically, or you can sed first.

If you already have a v1.0v1.5 install, run MIGRATIONS.md instead.

3. Enable the addon

FastAdmin Admin → 插件管理 → local install → upload this dir → enable.

4. Configure

FastAdmin Admin → 插件管理 → ChatHub → 配置:

Field Example Required
provision_server_url http://CoPaw:5566 yes
site_name ChatHub yes
chatwoot_url https://chatwoot.example.com yes
chatwoot_api_token (your personal access token) yes
alipay_app_id / alipay_merchant_private_key / alipay_public_key (from 支付宝开放平台) if 支付宝 enabled
wechat_app_id / wechat_mch_id / wechat_api_v3_key / wechat_cert_path / wechat_key_path (from 微信支付商户平台) if 微信支付 enabled

Important: All payment fields use sandbox values by default. Switch off alipay_sandbox / wechat_sandbox for production.

5. Verify

curl -I https://<your-site>/addons/chathub/index/landing
# should be HTTP 200

Tenant lifecycle

register()         →  status='provisioning' (called from chathub-provision sync)
                     │
                     ├─ success  →  status='active' (embed_code populated)
                     │
                     └─ failure  →  status='pending' (user can retry from my.html)

payNotify()        →  _markOrderPaid() + _provisionAsync() if embed_code empty
                     │
                     └─ _provisionAsync() success → status='active' + embed_code written

reprovision()      →  user-initiated retry; same path as payNotify fallback

The provisioning state is new in v1.6 (see MIGRATIONS.md). Before that, the state machine went straight pending → active, which meant a payment that succeeded but the chathub-provision call timed out left the user in a "paid but no embed code" limbo.

Changelog (this component)

  • v1.8 — Payment flow completion: _markOrderPaid calls _provisionAsync on empty embed_code; new reprovision action; payReturn smart redirect (3 branches); provisioning state badge; schema migration
  • v1.6 — Channel bindings (channelAuth / channelCallback) for Amazon/JD/Taobao/PDD/TikTok
  • v1.0 — Initial release: register/login/my/landing

License

AGPL v3 — see LICENSE and root LICENSE.

  • ../gateway/ — Platform Gateway Python library (5 platform adapters)
  • ../chatwoot_ws_agent.py — WebSocket agent that calls Gateway
  • ../provision_server.py — HTTP provisioning API that the addon calls
  • ../CHANGELOG.md — Top-level changelog