WordPress и мультиязычные сайты

Лицензирование плагинов WordPress: как устроен лицензионный сервер

Лицензионные ключи, привязка к домену, сервер обновлений и мягкая деградация без поломки сайта клиента: разбираем архитектуру системы лицензирования платного плагина.

Автор: AYM FlowОпубликовано: Обновлено: 8 мин чтения

Как только вы решаете продавать плагин WordPress, возникают сразу три вопроса: как проверять, что клиент заплатил, как ограничивать число сайтов и как доставлять обновления только платящим пользователям. Ответ на все три даёт лицензионный сервер. Мы построили такую систему лицензирования плагинов WordPress для AYM Multilingual, поэтому делимся архитектурой, которая выдержала реальную эксплуатацию.

Что лицензия продаёт на самом деле

Код плагинов WordPress распространяется под лицензией GPL, поэтому технически «запретить» запуск кода невозможно, да и не нужно. Лицензионный ключ на практике продаёт доступ к обновлениям, поддержке и сервисным функциям, например к ИИ-переводу или облачным возможностям. Понимание этого определяет архитектуру: система должна быть строгой с точки зрения доступа к обновлениям и мягкой с точки зрения работы сайта клиента.

Поэтому не пытайтесь превращать лицензирование в сложную защиту с обфускацией кода. Она ломается, раздражает честных клиентов и увеличивает число обращений в поддержку. Сосредоточьтесь на удобстве: понятная активация, прозрачные лимиты, честные сообщения об ошибках и ценные функции, которые доступны только по активной лицензии.

Лицензирование плагинов WordPress: компоненты системы

Полноценная система лицензирования состоит из нескольких частей:

  • База лицензий: ключ, тариф, статус, срок действия, лимит сайтов, список активаций.
  • Лицензионный сервер: веб-сервис с API для активации, деактивации и проверки лицензий.
  • Клиентский модуль в плагине: форма ввода ключа, обращения к серверу, кэш статуса.
  • Сервер обновлений: выдаёт информацию о новой версии и ссылку на архив.
  • Интеграция с оплатой: выпуск ключа после покупки, продление и отзыв при возврате платежа.
  • Панель управления: просмотр лицензий и активаций, ручная выдача и отзыв.

Сервер лицензий — это обычное веб-приложение со своей базой данных, резервным копированием и мониторингом. Размещайте его отдельно от сайта продаж, чтобы нагрузка от проверок и обновлений не влияла на витрину и оплату. Помните, что каждый сайт ваших клиентов будет периодически обращаться к нему, поэтому его стабильность определяет репутацию плагина.

Активация, проверка и привязка к домену

  • activate: принимает ключ и адрес сайта, создаёт активацию и возвращает статус;
  • deactivate: освобождает место в лимите, чтобы ключ можно было перенести;
  • check: возвращает текущий статус и срок действия, вызывается по расписанию;
  • update-info: сообщает последнюю версию, список изменений и защищённую ссылку на архив.

Типичный сценарий выглядит так. Клиент вводит ключ в настройках плагина, плагин отправляет на сервер ключ, адрес сайта и версию. Сервер проверяет статус, срок и лимит сайтов, записывает активацию и возвращает ответ. Дальше плагин периодически, например раз в сутки, перепроверяет статус и кэширует результат, а не обращается к серверу при каждой загрузке страницы.

Тарифы с разным числом сайтов реализуются счётчиком активаций. У AYM Multilingual, например, есть тарифы на один сайт и тариф для агентств на пять сайтов с одним ключом, поэтому клиент должен иметь возможность самостоятельно деактивировать ключ на старом сайте и перенести его на новый. Без этого служба поддержки утонет в письмах о переезде. Подробнее о наших тарифах смотрите на странице цен.

Продумайте, что происходит при окончании лицензии. Хороший вариант: плагин продолжает выполнять основные функции, но обновления, поддержка и сервисные возможности отключаются, а в админке появляется спокойное напоминание о продлении. Блокировка сайта клиента из-за просроченной подписки разрушает доверие и приводит к возвратам платежей.

Главное правило: недоступность лицензионного сервера никогда не должна ломать сайт клиента. Если сервер не ответил, плагин использует последний известный статус и повторяет проверку позже.

Формат ключа тоже имеет значение. Используйте длинные случайные значения, а не предсказуемые последовательности, и храните на сервере только хэш, если ключ не нужно показывать повторно. В интерфейсе плагина показывайте ключ частично замаскированным, а при ошибках давайте понятные сообщения: «лимит сайтов исчерпан», «срок лицензии истёк», «ключ не найден». Так обращений в поддержку станет заметно меньше.

