wpbridge/includes/Commercial/SubscriptionManager.php
wenpai e9d28817fe style: phpcbf 自动格式化 — 全量 WPCS 3.x 规范对齐
74 文件 14,082 处自动修复:空格→Tab 缩进、括号间距、
函数声明空格、前置自增、尾逗号等纯格式化改动。
零逻辑变更,php -l + token 级对比验证通过。

新增 phpcs.xml.dist / phpstan.neon.dist 项目配置。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 00:43:46 +08:00

304 lines
7.2 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* 订阅管理器
*
* 通过 WC AM API 验证用户购买状态,管理订阅等级和功能门控
*
* @package WPBridge
* @since 1.1.0
*/
declare(strict_types=1);
namespace WPBridge\Commercial;
use WPBridge\Core\Settings;
use WPBridge\Core\Logger;
use WPBridge\Commercial\Vendors\VendorManager;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class SubscriptionManager {
/**
* 订阅供应商 ID薇晓朵商城
*/
private const SUBSCRIPTION_VENDOR_ID = 'weixiaoduo-mall';
/**
* 订阅缓存 transient key
*/
private const CACHE_KEY = 'wpbridge_subscription';
/**
* 缓存时长(秒)— 缩短至 15 分钟以减少 TOCTOU 窗口
*/
private const CACHE_TTL = 900;
/**
* 产品 → 订阅等级映射
*
* 等级优先级: all_access(100) > pro_enterprise(50) > pro(10) > free(0)
* 41706 为 variable 父产品41708/41709 为变体,均需映射
*/
private const PRODUCT_PLANS = [
41705 => [
'plan' => 'all_access',
'label' => 'All Access',
'priority' => 100,
'plugins_limit' => PHP_INT_MAX,
'daily_downloads' => PHP_INT_MAX,
'features' => [ 'bridge_api', 'bridge_server', 'license_proxy', 'priority_support' ],
],
41706 => [
'plan' => 'pro',
'label' => 'Pro',
'priority' => 10,
'plugins_limit' => PHP_INT_MAX,
'daily_downloads' => 50,
'features' => [ 'bridge_api', 'bridge_server' ],
],
41708 => [
'plan' => 'pro',
'label' => 'Pro 个人版',
'priority' => 10,
'plugins_limit' => PHP_INT_MAX,
'daily_downloads' => 50,
'features' => [ 'bridge_api', 'bridge_server' ],
],
41709 => [
'plan' => 'pro_enterprise',
'label' => 'Pro 企业版',
'priority' => 50,
'plugins_limit' => PHP_INT_MAX,
'daily_downloads' => 500,
'features' => [ 'bridge_api', 'bridge_server', 'priority_support' ],
],
];
/**
* Free 计划默认值
*/
private const FREE_PLAN = [
'plan' => 'free',
'label' => 'Free',
'priority' => 0,
'plugins_limit' => 0,
'daily_downloads' => 0,
'features' => [],
'product_id' => 0,
];
/**
* @var Settings
*/
private Settings $settings;
/**
* @var VendorManager
*/
private VendorManager $vendor_manager;
public function __construct( Settings $settings, VendorManager $vendor_manager ) {
$this->settings = $settings;
$this->vendor_manager = $vendor_manager;
}
/**
* 获取当前订阅信息
*
* @param bool $force_refresh 强制刷新(跳过缓存)
* @return array
*/
public function get_subscription( bool $force_refresh = false ): array {
if ( ! $force_refresh ) {
$cached = get_transient( self::CACHE_KEY );
if ( false !== $cached ) {
return $cached;
}
}
$subscription = $this->resolve_subscription();
// 免费版(可能是网络失败导致)短缓存 5 分钟,付费版缓存 1 小时
$ttl = $subscription['plan'] === 'free' ? 300 : self::CACHE_TTL;
set_transient( self::CACHE_KEY, $subscription, $ttl );
return $subscription;
}
/**
* 从商城 API 解析当前订阅等级
*
* @return array
*/
private function resolve_subscription(): array {
$vendor = $this->vendor_manager->get_vendor( self::SUBSCRIPTION_VENDOR_ID );
if ( null === $vendor ) {
return self::FREE_PLAN;
}
if ( ! method_exists( $vendor, 'wc_am_product_list' ) ) {
return self::FREE_PLAN;
}
$response = $vendor->wc_am_product_list();
if ( ! is_array( $response ) || empty( $response['success'] ) ) {
Logger::warning(
'Subscription check failed',
[
'vendor' => self::SUBSCRIPTION_VENDOR_ID,
'response' => $response,
]
);
return self::FREE_PLAN;
}
$product_ids = $this->extract_product_ids( $response );
if ( empty( $product_ids ) ) {
return self::FREE_PLAN;
}
return $this->resolve_plan( $product_ids );
}
/**
* 从 WC AM product_list 响应中提取 product_id 列表
*
* 适配 Kestrel API Manager 的嵌套响应格式
*
* @param array $response WC AM API 响应
* @return int[]
*/
private function extract_product_ids( array $response ): array {
$product_ids = [];
$product_list = $response['data']['product_list'] ?? [];
// Kestrel 返回两类资源,需要合并:
// - non_wc_subs_resources: 普通(非订阅)产品
// - wc_subs_resources: WooCommerce Subscriptions 产品
$resource_keys = [ 'non_wc_subs_resources', 'wc_subs_resources' ];
$has_resources = false;
foreach ( $resource_keys as $key ) {
if ( ! empty( $product_list[ $key ] ) && is_array( $product_list[ $key ] ) ) {
$has_resources = true;
foreach ( $product_list[ $key ] as $resource ) {
if ( isset( $resource['product_id'] ) ) {
$product_ids[] = (int) $resource['product_id'];
}
}
}
}
// Fallback: 如果两个 key 都不存在,尝试直接遍历 product_list
if ( ! $has_resources && is_array( $product_list ) ) {
foreach ( $product_list as $resource ) {
if ( is_array( $resource ) && isset( $resource['product_id'] ) ) {
$product_ids[] = (int) $resource['product_id'];
}
}
}
return $product_ids;
}
/**
* 根据 product_id 列表确定最高订阅等级
*
* @param int[] $product_ids 已购产品 ID 列表
* @return array
*/
private function resolve_plan( array $product_ids ): array {
/**
* 允许扩展产品映射
*
* @param array $plans product_id => plan config
*/
$plans = apply_filters( 'wpbridge_subscription_product_plans', self::PRODUCT_PLANS );
$best_plan = self::FREE_PLAN;
foreach ( $product_ids as $product_id ) {
if ( ! isset( $plans[ $product_id ] ) ) {
continue;
}
$plan = $plans[ $product_id ];
$plan['product_id'] = $product_id;
if ( $plan['priority'] > $best_plan['priority'] ) {
$best_plan = $plan;
}
}
$best_plan['status'] = 'active';
$best_plan['checked_at'] = time();
return $best_plan;
}
/**
* 检查是否启用了指定功能
*
* @param string $feature 功能标识 (bridge_api, bridge_server, license_proxy, priority_support)
* @return bool
*/
public function is_feature_enabled( string $feature ): bool {
$subscription = $this->get_subscription();
return in_array( $feature, $subscription['features'] ?? [], true );
}
/**
* 获取当前计划名称
*
* @return string
*/
public function get_plan(): string {
$subscription = $this->get_subscription();
return $subscription['plan'] ?? 'free';
}
/**
* 获取当前计划限制
*
* @return array
*/
public function get_limits(): array {
$subscription = $this->get_subscription();
return [
'plugins_limit' => $subscription['plugins_limit'] ?? 0,
'daily_downloads' => $subscription['daily_downloads'] ?? 0,
];
}
/**
* 是否为付费用户
*
* @return bool
*/
public function is_paid(): bool {
return $this->get_plan() !== 'free';
}
/**
* 清除订阅缓存
*/
public function clear_cache(): void {
delete_transient( self::CACHE_KEY );
}
/**
* 获取订阅供应商 ID
*
* @return string
*/
public static function get_subscription_vendor_id(): string {
return self::SUBSCRIPTION_VENDOR_ID;
}
}