Якщо ви використовуєте WHMCS, натомість застосовуйте модуль WHMCS — ця сторінка призначена для всього іншого. Є два варіанти, обидва безкоштовні та обидва ліцензовані під GPL-2.0-or-later.
HostBill
Модуль хостингу з тими самими шістьма діями, що й модуль для WHMCS.
- Завантажте його та розархівуйте.
- Завантажте каталог
zinn до includes/modules/Hosting/, щоб файли опинилися за шляхами
includes/modules/Hosting/zinn/class.zinn.php та includes/modules/Hosting/zinn/ZinnHostBillClient.php.
- Settings → Modules → Hosting, активуйте Zinn Digital® і додайте підключення:
- API address:
https://api.zinndigital.com
- API key: ваш API-ключ Zinn® (поле типу
password, тому HostBill зберігає його в зашифрованому вигляді)
- У продукті вкажіть Product line (наприклад,
mainstream), Stack (за замовчуванням wordpress), а також, за бажанням, Application і PHP version.
- Натисніть Test connection.
⛔ Не перейменовуйте каталог zinn. Завантажувач HostBill визначає потрібний клас за назвою каталогу, тому перейменування призведе до того, що модуль відображатиметься в панелі, але ніколи не викликатиметься.
Усе інше — клієнт PHP
Один файл, жодних залежностей, жодних прив'язок до конкретної панелі. Додайте ZinnProvisioning.php до власної кодової бази та викликайте шість методів.
require_once 'ZinnProvisioning.php';
$zinn = new ZinnProvisioning(getenv('ZINN_API_KEY'));
// When an order is paid — once per CUSTOMER, then once per SERVICE:
$orgId = $zinn->account('customer-4211', 'Acme Ltd');
$siteId = $zinn->provision('service-9915', $orgId, 'acme.com', 'mainstream', 'wordpress');
// Store $orgId against your customer and $siteId against your service.
$zinn->suspend($siteId, 'INV-2026-114 unpaid'); // an invoice goes unpaid
$zinn->unsuspend($siteId); // they pay
$zinn->terminate($siteId); // they cancel — schedules a deletion
// When they click "log in to my hosting" — mint it on the CLICK, never on page render:
header('Location: ' . $zinn->signInLink($siteId));
Не пишете на PHP? Ті самі виклики описані в довіднику з API, і ви можете здійснювати їх з будь-якого середовища.
Ключ API
Сім дозволів і не більше: org.create, org.read, sites.create, sites.view, sites.delete, reseller.view, reseller.provision.
⛔ Не reseller.manage. Ключ, який ви вставляєте в білінгову панель, повинен мати можливість заблокувати клієнта за несплату та виконати його вхід; він не повинен мати змоги змінювати облікові дані вашого власного платіжного шлюзу.
Два правила, порушення яких коштує грошей
- Прив'язуйте обліковий запис до вашого КЛІЄНТА, а сайт — до вашої ПОСЛУГИ. Друге замовлення клієнта повинно потрапляти до облікового запису, який у нього вже є. Прив'яжіть обидва до послуги — і один клієнт отримає три незв'язані облікові записи та три окремі панелі керування.
- Надсилайте ключ ідемпотентності під час кожного створення, згенерований на основі вашого власного ідентифікатора об'єкта. Кожна білінгова система робить повторні спроби — зворотний виклик шлюзу надходить двічі, адміністратор повторно запускає невдале надання послуги, клієнт двічі клікає. Без цього друга спроба створить другий сайт, і вам буде виставлено за нього рахунок. Обидва модулі та клієнт PHP роблять це за вас; якщо ви звертаєтеся до API безпосередньо, надсилайте
Idempotency-Key.
Три поля, які легко пропустити
Кожне з них порушує роботу цілої операції, і жодне не відображається інакше, як помилка валідації — крім першого, яке відображається як взагалі нічого:
- ⛔⛔ Клієнта необхідно перевести на тариф ДО розгортання його першого сайту, використовуючи
POST /v1/reseller/clients/{orgId}/plan, і передати його subscription_id у POST /v1/sites. Це випадок, коли помилки для зчитування немає. Новий обліковий запис клієнта не містить підписки, а POST /v1/sites не має поля тарифу, тому сайт, розгорнутий без нього, залишається взагалі без тарифу: ваша оптова виписка формується на основі активних підписок ваших клієнтів, тому жоден рядок не додається і Zinn® не виставляє вам рахунків упродовж усього часу роботи хостингу; ваш клієнт не успадковує лімітів, тому до його сайту не застосовується жодна квота; і для оновлення немає чого змінювати. Замовлення повертає 201, сайт розгортається й працює бездоганно. Жодна зі сторін ні про що з цього не повідомляє.
Послідовність має значення: POST /v1/sites фіксує будь-яку підписку, яка існує на момент його виконання, тому встановлений згодом тариф не застосовується заднім числом.
POST /v1/sites вимагає stack_type. Пропустіть його — і жодне замовлення не буде успішним.
- **
DELETE /v1/sites/{siteId} вимагає тіло `{"confirm_domain": "<the site's own
domain>"}** — явне підтвердження введенням, оскільки це деструктивна дія. Пропустіть його — і жодне скасування не вдасться, тому послуга продовжуватиме працювати, а вам і надалі виставлятимуться за неї рахунки. Зчитуйте домен із GET /v1/reseller/services/{siteId}`, а не з власного запису: він звіряється з власним основним доменом сайту і ніколи з псевдонімом.
Читайте помилку повністю, а не лише одне речення
Повідомлення про відхилення валідації навмисно зроблене загальним — "The request was well-formed but failed validation." — оскільки корисна для дій частина міститься в details, де зазначено поле та причину. Обидва модулі та PHP-клієнт додають його до повідомлення, яке показують вашому адміністратору. Якщо ви викликаєте API самостійно, зчитуйте error.details; деякі записи містять лише значення, а не речення, і саме так ви отримуєте max_sites: 0, коли у вашому реселерському тарифі немає вільного місця.
Перед вашим першим замовленням
Сайти, які купують ваші клієнти, враховуються в ліміт сайтів вашого реселерського тарифу, тому вам потрібен тариф із вільним місцем. Перевірка з'єднання не може цього виявити — вона зчитує вашу програму й нічого не розгортає — тому вона повідомляє про справний обліковий запис, який поки що не може здійснювати продажі. Замовлення, розміщене без доступного ліміту, відхиляється з повідомленням, яке саме про це й зазначає.
Перевірте ваше завантаження
Обидва архіви передаються через TLS із нашого власного імені хоста, ніколи не перенаправляються в інше місце, а їхні контрольні суми SHA-256 зазначені поруч із посиланнями для завантаження на сторінці завантажень.