Пример: проверка лицензии, которая не ломает сайт

Клиентская часть в плагине должна быть терпимой. Эта функция обращается к лицензионному серверу не чаще раза в 12 часов, кэширует результат в transient и считает сетевую ошибку или ответ со статусом, отличным от 200, сбоем, а не вердиктом. Поэтому проблема на сервере никогда не отключит платящего клиента.

<?php
defined( 'ABSPATH' ) || exit;

/**
 * True while the licence is active. Asks the server at most every 12 hours;
 * an outage keeps the last known verdict instead of locking the customer out.
 */
function acme_license_is_active(): bool {
	$cached = get_transient( 'acme_license_status' );
	if ( false !== $cached ) {
		return 'active' === $cached;
	}

	$response = wp_remote_post( 'https://license.example.com/wp-json/acme-license/v1/check', array(
		'timeout' => 8,
		'body'    => array(
			'license_key' => get_option( 'acme_license_key', '' ),
			'domain'      => wp_parse_url( home_url(), PHP_URL_HOST ),
			'version'     => ACME_VERSION, // defined in the main plugin file
		),
	) );

	// Network error or non-200 = outage, not a verdict: retry in an hour.
	if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
		$last = get_option( 'acme_license_last_status', '' );
		set_transient( 'acme_license_status', $last, HOUR_IN_SECONDS );
		return 'active' === $last;
	}

	$data   = json_decode( wp_remote_retrieve_body( $response ), true );
	$status = isset( $data['status'] ) ? sanitize_key( $data['status'] ) : 'invalid';

	update_option( 'acme_license_last_status', $status, false );
	set_transient( 'acme_license_status', $status, 12 * HOUR_IN_SECONDS );
	return 'active' === $status;
}

Поля запроса (license_key, domain, version) и правило, что при сбое сервер отвечает статусом, отличным от 200, повторяют контракт нашего собственного лицензионного сервера. Определите ACME_VERSION в главном файле плагина, сохраняйте ключ через update_option при активации и привязывайте платные функции к acme_license_is_active().

Доставка обновлений через WordPress

Чтобы платные плагины обновлялись из админки как обычные, нужно подключиться к механизму обновлений WordPress. Стандартный способ состоит из двух шагов. Во-первых, фильтр transient обновлений плагинов дополняется данными о вашей новой версии. Во-вторых, фильтр plugins_api отвечает на запрос «Подробнее о версии» и отдаёт описание, список изменений и ссылку на архив. Ссылка на пакет защищается: сервер выдаёт её только при действующей лицензии, и срок жизни ссылки ограничен.

Все запросы клиентского модуля мы строим на REST API: подробно про него рассказано в документации WordPress. Не забывайте указывать для каждого маршрута проверку прав и валидацию аргументов.

Пример: обновления только для лицензированных сайтов

WordPress запрашивает обновления плагинов через site transient update_plugins. Через фильтр pre_set_site_transient_update_plugins платный плагин может добавить в этот список свою версию, но только пока лицензия активна.

<?php
// Must live in the main plugin file: plugin_basename( __FILE__ ) has to match the plugin path.
add_filter( 'pre_set_site_transient_update_plugins', function ( $transient ) {
	if ( empty( $transient->checked ) || ! acme_license_is_active() ) {
		return $transient;
	}
	$plugin = plugin_basename( __FILE__ );

	$remote = get_transient( 'acme_update_info' );
	if ( false === $remote ) {
		// Your endpoint returns {"new_version": "...", "package": "...", "url": "..."}.
		$response = wp_remote_post( 'https://license.example.com/wp-json/acme-license/v1/update', array(
			'timeout' => 8,
			'body'    => array(
				'license_key' => get_option( 'acme_license_key', '' ),
				'domain'      => wp_parse_url( home_url(), PHP_URL_HOST ),
			),
		) );
		$remote = is_wp_error( $response ) ? array() : json_decode( wp_remote_retrieve_body( $response ), true );
		$remote = is_array( $remote ) ? $remote : array();
		set_transient( 'acme_update_info', $remote, 6 * HOUR_IN_SECONDS );
	}

	$installed = $transient->checked[ $plugin ] ?? '0';
	if ( ! empty( $remote['new_version'] ) && ! empty( $remote['package'] )
		&& version_compare( $remote['new_version'], $installed, '>' ) ) {
		$transient->response[ $plugin ] = (object) array(
			'slug'        => dirname( $plugin ),
			'plugin'      => $plugin,
			'new_version' => sanitize_text_field( $remote['new_version'] ),
			'package'     => esc_url_raw( $remote['package'] ), // short-lived, signed URL
			'url'         => esc_url_raw( $remote['url'] ?? '' ),
		);
	}
	return $transient;
} );

