仰望星辰工作室

feat: 初始化MNBT虚拟主机销售WordPress插件

Y yang1145 提交于 2026-08-14 04:50 · 183024d ·父提交 4d202ef
feat: 初始化MNBT虚拟主机销售WordPress插件

该提交实现了完整的MNBT虚拟主机销售插件功能,包含以下核心模块:
1. 插件基础框架与激活卸载逻辑
2. WooCommerce商品集成与订单处理
3. MNBT API客户端与异常处理
4. 主机开通、续费、状态管理业务逻辑
5. 前台我的主机与后台管理页面
6. 日志记录与定时任务同步
7. 数据加密与工具类函数
20 个文件变更 +2743 -0 13172193298@163.com
•wp_seller_plugin/includes/admin/class-admin.php +369 -0
•wp_seller_plugin/includes/admin/views/hosts.php +163 -0
•wp_seller_plugin/includes/admin/views/logs.php +54 -0
•wp_seller_plugin/includes/admin/views/settings.php +207 -0
•wp_seller_plugin/includes/class-activator.php +144 -0
•wp_seller_plugin/includes/class-crypto.php +73 -0
•wp_seller_plugin/includes/class-helpers.php +133 -0
•wp_seller_plugin/includes/class-logger.php +62 -0
•wp_seller_plugin/includes/class-plugin.php +109 -0
•wp_seller_plugin/includes/front/class-front.php +155 -0
•wp_seller_plugin/includes/front/views/my-hosts.php +144 -0
•wp_seller_plugin/includes/mnbt/class-client.php +302 -0
•wp_seller_plugin/includes/mnbt/class-exception.php +45 -0
•wp_seller_plugin/includes/mnbt/interface-provider.php +111 -0
•wp_seller_plugin/includes/services/class-billing.php +176 -0
•wp_seller_plugin/includes/services/class-cron.php +90 -0
•wp_seller_plugin/includes/services/class-provision.php +160 -0
•wp_seller_plugin/includes/woocommerce/class-woo.php +183 -0
•wp_seller_plugin/uninstall.php +22 -0
•wp_seller_plugin/wp-seller-plugin.php +41 -0
变更内容
diff --git a/wp_seller_plugin/includes/admin/class-admin.php b/wp_seller_plugin/includes/admin/class-admin.php
new file mode 100644
index 0000000..95b29cf
--- /dev/null
+++ b/wp_seller_plugin/includes/admin/class-admin.php
@@ -0,0 +1,369 @@
+<?php
+/**
+ * 后台管理:菜单、连接配置页、API 日志页与 AJAX 处理。
+ *
+ * @package MnbtWp
+ */
+
+namespace MnbtWp;
+
+use MnbtWp\Mnbt\Client;
+use MnbtWp\Mnbt\Exception as MnbtException;
+use MnbtWp\Services\Provision;
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+/**
+ * 后台管理类。
+ */
+class Admin {
+
+	/**
+	 * 构造:挂载钩子。
+	 */
+	public function __construct() {
+		add_action( 'admin_menu', array( $this, 'menu' ) );
+		add_action( 'wp_ajax_mnbtwp_save_provider', array( $this, 'ajax_save_provider' ) );
+		add_action( 'wp_ajax_mnbtwp_get_provider', array( $this, 'ajax_get_provider' ) );
+		add_action( 'wp_ajax_mnbtwp_delete_provider', array( $this, 'ajax_delete_provider' ) );
+		add_action( 'wp_ajax_mnbtwp_test_provider', array( $this, 'ajax_test_provider' ) );
+		add_action( 'wp_ajax_mnbtwp_admin_host_action', array( $this, 'ajax_admin_host_action' ) );
+		add_action( 'wp_ajax_mnbtwp_admin_retry_provision', array( $this, 'ajax_admin_retry_provision' ) );
+	}
+
+	/**
+	 * 注册菜单。
+	 */
+	public function menu() {
+		add_menu_page(
+			__( 'MNBT 主机销售', 'wp-seller-plugin' ),
+			__( 'MNBT 主机销售', 'wp-seller-plugin' ),
+			'manage_options',
+			'mnbtwp',
+			array( $this, 'page_settings' ),
+			'dashicons-cloud',
+			56
+		);
+		add_submenu_page(
+			'mnbtwp',
+			__( '主机管理', 'wp-seller-plugin' ),
+			__( '主机管理', 'wp-seller-plugin' ),
+			'manage_options',
+			'mnbtwp-hosts',
+			array( $this, 'page_hosts' )
+		);
+		add_submenu_page(
+			'mnbtwp',
+			__( '连接配置', 'wp-seller-plugin' ),
+			__( '连接配置', 'wp-seller-plugin' ),
+			'manage_options',
+			'mnbtwp',
+			array( $this, 'page_settings' )
+		);
+		add_submenu_page(
+			'mnbtwp',
+			__( 'API 日志', 'wp-seller-plugin' ),
+			__( 'API 日志', 'wp-seller-plugin' ),
+			'manage_options',
+			'mnbtwp-logs',
+			array( $this, 'page_logs' )
+		);
+	}
+
+	/**
+	 * 主机总览页。
+	 */
+	public function page_hosts() {
+		global $wpdb;
+		$hosts = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}mnbt_hosts ORDER BY id DESC LIMIT 500", ARRAY_A );
+		$provider_map = array();
+		foreach ( $hosts as $h ) {
+			if ( ! isset( $provider_map[ $h['provider_id'] ] ) ) {
+				$provider_map[ $h['provider_id'] ] = Helpers::get_provider( $h['provider_id'] );
+			}
+		}
+		$failed_orders = array();
+		if ( class_exists( 'WooCommerce' ) ) {
+			$failed_orders = wc_get_orders(
+				array(
+					'limit'        => 50,
+					'status'       => array( 'pending', 'processing', 'on-hold', 'completed' ),
+					'meta_key'     => '_mnbtwp_provision_error',
+					'meta_compare' => 'EXISTS',
+				)
+			);
+		}
+		include MNBTWP_DIR . 'includes/admin/views/hosts.php';
+	}
+
+	/**
+	 * 连接配置页。
+	 */
+	public function page_settings() {
+		global $wpdb;
+		$providers = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}mnbt_providers ORDER BY id ASC", ARRAY_A );
+		include MNBTWP_DIR . 'includes/admin/views/settings.php';
+	}
+
+	/**
+	 * API 日志页。
+	 */
+	public function page_logs() {
+		global $wpdb;
+		$logs = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}mnbt_api_log ORDER BY id DESC LIMIT 200", ARRAY_A );
+		include MNBTWP_DIR . 'includes/admin/views/logs.php';
+	}
+
+	/**
+	 * 保存/更新节点配置。
+	 */
+	public function ajax_save_provider() {
+		check_ajax_referer( 'mnbtwp_admin', 'nonce' );
+		if ( ! current_user_can( 'manage_options' ) ) {
+			wp_send_json_error( __( '权限不足', 'wp-seller-plugin' ) );
+		}
+
+		global $wpdb;
+		$id      = (int) ( $_POST['id'] ?? 0 );
+		$name    = sanitize_text_field( $_POST['name'] ?? '' );
+		$api_url = esc_url_raw( trim( (string) ( $_POST['api_url'] ?? '' ) ) );
+		$btdh    = sanitize_text_field( $_POST['btdh'] ?? '' );
+		$mn_key  = sanitize_text_field( $_POST['mn_key'] ?? '' );
+		$mn_keye = sanitize_text_field( $_POST['mn_keye'] ?? '' );
+		$mn_vs   = max( 15, (int) ( $_POST['mn_vs'] ?? 15 ) );
+		$qk      = ( ! empty( $_POST['qk'] ) && '1' === (string) $_POST['qk'] ) ? 'true' : 'false';
+
+		if ( '' === $name || '' === $api_url || '' === $btdh || '' === $mn_key || '' === $mn_keye ) {
+			wp_send_json_error( __( '参数不能为空', 'wp-seller-plugin' ) );
+		}
+		if ( ! preg_match( '#^https?://#i', $api_url ) ) {
+			wp_send_json_error( __( 'API 地址需以 http(s):// 开头', 'wp-seller-plugin' ) );
+		}
+
+		$table = $wpdb->prefix . 'mnbt_providers';
+
+		if ( $id > 0 ) {
+			$current = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id=%d", $id ), ARRAY_A );
+			if ( ! $current ) {
+				wp_send_json_error( __( '节点不存在', 'wp-seller-plugin' ) );
+			}
+			// 密钥未修改则保留原密文,避免每次保存变更密文。
+			$mn_key_enc  = ( Crypto::decrypt( $current['mn_key_enc'] ) === $mn_key ) ? $current['mn_key_enc'] : Crypto::encrypt( $mn_key );
+			$mn_keye_enc = ( Crypto::decrypt( $current['mn_keye_enc'] ) === $mn_keye ) ? $current['mn_keye_enc'] : Crypto::encrypt( $mn_keye );
+
+			$wpdb->update(
+				$table,
+				array(
+					'name'        => $name,
+					'api_url'     => $api_url,
+					'btdh'        => $btdh,
+					'mn_key_enc'  => $mn_key_enc,
+					'mn_keye_enc' => $mn_keye_enc,
+					'mn_vs'       => $mn_vs,
+					'qk'          => $qk,
+					'updated_at'  => current_time( 'mysql' ),
+				),
+				array( 'id' => $id )
+			);
+		} else {
+			$wpdb->insert(
+				$table,
+				array(
+					'name'        => $name,
+					'api_url'     => $api_url,
+					'btdh'        => $btdh,
+					'mn_key_enc'  => Crypto::encrypt( $mn_key ),
+					'mn_keye_enc' => Crypto::encrypt( $mn_keye ),
+					'mn_vs'       => $mn_vs,
+					'qk'          => $qk,
+					'created_at'  => current_time( 'mysql' ),
+					'updated_at'  => current_time( 'mysql' ),
+				)
+			);
+		}
+
+		if ( $wpdb->last_error ) {
+			wp_send_json_error( __( '保存失败', 'wp-seller-plugin' ) . ':' . $wpdb->last_error );
+		}
+		wp_send_json_success( __( '保存成功', 'wp-seller-plugin' ) );
+	}
+
+	/**
+	 * 获取节点配置(编辑回显,解密密钥)。
+	 */
+	public function ajax_get_provider() {
+		check_ajax_referer( 'mnbtwp_admin', 'nonce' );
+		if ( ! current_user_can( 'manage_options' ) ) {
+			wp_send_json_error( __( '权限不足', 'wp-seller-plugin' ) );
+		}
+		global $wpdb;
+		$id  = (int) ( $_POST['id'] ?? 0 );
+		$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}mnbt_providers WHERE id=%d", $id ), ARRAY_A );
+		if ( ! $row ) {
+			wp_send_json_error( __( '节点不存在', 'wp-seller-plugin' ) );
+		}
+		wp_send_json_success(
+			array(
+				'id'      => (int) $row['id'],
+				'name'    => $row['name'],
+				'api_url' => $row['api_url'],
+				'btdh'    => $row['btdh'],
+				'mn_key'  => Crypto::decrypt( $row['mn_key_enc'] ),
+				'mn_keye' => Crypto::decrypt( $row['mn_keye_enc'] ),
+				'mn_vs'   => (int) $row['mn_vs'],
+				'qk'      => $row['qk'],
+			)
+		);
+	}
+
+	/**
+	 * 删除节点配置。
+	 */
+	public function ajax_delete_provider() {
+		check_ajax_referer( 'mnbtwp_admin', 'nonce' );
+		if ( ! current_user_can( 'manage_options' ) ) {
+			wp_send_json_error( __( '权限不足', 'wp-seller-plugin' ) );
+		}
+		global $wpdb;
+		$id = (int) ( $_POST['id'] ?? 0 );
+		$wpdb->delete( $wpdb->prefix . 'mnbt_providers', array( 'id' => $id ), array( '%d' ) );
+		if ( $wpdb->last_error ) {
+			wp_send_json_error( __( '删除失败', 'wp-seller-plugin' ) . ':' . $wpdb->last_error );
+		}
+		wp_send_json_success( __( '删除成功', 'wp-seller-plugin' ) );
+	}
+
+	/**
+	 * 连接测试(cfif)。
+	 */
+	public function ajax_test_provider() {
+		check_ajax_referer( 'mnbtwp_admin', 'nonce' );
+		if ( ! current_user_can( 'manage_options' ) ) {
+			wp_send_json_error( __( '权限不足', 'wp-seller-plugin' ) );
+		}
+		global $wpdb;
+		$id  = (int) ( $_POST['id'] ?? 0 );
+		$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}mnbt_providers WHERE id=%d", $id ), ARRAY_A );
+		if ( ! $row ) {
+			wp_send_json_error( __( '节点不存在', 'wp-seller-plugin' ) );
+		}
+		try {
+			$client = new Client(
+				array(
+					'api_url'     => $row['api_url'],
+					'btdh'        => $row['btdh'],
+					'mn_key'      => Crypto::decrypt( $row['mn_key_enc'] ),
+					'mn_keye'     => Crypto::decrypt( $row['mn_keye_enc'] ),
+					'mn_vs'       => (int) $row['mn_vs'],
+					'provider_id' => (int) $row['id'],
+				)
+			);
+			$result = $client->testConnection();
+			/* translators: %s: MNBT 返回消息 */
+			wp_send_json_success( sprintf( __( '连接成功:%s', 'wp-seller-plugin' ), $result['msg'] ) );
+		} catch ( MnbtException $e ) {
+			wp_send_json_error( __( '连接失败', 'wp-seller-plugin' ) . ':' . $e->getMessage() );
+		}
+	}
+
+	/**
+	 * 管理端主机操作:start/stop/suspend/resume/delete/change_pass/renew/change_package。
+	 */
+	public function ajax_admin_host_action() {
+		check_ajax_referer( 'mnbtwp_admin', 'nonce' );
+		if ( ! current_user_can( 'manage_options' ) ) {
+			wp_send_json_error( __( '权限不足', 'wp-seller-plugin' ) );
+		}
+		$action = sanitize_key( $_POST['host_action'] ?? '' );
+		$host_id = (int) ( $_POST['host_id'] ?? 0 );
+		$host    = Helpers::get_host( $host_id );
+		if ( ! $host ) {
+			wp_send_json_error( __( '主机不存在', 'wp-seller-plugin' ) );
+		}
+		$provider = Helpers::get_provider( $host['provider_id'] );
+		if ( ! $provider ) {
+			wp_send_json_error( __( '节点不可用', 'wp-seller-plugin' ) );
+		}
+
+		global $wpdb;
+		$table = $wpdb->prefix . 'mnbt_hosts';
+
+		try {
+			$client = Helpers::client_for_provider( $provider );
+			switch ( $action ) {
+				case 'start':
+					$client->startSite( $host['username'] );
+					break;
+				case 'stop':
+					$client->stopSite( $host['username'] );
+					break;
+				case 'suspend':
+					$client->suspendHost( $host['username'] );
+					$wpdb->update( $table, array( 'status' => 'suspended', 'updated_at' => current_time( 'mysql' ) ), array( 'id' => $host_id ) );
+					break;
+				case 'resume':
+					$client->resumeHost( $host['username'] );
+					$wpdb->update( $table, array( 'status' => 'active', 'updated_at' => current_time( 'mysql' ) ), array( 'id' => $host_id ) );
+					break;
+				case 'delete':
+					$client->deleteHost( $host['username'] );
+					$wpdb->delete( $table, array( 'id' => $host_id ), array( '%d' ) );
+					break;
+				case 'change_pass':
+					$password = sanitize_text_field( $_POST['password'] ?? '' );
+					if ( mb_strlen( $password ) < 6 ) {
+						wp_send_json_error( __( '密码不能少于 6 位', 'wp-seller-plugin' ) );
+					}
+					$client->changePassword( $host['username'], $password );
+					$wpdb->update( $table, array( 'password_enc' => Crypto::encrypt( $password ), 'updated_at' => current_time( 'mysql' ) ), array( 'id' => $host_id ) );
+					break;
+				case 'renew':
+					$setdate = sanitize_text_field( $_POST['setdate'] ?? '' );
+					if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $setdate ) ) {
+						wp_send_json_error( __( '到期日期格式错误', 'wp-seller-plugin' ) );
+					}
+					$client->renewHost( $host['username'], $setdate );
+					$wpdb->update( $table, array( 'expire_date' => $setdate, 'updated_at' => current_time( 'mysql' ) ), array( 'id' => $host_id ) );
+					break;
+				case 'change_package':
+					$web  = max( 0, (int) ( $_POST['websize'] ?? 0 ) );
+					$sql  = max( 0, (int) ( $_POST['sqlsize'] ?? 0 ) );
+					$flow = max( 0, (int) ( $_POST['ll'] ?? 0 ) );
+					$client->changePackage( $host['username'], array( 'websize' => $web, 'sqlsize' => $sql, 'll' => $flow ) );
+					$quota = Helpers::parse_quota( $host['quota_json'] );
+					$quota['web']['max']  = $web;
+					$quota['sql']['max']  = $sql;
+					$quota['flow']['max'] = $flow;
+					$wpdb->update( $table, array( 'quota_json' => wp_json_encode( $quota ), 'updated_at' => current_time( 'mysql' ) ), array( 'id' => $host_id ) );
+					break;
+				default:
+					wp_send_json_error( __( '未知操作', 'wp-seller-plugin' ) );
+			}
+			wp_send_json_success( __( '操作成功', 'wp-seller-plugin' ) );
+		} catch ( \Exception $e ) {
+			wp_send_json_error( $e->getMessage() );
+		}
+	}
+
+	/**
+	 * 重试开通失败的订单。
+	 */
+	public function ajax_admin_retry_provision() {
+		check_ajax_referer( 'mnbtwp_admin', 'nonce' );
+		if ( ! current_user_can( 'manage_options' ) ) {
+			wp_send_json_error( __( '权限不足', 'wp-seller-plugin' ) );
+		}
+		$order_id = (int) ( $_POST['order_id'] ?? 0 );
+		if ( ! $order_id ) {
+			wp_send_json_error( __( '参数错误', 'wp-seller-plugin' ) );
+		}
+		try {
+			$result = Provision::provision_order( $order_id );
+			wp_send_json_success( $result['msg'] );
+		} catch ( \Exception $e ) {
+			wp_send_json_error( $e->getMessage() );
+		}
+	}
+}
diff --git a/wp_seller_plugin/includes/admin/views/hosts.php b/wp_seller_plugin/includes/admin/views/hosts.php
new file mode 100644
index 0000000..9aa4aaf
--- /dev/null
+++ b/wp_seller_plugin/includes/admin/views/hosts.php
@@ -0,0 +1,163 @@
+<?php
+/**
+ * 主机总览页视图。
+ *
+ * @package MnbtWp
+ */
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+$mnbtwp_nonce = wp_create_nonce( 'mnbtwp_admin' );
+?>
+<div class="wrap">
+	<h1><?php esc_html_e( '主机管理', 'wp-seller-plugin' ); ?></h1>
+	<p class="description"><?php esc_html_e( '所有已开通主机(含购买自动开通与手动开通)。操作会直接调用 MNBT 生效。', 'wp-seller-plugin' ); ?></p>
+
+	<?php if ( ! empty( $failed_orders ) ) : ?>
+		<h2 style="margin-top:20px;"><?php esc_html_e( '开通失败订单', 'wp-seller-plugin' ); ?></h2>
+		<p class="description"><?php esc_html_e( '以下订单支付成功但自动开通失败,可重试开通。', 'wp-seller-plugin' ); ?></p>
+		<table class="widefat striped">
+			<thead>
+				<tr>
+					<th><?php esc_html_e( '订单', 'wp-seller-plugin' ); ?></th>
+					<th><?php esc_html_e( '客户', 'wp-seller-plugin' ); ?></th>
+					<th><?php esc_html_e( '金额', 'wp-seller-plugin' ); ?></th>
+					<th><?php esc_html_e( '错误信息', 'wp-seller-plugin' ); ?></th>
+					<th><?php esc_html_e( '操作', 'wp-seller-plugin' ); ?></th>
+				</tr>
+			</thead>
+			<tbody>
+				<?php foreach ( $failed_orders as $mnbtwp_fo ) : ?>
+					<?php $mnbtwp_err = get_post_meta( $mnbtwp_fo->get_id(), '_mnbtwp_provision_error', true ); ?>
+					<tr>
+						<td><a href="<?php echo esc_url( $mnbtwp_fo->get_edit_order_url() ); ?>">#<?php echo (int) $mnbtwp_fo->get_id(); ?></a></td>
+						<td><?php echo esc_html( $mnbtwp_fo->get_billing_email() ? $mnbtwp_fo->get_billing_email() : '#' . (int) $mnbtwp_fo->get_user_id() ); ?></td>
+						<td><?php echo esc_html( $mnbtwp_fo->get_total() . ' ' . $mnbtwp_fo->get_currency() ); ?></td>
+						<td style="color:#c62828;"><?php echo esc_html( $mnbtwp_err ); ?></td>
+						<td>
+							<button type="button" class="button button-small button-primary" onclick="mnbtwpRetry(<?php echo (int) $mnbtwp_fo->get_id(); ?>)"><?php esc_html_e( '重试开通', 'wp-seller-plugin' ); ?></button>
+						</td>
+					</tr>
+				<?php endforeach; ?>
+			</tbody>
+		</table>
+	<?php endif; ?>
+
+	<h2 style="margin-top:20px;"><?php esc_html_e( '主机列表', 'wp-seller-plugin' ); ?></h2>
+	<?php if ( empty( $hosts ) ) : ?>
+		<p><?php esc_html_e( '暂无主机记录。', 'wp-seller-plugin' ); ?></p>
+	<?php else : ?>
+		<table class="widefat striped">
+			<thead>
+				<tr>
+					<th><?php esc_html_e( 'ID', 'wp-seller-plugin' ); ?></th>
+					<th><?php esc_html_e( '节点', 'wp-seller-plugin' ); ?></th>
+					<th><?php esc_html_e( '用户名', 'wp-seller-plugin' ); ?></th>
+					<th><?php esc_html_e( '站点', 'wp-seller-plugin' ); ?></th>
+					<th><?php esc_html_e( '状态', 'wp-seller-plugin' ); ?></th>
+					<th><?php esc_html_e( '到期', 'wp-seller-plugin' ); ?></th>
+					<th><?php esc_html_e( '订单', 'wp-seller-plugin' ); ?></th>
+					<th><?php esc_html_e( '操作', 'wp-seller-plugin' ); ?></th>
+				</tr>
+			</thead>
+			<tbody>
+				<?php foreach ( $hosts as $mnbtwp_h ) : ?>
+					<?php
+					$mnbtwp_p = isset( $provider_map[ $mnbtwp_h['provider_id'] ] ) ? $provider_map[ $mnbtwp_h['provider_id'] ] : null;
+					$mnbtwp_label = Helpers::host_status_label( $mnbtwp_h['status'] );
+					?>
+					<tr>
+						<td><?php echo (int) $mnbtwp_h['id']; ?></td>
+						<td><?php echo $mnbtwp_p ? esc_html( $mnbtwp_p['name'] ) : esc_html( '#' . (int) $mnbtwp_h['provider_id'] ); ?></td>
+						<td><code><?php echo esc_html( $mnbtwp_h['username'] ); ?></code></td>
+						<td><?php echo esc_html( $mnbtwp_h['site_domain'] ? $mnbtwp_h['site_domain'] : '-' ); ?></td>
+						<td>
+							<?php if ( in_array( $mnbtwp_h['status'], array( 'active', 'provisioning' ), true ) ) : ?>
+								<span style="color:#1a7f37;"><?php echo esc_html( $mnbtwp_label ); ?></span>
+							<?php else : ?>
+								<span style="color:#c62828;"><?php echo esc_html( $mnbtwp_label ); ?></span>
+							<?php endif; ?>
+						</td>
+						<td><?php echo esc_html( $mnbtwp_h['expire_date'] && '0000-00-00' !== $mnbtwp_h['expire_date'] ? $mnbtwp_h['expire_date'] : __( '长期', 'wp-seller-plugin' ) ); ?></td>
+						<td><?php echo $mnbtwp_h['order_id'] ? '<a href="' . esc_url( get_edit_post_link( (int) $mnbtwp_h['order_id'] ) ) . '">#' . (int) $mnbtwp_h['order_id'] . '</a>' : '-'; ?></td>
+						<td>
+							<button type="button" class="button button-small" onclick="mnbtwpHostAction(<?php echo (int) $mnbtwp_h['id']; ?>,'start')"><?php esc_html_e( '启动', 'wp-seller-plugin' ); ?></button>
+							<button type="button" class="button button-small" onclick="mnbtwpHostAction(<?php echo (int) $mnbtwp_h['id']; ?>,'stop')"><?php esc_html_e( '停止', 'wp-seller-plugin' ); ?></button>
+							<button type="button" class="button button-small" onclick="mnbtwpHostAction(<?php echo (int) $mnbtwp_h['id']; ?>,'suspend')"><?php esc_html_e( '暂停', 'wp-seller-plugin' ); ?></button>
+							<button type="button" class="button button-small" onclick="mnbtwpHostAction(<?php echo (int) $mnbtwp_h['id']; ?>,'resume')"><?php esc_html_e( '恢复', 'wp-seller-plugin' ); ?></button>
+							<button type="button" class="button button-small" onclick="mnbtwpPass(<?php echo (int) $mnbtwp_h['id']; ?>)"><?php esc_html_e( '改密', 'wp-seller-plugin' ); ?></button>
+							<button type="button" class="button button-small" onclick="mnbtwpRenew(<?php echo (int) $mnbtwp_h['id']; ?>)"><?php esc_html_e( '续费', 'wp-seller-plugin' ); ?></button>
+							<button type="button" class="button button-small" onclick="mnbtwpPackage(<?php echo (int) $mnbtwp_h['id']; ?>)"><?php esc_html_e( '升降级', 'wp-seller-plugin' ); ?></button>
+							<button type="button" class="button button-small button-link-delete" onclick="mnbtwpDelete(<?php echo (int) $mnbtwp_h['id']; ?>)"><?php esc_html_e( '删除', 'wp-seller-plugin' ); ?></button>
+							<?php if ( 'failed' === $mnbtwp_h['status'] && $mnbtwp_h['order_id'] ) : ?>
+								<button type="button" class="button button-small button-primary" onclick="mnbtwpRetry(<?php echo (int) $mnbtwp_h['order_id']; ?>)"><?php esc_html_e( '重试开通', 'wp-seller-plugin' ); ?></button>
+							<?php endif; ?>
+						</td>
+					</tr>
+				<?php endforeach; ?>
+			</tbody>
+		</table>
+	<?php endif; ?>
+</div>
+
+<script type="text/javascript">
+	var mnbtwpAdminNonce = '<?php echo esc_js( $mnbtwp_nonce ); ?>';
+
+	function mnbtwpAdminAjax(data, done) {
+		data.nonce = mnbtwpAdminNonce;
+		jQuery.post(ajaxurl, data, function (r) {
+			if (r && r.success) {
+				if (done) done(r); else location.reload();
+			} else {
+				alert((r && r.data) ? r.data : '<?php echo esc_js( __( '操作失败', 'wp-seller-plugin' ) ); ?>');
+			}
+		}).fail(function () {
+			alert('<?php echo esc_js( __( '网络错误', 'wp-seller-plugin' ) ); ?>');
+		});
+	}
+
+	function mnbtwpHostAction(id, action) {
+		mnbtwpAdminAjax({ action: 'mnbtwp_admin_host_action', host_action: action, host_id: id });
+	}
+
+	function mnbtwpPass(id) {
+		var pw = prompt('<?php echo esc_js( __( '请输入新密码(至少 6 位)', 'wp-seller-plugin' ) ); ?>');
+		if (!pw || pw.length < 6) return;
+		mnbtwpAdminAjax({ action: 'mnbtwp_admin_host_action', host_action: 'change_pass', host_id: id, password: pw });
+	}
+
+	function mnbtwpRenew(id) {
+		var d = prompt('<?php echo esc_js( __( '请输入新到期日期(YYYY-MM-DD)', 'wp-seller-plugin' ) ); ?>');
+		if (!d || !/^\d{4}-\d{2}-\d{2}$/.test(d)) return;
+		mnbtwpAdminAjax({ action: 'mnbtwp_admin_host_action', host_action: 'renew', host_id: id, setdate: d });
+	}
+
+	function mnbtwpPackage(id) {
+		var web = prompt('<?php echo esc_js( __( '网站空间 (MB)', 'wp-seller-plugin' ) ); ?>', '0');
+		if (web === null) return;
+		var sql = prompt('<?php echo esc_js( __( '数据库空间 (MB)', 'wp-seller-plugin' ) ); ?>', '0');
+		if (sql === null) return;
+		var ll = prompt('<?php echo esc_js( __( '流量 (MB,0=不限)', 'wp-seller-plugin' ) ); ?>', '0');
+		if (ll === null) return;
+		mnbtwpAdminAjax({
+			action: 'mnbtwp_admin_host_action',
+			host_action: 'change_package',
+			host_id: id,
+			websize: parseInt(web, 10) || 0,
+			sqlsize: parseInt(sql, 10) || 0,
+			ll: parseInt(ll, 10) || 0
+		});
+	}
+
+	function mnbtwpDelete(id) {
+		if (!confirm('<?php echo esc_js( __( '删除后站点、数据库、FTP 将被清除且不可恢复,确定删除?', 'wp-seller-plugin' ) ); ?>')) return;
+		mnbtwpAdminAjax({ action: 'mnbtwp_admin_host_action', host_action: 'delete', host_id: id });
+	}
+
+	function mnbtwpRetry(orderId) {
+		if (!confirm('<?php echo esc_js( __( '确定重试开通该订单?', 'wp-seller-plugin' ) ); ?>')) return;
+		mnbtwpAdminAjax({ action: 'mnbtwp_admin_retry_provision', order_id: orderId });
+	}
+</script>
diff --git a/wp_seller_plugin/includes/admin/views/logs.php b/wp_seller_plugin/includes/admin/views/logs.php
new file mode 100644
index 0000000..a7f0875
--- /dev/null
+++ b/wp_seller_plugin/includes/admin/views/logs.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * API 日志页视图。
+ *
+ * @package MnbtWp
+ */
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+?>
+<div class="wrap">
+	<h1><?php esc_html_e( 'MNBT API 日志', 'wp-seller-plugin' ); ?></h1>
+	<?php if ( empty( $logs ) ) : ?>
+		<p class="description"><?php esc_html_e( '暂无日志。配置节点并测试连接后,这里会记录每次 API 调用(密钥已脱敏)。', 'wp-seller-plugin' ); ?></p>
+	<?php else : ?>
+		<table class="widefat striped">
+			<thead>
+				<tr>
+					<th><?php esc_html_e( 'ID', 'wp-seller-plugin' ); ?></th>
+					<th><?php esc_html_e( '节点', 'wp-seller-plugin' ); ?></th>
+					<th><?php esc_html_e( '动作', 'wp-seller-plugin' ); ?></th>
+					<th><?php esc_html_e( '参数', 'wp-seller-plugin' ); ?></th>
+					<th><?php esc_html_e( 'Code', 'wp-seller-plugin' ); ?></th>
+					<th><?php esc_html_e( '消息', 'wp-seller-plugin' ); ?></th>
+					<th><?php esc_html_e( '耗时(s)', 'wp-seller-plugin' ); ?></th>
+					<th><?php esc_html_e( '时间', 'wp-seller-plugin' ); ?></th>
+				</tr>
+			</thead>
+			<tbody>
+				<?php foreach ( $logs as $mnbtwp_log ) : ?>
+					<tr>
+						<td><?php echo (int) $mnbtwp_log['id']; ?></td>
+						<td><?php echo (int) $mnbtwp_log['provider_id']; ?></td>
+						<td><code><?php echo esc_html( $mnbtwp_log['action'] ); ?></code></td>
+						<td style="max-width:300px;word-break:break-all;">
+							<code style="font-size:11px;"><?php echo esc_html( $mnbtwp_log['params_json'] ); ?></code>
+						</td>
+						<td>
+							<?php if ( 200 === (int) $mnbtwp_log['code'] ) : ?>
+								<span style="color:#00a32a;"><?php echo (int) $mnbtwp_log['code']; ?></span>
+							<?php else : ?>
+								<span style="color:#d63638;"><?php echo (int) $mnbtwp_log['code']; ?></span>
+							<?php endif; ?>
+						</td>
+						<td style="max-width:240px;"><?php echo esc_html( $mnbtwp_log['msg'] ); ?></td>
+						<td><?php echo esc_html( $mnbtwp_log['duration'] ); ?></td>
+						<td><?php echo esc_html( $mnbtwp_log['created_at'] ); ?></td>
+					</tr>
+				<?php endforeach; ?>
+			</tbody>
+		</table>
+	<?php endif; ?>
+</div>
diff --git a/wp_seller_plugin/includes/admin/views/settings.php b/wp_seller_plugin/includes/admin/views/settings.php
new file mode 100644
index 0000000..4a76b95
--- /dev/null
+++ b/wp_seller_plugin/includes/admin/views/settings.php
@@ -0,0 +1,207 @@
+<?php
+/**
+ * 连接配置页视图。
+ *
+ * @package MnbtWp
+ */
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+$mnbtwp_nonce = wp_create_nonce( 'mnbtwp_admin' );
+?>
+<div class="wrap">
+	<h1><?php esc_html_e( 'MNBT 连接配置', 'wp-seller-plugin' ); ?></h1>
+	<p class="description">
+		<?php esc_html_e( '配置 MNBT(梦奈宝塔)节点连接信息。密钥在「系统设置 → API 密钥」与「宝塔列表 → ktmy」处获取。', 'wp-seller-plugin' ); ?>
+	</p>
+
+	<div style="display:flex;gap:24px;align-items:flex-start;flex-wrap:wrap;">
+		<!-- 左侧:表单 -->
+		<div style="min-width:380px;max-width:520px;flex:1;">
+			<form id="mnbtwp-provider-form" autocomplete="off">
+				<input type="hidden" id="mnbtwp-pid" value="0">
+				<table class="form-table" role="presentation">
+					<tr>
+						<th scope="row"><label for="mnbtwp-name"><?php esc_html_e( '名称', 'wp-seller-plugin' ); ?></label></th>
+						<td>
+							<input type="text" id="mnbtwp-name" class="regular-text" placeholder="<?php esc_attr_e( '如:主节点', 'wp-seller-plugin' ); ?>">
+						</td>
+					</tr>
+					<tr>
+						<th scope="row"><label for="mnbtwp-api-url"><?php esc_html_e( 'MNBT 站点地址', 'wp-seller-plugin' ); ?></label></th>
+						<td>
+							<input type="text" id="mnbtwp-api-url" class="regular-text" placeholder="https://mnbt.example.com">
+							<p class="description"><?php esc_html_e( 'MNBT 站点根地址,接口为 地址/api/api.php', 'wp-seller-plugin' ); ?></p>
+						</td>
+					</tr>
+					<tr>
+						<th scope="row"><label for="mnbtwp-btdh"><?php esc_html_e( '宝塔节点代号 (btdh)', 'wp-seller-plugin' ); ?></label></th>
+						<td>
+							<input type="text" id="mnbtwp-btdh" class="regular-text" placeholder="如 mn123456f">
+							<p class="description"><?php esc_html_e( 'MNBT 后台「宝塔列表」中的宝塔编号', 'wp-seller-plugin' ); ?></p>
+						</td>
+					</tr>
+					<tr>
+						<th scope="row"><label for="mnbtwp-mn-key"><?php esc_html_e( '系统 API 密钥 (mn_key)', 'wp-seller-plugin' ); ?></label></th>
+						<td>
+							<input type="password" id="mnbtwp-mn-key" class="regular-text" autocomplete="new-password">
+							<p class="description"><?php esc_html_e( 'MNBT 后台「系统设置 → API 密钥」', 'wp-seller-plugin' ); ?></p>
+						</td>
+					</tr>
+					<tr>
+						<th scope="row"><label for="mnbtwp-mn-keye"><?php esc_html_e( '节点调用密钥 (mn_keye)', 'wp-seller-plugin' ); ?></label></th>
+						<td>
+							<input type="password" id="mnbtwp-mn-keye" class="regular-text" autocomplete="new-password">
+							<p class="description"><?php esc_html_e( 'MNBT 后台「宝塔列表」ktmy 列 👁 显示的值(md5(ktmy.qmk))', 'wp-seller-plugin' ); ?></p>
+						</td>
+					</tr>
+					<tr>
+						<th scope="row"><label for="mnbtwp-mn-vs"><?php esc_html_e( 'API 版本号 (mn_vs)', 'wp-seller-plugin' ); ?></label></th>
+						<td>
+							<input type="number" id="mnbtwp-mn-vs" class="small-text" value="15" min="15">
+							<p class="description"><?php esc_html_e( '默认 15,MNBT 要求 ≥ 15', 'wp-seller-plugin' ); ?></p>
+						</td>
+					</tr>
+					<tr>
+						<th scope="row"><?php esc_html_e( '启用', 'wp-seller-plugin' ); ?></th>
+						<td>
+							<label>
+								<input type="checkbox" id="mnbtwp-qk" checked>
+								<?php esc_html_e( '启用该节点', 'wp-seller-plugin' ); ?>
+							</label>
+						</td>
+					</tr>
+				</table>
+				<p class="submit">
+					<button type="button" class="button button-primary" onclick="mnbtwpSaveProvider()"><?php esc_html_e( '保存', 'wp-seller-plugin' ); ?></button>
+					<button type="button" class="button" onclick="mnbtwpResetForm()"><?php esc_html_e( '重置', 'wp-seller-plugin' ); ?></button>
+				</p>
+			</form>
+		</div>
+
+		<!-- 右侧:节点列表 -->
+		<div style="min-width:520px;flex:2;">
+			<h2><?php esc_html_e( '已配置节点', 'wp-seller-plugin' ); ?></h2>
+			<?php if ( empty( $providers ) ) : ?>
+				<p class="description"><?php esc_html_e( '暂无配置,请在左侧添加。', 'wp-seller-plugin' ); ?></p>
+			<?php else : ?>
+				<table class="widefat striped">
+					<thead>
+						<tr>
+							<th><?php esc_html_e( 'ID', 'wp-seller-plugin' ); ?></th>
+							<th><?php esc_html_e( '名称', 'wp-seller-plugin' ); ?></th>
+							<th><?php esc_html_e( 'MNBT 地址', 'wp-seller-plugin' ); ?></th>
+							<th><?php esc_html_e( '节点代号', 'wp-seller-plugin' ); ?></th>
+							<th><?php esc_html_e( '状态', 'wp-seller-plugin' ); ?></th>
+							<th><?php esc_html_e( '操作', 'wp-seller-plugin' ); ?></th>
+						</tr>
+					</thead>
+					<tbody>
+						<?php foreach ( $providers as $mnbtwp_p ) : ?>
+							<tr>
+								<td><?php echo (int) $mnbtwp_p['id']; ?></td>
+								<td><?php echo esc_html( $mnbtwp_p['name'] ); ?></td>
+								<td><?php echo esc_html( $mnbtwp_p['api_url'] ); ?></td>
+								<td><code><?php echo esc_html( $mnbtwp_p['btdh'] ); ?></code></td>
+								<td>
+									<?php if ( 'true' === $mnbtwp_p['qk'] ) : ?>
+										<span class="dashicons dashicons-yes" style="color:#00a32a;" title="<?php esc_attr_e( '启用', 'wp-seller-plugin' ); ?>"></span>
+									<?php else : ?>
+										<span class="dashicons dashicons-no" style="color:#d63638;" title="<?php esc_attr_e( '禁用', 'wp-seller-plugin' ); ?>"></span>
+									<?php endif; ?>
+								</td>
+								<td>
+									<button type="button" class="button button-small" onclick="mnbtwpEditProvider(<?php echo (int) $mnbtwp_p['id']; ?>)"><?php esc_html_e( '编辑', 'wp-seller-plugin' ); ?></button>
+									<button type="button" class="button button-small" onclick="mnbtwpTestProvider(<?php echo (int) $mnbtwp_p['id']; ?>)"><?php esc_html_e( '测试连接', 'wp-seller-plugin' ); ?></button>
+									<button type="button" class="button button-small button-link-delete" onclick="mnbtwpDeleteProvider(<?php echo (int) $mnbtwp_p['id']; ?>)"><?php esc_html_e( '删除', 'wp-seller-plugin' ); ?></button>
+								</td>
+							</tr>
+						<?php endforeach; ?>
+					</tbody>
+				</table>
+			<?php endif; ?>
+		</div>
+	</div>
+</div>
+
+<script type="text/javascript">
+	var mnbtwpNonce = '<?php echo esc_js( $mnbtwp_nonce ); ?>';
+
+	function mnbtwpResetForm() {
+		document.getElementById('mnbtwp-pid').value = '0';
+		document.getElementById('mnbtwp-name').value = '';
+		document.getElementById('mnbtwp-api-url').value = '';
+		document.getElementById('mnbtwp-btdh').value = '';
+		document.getElementById('mnbtwp-mn-key').value = '';
+		document.getElementById('mnbtwp-mn-keye').value = '';
+		document.getElementById('mnbtwp-mn-vs').value = '15';
+		document.getElementById('mnbtwp-qk').checked = true;
+	}
+
+	function mnbtwpSaveProvider() {
+		var data = {
+			action: 'mnbtwp_save_provider',
+			nonce: mnbtwpNonce,
+			id: document.getElementById('mnbtwp-pid').value,
+			name: document.getElementById('mnbtwp-name').value,
+			api_url: document.getElementById('mnbtwp-api-url').value,
+			btdh: document.getElementById('mnbtwp-btdh').value,
+			mn_key: document.getElementById('mnbtwp-mn-key').value,
+			mn_keye: document.getElementById('mnbtwp-mn-keye').value,
+			mn_vs: document.getElementById('mnbtwp-mn-vs').value,
+			qk: document.getElementById('mnbtwp-qk').checked ? '1' : '0'
+		};
+		jQuery.post(ajaxurl, data, function (r) {
+			if (r && r.success) {
+				alert(r.data);
+				location.reload();
+			} else {
+				alert((r && r.data) ? r.data : '<?php echo esc_js( __( '保存失败', 'wp-seller-plugin' ) ); ?>');
+			}
+		}).fail(function () {
+			alert('<?php echo esc_js( __( '网络错误', 'wp-seller-plugin' ) ); ?>');
+		});
+	}
+
+	function mnbtwpEditProvider(id) {
+		jQuery.post(ajaxurl, { action: 'mnbtwp_get_provider', nonce: mnbtwpNonce, id: id }, function (r) {
+			if (!r || !r.success) {
+				alert((r && r.data) ? r.data : '<?php echo esc_js( __( '获取失败', 'wp-seller-plugin' ) ); ?>');
+				return;
+			}
+			var d = r.data;
+			document.getElementById('mnbtwp-pid').value = d.id;
+			document.getElementById('mnbtwp-name').value = d.name;
+			document.getElementById('mnbtwp-api-url').value = d.api_url;
+			document.getElementById('mnbtwp-btdh').value = d.btdh;
+			document.getElementById('mnbtwp-mn-key').value = d.mn_key;
+			document.getElementById('mnbtwp-mn-keye').value = d.mn_keye;
+			document.getElementById('mnbtwp-mn-vs').value = d.mn_vs;
+			document.getElementById('mnbtwp-qk').checked = (d.qk === 'true');
+			window.scrollTo({ top: 0, behavior: 'smooth' });
+		});
+	}
+
+	function mnbtwpTestProvider(id) {
+		jQuery.post(ajaxurl, { action: 'mnbtwp_test_provider', nonce: mnbtwpNonce, id: id }, function (r) {
+			if (r && r.success) {
+				alert(r.data);
+			} else {
+				alert((r && r.data) ? r.data : '<?php echo esc_js( __( '连接失败', 'wp-seller-plugin' ) ); ?>');
+			}
+		});
+	}
+
+	function mnbtwpDeleteProvider(id) {
+		if (!confirm('<?php echo esc_js( __( '确定删除该节点配置?不影响 MNBT 中已有主机。', 'wp-seller-plugin' ) ); ?>')) return;
+		jQuery.post(ajaxurl, { action: 'mnbtwp_delete_provider', nonce: mnbtwpNonce, id: id }, function (r) {
+			if (r && r.success) {
+				location.reload();
+			} else {
+				alert((r && r.data) ? r.data : '<?php echo esc_js( __( '删除失败', 'wp-seller-plugin' ) ); ?>');
+			}
+		});
+	}
+</script>
diff --git a/wp_seller_plugin/includes/class-activator.php b/wp_seller_plugin/includes/class-activator.php
new file mode 100644
index 0000000..85a86ba
--- /dev/null
+++ b/wp_seller_plugin/includes/class-activator.php
@@ -0,0 +1,144 @@
+<?php
+/**
+ * 激活/停用逻辑:建表、默认配置、定时任务注册。
+ *
+ * @package MnbtWp
+ */
+
+namespace MnbtWp;
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+/**
+ * 激活器。
+ */
+class Activator {
+
+	/**
+	 * 激活:建表 + 默认设置 + 注册定时任务。
+	 */
+	public static function activate() {
+		self::create_tables();
+		self::defaults();
+		self::schedule_cron();
+	}
+
+	/**
+	 * 停用:清理定时任务(数据保留)。
+	 */
+	public static function deactivate() {
+		self::clear_cron();
+	}
+
+	/**
+	 * 创建插件数据表。
+	 */
+	private static function create_tables() {
+		global $wpdb;
+
+		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
+
+		$charset_collate = $wpdb->get_charset_collate();
+		$prefix          = $wpdb->prefix;
+
+		$tables = array();
+
+		// MNBT 节点连接配置(可多节点)。
+		$tables[] = "CREATE TABLE {$prefix}mnbt_providers (
+			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+			name varchar(100) NOT NULL DEFAULT '',
+			api_url varchar(255) NOT NULL DEFAULT '',
+			btdh varchar(50) NOT NULL DEFAULT '',
+			mn_key_enc varchar(255) NOT NULL DEFAULT '',
+			mn_keye_enc varchar(255) NOT NULL DEFAULT '',
+			mn_vs int(11) NOT NULL DEFAULT 15,
+			qk varchar(20) NOT NULL DEFAULT 'true',
+			created_at datetime DEFAULT NULL,
+			updated_at datetime DEFAULT NULL,
+			PRIMARY KEY  (id),
+			KEY qk (qk)
+		) $charset_collate;";
+
+		// 主机记录(本地状态缓存,购买开通后写入)。
+		$tables[] = "CREATE TABLE {$prefix}mnbt_hosts (
+			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+			provider_id bigint(20) unsigned NOT NULL DEFAULT 0,
+			order_id bigint(20) unsigned NOT NULL DEFAULT 0,
+			user_id bigint(20) unsigned NOT NULL DEFAULT 0,
+			username varchar(64) NOT NULL DEFAULT '',
+			password_enc varchar(255) NOT NULL DEFAULT '',
+			site_domain varchar(128) NOT NULL DEFAULT '',
+			status varchar(20) NOT NULL DEFAULT 'pending',
+			expire_date varchar(20) NOT NULL DEFAULT '',
+			quota_json text NOT NULL,
+			last_remind datetime DEFAULT NULL,
+			created_at datetime DEFAULT NULL,
+			updated_at datetime DEFAULT NULL,
+			PRIMARY KEY  (id),
+			KEY user_id (user_id),
+			KEY username (username),
+			KEY status (status)
+		) $charset_collate;";
+
+		// API 调用日志(脱敏)。
+		$tables[] = "CREATE TABLE {$prefix}mnbt_api_log (
+			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+			provider_id bigint(20) unsigned NOT NULL DEFAULT 0,
+			action varchar(50) NOT NULL DEFAULT '',
+			params_json text NOT NULL,
+			code int(11) NOT NULL DEFAULT 0,
+			msg text NOT NULL,
+			duration float NOT NULL DEFAULT 0,
+			created_at datetime DEFAULT NULL,
+			PRIMARY KEY  (id),
+			KEY action (action),
+			KEY created_at (created_at)
+		) $charset_collate;";
+
+		foreach ( $tables as $sql ) {
+			dbDelta( $sql );
+		}
+	}
+
+	/**
+	 * 默认设置。
+	 */
+	private static function defaults() {
+		if ( false === get_option( 'mnbtwp_settings' ) ) {
+			add_option(
+				'mnbtwp_settings',
+				array(
+					'expire_days'      => 3, // 到期前提醒天数。
+					'grace_days'       => 0, // 宽限期(天)。
+					'keep_data_on_del' => 1, // 卸载时保留数据。
+				)
+			);
+		}
+	}
+
+	/**
+	 * 注册定时任务:每日到期维护 + 每 10 分钟状态同步。
+	 */
+	public static function schedule_cron() {
+		if ( ! wp_next_scheduled( 'mnbtwp_daily_maintenance' ) ) {
+			wp_schedule_event( time() + 60, 'daily', 'mnbtwp_daily_maintenance' );
+		}
+		if ( ! wp_next_scheduled( 'mnbtwp_sync_hosts' ) ) {
+			wp_schedule_event( time() + 120, 'twicedaily', 'mnbtwp_sync_hosts' );
+		}
+	}
+
+	/**
+	 * 清理定时任务。
+	 */
+	private static function clear_cron() {
+		foreach ( array( 'mnbtwp_daily_maintenance', 'mnbtwp_sync_hosts' ) as $hook ) {
+			$timestamp = wp_next_scheduled( $hook );
+			if ( $timestamp ) {
+				wp_unschedule_event( $timestamp, $hook );
+			}
+		}
+	}
+}
diff --git a/wp_seller_plugin/includes/class-crypto.php b/wp_seller_plugin/includes/class-crypto.php
new file mode 100644
index 0000000..3c61ba4
--- /dev/null
+++ b/wp_seller_plugin/includes/class-crypto.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * 密钥加解密工具:基于 WordPress 盐派生密钥,AES-256-CBC 加密。
+ *
+ * @package MnbtWp
+ */
+
+namespace MnbtWp;
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+/**
+ * 密钥加解密。
+ */
+class Crypto {
+
+	/**
+	 * 派生 32 字节 AES 密钥(基于 wp_salt('auth'),站点级唯一)。
+	 *
+	 * @return string
+	 */
+	private static function key() {
+		return substr( hash( 'sha256', wp_salt( 'auth' ) ), 0, 32 );
+	}
+
+	/**
+	 * 加密明文。
+	 *
+	 * @param string $plain 明文。
+	 * @return string 密文(base64),空串原样返回。
+	 */
+	public static function encrypt( $plain ) {
+		$plain = (string) $plain;
+		if ( '' === $plain ) {
+			return '';
+		}
+		if ( ! function_exists( 'openssl_encrypt' ) ) {
+			return base64_encode( $plain );
+		}
+		$iv     = openssl_random_pseudo_bytes( 16 );
+		$cipher = openssl_encrypt( $plain, 'AES-256-CBC', self::key(), 0, $iv );
+		if ( false === $cipher ) {
+			return '';
+		}
+		return base64_encode( $iv . $cipher );
+	}
+
+	/**
+	 * 解密密文。
+	 *
+	 * @param string $enc 密文(base64)。
+	 * @return string 明文,失败返回空串。
+	 */
+	public static function decrypt( $enc ) {
+		$enc = (string) $enc;
+		if ( '' === $enc ) {
+			return '';
+		}
+		$raw = base64_decode( $enc, true );
+		if ( false === $raw || strlen( $raw ) < 16 ) {
+			return '';
+		}
+		$iv     = substr( $raw, 0, 16 );
+		$cipher = substr( $raw, 16 );
+		if ( ! function_exists( 'openssl_decrypt' ) ) {
+			return '';
+		}
+		$plain = openssl_decrypt( $cipher, 'AES-256-CBC', self::key(), 0, $iv );
+		return ( false === $plain ) ? '' : $plain;
+	}
+}
diff --git a/wp_seller_plugin/includes/class-helpers.php b/wp_seller_plugin/includes/class-helpers.php
new file mode 100644
index 0000000..f89aa31
--- /dev/null
+++ b/wp_seller_plugin/includes/class-helpers.php
@@ -0,0 +1,133 @@
+<?php
+/**
+ * 通用工具:节点/主机查询、Client 构造、用户名生成、状态文案。
+ *
+ * @package MnbtWp
+ */
+
+namespace MnbtWp;
+
+use MnbtWp\Mnbt\Client;
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+/**
+ * 工具类。
+ */
+class Helpers {
+
+	/**
+	 * 按 ID 取节点配置行。
+	 *
+	 * @param int $id 节点 ID。
+	 * @return array|null
+	 */
+	public static function get_provider( $id ) {
+		global $wpdb;
+		$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}mnbt_providers WHERE id=%d", (int) $id ), ARRAY_A );
+		return $row ? $row : null;
+	}
+
+	/**
+	 * 根据节点行构造 Client(解密密钥)。
+	 *
+	 * @param array $provider 节点行。
+	 * @return Client
+	 */
+	public static function client_for_provider( array $provider ) {
+		return new Client(
+			array(
+				'api_url'     => $provider['api_url'],
+				'btdh'        => $provider['btdh'],
+				'mn_key'      => Crypto::decrypt( $provider['mn_key_enc'] ),
+				'mn_keye'     => Crypto::decrypt( $provider['mn_keye_enc'] ),
+				'mn_vs'       => (int) $provider['mn_vs'],
+				'provider_id' => (int) $provider['id'],
+			)
+		);
+	}
+
+	/**
+	 * 按 ID 取主机行。
+	 *
+	 * @param int $id 主机 ID。
+	 * @return array|null
+	 */
+	public static function get_host( $id ) {
+		global $wpdb;
+		$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}mnbt_hosts WHERE id=%d", (int) $id ), ARRAY_A );
+		return $row ? $row : null;
+	}
+
+	/**
+	 * 主机状态文案。
+	 *
+	 * @param string $status 状态码。
+	 * @return string
+	 */
+	public static function host_status_label( $status ) {
+		$labels = array(
+			'pending'     => __( '待开通', 'wp-seller-plugin' ),
+			'provisioning' => __( '开通中', 'wp-seller-plugin' ),
+			'active'      => __( '运行中', 'wp-seller-plugin' ),
+			'suspended'   => __( '已暂停', 'wp-seller-plugin' ),
+			'expired'     => __( '已到期', 'wp-seller-plugin' ),
+			'failed'      => __( '开通失败', 'wp-seller-plugin' ),
+		);
+		return isset( $labels[ $status ] ) ? $labels[ $status ] : $status;
+	}
+
+	/**
+	 * 生成主机用户名(≥6 位,避免与 MNBT 现有用户冲突)。
+	 *
+	 * @param int $user_id WP 用户 ID。
+	 * @return string
+	 */
+	public static function generate_username( $user_id ) {
+		$suffix = substr( str_replace( array( '0', '.' ), '', uniqid( '', true ) ), 0, 4 );
+		return 'wp' . (int) $user_id . $suffix;
+	}
+
+	/**
+	 * 主机控制台地址(MNBT 用户登录页)。
+	 *
+	 * @param array $provider 节点行。
+	 * @return string
+	 */
+	public static function control_url( array $provider ) {
+		return untrailingslashit( $provider['api_url'] ) . '/user/login.php';
+	}
+
+	/**
+	 * 从 quota_json 读取配额(含用量)。
+	 *
+	 * @param string $quota_json 主机 quota_json。
+	 * @return array ['web'=>['max','used'],'sql'=>...,'flow'=>...]
+	 */
+	public static function parse_quota( $quota_json ) {
+		$data = json_decode( (string) $quota_json, true );
+		if ( ! is_array( $data ) ) {
+			$data = array();
+		}
+		return array(
+			'web'  => isset( $data['web'] ) && is_array( $data['web'] ) ? $data['web'] : array( 'max' => 0, 'used' => 0 ),
+			'sql'  => isset( $data['sql'] ) && is_array( $data['sql'] ) ? $data['sql'] : array( 'max' => 0, 'used' => 0 ),
+			'flow' => isset( $data['flow'] ) && is_array( $data['flow'] ) ? $data['flow'] : array( 'max' => 0, 'used' => 0 ),
+		);
+	}
+
+	/**
+	 * 发送邮件(包装 wp_mail,统一发件人)。
+	 *
+	 * @param string $to      收件人。
+	 * @param string $subject 主题。
+	 * @param string $body    HTML 正文。
+	 * @return bool
+	 */
+	public static function send_mail( $to, $subject, $body ) {
+		$headers = array( 'Content-Type: text/html; charset=UTF-8' );
+		return wp_mail( $to, $subject, $body, $headers );
+	}
+}
diff --git a/wp_seller_plugin/includes/class-logger.php b/wp_seller_plugin/includes/class-logger.php
new file mode 100644
index 0000000..1110a73
--- /dev/null
+++ b/wp_seller_plugin/includes/class-logger.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * 日志:API 调用日志(脱敏写入 mnbt_api_log 表)。
+ *
+ * @package MnbtWp
+ */
+
+namespace MnbtWp;
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+/**
+ * 日志器。
+ */
+class Logger {
+
+	/**
+	 * 记录一次 API 调用。
+	 *
+	 * @param int    $provider_id 节点配置 ID。
+	 * @param string $action      MNBT gn(cfif/kt/...)。
+	 * @param array  $params      请求参数(写入前脱敏)。
+	 * @param int    $code        返回 code(200 成功 / 100 业务失败 / 0 网络错误)。
+	 * @param string $msg         返回消息或错误信息。
+	 * @param float  $duration    耗时(秒)。
+	 */
+	public static function api( $provider_id, $action, array $params, $code, $msg, $duration ) {
+		global $wpdb;
+
+		$wpdb->insert(
+			$wpdb->prefix . 'mnbt_api_log',
+			array(
+				'provider_id' => (int) $provider_id,
+				'action'      => sanitize_key( (string) $action ),
+				'params_json' => wp_json_encode( self::mask( $params ) ),
+				'code'        => (int) $code,
+				'msg'         => mb_substr( (string) $msg, 0, 500 ),
+				'duration'    => round( (float) $duration, 3 ),
+				'created_at'  => current_time( 'mysql' ),
+			),
+			array( '%d', '%s', '%s', '%d', '%s', '%f', '%s' )
+		);
+	}
+
+	/**
+	 * 对敏感参数脱敏。
+	 *
+	 * @param array $params 原始参数。
+	 * @return array
+	 */
+	private static function mask( array $params ) {
+		$masked = $params;
+		foreach ( array( 'mn_key', 'mn_keye', 'password' ) as $k ) {
+			if ( array_key_exists( $k, $masked ) && '' !== $masked[ $k ] ) {
+				$masked[ $k ] = '***';
+			}
+		}
+		return $masked;
+	}
+}
diff --git a/wp_seller_plugin/includes/class-plugin.php b/wp_seller_plugin/includes/class-plugin.php
new file mode 100644
index 0000000..2081166
--- /dev/null
+++ b/wp_seller_plugin/includes/class-plugin.php
@@ -0,0 +1,109 @@
+<?php
+/**
+ * 插件生命周期:加载、文本域、激活/停用入口。
+ *
+ * @package MnbtWp
+ */
+
+namespace MnbtWp;
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+/**
+ * 插件主类(单例)。
+ */
+final class Plugin {
+
+	/**
+	 * 单例实例。
+	 *
+	 * @var Plugin|null
+	 */
+	private static $instance = null;
+
+	/**
+	 * 获取单例。
+	 *
+	 * @return Plugin
+	 */
+	public static function instance() {
+		if ( null === self::$instance ) {
+			self::$instance = new self();
+		}
+		return self::$instance;
+	}
+
+	/**
+	 * 构造:加载依赖并挂载钩子。
+	 */
+	private function __construct() {
+		$this->includes();
+		$this->hooks();
+	}
+
+	/**
+	 * 加载插件类文件(按依赖顺序)。
+	 */
+	private function includes() {
+		require_once MNBTWP_DIR . 'includes/class-activator.php';
+		require_once MNBTWP_DIR . 'includes/class-crypto.php';
+		require_once MNBTWP_DIR . 'includes/class-logger.php';
+		require_once MNBTWP_DIR . 'includes/class-helpers.php';
+		require_once MNBTWP_DIR . 'includes/mnbt/class-exception.php';
+		require_once MNBTWP_DIR . 'includes/mnbt/interface-provider.php';
+		require_once MNBTWP_DIR . 'includes/mnbt/class-client.php';
+		require_once MNBTWP_DIR . 'includes/services/class-provision.php';
+		require_once MNBTWP_DIR . 'includes/services/class-billing.php';
+		require_once MNBTWP_DIR . 'includes/services/class-cron.php';
+		require_once MNBTWP_DIR . 'includes/woocommerce/class-woo.php';
+		require_once MNBTWP_DIR . 'includes/front/class-front.php';
+
+		if ( is_admin() ) {
+			require_once MNBTWP_DIR . 'includes/admin/class-admin.php';
+		}
+	}
+
+	/**
+	 * 挂载全局钩子。
+	 */
+	private function hooks() {
+		add_action( 'init', array( $this, 'load_textdomain' ) );
+
+		\MnbtWp\Services\Cron::init();
+
+		if ( class_exists( 'WooCommerce' ) ) {
+			new \MnbtWp\Woo\Woo();
+		}
+
+		if ( ! is_admin() ) {
+			new Front();
+		} else {
+			new Admin();
+		}
+	}
+
+	/**
+	 * 加载语言包。
+	 */
+	public function load_textdomain() {
+		load_plugin_textdomain( 'wp-seller-plugin', false, dirname( plugin_basename( MNBTWP_FILE ) ) . '/languages' );
+	}
+
+	/**
+	 * 激活钩子。
+	 */
+	public static function activate() {
+		require_once MNBTWP_DIR . 'includes/class-activator.php';
+		Activator::activate();
+	}
+
+	/**
+	 * 停用钩子。
+	 */
+	public static function deactivate() {
+		require_once MNBTWP_DIR . 'includes/class-activator.php';
+		Activator::deactivate();
+	}
+}
diff --git a/wp_seller_plugin/includes/front/class-front.php b/wp_seller_plugin/includes/front/class-front.php
new file mode 100644
index 0000000..409e89a
--- /dev/null
+++ b/wp_seller_plugin/includes/front/class-front.php
@@ -0,0 +1,155 @@
+<?php
+/**
+ * 前台:我的主机 Shortcode + 前端 AJAX。
+ *
+ * @package MnbtWp
+ */
+
+namespace MnbtWp;
+
+use MnbtWp\Services\Provision;
+use MnbtWp\Woo\Woo;
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+/**
+ * 前台控制器。
+ */
+class Front {
+
+	/**
+	 * 构造:注册 Shortcode 与 AJAX。
+	 */
+	public function __construct() {
+		add_shortcode( 'mnbt_my_hosts', array( $this, 'shortcode_my_hosts' ) );
+		add_action( 'wp_ajax_mnbtwp_host_action', array( $this, 'ajax_host_action' ) );
+		add_action( 'wp_ajax_nopriv_mnbtwp_host_action', array( $this, 'ajax_denied' ) );
+		add_action( 'wp_ajax_mnbtwp_create_renew', array( $this, 'ajax_create_renew' ) );
+		add_action( 'wp_ajax_nopriv_mnbtwp_create_renew', array( $this, 'ajax_denied' ) );
+	}
+
+	/**
+	 * 未登录 AJAX 拒绝。
+	 */
+	public function ajax_denied() {
+		wp_send_json_error( __( '请先登录', 'wp-seller-plugin' ) );
+	}
+
+	/**
+	 * [mnbt_my_hosts] Shortcode:列出当前用户主机。
+	 *
+	 * @param array $atts Shortcode 属性。
+	 * @return string
+	 */
+	public function shortcode_my_hosts( $atts ) {
+		if ( ! is_user_logged_in() ) {
+			return '<p>' . esc_html__( '请先登录后查看您的主机。', 'wp-seller-plugin' ) . '</p>';
+		}
+
+		global $wpdb;
+		$user_id = get_current_user_id();
+		$hosts   = $wpdb->get_results(
+			$wpdb->prepare( "SELECT * FROM {$wpdb->prefix}mnbt_hosts WHERE user_id=%d ORDER BY id DESC", $user_id ),
+			ARRAY_A
+		);
+
+		$provider_map = array();
+		foreach ( $hosts as $h ) {
+			if ( ! isset( $provider_map[ $h['provider_id'] ] ) ) {
+				$provider_map[ $h['provider_id'] ] = Helpers::get_provider( $h['provider_id'] );
+			}
+		}
+
+		ob_start();
+		include MNBTWP_DIR . 'includes/front/views/my-hosts.php';
+		return ob_get_clean();
+	}
+
+	/**
+	 * 主机操作:start / stop / change_pass / delete。
+	 */
+	public function ajax_host_action() {
+		check_ajax_referer( 'mnbtwp_front', 'nonce' );
+		if ( ! is_user_logged_in() ) {
+			wp_send_json_error( __( '请先登录', 'wp-seller-plugin' ) );
+		}
+
+		$action = sanitize_key( $_POST['host_action'] ?? '' );
+		$host_id = (int) ( $_POST['host_id'] ?? 0 );
+		$host    = Helpers::get_host( $host_id );
+
+		if ( ! $host || (int) $host['user_id'] !== get_current_user_id() ) {
+			wp_send_json_error( __( '无权操作', 'wp-seller-plugin' ) );
+		}
+		$provider = Helpers::get_provider( $host['provider_id'] );
+		if ( ! $provider ) {
+			wp_send_json_error( __( '节点不可用', 'wp-seller-plugin' ) );
+		}
+
+		try {
+			$client = Helpers::client_for_provider( $provider );
+			switch ( $action ) {
+				case 'start':
+					$client->startSite( $host['username'] );
+					break;
+				case 'stop':
+					$client->stopSite( $host['username'] );
+					break;
+				case 'change_pass':
+					$password = sanitize_text_field( $_POST['password'] ?? '' );
+					if ( mb_strlen( $password ) < 6 ) {
+						wp_send_json_error( __( '密码不能少于 6 位', 'wp-seller-plugin' ) );
+					}
+					$client->changePassword( $host['username'], $password );
+					global $wpdb;
+					$wpdb->update(
+						$wpdb->prefix . 'mnbt_hosts',
+						array( 'password_enc' => Crypto::encrypt( $password ), 'updated_at' => current_time( 'mysql' ) ),
+						array( 'id' => (int) $host['id'] )
+					);
+					break;
+				case 'delete':
+					$client->deleteHost( $host['username'] );
+					global $wpdb;
+					$wpdb->delete( $wpdb->prefix . 'mnbt_hosts', array( 'id' => (int) $host['id'] ), array( '%d' ) );
+					break;
+				default:
+					wp_send_json_error( __( '未知操作', 'wp-seller-plugin' ) );
+			}
+			wp_send_json_success( __( '操作成功', 'wp-seller-plugin' ) );
+		} catch ( \Exception $e ) {
+			wp_send_json_error( $e->getMessage() );
+		}
+	}
+
+	/**
+	 * 创建续费订单并返回结账支付地址。
+	 */
+	public function ajax_create_renew() {
+		check_ajax_referer( 'mnbtwp_front', 'nonce' );
+		if ( ! is_user_logged_in() ) {
+			wp_send_json_error( __( '请先登录', 'wp-seller-plugin' ) );
+		}
+		$host_id = (int) ( $_POST['host_id'] ?? 0 );
+		$months  = max( 1, (int) ( $_POST['months'] ?? 1 ) );
+		$host    = Helpers::get_host( $host_id );
+		if ( ! $host || (int) $host['user_id'] !== get_current_user_id() ) {
+			wp_send_json_error( __( '无权操作', 'wp-seller-plugin' ) );
+		}
+		if ( ! class_exists( 'WooCommerce' ) ) {
+			wp_send_json_error( __( '未启用 WooCommerce', 'wp-seller-plugin' ) );
+		}
+		try {
+			$order = Woo::create_renew_order( $host_id, $months );
+			wp_send_json_success(
+				array(
+					'checkout_url' => $order->get_checkout_payment_url(),
+				)
+			);
+		} catch ( \Exception $e ) {
+			wp_send_json_error( $e->getMessage() );
+		}
+	}
+}
diff --git a/wp_seller_plugin/includes/front/views/my-hosts.php b/wp_seller_plugin/includes/front/views/my-hosts.php
new file mode 100644
index 0000000..3cfe9b1
--- /dev/null
+++ b/wp_seller_plugin/includes/front/views/my-hosts.php
@@ -0,0 +1,144 @@
+<?php
+/**
+ * 我的主机视图([mnbt_my_hosts])。
+ *
+ * @package MnbtWp
+ */
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+$mnbtwp_nonce = wp_create_nonce( 'mnbtwp_front' );
+?>
+<div class="mnbtwp-hosts">
+	<?php if ( empty( $hosts ) ) : ?>
+		<p><?php esc_html_e( '您还没有购买任何主机。', 'wp-seller-plugin' ); ?></p>
+	<?php else : ?>
+		<?php foreach ( $hosts as $mnbtwp_host ) : ?>
+			<?php
+			$mnbtwp_provider = isset( $provider_map[ $mnbtwp_host['provider_id'] ] ) ? $provider_map[ $mnbtwp_host['provider_id'] ] : null;
+			$mnbtwp_quota    = Helpers::parse_quota( $mnbtwp_host['quota_json'] );
+			$mnbtwp_status   = $mnbtwp_host['status'];
+			$mnbtwp_label    = Helpers::host_status_label( $mnbtwp_status );
+			$mnbtwp_console  = $mnbtwp_provider ? Helpers::control_url( $mnbtwp_provider ) : '';
+			?>
+			<div class="mnbtwp-host-card" style="border:1px solid #ddd;border-radius:8px;padding:16px;margin-bottom:16px;background:#fff;">
+				<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px;">
+					<div>
+						<strong style="font-size:16px;"><?php echo esc_html( $mnbtwp_host['site_domain'] ? $mnbtwp_host['site_domain'] : $mnbtwp_host['username'] ); ?></strong>
+						<span style="margin-left:8px;padding:2px 8px;border-radius:10px;font-size:12px;
+							<?php echo in_array( $mnbtwp_status, array( 'active', 'provisioning' ), true ) ? 'background:#e6f7e6;color:#1a7f37;' : 'background:#fdeaea;color:#c62828;'; ?>">
+							<?php echo esc_html( $mnbtwp_label ); ?>
+						</span>
+					</div>
+					<div>
+						<?php if ( $mnbtwp_console ) : ?>
+							<a class="button" href="<?php echo esc_url( $mnbtwp_console ); ?>" target="_blank" rel="noopener">
+								<?php esc_html_e( '打开控制台', 'wp-seller-plugin' ); ?>
+							</a>
+						<?php endif; ?>
+					</div>
+				</div>
+
+				<table style="width:100%;margin-top:12px;border-collapse:collapse;font-size:13px;">
+					<tr>
+						<td style="padding:4px 0;color:#666;width:90px;"><?php esc_html_e( '主机账号', 'wp-seller-plugin' ); ?></td>
+						<td><code><?php echo esc_html( $mnbtwp_host['username'] ); ?></code></td>
+						<td style="padding:4px 0;color:#666;width:90px;"><?php esc_html_e( '到期时间', 'wp-seller-plugin' ); ?></td>
+						<td>
+							<?php
+							echo esc_html(
+								$mnbtwp_host['expire_date'] && '0000-00-00' !== $mnbtwp_host['expire_date']
+									? $mnbtwp_host['expire_date']
+									: __( '长期', 'wp-seller-plugin' )
+							);
+							?>
+						</td>
+					</tr>
+				</table>
+
+				<?php foreach ( array( 'web' => __( '网站空间', 'wp-seller-plugin' ), 'sql' => __( '数据库', 'wp-seller-plugin' ), 'flow' => __( '流量', 'wp-seller-plugin' ) ) as $mnbtwp_key => $mnbtwp_name ) : ?>
+					<?php
+					$mnbtwp_max  = (int) ( $mnbtwp_quota[ $mnbtwp_key ]['max'] ?? 0 );
+					$mnbtwp_used = (int) ( $mnbtwp_quota[ $mnbtwp_key ]['used'] ?? 0 );
+					if ( $mnbtwp_max <= 0 ) {
+						continue;
+					}
+					$mnbtwp_pct = min( 100, (int) round( $mnbtwp_used / $mnbtwp_max * 100 ) );
+					?>
+					<div style="margin-top:8px;">
+						<div style="display:flex;justify-content:space-between;font-size:12px;color:#666;">
+							<span><?php echo esc_html( $mnbtwp_name ); ?></span>
+							<span><?php echo (int) $mnbtwp_used; ?> / <?php echo (int) $mnbtwp_max; ?> MB</span>
+						</div>
+						<div style="background:#eee;border-radius:4px;height:8px;margin-top:4px;">
+							<div style="width:<?php echo (int) $mnbtwp_pct; ?>%;height:8px;border-radius:4px;background:<?php echo $mnbtwp_pct >= 90 ? '#d63638' : '#2271b1'; ?>;"></div>
+						</div>
+					</div>
+				<?php endforeach; ?>
+
+				<div style="margin-top:14px;display:flex;gap:8px;flex-wrap:wrap;">
+					<button type="button" class="button" onclick="mnbtwpAction(<?php echo (int) $mnbtwp_host['id']; ?>,'start')"><?php esc_html_e( '启动', 'wp-seller-plugin' ); ?></button>
+					<button type="button" class="button" onclick="mnbtwpAction(<?php echo (int) $mnbtwp_host['id']; ?>,'stop')"><?php esc_html_e( '停止', 'wp-seller-plugin' ); ?></button>
+					<button type="button" class="button" onclick="mnbtwpChangePass(<?php echo (int) $mnbtwp_host['id']; ?>)"><?php esc_html_e( '重置密码', 'wp-seller-plugin' ); ?></button>
+					<button type="button" class="button" onclick="mnbtwpRenew(<?php echo (int) $mnbtwp_host['id']; ?>)"><?php esc_html_e( '续费', 'wp-seller-plugin' ); ?></button>
+					<button type="button" class="button" style="color:#c62828;" onclick="mnbtwpDelete(<?php echo (int) $mnbtwp_host['id']; ?>)"><?php esc_html_e( '删除', 'wp-seller-plugin' ); ?></button>
+				</div>
+			</div>
+		<?php endforeach; ?>
+	<?php endif; ?>
+</div>
+
+<style>
+.mnbtwp-host-card .button { margin: 0; }
+</style>
+
+<script type="text/javascript">
+	var mnbtwpFrontNonce = '<?php echo esc_js( $mnbtwp_nonce ); ?>';
+
+	function mnbtwpAjax(data, done) {
+		data.nonce = mnbtwpFrontNonce;
+		jQuery.post('<?php echo esc_js( admin_url( 'admin-ajax.php' ) ); ?>', data, function (r) {
+			if (r && r.success) {
+				done(r);
+			} else {
+				alert((r && r.data) ? r.data : '<?php echo esc_js( __( '操作失败', 'wp-seller-plugin' ) ); ?>');
+			}
+		}).fail(function () {
+			alert('<?php echo esc_js( __( '网络错误', 'wp-seller-plugin' ) ); ?>');
+		});
+	}
+
+	function mnbtwpAction(hostId, action) {
+		if (action === 'delete') return;
+		mnbtwpAjax({ action: 'mnbtwp_host_action', host_action: action, host_id: hostId }, function () {
+			location.reload();
+		});
+	}
+
+	function mnbtwpChangePass(hostId) {
+		var pw = prompt('<?php echo esc_js( __( '请输入新密码(至少 6 位)', 'wp-seller-plugin' ) ); ?>');
+		if (!pw || pw.length < 6) return;
+		mnbtwpAjax({ action: 'mnbtwp_host_action', host_action: 'change_pass', host_id: hostId, password: pw }, function () {
+			alert('<?php echo esc_js( __( '密码已重置', 'wp-seller-plugin' ) ); ?>');
+		});
+	}
+
+	function mnbtwpDelete(hostId) {
+		if (!confirm('<?php echo esc_js( __( '删除后站点、数据库、FTP 将被清除且不可恢复,确定删除?', 'wp-seller-plugin' ) ); ?>')) return;
+		mnbtwpAjax({ action: 'mnbtwp_host_action', host_action: 'delete', host_id: hostId }, function () {
+			location.reload();
+		});
+	}
+
+	function mnbtwpRenew(hostId) {
+		var months = prompt('<?php echo esc_js( __( '请输入续费月数(如 1 / 3 / 6 / 12)', 'wp-seller-plugin' ) ); ?>', '1');
+		if (!months || parseInt(months, 10) < 1) return;
+		mnbtwpAjax({ action: 'mnbtwp_create_renew', host_id: hostId, months: parseInt(months, 10) }, function (r) {
+			if (r.data && r.data.checkout_url) {
+				window.location.href = r.data.checkout_url;
+			}
+		});
+	}
+</script>
diff --git a/wp_seller_plugin/includes/mnbt/class-client.php b/wp_seller_plugin/includes/mnbt/class-client.php
new file mode 100644
index 0000000..67a6644
--- /dev/null
+++ b/wp_seller_plugin/includes/mnbt/class-client.php
@@ -0,0 +1,302 @@
+<?php
+/**
+ * MNBT API 客户端(适配器实现)。
+ *
+ * 封装 MNBT `api/api.php` 的鉴权、请求、重试与错误归一化。
+ * 鉴权参数:mn_bh(节点代号)/ mn_key(系统密钥)/ mn_keye(调用密钥 md5(ktmy.qmk))/ mn_vs(版本号)。
+ *
+ * @package MnbtWp
+ */
+
+namespace MnbtWp\Mnbt;
+
+use MnbtWp\Logger;
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+/**
+ * MNBT API 客户端。
+ */
+class Client implements HostProviderInterface {
+
+	/**
+	 * 节点配置。
+	 *
+	 * @var array
+	 */
+	private $config = array();
+
+	/**
+	 * 构造。
+	 *
+	 * @param array $config 节点配置(api_url/btdh/mn_key/mn_keye/mn_vs/provider_id)。
+	 */
+	public function __construct( array $config ) {
+		$this->config = wp_parse_args(
+			$config,
+			array(
+				'api_url'     => '',
+				'btdh'        => '',
+				'mn_key'      => '',
+				'mn_keye'     => '',
+				'mn_vs'       => 15,
+				'provider_id' => 0,
+			)
+		);
+	}
+
+	/**
+	 * 拼接 API 地址。
+	 *
+	 * @return string
+	 */
+	private function url() {
+		return untrailingslashit( $this->config['api_url'] ) . '/api/api.php';
+	}
+
+	/**
+	 * 连接测试。
+	 *
+	 * @return array
+	 */
+	public function testConnection() {
+		return $this->request( 'cfif', array( 'username' => 'mnbtwp_test' ) );
+	}
+
+	/**
+	 * 开通主机。
+	 *
+	 * @param array $params 开通参数。
+	 * @return array
+	 */
+	public function createHost( array $params ) {
+		$params = wp_parse_args(
+			$params,
+			array(
+				'password' => '',
+				'sizemax'  => 0,
+				'dqtime'   => 0,
+				'webdx'    => 0,
+				'sqldx'    => 0,
+				'ymbds'    => 0,
+			)
+		);
+		return $this->request( 'kt', $params );
+	}
+
+	/**
+	 * 暂停主机。
+	 *
+	 * @param string $username 主机用户名。
+	 * @return array
+	 */
+	public function suspendHost( $username ) {
+		return $this->request( 'zt', array( 'username' => $username ) );
+	}
+
+	/**
+	 * 恢复主机。
+	 *
+	 * @param string $username 主机用户名。
+	 * @return array
+	 */
+	public function resumeHost( $username ) {
+		return $this->request( 'jc', array( 'username' => $username ) );
+	}
+
+	/**
+	 * 删除主机。
+	 *
+	 * @param string $username 主机用户名。
+	 * @return array
+	 */
+	public function deleteHost( $username ) {
+		return $this->request( 'tz', array( 'username' => $username ) );
+	}
+
+	/**
+	 * 续费。
+	 *
+	 * @param string $username    主机用户名。
+	 * @param string $expire_date 到期日期 Y-m-d。
+	 * @return array
+	 */
+	public function renewHost( $username, $expire_date ) {
+		return $this->request(
+			'xf',
+			array(
+				'username' => $username,
+				'setdate'  => $expire_date,
+			)
+		);
+	}
+
+	/**
+	 * 重置密码。
+	 *
+	 * @param string $username 主机用户名。
+	 * @param string $password 新密码。
+	 * @return array
+	 */
+	public function changePassword( $username, $password ) {
+		return $this->request(
+			'czmm',
+			array(
+				'username' => $username,
+				'password' => $password,
+			)
+		);
+	}
+
+	/**
+	 * 升降级。
+	 *
+	 * @param string $username 主机用户名。
+	 * @param array  $quota    配额(websize/sqlsize/ll,MB)。
+	 * @return array
+	 */
+	public function changePackage( $username, array $quota ) {
+		$quota = wp_parse_args(
+			$quota,
+			array(
+				'websize' => 0,
+				'sqlsize' => 0,
+				'll'      => 0,
+			)
+		);
+		return $this->request(
+			'zjmode',
+			array(
+				'username' => $username,
+				'websize'  => $quota['websize'],
+				'sqlsize'  => $quota['sqlsize'],
+				'll'       => $quota['ll'],
+			)
+		);
+	}
+
+	/**
+	 * 启动站点。
+	 *
+	 * @param string $username 主机用户名。
+	 * @return array
+	 */
+	public function startSite( $username ) {
+		return $this->request( 'start', array( 'username' => $username ) );
+	}
+
+	/**
+	 * 停止站点。
+	 *
+	 * @param string $username 主机用户名。
+	 * @return array
+	 */
+	public function stopSite( $username ) {
+		return $this->request( 'stop', array( 'username' => $username ) );
+	}
+
+	/**
+	 * 状态与配额查询。
+	 *
+	 * @param string $username 主机用户名。
+	 * @return array
+	 */
+	public function getHostStatus( $username ) {
+		return $this->request( 'ztcx', array( 'username' => $username ) );
+	}
+
+	/**
+	 * 发起 MNBT API 请求。
+	 *
+	 * 网络层失败(超时/连接失败)自动重试 2 次(1s/2s 退避);
+	 * 业务失败(code!=200)不重试,抛出异常。
+	 *
+	 * @param string $gn     API 动作。
+	 * @param array  $params 业务参数。
+	 * @return array ['ok'=>true, 'msg'=>string, 'data'=>array]
+	 *
+	 * @throws Exception 请求失败时抛出。
+	 */
+	private function request( $gn, array $params ) {
+		$body = array_merge(
+			array(
+				'mn_bh'  => $this->config['btdh'],
+				'mn_key' => $this->config['mn_key'],
+				'mn_keye' => $this->config['mn_keye'],
+				'mn_vs'  => (int) $this->config['mn_vs'],
+			),
+			$params
+		);
+		if ( empty( $body['username'] ) ) {
+			$body['username'] = 'mnbtwp_test';
+		}
+
+		$start       = microtime( true );
+		$response    = null;
+		$last_error  = '';
+
+		// 网络层失败重试(最多 3 次尝试)。
+		for ( $attempt = 0; $attempt < 3; $attempt++ ) {
+			if ( $attempt > 0 ) {
+				usleep( $attempt * 1000000 ); // 1s / 2s 退避。
+			}
+			$response = wp_remote_post(
+				$this->url(),
+				array(
+					'timeout'   => 15,
+					'body'      => $body,
+					'sslverify' => true,
+				)
+			);
+			if ( ! is_wp_error( $response ) ) {
+				break;
+			}
+			$last_error = $response->get_error_message();
+		}
+
+		$duration = microtime( true ) - $start;
+
+		if ( is_wp_error( $response ) ) {
+			Logger::api( (int) $this->config['provider_id'], $gn, $body, 0, '网络错误:' . $last_error, $duration );
+			throw ( new Exception( '网络错误:' . $last_error ) )->set_context(
+				array( 'gn' => $gn, 'provider_id' => $this->config['provider_id'] )
+			);
+		}
+
+		$code = wp_remote_retrieve_response_code( $response );
+		if ( 200 !== $code ) {
+			Logger::api( (int) $this->config['provider_id'], $gn, $body, 0, 'HTTP ' . $code, $duration );
+			throw ( new Exception( 'MNBT 接口返回 HTTP ' . $code ) )->set_context(
+				array( 'gn' => $gn, 'provider_id' => $this->config['provider_id'], 'http_code' => $code )
+			);
+		}
+
+		$raw  = wp_remote_retrieve_body( $response );
+		$data = json_decode( $raw, true );
+		if ( ! is_array( $data ) ) {
+			Logger::api( (int) $this->config['provider_id'], $gn, $body, 0, '响应解析失败', $duration );
+			throw ( new Exception( 'MNBT 响应解析失败' ) )->set_context(
+				array( 'gn' => $gn, 'provider_id' => $this->config['provider_id'] )
+			);
+		}
+
+		$ok  = isset( $data['code'] ) && 200 === (int) $data['code'];
+		$msg = isset( $data['msg'] ) ? (string) $data['msg'] : 'ok';
+
+		Logger::api( (int) $this->config['provider_id'], $gn, $body, $ok ? 200 : 100, $msg, $duration );
+
+		if ( ! $ok ) {
+			throw ( new Exception( $msg ) )->set_context(
+				array( 'gn' => $gn, 'provider_id' => $this->config['provider_id'], 'code' => isset( $data['code'] ) ? $data['code'] : null )
+			);
+		}
+
+		return array(
+			'ok'   => true,
+			'msg'  => $msg,
+			'data' => isset( $data['data'] ) && is_array( $data['data'] ) ? $data['data'] : array(),
+		);
+	}
+}
diff --git a/wp_seller_plugin/includes/mnbt/class-exception.php b/wp_seller_plugin/includes/mnbt/class-exception.php
new file mode 100644
index 0000000..ace7890
--- /dev/null
+++ b/wp_seller_plugin/includes/mnbt/class-exception.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * MNBT 对接异常:错误归一化后统一抛出。
+ *
+ * @package MnbtWp
+ */
+
+namespace MnbtWp\Mnbt;
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+/**
+ * MNBT API 异常。
+ */
+class Exception extends \Exception {
+
+	/**
+	 * 原始响应数据(脱敏后),便于上层记录上下文。
+	 *
+	 * @var array
+	 */
+	protected $context = array();
+
+	/**
+	 * 设置上下文。
+	 *
+	 * @param array $context 上下文。
+	 * @return $this
+	 */
+	public function set_context( array $context ) {
+		$this->context = $context;
+		return $this;
+	}
+
+	/**
+	 * 获取上下文。
+	 *
+	 * @return array
+	 */
+	public function get_context() {
+		return $this->context;
+	}
+}
diff --git a/wp_seller_plugin/includes/mnbt/interface-provider.php b/wp_seller_plugin/includes/mnbt/interface-provider.php
new file mode 100644
index 0000000..8e15c30
--- /dev/null
+++ b/wp_seller_plugin/includes/mnbt/interface-provider.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * 主机服务商适配器接口。
+ *
+ * 业务层只依赖本接口,便于未来扩展其他面板/系统
+ * (如直接对接宝塔、其他财务系统),不修改业务代码。
+ *
+ * @package MnbtWp
+ */
+
+namespace MnbtWp\Mnbt;
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+/**
+ * 主机服务商接口。
+ */
+interface HostProviderInterface {
+
+	/**
+	 * 连接测试。
+	 *
+	 * @return array ['ok'=>bool, 'msg'=>string, 'data'=>array]
+	 */
+	public function testConnection();
+
+	/**
+	 * 开通主机(建站 + FTP + 数据库)。
+	 *
+	 * @param array $params 开通参数(password/sizemax/dqtime/webdx/sqldx/ymbds 等)。
+	 * @return array
+	 */
+	public function createHost( array $params );
+
+	/**
+	 * 暂停主机。
+	 *
+	 * @param string $username 主机用户名。
+	 * @return array
+	 */
+	public function suspendHost( $username );
+
+	/**
+	 * 恢复主机。
+	 *
+	 * @param string $username 主机用户名。
+	 * @return array
+	 */
+	public function resumeHost( $username );
+
+	/**
+	 * 删除主机。
+	 *
+	 * @param string $username 主机用户名。
+	 * @return array
+	 */
+	public function deleteHost( $username );
+
+	/**
+	 * 续费(更新到期时间)。
+	 *
+	 * @param string $username   主机用户名。
+	 * @param string $expire_date 到期日期 Y-m-d。
+	 * @return array
+	 */
+	public function renewHost( $username, $expire_date );
+
+	/**
+	 * 重置密码(FTP + 控制面板)。
+	 *
+	 * @param string $username 主机用户名。
+	 * @param string $password 新密码。
+	 * @return array
+	 */
+	public function changePassword( $username, $password );
+
+	/**
+	 * 升降级(更新配额)。
+	 *
+	 * @param string $username 主机用户名。
+	 * @param array  $quota    配额 ['websize'=>, 'sqlsize'=>, 'll'=>](MB)。
+	 * @return array
+	 */
+	public function changePackage( $username, array $quota );
+
+	/**
+	 * 启动站点。
+	 *
+	 * @param string $username 主机用户名。
+	 * @return array
+	 */
+	public function startSite( $username );
+
+	/**
+	 * 停止站点。
+	 *
+	 * @param string $username 主机用户名。
+	 * @return array
+	 */
+	public function stopSite( $username );
+
+	/**
+	 * 查询主机状态与配额用量。
+	 *
+	 * @param string $username 主机用户名。
+	 * @return array
+	 */
+	public function getHostStatus( $username );
+}
diff --git a/wp_seller_plugin/includes/services/class-billing.php b/wp_seller_plugin/includes/services/class-billing.php
new file mode 100644
index 0000000..9de4e54
--- /dev/null
+++ b/wp_seller_plugin/includes/services/class-billing.php
@@ -0,0 +1,176 @@
+<?php
+/**
+ * 计费:续费处理与到期维护(暂停/提醒)。
+ *
+ * @package MnbtWp
+ */
+
+namespace MnbtWp\Services;
+
+use MnbtWp\Helpers;
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+/**
+ * 计费服务。
+ */
+class Billing {
+
+	/**
+	 * 续费订单支付完成后处理:xf 更新到期时间,必要时恢复主机。
+	 *
+	 * @param int $order_id 订单 ID(含 _mnbtwp_renew_host_id / _mnbtwp_renew_months meta)。
+	 * @return array
+	 *
+	 * @throws \Exception 续费失败。
+	 */
+	public static function renew_order( $order_id ) {
+		$host_id = (int) get_post_meta( $order_id, '_mnbtwp_renew_host_id', true );
+		if ( ! $host_id ) {
+			throw new \Exception( __( '续费订单缺少主机信息', 'wp-seller-plugin' ) );
+		}
+		if ( get_post_meta( $order_id, '_mnbtwp_renewed', true ) ) {
+			return array( 'ok' => true, 'msg' => __( '该订单已续费', 'wp-seller-plugin' ) );
+		}
+
+		$host = Helpers::get_host( $host_id );
+		if ( ! $host ) {
+			throw new \Exception( __( '主机不存在', 'wp-seller-plugin' ) );
+		}
+
+		$months  = max( 1, (int) get_post_meta( $order_id, '_mnbtwp_renew_months', true ) ?: 1 );
+		$base    = ( $host['expire_date'] && '0000-00-00' !== $host['expire_date'] && strtotime( $host['expire_date'] ) > time() )
+			? $host['expire_date']
+			: date( 'Y-m-d' );
+		$new_date = date( 'Y-m-d', strtotime( "+{$months} months", strtotime( $base ) ) );
+
+		$provider = Helpers::get_provider( $host['provider_id'] );
+		if ( ! $provider ) {
+			throw new \Exception( __( '节点配置不存在', 'wp-seller-plugin' ) );
+		}
+
+		$client = Helpers::client_for_provider( $provider );
+		$client->renewHost( $host['username'], $new_date );
+
+		global $wpdb;
+		$wpdb->update(
+			$wpdb->prefix . 'mnbt_hosts',
+			array(
+				'expire_date' => $new_date,
+				'updated_at'  => current_time( 'mysql' ),
+			),
+			array( 'id' => (int) $host['id'] )
+		);
+
+		// 已到期/暂停的主机续费后恢复。
+		if ( in_array( $host['status'], array( 'expired', 'suspended' ), true ) ) {
+			try {
+				$client->resumeHost( $host['username'] );
+				$wpdb->update(
+					$wpdb->prefix . 'mnbt_hosts',
+					array( 'status' => 'active', 'updated_at' => current_time( 'mysql' ) ),
+					array( 'id' => (int) $host['id'] )
+				);
+			} catch ( \Exception $e ) { // 恢复失败不影响续费结果。
+			}
+		}
+
+		update_post_meta( $order_id, '_mnbtwp_renewed', '1' );
+		self::notify_renew( $host, $new_date );
+
+		return array( 'ok' => true, 'msg' => __( '续费成功', 'wp-seller-plugin' ), 'expire_date' => $new_date );
+	}
+
+	/**
+	 * 每日到期维护:过期自动暂停 + 到期提醒。
+	 */
+	public static function expire_maintenance() {
+		global $wpdb;
+
+		$settings   = get_option( 'mnbtwp_settings', array() );
+		$grace_days = max( 0, (int) ( $settings['grace_days'] ?? 0 ) );
+		$remind_days = max( 0, (int) ( $settings['expire_days'] ?? 3 ) );
+
+		$hosts = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}mnbt_hosts WHERE status='active'", ARRAY_A );
+		$now   = time();
+
+		foreach ( $hosts as $host ) {
+			if ( empty( $host['expire_date'] ) || '0000-00-00' === $host['expire_date'] ) {
+				continue;
+			}
+			$expire_ts = strtotime( $host['expire_date'] . ' 23:59:59' );
+
+			// 已过期且超宽限期 → 暂停。
+			if ( $expire_ts + $grace_days * DAY_IN_SECONDS < $now ) {
+				$provider = Helpers::get_provider( $host['provider_id'] );
+				if ( $provider ) {
+					try {
+						Helpers::client_for_provider( $provider )->suspendHost( $host['username'] );
+						$wpdb->update(
+							$wpdb->prefix . 'mnbt_hosts',
+							array( 'status' => 'expired', 'updated_at' => current_time( 'mysql' ) ),
+							array( 'id' => (int) $host['id'] )
+						);
+					} catch ( \Exception $e ) {
+						// 暂停失败留待下次维护重试。
+					}
+				}
+				continue;
+			}
+
+			// 到期前提醒(仅一次/每周期一次)。
+			if ( $remind_days > 0 ) {
+				$remind_ts = $expire_ts - $remind_days * DAY_IN_SECONDS;
+				if ( $now >= $remind_ts && $now < $expire_ts ) {
+					$last = $host['last_remind'] ? strtotime( $host['last_remind'] ) : 0;
+					if ( $last < $remind_ts ) {
+						self::send_expire_remind( $host );
+						$wpdb->update(
+							$wpdb->prefix . 'mnbt_hosts',
+							array( 'last_remind' => current_time( 'mysql' ) ),
+							array( 'id' => (int) $host['id'] )
+						);
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * 发送到期提醒邮件。
+	 *
+	 * @param array $host 主机行。
+	 */
+	private static function send_expire_remind( array $host ) {
+		$user = get_userdata( (int) $host['user_id'] );
+		if ( ! $user || empty( $user->user_email ) ) {
+			return;
+		}
+		$subject = sprintf( __( '【%s】主机即将到期', 'wp-seller-plugin' ), get_bloginfo( 'name' ) );
+		$body    = '<p>' . esc_html__( '您的主机即将到期,请及时续费以免暂停服务:', 'wp-seller-plugin' ) . '</p>'
+			. '<p>主机账号:<code>' . esc_html( $host['username'] ) . '</code></p>'
+			. '<p>' . esc_html__( '到期时间:', 'wp-seller-plugin' ) . esc_html( $host['expire_date'] ) . '</p>'
+			. '<p>' . esc_html__( '请在「我的主机」中点击续费完成支付。', 'wp-seller-plugin' ) . '</p>';
+		Helpers::send_mail( $user->user_email, $subject, $body );
+	}
+
+	/**
+	 * 发送续费成功邮件。
+	 *
+	 * @param array  $host     主机行。
+	 * @param string $new_date 新到期日期。
+	 */
+	private static function notify_renew( array $host, $new_date ) {
+		$user = get_userdata( (int) $host['user_id'] );
+		if ( ! $user || empty( $user->user_email ) ) {
+			return;
+		}
+		$subject = sprintf( __( '【%s】主机续费成功', 'wp-seller-plugin' ), get_bloginfo( 'name' ) );
+		$body    = '<p>' . esc_html__( '您的主机已成功续费:', 'wp-seller-plugin' ) . '</p>'
+			. '<p>主机账号:<code>' . esc_html( $host['username'] ) . '</code></p>'
+			. '<p>' . esc_html__( '新到期时间:', 'wp-seller-plugin' ) . esc_html( $new_date ) . '</p>';
+		Helpers::send_mail( $user->user_email, $subject, $body );
+	}
+}
diff --git a/wp_seller_plugin/includes/services/class-cron.php b/wp_seller_plugin/includes/services/class-cron.php
new file mode 100644
index 0000000..2773a37
--- /dev/null
+++ b/wp_seller_plugin/includes/services/class-cron.php
@@ -0,0 +1,90 @@
+<?php
+/**
+ * 定时任务:每日到期维护 + 状态/用量同步。
+ *
+ * @package MnbtWp
+ */
+
+namespace MnbtWp\Services;
+
+use MnbtWp\Helpers;
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+/**
+ * 定时任务调度。
+ */
+class Cron {
+
+	/**
+	 * 注册定时任务回调。
+	 */
+	public static function init() {
+		add_action( 'mnbtwp_daily_maintenance', array( __CLASS__, 'daily' ) );
+		add_action( 'mnbtwp_sync_hosts', array( __CLASS__, 'sync' ) );
+	}
+
+	/**
+	 * 每日到期维护。
+	 */
+	public static function daily() {
+		Billing::expire_maintenance();
+	}
+
+	/**
+	 * 同步主机状态与用量(ztcx),带锁防并发。
+	 */
+	public static function sync() {
+		if ( get_transient( 'mnbtwp_sync_lock' ) ) {
+			return;
+		}
+		set_transient( 'mnbtwp_sync_lock', 1, 10 * MINUTE_IN_SECONDS );
+
+		global $wpdb;
+		$hosts = $wpdb->get_results(
+			"SELECT * FROM {$wpdb->prefix}mnbt_hosts WHERE status IN ('active','suspended','expired')",
+			ARRAY_A
+		);
+
+		foreach ( $hosts as $host ) {
+			$provider = Helpers::get_provider( $host['provider_id'] );
+			if ( ! $provider ) {
+				continue;
+			}
+			try {
+				$result = Helpers::client_for_provider( $provider )->getHostStatus( $host['username'] );
+				$data   = isset( $result['data'] ) && is_array( $result['data'] ) ? $result['data'] : array();
+
+				$quota = Helpers::parse_quota( $host['quota_json'] );
+				if ( isset( $data['quota'] ) && is_array( $data['quota'] ) ) {
+					$q = $data['quota'];
+					$quota['web']['max']  = (int) ( $q['web_size_max'] ?? $quota['web']['max'] );
+					$quota['web']['used'] = (int) ( $q['web_size_used'] ?? $quota['web']['used'] );
+					$quota['sql']['max']  = (int) ( $q['sql_size_max'] ?? $quota['sql']['max'] );
+					$quota['sql']['used'] = (int) ( $q['sql_size_used'] ?? $quota['sql']['used'] );
+					$quota['flow']['max'] = (int) ( $q['flow_max'] ?? $quota['flow']['max'] );
+					$quota['flow']['used'] = (int) ( $q['flow_used'] ?? $quota['flow']['used'] );
+				}
+
+				$site_domain = isset( $data['user']['domain'] ) ? (string) $data['user']['domain'] : '';
+
+				$wpdb->update(
+					$wpdb->prefix . 'mnbt_hosts',
+					array(
+						'site_domain' => $site_domain ? $site_domain : $host['site_domain'],
+						'quota_json'  => wp_json_encode( $quota ),
+						'updated_at'  => current_time( 'mysql' ),
+					),
+					array( 'id' => (int) $host['id'] )
+				);
+			} catch ( \Exception $e ) {
+				// 单台同步失败不影响其他主机。
+				continue;
+			}
+		}
+
+		delete_transient( 'mnbtwp_sync_lock' );
+	}
+}
diff --git a/wp_seller_plugin/includes/services/class-provision.php b/wp_seller_plugin/includes/services/class-provision.php
new file mode 100644
index 0000000..3f30c64
--- /dev/null
+++ b/wp_seller_plugin/includes/services/class-provision.php
@@ -0,0 +1,160 @@
+<?php
+/**
+ * 开通编排:订单支付成功后创建主机(幂等 + 失败记录可重试)。
+ *
+ * @package MnbtWp
+ */
+
+namespace MnbtWp\Services;
+
+use MnbtWp\Crypto;
+use MnbtWp\Helpers;
+use MnbtWp\Mnbt\Exception as MnbtException;
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+/**
+ * 开通服务。
+ */
+class Provision {
+
+	/**
+	 * 根据订单开通主机。
+	 *
+	 * 幂等:订单已开通(_mnbtwp_provisioned)则直接返回。
+	 * 失败:抛出异常并记录 _mnbtwp_provision_error,可后台重试。
+	 *
+	 * @param int $order_id 订单 ID。
+	 * @return array ['ok'=>bool, 'msg'=>string, 'host_id'=>int]
+	 *
+	 * @throws \Exception 开通失败。
+	 */
+	public static function provision_order( $order_id ) {
+		$order = wc_get_order( $order_id );
+		if ( ! $order ) {
+			throw new \Exception( __( '订单不存在', 'wp-seller-plugin' ) );
+		}
+
+		if ( get_post_meta( $order_id, '_mnbtwp_provisioned', true ) ) {
+			return array(
+				'ok'      => true,
+				'msg'     => __( '该订单已开通', 'wp-seller-plugin' ),
+				'host_id' => (int) get_post_meta( $order_id, '_mnbtwp_host_id', true ),
+			);
+		}
+
+		// 定位主机商品。
+		$product_id = 0;
+		foreach ( $order->get_items() as $item ) {
+			$pid = (int) $item->get_product_id();
+			if ( 'yes' === get_post_meta( $pid, '_mnbtwp_enabled', true ) ) {
+				$product_id = $pid;
+				break;
+			}
+		}
+		if ( ! $product_id ) {
+			throw new \Exception( __( '订单中无主机商品', 'wp-seller-plugin' ) );
+		}
+
+		$provider_id = (int) get_post_meta( $product_id, '_mnbtwp_provider_id', true );
+		$provider    = Helpers::get_provider( $provider_id );
+		if ( ! $provider || 'true' !== $provider['qk'] ) {
+			throw new \Exception( __( '节点配置不可用', 'wp-seller-plugin' ) );
+		}
+
+		$user_id  = (int) $order->get_user_id();
+		$username = Helpers::generate_username( $user_id );
+		$password = wp_generate_password( 12, true );
+
+		$months  = max( 1, (int) get_post_meta( $product_id, '_mnbtwp_period_months', true ) ?: 12 );
+		$expire  = date( 'Y-m-d', strtotime( "+{$months} months" ) );
+		$webdx   = (int) get_post_meta( $product_id, '_mnbtwp_webdx', true );
+		$sqldx   = (int) get_post_meta( $product_id, '_mnbtwp_sqldx', true );
+		$flow    = (int) get_post_meta( $product_id, '_mnbtwp_ll', true );
+		$ymbds   = (int) get_post_meta( $product_id, '_mnbtwp_ymbds', true );
+
+		$client = Helpers::client_for_provider( $provider );
+		try {
+			$client->createHost(
+				array(
+					'username' => $username,
+					'password' => $password,
+					'webdx'    => $webdx,
+					'sqldx'    => $sqldx,
+					'll'       => $flow,
+					'ymbds'    => $ymbds,
+					'dqtime'   => $expire,
+				)
+			);
+		} catch ( MnbtException $e ) {
+			update_post_meta( $order_id, '_mnbtwp_provision_error', $e->getMessage() );
+			throw $e;
+		}
+
+		global $wpdb;
+		$quota = wp_json_encode(
+			array(
+				'web'  => array( 'max' => $webdx, 'used' => 0 ),
+				'sql'  => array( 'max' => $sqldx, 'used' => 0 ),
+				'flow' => array( 'max' => $flow, 'used' => 0 ),
+			)
+		);
+
+		$wpdb->insert(
+			$wpdb->prefix . 'mnbt_hosts',
+			array(
+				'provider_id'  => $provider_id,
+				'order_id'     => (int) $order_id,
+				'user_id'      => $user_id,
+				'username'     => $username,
+				'password_enc' => Crypto::encrypt( $password ),
+				'site_domain'  => '',
+				'status'       => 'active',
+				'expire_date'  => $expire,
+				'quota_json'   => $quota,
+				'created_at'   => current_time( 'mysql' ),
+				'updated_at'   => current_time( 'mysql' ),
+			),
+			array( '%d', '%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' )
+		);
+		$host_id = (int) $wpdb->insert_id;
+
+		update_post_meta( $order_id, '_mnbtwp_provisioned', '1' );
+		update_post_meta( $order_id, '_mnbtwp_host_id', $host_id );
+		delete_post_meta( $order_id, '_mnbtwp_provision_error' );
+
+		self::notify( $user_id, $username, $password, $provider, $expire );
+
+		return array(
+			'ok'      => true,
+			'msg'     => __( '主机开通成功', 'wp-seller-plugin' ),
+			'host_id' => $host_id,
+		);
+	}
+
+	/**
+	 * 发送开通通知邮件(含账号/密码/控制台地址)。
+	 *
+	 * @param int    $user_id  WP 用户 ID。
+	 * @param string $username 主机用户名。
+	 * @param string $password 明文密码。
+	 * @param array  $provider 节点行。
+	 * @param string $expire   到期日期。
+	 */
+	private static function notify( $user_id, $username, $password, array $provider, $expire ) {
+		$user = get_userdata( $user_id );
+		if ( ! $user || empty( $user->user_email ) ) {
+			return;
+		}
+		$subject = sprintf( __( '【%s】主机开通成功', 'wp-seller-plugin' ), get_bloginfo( 'name' ) );
+		$body    = '<p>' . esc_html__( '您购买的虚拟主机已开通:', 'wp-seller-plugin' ) . '</p>'
+			. '<p>主机账号:<code>' . esc_html( $username ) . '</code></p>'
+			. '<p>主机密码:<code>' . esc_html( $password ) . '</code></p>'
+			. '<p>' . esc_html__( '到期时间:', 'wp-seller-plugin' ) . esc_html( $expire ) . '</p>'
+			. '<p>' . esc_html__( '控制台:', 'wp-seller-plugin' ) . '<a href="' . esc_url( Helpers::control_url( $provider ) ) . '">' . esc_html( Helpers::control_url( $provider ) ) . '</a></p>'
+			. '<p style="color:#888;">' . esc_html__( '请妥善保管账号密码,可在「我的主机」中随时重置密码。', 'wp-seller-plugin' ) . '</p>';
+		Helpers::send_mail( $user->user_email, $subject, $body );
+	}
+}
diff --git a/wp_seller_plugin/includes/woocommerce/class-woo.php b/wp_seller_plugin/includes/woocommerce/class-woo.php
new file mode 100644
index 0000000..5e8c01c
--- /dev/null
+++ b/wp_seller_plugin/includes/woocommerce/class-woo.php
@@ -0,0 +1,183 @@
+<?php
+/**
+ * WooCommerce 集成:商品扩展、支付回调开通/续费、续费下单。
+ *
+ * @package MnbtWp
+ */
+
+namespace MnbtWp\Woo;
+
+use MnbtWp\Helpers;
+use MnbtWp\Services\Billing;
+use MnbtWp\Services\Provision;
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+/**
+ * WooCommerce 集成。
+ */
+class Woo {
+
+	/**
+	 * 构造:挂载钩子。
+	 */
+	public function __construct() {
+		add_action( 'woocommerce_product_options_general_product_data', array( $this, 'product_meta_fields' ) );
+		add_action( 'woocommerce_process_product_meta', array( $this, 'save_product_meta' ) );
+		add_action( 'woocommerce_payment_complete', array( $this, 'on_order_paid' ), 20, 1 );
+		add_action( 'woocommerce_order_status_completed', array( $this, 'on_order_paid' ), 20, 1 );
+	}
+
+	/**
+	 * 商品常规设置面板输出 MNBT 主机套餐字段。
+	 */
+	public function product_meta_fields() {
+		global $post;
+		$pid = (int) $post->ID;
+
+		$enabled   = 'yes' === get_post_meta( $pid, '_mnbtwp_enabled', true );
+		$provider  = (int) get_post_meta( $pid, '_mnbtwp_provider_id', true );
+		$webdx     = (int) get_post_meta( $pid, '_mnbtwp_webdx', true );
+		$sqldx     = (int) get_post_meta( $pid, '_mnbtwp_sqldx', true );
+		$flow      = (int) get_post_meta( $pid, '_mnbtwp_ll', true );
+		$ymbds     = (int) get_post_meta( $pid, '_mnbtwp_ymbds', true );
+		$months    = (int) get_post_meta( $pid, '_mnbtwp_period_months', true ) ?: 12;
+
+		global $wpdb;
+		$providers = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}mnbt_providers WHERE qk='true' ORDER BY id ASC", ARRAY_A );
+
+		echo '<div class="options_group show_if_simple">';
+		echo '<p class="form-field"><label style="font-weight:600;">' . esc_html__( 'MNBT 主机套餐', 'wp-seller-plugin' ) . '</label></p>';
+
+		woocommerce_wp_checkbox(
+			array(
+				'id'      => '_mnbtwp_enabled',
+				'label'   => __( '作为 MNBT 主机套餐', 'wp-seller-plugin' ),
+				'cbvalue' => 'yes',
+			)
+		);
+
+		echo '<p class="form-field _mnbtwp_fields">';
+		echo '<label for="_mnbtwp_provider_id">' . esc_html__( '节点', 'wp-seller-plugin' ) . '</label>';
+		echo '<select id="_mnbtwp_provider_id" name="_mnbtwp_provider_id" class="select short">';
+		echo '<option value="0">' . esc_html__( '选择节点', 'wp-seller-plugin' ) . '</option>';
+		foreach ( $providers as $p ) {
+			$sel = ( $provider === (int) $p['id'] ) ? ' selected' : '';
+			echo '<option value="' . (int) $p['id'] . '"' . $sel . '>' . esc_html( $p['name'] ) . '</option>';
+		}
+		echo '</select></p>';
+
+		woocommerce_wp_text_input( array( 'id' => '_mnbtwp_webdx', 'label' => __( '网站空间 (MB)', 'wp-seller-plugin' ), 'type' => 'number', 'value' => $webdx ) );
+		woocommerce_wp_text_input( array( 'id' => '_mnbtwp_sqldx', 'label' => __( '数据库空间 (MB)', 'wp-seller-plugin' ), 'type' => 'number', 'value' => $sqldx ) );
+		woocommerce_wp_text_input( array( 'id' => '_mnbtwp_ll', 'label' => __( '流量 (MB,0=不限)', 'wp-seller-plugin' ), 'type' => 'number', 'value' => $flow ) );
+		woocommerce_wp_text_input( array( 'id' => '_mnbtwp_ymbds', 'label' => __( '域名绑定数', 'wp-seller-plugin' ), 'type' => 'number', 'value' => $ymbds ) );
+		woocommerce_wp_text_input( array( 'id' => '_mnbtwp_period_months', 'label' => __( '周期 (月)', 'wp-seller-plugin' ), 'type' => 'number', 'value' => $months ) );
+		echo '</div>';
+
+		// 当启用开关变化时显隐字段。
+		?>
+		<script type="text/javascript">
+		jQuery(function ($) {
+			function mnbtwpToggle() {
+				if ($('#_mnbtwp_enabled').prop('checked')) {
+					$('._mnbtwp_fields').show();
+				} else {
+					$('._mnbtwp_fields').hide();
+				}
+			}
+			$('#_mnbtwp_enabled').on('change', mnbtwpToggle);
+			mnbtwpToggle();
+		});
+		</script>
+		<?php
+	}
+
+	/**
+	 * 保存商品 meta。
+	 *
+	 * @param int $post_id 商品 ID。
+	 */
+	public function save_product_meta( $post_id ) {
+		if ( ! empty( $_POST['_mnbtwp_enabled'] ) && 'yes' === $_POST['_mnbtwp_enabled'] ) {
+			update_post_meta( $post_id, '_mnbtwp_enabled', 'yes' );
+		} else {
+			delete_post_meta( $post_id, '_mnbtwp_enabled' );
+		}
+		update_post_meta( $post_id, '_mnbtwp_provider_id', max( 0, (int) ( $_POST['_mnbtwp_provider_id'] ?? 0 ) ) );
+		update_post_meta( $post_id, '_mnbtwp_webdx', max( 0, (int) ( $_POST['_mnbtwp_webdx'] ?? 0 ) ) );
+		update_post_meta( $post_id, '_mnbtwp_sqldx', max( 0, (int) ( $_POST['_mnbtwp_sqldx'] ?? 0 ) ) );
+		update_post_meta( $post_id, '_mnbtwp_ll', max( 0, (int) ( $_POST['_mnbtwp_ll'] ?? 0 ) ) );
+		update_post_meta( $post_id, '_mnbtwp_ymbds', max( 0, (int) ( $_POST['_mnbtwp_ymbds'] ?? 0 ) ) );
+		update_post_meta( $post_id, '_mnbtwp_period_months', max( 1, (int) ( $_POST['_mnbtwp_period_months'] ?? 12 ) ) );
+	}
+
+	/**
+	 * 订单支付完成:续费订单走续费,否则走开通。
+	 *
+	 * @param int $order_id 订单 ID。
+	 */
+	public function on_order_paid( $order_id ) {
+		$order = wc_get_order( $order_id );
+		if ( ! $order ) {
+			return;
+		}
+		try {
+			if ( get_post_meta( $order_id, '_mnbtwp_renew_host_id', true ) ) {
+				Billing::renew_order( $order_id );
+			} else {
+				Provision::provision_order( $order_id );
+			}
+		} catch ( \Exception $e ) {
+			// 记录失败(开通失败可在后台重试;续费失败待支付单手动处理)。
+			$order->add_order_note( 'MNBT 处理失败:' . $e->getMessage() );
+		}
+	}
+
+	/**
+	 * 创建续费订单(含原商品 + 续费 meta),返回订单对象。
+	 *
+	 * @param int $host_id 主机 ID。
+	 * @param int $months  续费月数。
+	 * @return \WC_Order
+	 *
+	 * @throws \Exception 原商品/订单缺失时抛出。
+	 */
+	public static function create_renew_order( $host_id, $months ) {
+		$host = Helpers::get_host( $host_id );
+		if ( ! $host ) {
+			throw new \Exception( __( '主机不存在', 'wp-seller-plugin' ) );
+		}
+		$months = max( 1, (int) $months );
+
+		$order = $host['order_id'] ? wc_get_order( (int) $host['order_id'] ) : null;
+		$product_id = 0;
+		if ( $order ) {
+			foreach ( $order->get_items() as $item ) {
+				$pid = (int) $item->get_product_id();
+				if ( 'yes' === get_post_meta( $pid, '_mnbtwp_enabled', true ) ) {
+					$product_id = $pid;
+					break;
+				}
+			}
+		}
+		if ( ! $product_id ) {
+			throw new \Exception( __( '找不到原主机商品', 'wp-seller-plugin' ) );
+		}
+		$product = wc_get_product( $product_id );
+		if ( ! $product ) {
+			throw new \Exception( __( '原商品不存在', 'wp-seller-plugin' ) );
+		}
+
+		$order = wc_create_order( array( 'customer_id' => (int) $host['user_id'] ) );
+		$order->add_product( $product, $months );
+		$order->add_meta_data( '_mnbtwp_renew_host_id', (int) $host_id );
+		$order->add_meta_data( '_mnbtwp_renew_months', $months );
+		$order->set_status( 'pending' );
+		$order->calculate_totals();
+		$order->save();
+
+		return $order;
+	}
+}
diff --git a/wp_seller_plugin/uninstall.php b/wp_seller_plugin/uninstall.php
new file mode 100644
index 0000000..702555b
--- /dev/null
+++ b/wp_seller_plugin/uninstall.php
@@ -0,0 +1,22 @@
+<?php
+/**
+ * 卸载脚本:默认保留数据(避免误删客户主机记录)。
+ * 如需彻底删除数据,取消下方注释。
+ *
+ * @package MnbtWp
+ */
+
+if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
+	exit;
+}
+
+$mnbtwp_keep = get_option( 'mnbtwp_settings', array() );
+$mnbtwp_keep = is_array( $mnbtwp_keep ) ? $mnbtwp_keep : array();
+
+if ( empty( $mnbtwp_keep['keep_data_on_del'] ) ) {
+	global $wpdb;
+	$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}mnbt_providers" );
+	$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}mnbt_hosts" );
+	$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}mnbt_api_log" );
+	delete_option( 'mnbtwp_settings' );
+}
diff --git a/wp_seller_plugin/wp-seller-plugin.php b/wp_seller_plugin/wp-seller-plugin.php
new file mode 100644
index 0000000..c9161b5
--- /dev/null
+++ b/wp_seller_plugin/wp-seller-plugin.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Plugin Name:       MNBT 虚拟主机销售
+ * Plugin URI:        https://github.com/MNBT/wp-seller-plugin
+ * Description:       在 WordPress 上售卖 MNBT(梦奈宝塔)虚拟主机:商品下单、自动开通、我的主机与到期管理,向 cPanel 式体验靠拢。
+ * Version:           0.1.0
+ * Requires at least: 6.0
+ * Requires PHP:      7.4
+ * Author:            MNBT
+ * License:           GPL-2.0-or-later
+ * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
+ * Text Domain:       wp-seller-plugin
+ * Domain Path:       /languages
+ *
+ * @package MnbtWp
+ */
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+define( 'MNBTWP_VERSION', '0.1.0' );
+define( 'MNBTWP_FILE', __FILE__ );
+define( 'MNBTWP_DIR', plugin_dir_path( __FILE__ ) );
+define( 'MNBTWP_URL', plugin_dir_url( __FILE__ ) );
+
+require_once MNBTWP_DIR . 'includes/class-plugin.php';
+
+register_activation_hook( __FILE__, array( '\MnbtWp\Plugin', 'activate' ) );
+register_deactivation_hook( __FILE__, array( '\MnbtWp\Plugin', 'deactivate' ) );
+
+/**
+ * 插件入口单例。
+ *
+ * @return \MnbtWp\Plugin
+ */
+function mnbtwp() {
+	return \MnbtWp\Plugin::instance();
+}
+
+mnbtwp();