diff --git a/README.md b/README.md
index e5c3729..341f712 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,12 @@
为 WooCommerce 提供基础订阅能力:在单个产品层面配置订阅计划(周期、数量、订阅价、一次性购买选项),在前端展示订阅选项,并在购物车/订单中显示订阅摘要与按订阅规则计算价格。
+本版本新增后台“面板式订阅计划”管理(可折叠面板),支持配置多个订阅计划并保存为产品元数据;保存时如存在至少一个计划会自动启用订阅。前端当前仍基于“基础字段”(周期/数量/价格)进行渲染与计价,订阅计划的前端选择将在后续版本提供。
+
## 功能概述
-- 产品级订阅计划:周期(月/年)、默认订阅数量、订阅价格(可选)、是否允许一次性购买;
+- 产品级订阅计划(基础字段):周期(月/年)、默认订阅数量、订阅价格(可选)、是否允许一次性购买;
+- 后台支持“多个订阅计划”的配置(名称、周期、每周期价格或折扣百分比);保存时若存在计划将自动启用订阅;
- 前端产品页展示订阅选项,显示每周期价格与相对常规价的折扣百分比;
- 加入购物车时记录订阅参数;
- 购物车行项目价格 = 每周期价格 × 周期系数(年=12)× 订阅数量 × 购物车数量;
@@ -20,11 +23,14 @@
1. 后台 → 产品 → 编辑某个产品;
2. 在“订阅计划”标签页:
- - 启用订阅;
- - 选择订阅周期(月/年);
- - 设置默认订阅数量(前端默认值,可修改);
- - 设置订阅价格(每周期、可选,留空则使用产品常规价);
- - 是否允许一次性购买(允许时前端可切换一次性购买或订阅购买)。
+ - 启用订阅(保存时如存在订阅计划将自动开启,即使未勾选也会开启);
+ - 基础字段(仍生效,当前前端基于这些字段渲染与计价):
+ - 订阅周期(月/年)、默认订阅数量、订阅价格(每周期,留空表示使用产品常规价)、分级折扣规则(数量:折扣%);
+ - 是否允许一次性购买(允许时前端可切换一次性/订阅购买)。
+ - 订阅计划面板(新增):使用“新增订阅计划”按钮添加计划,面板内包含:
+ - 计划名称、周期(月/年)、每周期价格(留空使用产品价)、折扣百分比(可选,10 表示约 9 折)。
+ - 支持折叠/展开、删除计划。保存后计划以数组形式写入产品元数据。
+ - 注:当前前端尚未提供“在产品页选择某个计划”的 UI,因此建议仍按需设置基础字段以保证前端行为;订阅计划的选择与联动价格将在后续版本实现。
## 前端效果
@@ -33,17 +39,24 @@
- 显示每周期订阅价与相对常规价的折扣提示;
- 加入购物车后行项目价格按规则动态计算,购物车显示订阅摘要。
-## 技术实现
+## 技术实现与设计
- 主入口:`yoone-subscriptions.php`,注册资源、加载国际化、依赖检查、加载模块;
- 核心:`includes/class-yoone-subscriptions.php` 定义 postmeta 键名,提供 `get_config()` 与周期系数方法;
-- 后台:`includes/admin/class-yoone-subscriptions-admin.php` 在产品编辑页添加订阅面板,保存 postmeta(含 nonce 与 sanitize);
+- 后台:`includes/admin/class-yoone-subscriptions-admin.php`
+ - 在产品编辑页添加订阅面板(simple/variable/yoone_bundle);
+ - 渲染基础字段与“订阅计划列表”(以表格输出,`assets/js/admin.js` 动态转换为可折叠面板);
+ - 保存 postmeta(含 nonce 校验与 sanitize),当存在至少一个有效订阅计划时自动设置“启用订阅”;
- 前端:`includes/frontend/class-yoone-subscriptions-frontend.php`
- `woocommerce_before_add_to_cart_button` 渲染订阅选项;
- `woocommerce_add_to_cart_validation` 校验参数;
- `woocommerce_add_cart_item_data` 存储订阅数据到购物车项;
- `woocommerce_get_item_data` 展示订阅摘要;
- `woocommerce_before_calculate_totals` 动态设置行项目价格。
+ - My Account:注册 `subscriptions` 端点与菜单项“我的订阅”,显示当前用户的订阅实例列表(数据来源于 `includes/models/class-yoone-subscriptions-db.php`)。
+- 资源:
+ - 后台:`assets/css/admin.css`(面板式样式)、`assets/js/admin.js`(隐藏旧字段、计划的新增/删除与折叠交互)
+ - 前端:`assets/css/frontend.css`(简版样式)、`assets/js/frontend.js`(一次性/订阅模式的样式钩子)
## 兼容性与支付
@@ -54,6 +67,7 @@
- 后台产品编辑页 → 订阅计划面板(示例)
- 前端产品页 → 订阅选项区块(示例)
+- My Account → 我的订阅(示例)
(请在实际部署后补充截图文件并更新此章节)
@@ -67,6 +81,10 @@
- 总价以两者乘积计算。若不希望用户在订阅场景改变购物车数量,可考虑在前端或后台强制 sold individually(可后续扩展)。
4. 是否支持变体产品?
- 面板在 simple/variable 产品上显示;变体粒度订阅可后续扩展至变体级别元数据。
+5. 后台新增的“多个订阅计划”前端是否可选?
+ - 当前版本尚未提供选择 UI,计划数据已保存并可用于后续扩展;当前前端渲染与计价仍基于基础字段。
+6. 为什么 My Account 没有出现“我的订阅”?
+ - 首次启用插件后,访问 设置 → 固定链接 → 保存 以刷新路由规则;或停用再启用插件。然后在“我的账户”页面即可看到“我的订阅”菜单。
## 性能与测试
diff --git a/assets/css/admin.css b/assets/css/admin.css
index 8431487..e3f335f 100644
--- a/assets/css/admin.css
+++ b/assets/css/admin.css
@@ -1,3 +1,66 @@
-/* Yoone Subscriptions 后台样式(简版) */
-#yoone_subscriptions_data .description { color: #666; display: block; margin-top: 6px; }
-#yoone_subscriptions_data .form-field { margin-bottom: 12px; }
\ No newline at end of file
+/*
+ * Yoone Subscriptions 后台样式
+ * - 兼容原有“表格式”订阅计划(由 JS 转换为面板,表格默认隐藏但保留样式)
+ * - 新增“面板式”订阅计划的外观:标题、操作按钮(折叠/删除)、详情区块
+ * - 风格尽量贴近 WooCommerce 后台组件,保持一致的交互与视觉
+ */
+#yoone_subscriptions_data .description { color: #666; display: block; margin-top: 6px; line-height: 1.5; }
+#yoone_subscriptions_data .form-field { margin-bottom: 12px; }
+
+/* 订阅计划表格布局优化(兼容保留但默认隐藏) */
+#yoone_subscriptions_data .yoone-sub-plans-table { margin-top: 10px; width: 100%; table-layout: fixed; }
+#yoone_subscriptions_data .yoone-sub-plans-table thead th { font-weight: 600; }
+#yoone_subscriptions_data .yoone-sub-plans-table th,
+#yoone_subscriptions_data .yoone-sub-plans-table td { padding: 8px 10px; vertical-align: middle; }
+#yoone_subscriptions_data .yoone-sub-plans-table tr:nth-child(even) { background-color: #f9f9f9; }
+
+/* 列宽(与 table-layout: fixed 配合) */
+#yoone_subscriptions_data .yoone-sub-plans-table colgroup col:nth-child(1) { width: 28%; }
+#yoone_subscriptions_data .yoone-sub-plans-table colgroup col:nth-child(2) { width: 16%; }
+#yoone_subscriptions_data .yoone-sub-plans-table colgroup col:nth-child(3) { width: 22%; }
+#yoone_subscriptions_data .yoone-sub-plans-table colgroup col:nth-child(4) { width: 18%; }
+#yoone_subscriptions_data .yoone-sub-plans-table colgroup col:nth-child(5) { width: 16%; }
+
+/* 输入控件统一风格(参考 Woo 后台表单) */
+#yoone_subscriptions_data .yoone-sub-plans-table input[type="text"],
+#yoone_subscriptions_data .yoone-sub-plans-table input[type="number"],
+#yoone_subscriptions_data .yoone-sub-plans-table select {
+ width: 100%;
+ box-sizing: border-box;
+ min-height: 32px;
+}
+
+/* 行内按钮精简样式 */
+#yoone_subscriptions_data .yoone-sub-plans-table .button {
+ min-height: 28px;
+ line-height: 26px;
+}
+
+/* 新增计划按钮间距 */
+#yoone_subscriptions_data #yoone-sub-plan-add { margin-top: 8px; }
+
+/* 面板式订阅计划样式 */
+#yoone_subscriptions_data #yoone-sub-plans-panels { margin-top: 12px; }
+#yoone_subscriptions_data .yoone-sub-plan-panel {
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ background: #fff;
+ margin-bottom: 12px;
+ box-shadow: 0 1px 1px rgba(0,0,0,.04);
+}
+#yoone_subscriptions_data .yoone-sub-plan-header {
+ padding: 10px 12px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background: #f6f7f7;
+ border-bottom: 1px solid #e2e4e7;
+}
+#yoone_subscriptions_data .yoone-sub-plan-title {
+ width: 60%;
+ max-width: 420px;
+}
+#yoone_subscriptions_data .yoone-sub-plan-actions .button { margin-left: 8px; }
+#yoone_subscriptions_data .yoone-sub-plan-body { padding: 12px; }
+#yoone_subscriptions_data .yoone-sub-plan-body p { margin: 0 0 10px; }
+#yoone_subscriptions_data .yoone-sub-plan-body.collapsed { display: none; }
\ No newline at end of file
diff --git a/assets/css/frontend.css b/assets/css/frontend.css
index 98d5c9b..41acd63 100644
--- a/assets/css/frontend.css
+++ b/assets/css/frontend.css
@@ -1,4 +1,8 @@
-/* Yoone Subscriptions 前端样式(简版) */
+/*
+ * Yoone Subscriptions 前端样式(简版)
+ * - 产品页订阅选项容器(.yoone-subs-block)与基础排版
+ * - 样式钩子:.yoone-subs-onetime 用于在选择一次性购买时做轻量视觉变化
+ */
.yoone-subs-block { border: 1px solid #e5e5e5; padding: 12px; margin: 12px 0; border-radius: 6px; }
.yoone-subs-block h3 { margin: 0 0 8px; }
.yoone-subs-row { margin: 8px 0; display: flex; gap: 8px; align-items: center; }
diff --git a/assets/js/admin.js b/assets/js/admin.js
index 9709fd8..d12aa31 100644
--- a/assets/js/admin.js
+++ b/assets/js/admin.js
@@ -1,29 +1,93 @@
+/**
+ * 后台 JS:订阅计划面板交互
+ * - 隐藏旧的单一订阅字段(周期、默认数量、最小起订量、每周期价格、分级折扣)
+ * - 将后端渲染的表格(保持字段 name 不变)转换为“可折叠面板”形式
+ * - 支持新增/删除计划,折叠/展开计划详情
+ * - 保持与后端 save_meta 的兼容:使用相同的 name 数组结构提交
+ */
(function($){
$(function(){
+ // 隐藏旧的单一订阅字段
+ ['yoone_sub_enabled','yoone_sub_period','yoone_sub_qty_default','yoone_sub_min_qty','yoone_sub_price','yoone_sub_tier_rules'].forEach(function(id){
+ var $input = $('#'+id);
+ if ($input.length) {
+ var $field = $input.closest('.form-field');
+ if ($field.length) { $field.hide(); }
+ }
+ });
+
+ var $table = $('.yoone-sub-plans-table');
var $tbody = $('#yoone-sub-plans-body');
var $addBtn = $('#yoone-sub-plan-add');
+ function buildPanel(labelInput, periodSelect, priceInput, discountInput){
+ var $panel = $('
');
+ var $header = $('');
+ var $actions = $('');
+ var $toggle = $('').text((typeof yooneSubsI18n !== 'undefined' ? yooneSubsI18n.collapse : '折叠'));
+ var $remove = $('').text((typeof yooneSubsI18n !== 'undefined' ? yooneSubsI18n.remove : '删除'));
+ var $title = $(labelInput || '');
+ $title.addClass('yoone-sub-plan-title');
+ $actions.append($toggle).append(' ').append($remove);
+ $header.append($title).append($actions);
+
+ var $body = $('');
+ var $periodRow = $('').append(' ');
+ var $period = $(periodSelect || [
+ ''
+ ].join(''));
+ $periodRow.append($period);
+
+ var $priceRow = $('').append(' ');
+ var $price = $(priceInput || '');
+ $priceRow.append($price);
+
+ var $discRow = $('').append(' ');
+ var $disc = $(discountInput || '');
+ $discRow.append($disc);
+
+ $body.append($periodRow).append($priceRow).append($discRow);
+ $panel.append($header).append($body);
+ return $panel;
+ }
+
if ($tbody.length && $addBtn.length) {
+ // 将现有表格行转换为面板
+ var $container = $('');
+ $table.before($container);
+ $tbody.find('tr').each(function(){
+ var $tr = $(this);
+ var $label = $tr.find('input[name="yoone_sub_plans[label][]"]').detach();
+ var $period = $tr.find('select[name="yoone_sub_plans[period][]"]').detach();
+ var $price = $tr.find('input[name="yoone_sub_plans[price][]"]').detach();
+ var $disc = $tr.find('input[name="yoone_sub_plans[discount_percent][]"]').detach();
+ var $panel = buildPanel($label, $period, $price, $disc);
+ $container.append($panel);
+ });
+ // 隐藏表格
+ $table.hide();
+
+ // 新增面板
$addBtn.on('click', function(){
- var row = [
- '',
- ' | ',
- ' | ',
- ' | ',
- ' | ',
- ' | ',
- '
'
- ].join('');
- $tbody.append(row);
+ $container.append(buildPanel());
});
- $tbody.on('click', '.yoone-sub-plan-remove', function(){
- $(this).closest('tr').remove();
+ // 删除面板
+ $(document).on('click', '.yoone-sub-plan-remove', function(){
+ $(this).closest('.yoone-sub-plan-panel').remove();
+ });
+
+ // 折叠/展开
+ $(document).on('click', '.yoone-sub-plan-toggle', function(){
+ var $btn = $(this);
+ var $body = $btn.closest('.yoone-sub-plan-panel').find('.yoone-sub-plan-body');
+ var expanded = $btn.attr('aria-expanded') === 'true';
+ $btn.attr('aria-expanded', expanded ? 'false' : 'true');
+ $btn.text(expanded ? (typeof yooneSubsI18n !== 'undefined' ? yooneSubsI18n.expand : '展开') : (typeof yooneSubsI18n !== 'undefined' ? yooneSubsI18n.collapse : '折叠'));
+ $body.toggleClass('collapsed', expanded);
});
}
});
diff --git a/assets/js/frontend.js b/assets/js/frontend.js
index 5e18cb6..2b75b1a 100644
--- a/assets/js/frontend.js
+++ b/assets/js/frontend.js
@@ -1,3 +1,7 @@
+/**
+ * 前端 JS:订阅选项的轻量交互
+ * - 当用户切换“一次性购买/订阅购买”时,为容器添加/移除样式钩子,便于样式控制
+ */
(function($){
// 切换一次性购买/订阅购买时,简单控制 UI(可按需扩展隐藏/显示订阅字段)。
$(document).on('change', 'input[name="yoone_sub_purchase_mode"]', function(){
diff --git a/docs/技术文档.md b/docs/技术文档.md
index 9e890fc..c7606de 100644
--- a/docs/技术文档.md
+++ b/docs/技术文档.md
@@ -1 +1,2 @@
-# 实现订阅功能
+
+# 实现订阅功能
\ No newline at end of file
diff --git a/docs/数据库模型.md b/docs/数据库模型.md
index 23c1e09..37e3e9b 100644
--- a/docs/数据库模型.md
+++ b/docs/数据库模型.md
@@ -1,3 +1,4 @@
+
# 数据库模型(Yoone Subscriptions)
本插件新增一个独立的数据库模型目录 `includes/models/`,用于管理用户订阅实例的持久化数据。当前实现增加了基础表结构与 CRUD 接口,方便未来接入续订、暂停、取消等业务。
diff --git a/docs/需求.md b/docs/需求.md
index bde0b88..bb95f51 100644
--- a/docs/需求.md
+++ b/docs/需求.md
@@ -1,4 +1,10 @@
+
- 扣款失败需要发邮件给客户
- 后台有用户订阅列表管理页面
- 数据存储在数据库
- 前端用户登陆后可以在 my account 页面中查看自己已设置的订阅
+- 配置订阅计划的产品显示订阅计划的选项 可以加入购物车
+- 支付手段可以使用 woocommerce 的payments 进行订阅配置
+- 订阅生成的订单应该包括订阅计划的信息
+- 订阅在第一次之后,定时自动续费并生成正确的订单
+-
\ No newline at end of file
diff --git a/docs/项目新增.md b/docs/项目新增.md
index c0b8e06..218a6cc 100644
--- a/docs/项目新增.md
+++ b/docs/项目新增.md
@@ -1,3 +1,4 @@
+
开发一个名为`yoone-subscriptions`的WordPress插件,实现订阅功能。
1. 插件基础框架:
@@ -14,6 +15,7 @@
- 产品页
- 设置了订阅计划的产品页显示订阅计划的选项(以及金额折扣)
- 可以加购
+ - 帮我生成订阅
- 购物车
- 可以在购物车中添加订阅项目(显示订阅标识)
- 购物车中订阅产品的行项目价格 = 订阅价格 × 订阅周期 × 订阅数量
diff --git a/includes/admin/class-yoone-subscriptions-admin.php b/includes/admin/class-yoone-subscriptions-admin.php
index e5e84e8..2bc27c2 100644
--- a/includes/admin/class-yoone-subscriptions-admin.php
+++ b/includes/admin/class-yoone-subscriptions-admin.php
@@ -1,6 +1,13 @@
' . esc_html__('可以为该产品配置多个订阅计划,前端用户可在购买时选择其一。每个计划可设置周期与每周期价格,或以折扣百分比表示相对产品价的优惠。', 'yoone-subscriptions') . '';
$plans = isset($cfg['plans']) && is_array($cfg['plans']) ? $cfg['plans'] : array();
- echo '';
+ echo '';
+ echo '';
echo ''
. '| ' . esc_html__('计划名称', 'yoone-subscriptions') . ' | '
. '' . esc_html__('周期', 'yoone-subscriptions') . ' | '
@@ -186,7 +194,7 @@ class Yoone_Subscriptions_Admin {
$qty = max(1, $qty);
$price_v = ($price === '' ? '' : wc_format_decimal($price, 2));
- update_post_meta($post_id, Yoone_Subscriptions::META_ENABLED, $enabled ? '1' : '');
+ // 启用标记延后到保存完订阅计划后,根据是否存在计划自动开启
update_post_meta($post_id, Yoone_Subscriptions::META_PERIOD, $period);
update_post_meta($post_id, Yoone_Subscriptions::META_QTY_DEFAULT, $qty);
update_post_meta($post_id, Yoone_Subscriptions::META_MIN_QTY, max(1, $min_qty));
@@ -228,6 +236,10 @@ class Yoone_Subscriptions_Admin {
}
}
update_post_meta($post_id, Yoone_Subscriptions::META_PLANS, $plans);
+
+ // 如果存在至少一个有效的订阅计划,则自动启用订阅
+ $auto_enabled = ! empty($plans);
+ update_post_meta($post_id, Yoone_Subscriptions::META_ENABLED, ($enabled || $auto_enabled) ? '1' : '');
}
/**
diff --git a/includes/frontend/class-yoone-subscriptions-frontend.php b/includes/frontend/class-yoone-subscriptions-frontend.php
index 43e2110..5c650a4 100644
--- a/includes/frontend/class-yoone-subscriptions-frontend.php
+++ b/includes/frontend/class-yoone-subscriptions-frontend.php
@@ -13,6 +13,14 @@ class Yoone_Subscriptions_Frontend {
}
private function __construct() {
+ // 注册“我的账户 → 订阅”端点与菜单
+ add_action('init', array($this, 'register_account_endpoint'));
+ add_filter('query_vars', array($this, 'add_query_var'));
+ add_filter('woocommerce_get_query_vars', array($this, 'register_wc_query_vars'));
+ add_filter('woocommerce_endpoint_subscriptions_title', array($this, 'endpoint_title'));
+ add_filter('woocommerce_account_menu_items', array($this, 'add_account_menu_item'));
+ add_action('woocommerce_account_subscriptions_endpoint', array($this, 'render_account_subscriptions'));
+
// 在简单产品的 add-to-cart 区域前渲染订阅选项
add_action('woocommerce_before_add_to_cart_button', array($this, 'render_subscription_options'));
@@ -27,6 +35,108 @@ class Yoone_Subscriptions_Frontend {
add_action('woocommerce_before_calculate_totals', array($this, 'adjust_subscription_price'), 20, 1);
}
+ /**
+ * 注册 My Account 的“subscriptions”端点。
+ * - 访问地址示例:/my-account/subscriptions/
+ */
+ public function register_account_endpoint() {
+ // 为 WP 添加 rewrite 端点(仅需注册一次;在插件激活时会 flush rules)
+ add_rewrite_endpoint('subscriptions', EP_ROOT | EP_PAGES);
+ }
+
+ /**
+ * 将自定义端点加入 WP 的 query vars,避免部分站点未识别导致 404。
+ */
+ public function add_query_var($vars) {
+ $vars[] = 'subscriptions';
+ return $vars;
+ }
+
+ /**
+ * 将端点注册到 WooCommerce 的 endpoint 映射中(用于标题与模板路由)。
+ */
+ public function register_wc_query_vars($vars) {
+ $vars['subscriptions'] = 'subscriptions';
+ return $vars;
+ }
+
+ /**
+ * 设置端点页面标题。
+ */
+ public function endpoint_title($title) {
+ return __('我的订阅', 'yoone-subscriptions');
+ }
+
+ /**
+ * 在 My Account 菜单中新增“我的订阅”。
+ */
+ public function add_account_menu_item($items) {
+ // 在“订单”后插入“订阅”菜单
+ $new = array();
+ foreach ($items as $key => $label) {
+ $new[$key] = $label;
+ if ('orders' === $key) {
+ $new['subscriptions'] = __('我的订阅', 'yoone-subscriptions');
+ }
+ }
+ // 如果没有“订单”菜单,则直接追加
+ if (! isset($new['subscriptions'])) {
+ $new['subscriptions'] = __('我的订阅', 'yoone-subscriptions');
+ }
+ return $new;
+ }
+
+ /**
+ * 渲染 My Account → 订阅 列表。
+ */
+ public function render_account_subscriptions() {
+ if (! is_user_logged_in()) {
+ echo '' . esc_html__('请先登录以查看您的订阅。', 'yoone-subscriptions') . '
';
+ return;
+ }
+ $uid = get_current_user_id();
+ if (! class_exists('Yoone_Subscriptions_DB')) {
+ echo '' . esc_html__('订阅数据模块未加载。', 'yoone-subscriptions') . '
';
+ return;
+ }
+ $subs = Yoone_Subscriptions_DB::get_by_user($uid, array('limit' => 50, 'offset' => 0));
+
+ echo '' . esc_html__('我的订阅', 'yoone-subscriptions') . '
';
+ if (empty($subs)) {
+ echo '' . esc_html__('您目前还没有订阅。', 'yoone-subscriptions') . '
';
+ return;
+ }
+
+ echo '
+
+
+ | ' . esc_html__('商品', 'yoone-subscriptions') . ' |
+ ' . esc_html__('周期', 'yoone-subscriptions') . ' |
+ ' . esc_html__('数量', 'yoone-subscriptions') . ' |
+ ' . esc_html__('每周期金额', 'yoone-subscriptions') . ' |
+ ' . esc_html__('状态', 'yoone-subscriptions') . ' |
+ ' . esc_html__('开始时间', 'yoone-subscriptions') . ' |
+ ' . esc_html__('下次续费', 'yoone-subscriptions') . ' |
+
+
+ ';
+ foreach ($subs as $row) {
+ $product = wc_get_product(intval($row['product_id']));
+ $product_name = $product ? $product->get_name() : ('#' . intval($row['product_id']));
+ $period_label = $row['period'] === 'year' ? __('年', 'yoone-subscriptions') : __('月', 'yoone-subscriptions');
+ echo '
+ | ' . esc_html($product_name) . ' |
+ ' . esc_html($period_label) . ' |
+ ' . esc_html(intval($row['qty'])) . ' |
+ ' . wc_price(floatval($row['price_per_cycle'])) . ' |
+ ' . esc_html($row['status']) . ' |
+ ' . esc_html($row['start_date']) . ' |
+ ' . esc_html($row['next_renewal_date']) . ' |
+
';
+ }
+ echo '
';
+ }
+
/**
* 渲染订阅选择 UI(产品页)。
*/
diff --git a/includes/logging/class-yoone-subscriptions-logger.php b/includes/logging/class-yoone-subscriptions-logger.php
index 4e5d02b..f4fa6f5 100644
--- a/includes/logging/class-yoone-subscriptions-logger.php
+++ b/includes/logging/class-yoone-subscriptions-logger.php
@@ -1,6 +1,8 @@