接口说明
这个接口用于其它卡尼奶插件校验当前插件能不能使用。接口会先通过 product_key 商品密钥找到资源商店商品:如果商品是免费资源,直接返回 allowed: true;如果商品是付费资源,再用 domain + license_key 找到当前站点授权码对应的卡尼奶账号,并查询这个账号是否购买过该密钥对应商品。
只有接口返回 allowed: true 时,插件才允许继续使用;否则插件应停止核心功能并提示用户购买。
后台配置
在资源商店后台添加或编辑资源商品时,填写“商品密钥”:
- 可以手动输入,例如
knnrs_merchant_settlement_2026。 - 也可以点击“生成密钥”自动填充。
- 每个付费插件应该使用一个唯一商品密钥。
- 插件源码里携带这个商品密钥,接口会用它找到对应资源商品。为了降低源码里直接搜索密钥的风险,可以像下面示例一样把密钥 base64 保存,运行时再还原成
product_key传给接口。 - “最新版本号”用于插件端判断是否有新版本,例如
1.0.13。 - “更新包地址”用于 WordPress 自动更新下载 zip 安装包。这个地址只会在购买校验通过、确实有新版本、且后台已填写更新包地址时返回给插件端。
接口地址
POST https://www.kaninai.cn/wp-json/knn-store/v1/license/check-purchase
正式插件建议使用 POST,避免授权码出现在浏览器地址或日志里。
请求参数
| 参数 | 必填 | 说明 |
|---|---|---|
product_key |
是 | 资源商品后台填写的“商品密钥”。 |
domain |
付费必填 | 当前网站域名,例如 www.example.com。接口会自动兼容带不带 www。免费资源可不传。 |
license_key |
付费必填 | 用户在当前网站填写的卡尼奶主题授权码。也兼容参数名 auth_key。免费资源可不传。 |
version |
否 | 当前插件版本号。 |
返回示例
已购买:
{
"success": true,
"allowed": true,
"purchased": true,
"code": "purchased",
"message": "账号已购买该插件,可以使用",
"product": { "id": 123, "title": "某某插件", "status": 1, "is_free": false },
"account": { "id": 1, "display_name": "卡尼奶" },
"order": { "order_no": "RS202609101234560001", "paid_at": "2026-09-10 12:34:56", "amount": "99.00" }
}
未购买:
{
"success": true,
"allowed": false,
"purchased": false,
"code": "not_purchased",
"message": "该授权码对应账号未购买该插件,无法使用"
}
免费资源:
{
"success": true,
"allowed": true,
"purchased": true,
"code": "free_product",
"message": "该插件为免费资源,无需购买即可使用"
}
curl 调用示例
curl -X POST "https://www.kaninai.cn/wp-json/knn-store/v1/license/check-purchase" \
-H "Content-Type: application/json" \
-d '{"domain":"www.example.com","license_key":"你的主题授权码","product_key":"knnrs_cUucTKpuaB3S7ymQN2HDVn2MhShRQxfGt6FM8LHucmXqJ5k8","version":"1.0.0"}'
其它插件 PHP 接入示例
建议在插件启用时校验一次。校验通过后保存本地授权状态;校验失败就立即停用当前插件并提示用户。插件正常运行时只读取本地授权状态,不要每次页面加载都远程请求。
下面示例演示的是付费插件接入方式。KNN_DEMO_RESOURCE_TOKEN 里保存的是商品密钥的 base64 结果,运行时通过 base64_decode() 还原后作为 product_key 传给接口。这样做只是降低源码里直接搜索密钥的风险,不是强加密;真正判断能不能用,仍然以后端接口根据“域名 + 主题授权码 + 商品密钥 + 购买订单”为准。
当前示例密钥:knnrs_cUucTKpuaB3S7ymQN2HDVn2MhShRQxfGt6FM8LHucmXqJ5k8
当前示例密钥 base64:a25ucnNfY1V1Y1RLcHVhQjNTN3ltUU4ySERWbjJNaFNoUlF4Zkd0NkZNOExIdWNtWHFKNWs4
生成 base64 可以用下面命令,把里面的商品密钥换成当前插件自己的密钥:
php -r "echo base64_encode('knnrs_cUucTKpuaB3S7ymQN2HDVn2MhShRQxfGt61266126515');"
<?php
/**
* Plugin Name: 示例付费插件
*/
if (!defined('ABSPATH')) {
exit;
}
define('KNN_DEMO_RESOURCE_SOURCE', 'https://www.kaninai.cn/wp-json/knn-store/v1/license/check-purchase');
define('KNN_DEMO_RESOURCE_TOKEN', 'a25ucnNfY1V1Y1RLcHVhQjNTN3ltUU4ySERWbjJNaFNoUlF4Zkd0NkZNOExIdWNtWHFKNWs4');
define('KNN_DEMO_RESOURCE_PLUGIN_VERSION', '1.0.0');
define('KNN_DEMO_RESOURCE_AUTH_OPTION', 'knn_demo_purchase_auth_status');
function knn_demo_get_product_key() {
return (string) base64_decode(KNN_DEMO_RESOURCE_TOKEN);
}
function knn_demo_get_theme_license() {
if (function_exists('knn_theme_get_license_data')) {
$data = knn_theme_get_license_data();
if (is_array($data)) {
return $data;
}
}
$data = get_option('knn_theme_license', array());
return is_array($data) ? $data : array();
}
function knn_demo_get_current_domain($license = array()) {
$domain = '';
if (is_array($license) && !empty($license['license_domain'])) {
$domain = (string) $license['license_domain'];
}
if ($domain === '') {
$domain = (string) wp_parse_url(home_url('/'), PHP_URL_HOST);
}
if ($domain === '' && !empty($_SERVER['HTTP_HOST'])) {
$domain = (string) $_SERVER['HTTP_HOST'];
}
return strtolower(trim(preg_replace('/:\d+$/', '', $domain)));
}
function knn_demo_remote_check_purchase() {
$license = knn_demo_get_theme_license();
$license_key = isset($license['license_key']) ? trim((string) $license['license_key']) : '';
if ($license_key === '') {
return new WP_Error('missing_license', '请先填写卡尼奶主题授权码');
}
$product_key = knn_demo_get_product_key();
if ($product_key === '') {
return new WP_Error('missing_product_key', '插件商品密钥为空,无法校验购买状态');
}
$response = wp_remote_post(KNN_DEMO_RESOURCE_SOURCE, array(
'timeout' => 12,
'headers' => array(
'Content-Type' => 'application/json',
),
'body' => wp_json_encode(array(
'domain' => knn_demo_get_current_domain($license),
'license_key' => $license_key,
'product_key' => $product_key,
'version' => KNN_DEMO_RESOURCE_PLUGIN_VERSION,
)),
));
if (is_wp_error($response)) {
return $response;
}
$body = json_decode(wp_remote_retrieve_body($response), true);
if (empty($body['success']) || empty($body['allowed'])) {
$message = !empty($body['message']) ? $body['message'] : '该账号未购买此插件,无法启用';
return new WP_Error('not_purchased', $message);
}
return $body;
}
function knn_demo_activate_check_purchase() {
$result = knn_demo_remote_check_purchase();
if (is_wp_error($result)) {
deactivate_plugins(plugin_basename(__FILE__));
wp_die(
esc_html($result->get_error_message()),
'插件购买校验失败',
array('back_link' => true)
);
}
update_option(KNN_DEMO_RESOURCE_AUTH_OPTION, array(
'allowed' => true,
'checked_at' => time(),
'domain' => knn_demo_get_current_domain(knn_demo_get_theme_license()),
'product_key_hash' => md5(knn_demo_get_product_key()),
), false);
}
register_activation_hook(__FILE__, 'knn_demo_activate_check_purchase');
function knn_demo_is_authorized() {
$status = get_option(KNN_DEMO_RESOURCE_AUTH_OPTION, array());
return is_array($status)
&& !empty($status['allowed'])
&& !empty($status['product_key_hash'])
&& hash_equals((string) $status['product_key_hash'], md5(knn_demo_get_product_key()));
}
add_action('admin_notices', function () {
if (!current_user_can('manage_options') || knn_demo_is_authorized()) {
return;
}
echo '<div class="notice notice-error"><p>该插件未完成购买校验,请停用后重新启用插件进行校验。</p></div>';
});
add_action('plugins_loaded', function () {
if (!knn_demo_is_authorized()) {
return;
}
// 已购买并通过启用校验后,才加载插件核心功能。
// require_once __DIR__ . '/includes/core.php';
});
knn_demo_remote_check_purchase(),通过后更新 KNN_DEMO_RESOURCE_AUTH_OPTION。插件更新接口
这个接口用于 WordPress 插件列表里的原生更新检查。插件端把当前版本号传给资源商店,资源商店用后台商品的“最新版本号”判断是否需要更新;如果当前授权允许使用、后台填写了“更新包地址”、并且最新版本号大于当前插件版本,就返回可安装的 zip 地址。
接口地址:
POST https://www.kaninai.cn/wp-json/knn-store/v1/license/update-info
请求参数和购买校验接口一致:
| 参数 | 必填 | 说明 |
|---|---|---|
product_key |
是 | 资源商品后台填写的“商品密钥”。 |
domain |
付费必填 | 当前网站域名。免费资源可不传。 |
license_key |
付费必填 | 用户在当前网站填写的卡尼奶主题授权码。也兼容参数名 auth_key。免费资源可不传。 |
version |
是 | 当前插件版本号,用来和后台“最新版本号”比较。 |
有更新返回示例:
{
"success": true,
"allowed": true,
"code": "purchased",
"message": "已获取插件更新信息",
"version": "1.0.12",
"latest_version": "1.0.13",
"update_available": true,
"package": "https://www.example.com/plugin.zip",
"product": { "id": 123, "title": "某某插件", "status": 1, "is_free": false },
"checked_at": "2026-09-17 10:00:00"
}
没有更新或没有更新包地址时,update_available 为 false,package 为空:
{
"success": true,
"allowed": true,
"code": "purchased",
"version": "1.0.13",
"latest_version": "1.0.13",
"update_available": false,
"package": ""
}
curl 调用示例:
curl -X POST "https://www.kaninai.cn/wp-json/knn-store/v1/license/update-info" \
-H "Content-Type: application/json" \
-d '{"domain":"www.example.com","license_key":"你的主题授权码","product_key":"knnrs_cUucTKpuaB3S7ymQN2HDVn2MhShRQxfGt6FM8LHucmXqJ5k8","version":"1.0.12"}'
WordPress 插件端接入更新
第一步:声明更新源。主插件文件必须填写版本号和 Update URI。地址主机名 www.kaninai.cn 对应动态过滤器 update_plugins_www.kaninai.cn。
/**
* Plugin Name: 示例插件
* Version: 1.0.0
* Update URI: https://www.kaninai.cn/wp-json/knn-store/v1/license/update-info
*/
define('KNN_DEMO_PLUGIN_VERSION', '1.0.0');
define('KNN_DEMO_PLUGIN_FILE', __FILE__);
define('KNN_DEMO_UPDATE_SOURCE', 'https://www.kaninai.cn/wp-json/knn-store/v1/license/update-info');
第二步:请求接口并转换为 WordPress 更新对象。下面代码复用前文的商品密钥、主题授权和域名读取函数。
function knn_demo_update_cache_key() {
return 'knn_demo_update_info_' . md5(KNN_DEMO_PLUGIN_VERSION . '|' . home_url('/'));
}
function knn_demo_remote_update_info($force = false) {
$cache_key = knn_demo_update_cache_key();
if (!$force) {
$cached = get_site_transient($cache_key);
if (is_array($cached)) return $cached;
}
$license = knn_demo_get_theme_license();
$response = wp_remote_post(KNN_DEMO_UPDATE_SOURCE, array(
'timeout' => 10,
'headers' => array('Content-Type' => 'application/json'),
'body' => wp_json_encode(array(
'domain' => knn_demo_get_current_domain($license),
'license_key' => (string) ($license['license_key'] ?? ''),
'product_key' => knn_demo_get_product_key(),
'version' => KNN_DEMO_PLUGIN_VERSION,
)),
));
if (is_wp_error($response)) return $response;
$body = json_decode(wp_remote_retrieve_body($response), true);
if (!is_array($body) || empty($body['success'])) {
return new WP_Error('update_check_failed', '插件更新检查失败');
}
set_site_transient($cache_key, $body, 30 * MINUTE_IN_SECONDS);
return $body;
}
function knn_demo_update_response($info) {
$plugin_file = plugin_basename(KNN_DEMO_PLUGIN_FILE);
$latest = (string) ($info['latest_version'] ?? '');
$package = esc_url_raw((string) ($info['package'] ?? ''));
if (empty($info['allowed']) || empty($info['update_available']) || $latest === '' || $package === '') return null;
return (object) array(
'id' => KNN_DEMO_UPDATE_SOURCE,
'slug' => dirname($plugin_file),
'plugin' => $plugin_file,
'version' => $latest,
'new_version' => $latest,
'url' => 'https://www.kaninai.cn/',
'package' => $package,
'requires' => '5.8',
'requires_php' => '7.4',
'tested' => get_bloginfo('version'),
);
}
第三步:注册原生更新源。返回更新对象后,WordPress 会自动生成插件列表更新提示、“现在更新”链接和官方 Ajax,不需要自己输出更新按钮或跳转页面。
add_filter('update_plugins_www.kaninai.cn', function ($update, $plugin_data, $plugin_file, $locales) {
if ($plugin_file !== plugin_basename(KNN_DEMO_PLUGIN_FILE)) return $update;
$info = knn_demo_remote_update_info();
if (is_wp_error($info)) return false;
return knn_demo_update_response($info) ?: false;
}, 10, 4);
第四步:在官方 Ajax 更新前刷新数据。官方“现在更新”按钮请求 admin-ajax.php?action=update-plugin,以下钩子在核心处理之前更新缓存,避免缓存过期后被判断为最新版。
add_action('wp_ajax_update-plugin', function () {
$plugin_file = isset($_POST['plugin']) ? plugin_basename(sanitize_text_field(wp_unslash($_POST['plugin']))) : '';
if ($plugin_file !== plugin_basename(KNN_DEMO_PLUGIN_FILE) || !current_user_can('update_plugins')) return;
if (!check_ajax_referer('updates', false, false)) return;
$info = knn_demo_remote_update_info(true);
if (!is_wp_error($info) && is_array($info)) {
$update = knn_demo_update_response($info);
if (!$update) return;
$transient = get_site_transient('update_plugins');
if (!is_object($transient)) $transient = new stdClass();
$plugin_file = plugin_basename(KNN_DEMO_PLUGIN_FILE);
if (empty($transient->checked) || !is_array($transient->checked)) $transient->checked = array();
if (empty($transient->response) || !is_array($transient->response)) $transient->response = array();
$transient->checked[$plugin_file] = KNN_DEMO_PLUGIN_VERSION;
$transient->response[$plugin_file] = $update;
$transient->last_checked = time();
set_site_transient('update_plugins', $transient);
}
}, 0);
plugins_api 只用于补充插件详情弹窗,可按需接入。未购买、接口失败、授权错误或 package 为空时,不要返回更新包。接入规则
- 每个插件在资源商店后台都要有一个对应资源商品,并填写唯一“商品密钥”。
- 免费资源只需要传
product_key就会返回可用。 - 付费资源必须传
domain、license_key、product_key。 - 服务端校验顺序是:先用域名和授权码找到当前账号,再用当前账号和商品密钥查询是否存在已支付订单。
- 返回
allowed: false、请求失败、授权错误、接口不可达,都按不可使用处理。 - 建议在插件启用时校验一次即可,校验通过后保存本地授权状态;不要每个页面都远程请求。需要重新校验时,在插件后台提供“重新校验授权”按钮。
- 自动更新只依赖后台“最新版本号”和“更新包地址”:最新版本号大于插件当前版本且更新包地址不为空时,插件列表才会提示更新。
- 卡尼奶主题关闭更新功能后,插件更新提示、官方 Ajax 和自动更新都会停用;需要更新插件时先开启主题更新功能。
- “更新包地址”必须能直接下载 zip 文件,不能返回登录页、网盘预览页或 HTML 页面。
- zip 解压后的顶层目录应与插件目录一致,主插件文件的
Version必须改成发布的新版本,否则安装后仍会继续提示旧版本。