Этот код должен находиться в главном файле плагина, потому что plugin_basename(__FILE__) должен совпадать с путём самого плагина. Адрес package лучше делать короткоживущей подписанной ссылкой, которую генерирует ваш сервер: утёкшая ссылка быстро перестанет работать.

Безопасность и защита от злоупотреблений

  1. Передавайте данные только по HTTPS и проверяйте входные параметры на сервере так же строго, как в плагине.
  2. Подписывайте ответы сервера, чтобы поддельный сервер не мог «включить» лицензию на сайте клиента.
  3. Ограничивайте частоту запросов и записывайте журнал обращений, чтобы замечать подбор ключей.
  4. Не храните в плагине секреты, которые позволяют генерировать ключи: любой код плагина доступен клиенту.
  5. Предусмотрите льготный период после окончания лицензии: плагин продолжает работать, но не получает обновления и сервисные функции.
  6. Не собирайте лишних персональных данных: достаточно адреса сайта, версии и статуса.

Базовые принципы безопасного кода плагина изложены в руководстве по безопасности, и они в полной мере относятся к клиентскому модулю лицензирования. Общий порядок разработки плагина мы описали в «руководстве по созданию плагина».

Готовое решение или своё

Продумайте жизненный цикл лицензии. После покупки платёжная система отправляет уведомление на сервер, и ключ выпускается автоматически. При продлении срок обновляется, при возврате платежа ключ отзывается, а при окончании срока плагин переходит в льготный режим. Автоматизируйте эти события через вебхуки платёжной системы и храните историю изменений, чтобы разбираться со спорными случаями.

Та же схема встречается не только в WordPress: наша программа табло Multi Score Screen тоже высылает по электронной почте лицензионный ключ на каждый корт, который площадка активирует в панели администратора.

Наконец, наблюдайте за системой. Ведите журнал обращений, отслеживайте долю ошибок активации и время ответа сервера, а о сбоях узнавайте до того, как о них напишут клиенты. Лицензирование — не разовая задача, а сервис, который нужно сопровождать так же внимательно, как сам плагин.

Для небольшого проекта можно взять готовый менеджер лицензий для WooCommerce, который уже умеет выпускать ключи и отдавать обновления. Собственный лицензионный сервер оправдан, когда нужны нестандартные тарифы, привязка к сервисным функциям, интеграция с вашей платёжной системой или полный контроль над данными клиентов. Это частный случай вопроса, который мы разбирали в статье «Заказная разработка или готовое решение».

Мы проектируем и реализуем такие системы в рамках разработки плагинов WordPress: от API и базы лицензий до клиентского модуля и сервера обновлений. Если вы готовите платный плагин к запуску, расскажите о проекте, и мы подберём архитектуру под ваш тариф и объём продаж.

Поделиться:XLinkedInWhatsApp

Частые вопросы

Можно ли защитить плагин WordPress от пиратства?

Полностью нет: код выполняется на стороне клиента и распространяется под GPL. Реалистичная цель — привязать к лицензии обновления, поддержку и сервисные функции, ради которых клиенту выгодно платить.

Как привязать лицензию к сайту?

При активации плагин отправляет на сервер ключ и адрес сайта. Сервер записывает активацию и сравнивает их число с лимитом тарифа. Для переезда нужна деактивация на старом сайте.

Что делать, если лицензионный сервер недоступен?

Использовать кэшированный статус и повторить проверку позже. Сайт клиента не должен ломаться из-за проблем на вашей стороне.

Как доставлять платные обновления из админки WordPress?

Подключиться к фильтрам обновлений плагинов и plugins_api, а ссылку на архив выдавать только при действующей лицензии и ограничить её срок действия.

WordPress и мультиязычные сайты

Локализация и перевод: разница и как применить её на сайте

Перевод переносит текст на другой язык, локализация перестраивает сайт под человека из целевого рынка. Объясняем разницу на примерах из нашего собственного подбора ключевых слов.

WordPress и мультиязычные сайты

Как добавить язык на сайт WordPress: пошаговое руководство

Хотите добавить английский или другой язык на существующий сайт WordPress? Настройки «Язык сайта» мало — рассказываем, как сделать это с правильной структурой, приоритетами и SEO.

WordPress и мультиязычные сайты

Переключатель языков на сайте: удобный, доступный и безопасный для SEO

Флаги или названия языков? Правый верхний угол или подвал? Принципы переключателя языков, который легко найти, можно использовать с клавиатуры и понимают поисковики.