feat(vip): Batch 1 per-level packages and admin schema

This commit is contained in:
2026-07-21 16:32:06 +08:00
parent 69836154fd
commit a4562627d8
6 changed files with 300 additions and 47 deletions
+92 -11
View File
@@ -17,12 +17,11 @@ trait VipServicePurchase {
public function get_catalog(int $user_id = 0): array {
$vip = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_payment_vip_settings()
: ['enabled' => false, 'packages_raw' => '', 'levels' => [], 'renew' => [], 'upgrade' => [], 'points_exchange' => []];
: ['enabled' => false, 'levels' => [], 'renew' => [], 'upgrade' => [], 'points_exchange' => []];
$level = function_exists('aksrt_get_user_vip_level') ? aksrt_get_user_vip_level($user_id) : 0;
$expires = $user_id > 0 ? (int) get_user_meta($user_id, 'aksrt_vip_expires', true) : 0;
$is_permanent = ($level > 0 && $expires <= 0);
$packages = $this->parse_packages((string) ($vip['packages_raw'] ?? ''));
$levels = is_array($vip['levels'] ?? null) ? $vip['levels'] : [];
$renew = is_array($vip['renew'] ?? null) ? $vip['renew'] : [];
$upgrade = is_array($vip['upgrade'] ?? null) ? $vip['upgrade'] : [];
@@ -37,6 +36,8 @@ trait VipServicePurchase {
if (empty($levels[$lv]['enabled'])) {
continue;
}
// Cash products can be disabled per level.
$product_s = !isset($levels[$lv]['product_s']) || !empty($levels[$lv]['product_s']);
if ($is_permanent && $lv <= $level) {
continue;
}
@@ -49,6 +50,9 @@ trait VipServicePurchase {
$name = (string) ($levels[$lv]['name'] ?? ('VIP' . $lv));
$items = [];
$packages = $product_s
? $this->parse_level_products((string) ($levels[$lv]['products_raw'] ?? ''))
: [];
foreach ($packages as $idx => $pkg) {
$base_price = (float) $pkg['price'];
$days = (int) $pkg['days'];
@@ -64,6 +68,22 @@ trait VipServicePurchase {
continue;
}
$show_price = (float) ($priced['show_price'] ?? 0);
if ($show_price <= 0 && !empty($pkg['show_price']) && $action === 'pay') {
$show_price = (float) $pkg['show_price'];
}
$discount_tag = (string) ($priced['tag'] ?? '');
if ($discount_tag === '' && $action === 'pay' && !empty($pkg['tag'])) {
$discount_tag = (string) $pkg['tag'];
}
$days_label = __('永久', 'aksrt');
if ((int) $pkg['time'] > 0) {
$days_label = ((string) ($pkg['unit'] ?? 'day') === 'month')
? sprintf(__('%d 个月', 'aksrt'), (int) $pkg['time'])
: sprintf(__('%d 天', 'aksrt'), (int) $pkg['time']);
}
$items[] = [
'package_id' => $this->encode_package_id($lv, $idx + 1),
'level' => $lv,
@@ -71,17 +91,17 @@ trait VipServicePurchase {
'action' => $action,
'price' => (float) $priced['price'],
'base_price' => $base_price,
'show_price' => (float) ($priced['show_price'] ?? 0),
'discount_tag' => (string) ($priced['tag'] ?? ''),
'show_price' => $show_price,
'discount_tag' => $discount_tag,
'days' => $days,
'time' => (int) ($pkg['time'] ?? 0),
'unit' => (string) ($pkg['unit'] ?? 'day'),
'title' => (string) $pkg['title'],
'price_label' => $this->format_cash_label((float) $priced['price']),
'show_price_label' => !empty($priced['show_price'])
? $this->format_cash_label((float) $priced['show_price'])
'show_price_label' => $show_price > 0
? $this->format_cash_label($show_price)
: '',
'days_label' => ($days <= 0)
? __('永久', 'aksrt')
: sprintf(__('%d 天', 'aksrt'), $days),
'days_label' => $days_label,
'cta_label' => $action === 'renew'
? __('立即续费', 'aksrt')
: ($action === 'upgrade' ? __('立即升级', 'aksrt') : __('立即开通', 'aksrt')),
@@ -163,7 +183,7 @@ trait VipServicePurchase {
$vip = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_payment_vip_settings()
: ['enabled' => false, 'packages_raw' => '', 'levels' => [], 'renew' => [], 'upgrade' => []];
: ['enabled' => false, 'levels' => [], 'renew' => [], 'upgrade' => []];
if (empty($vip['enabled'])) {
return new \WP_Error('aksrt_vip_disabled', __('会员功能未启用', 'aksrt'), ['status' => 400]);
}
@@ -203,6 +223,8 @@ trait VipServicePurchase {
// Dedicated upgrade product:
// 901 permanent/fixed, 902 unit remaining days, 903 jump permanent.
$upgrade_mode = '';
$pkg_time = 0;
$pkg_unit = 'day';
if (in_array($index, [901, 902, 903], true)) {
$upgrade_items = $this->build_upgrade_items($user_id, $expires, $is_permanent, (string) ($upgrade['product'] ?? ''));
$match = null;
@@ -222,18 +244,30 @@ trait VipServicePurchase {
];
$amount = round((float) $match['price'], 2);
$days = (int) $match['days'];
$pkg_time = $days; // unit upgrade keeps days as remaining; jump/permanent use 0
$pkg_unit = 'day';
$base_price = (float) ($match['base_price'] ?? $amount);
$discount_tag = (string) ($match['discount_tag'] ?? __('升级专享', 'aksrt'));
$upgrade_mode = sanitize_key((string) ($match['upgrade_mode'] ?? ''));
$action = 'upgrade';
if ($upgrade_mode === 'jump' || $upgrade_mode === 'permanent') {
$pkg_time = 0;
$days = 0;
}
} else {
$packages = $this->parse_packages((string) ($vip['packages_raw'] ?? ''));
$level_cfg = is_array($levels[$level] ?? null) ? $levels[$level] : [];
if (isset($level_cfg['product_s']) && empty($level_cfg['product_s'])) {
return new \WP_Error('aksrt_vip_product_disabled', __('该等级暂不支持购买', 'aksrt'), ['status' => 400]);
}
$packages = $this->parse_level_products((string) ($level_cfg['products_raw'] ?? ''));
$pkg = $packages[$index - 1] ?? null;
if (!$pkg) {
return new \WP_Error('aksrt_invalid_package', __('套餐不存在', 'aksrt'), ['status' => 400]);
}
$base_price = (float) $pkg['price'];
$days = (int) $pkg['days'];
$pkg_time = (int) ($pkg['time'] ?? 0);
$pkg_unit = (string) ($pkg['unit'] ?? 'day');
$priced = $this->apply_action_price($base_price, $action, $renew, $upgrade, $level, $user_level, $expires, $is_permanent, $days);
if ($priced === null) {
return new \WP_Error('aksrt_invalid_package', __('当前无法购买该套餐', 'aksrt'), ['status' => 400]);
@@ -259,6 +293,8 @@ trait VipServicePurchase {
'vip_pay' => 1,
'level' => $level,
'days' => $days,
'time' => $pkg_time,
'unit' => $pkg_unit,
'package_index' => $index,
'package_title' => (string) ($pkg['title'] ?? ''),
'package_id' => $package_id,
@@ -401,6 +437,11 @@ trait VipServicePurchase {
$level = (int) ($meta['level'] ?? 0);
$days = (int) ($meta['days'] ?? 0);
$time = (int) ($meta['time'] ?? 0);
$unit = sanitize_key((string) ($meta['unit'] ?? 'day'));
if (!in_array($unit, ['day', 'month'], true)) {
$unit = 'day';
}
$upgrade_mode = sanitize_key((string) ($meta['upgrade_mode'] ?? ''));
if ($level <= 0) {
$decoded = $this->decode_package_id((int) ($paid_order['product_id'] ?? 0));
@@ -418,6 +459,9 @@ trait VipServicePurchase {
// unit upgrade: raise level only, keep current expiry (zibll).
if ($upgrade_mode === 'unit') {
$this->activate_vip($user_id, $level, 0, true);
} elseif ($time > 0 || (isset($meta['time']) && (int) $meta['time'] === 0 && $days === 0)) {
// Prefer calendar time+unit when present (including permanent time=0).
$this->activate_vip_duration($user_id, $level, $time, $unit);
} else {
$this->activate_vip($user_id, $level, $days, false);
}
@@ -439,6 +483,8 @@ trait VipServicePurchase {
$current_level = (int) get_user_meta($user_id, 'aksrt_vip_level', true);
$new_level = max($current_level, $level);
update_user_meta($user_id, 'aksrt_vip_level', $new_level);
// Clear expired memory when re-granting.
delete_user_meta($user_id, 'aksrt_vip_level_expired');
// Unit upgrade (zibll): keep vip_exp_date, only raise level.
if ($keep_expiry) {
@@ -460,6 +506,41 @@ trait VipServicePurchase {
update_user_meta($user_id, 'aksrt_vip_expires', $base + ($days * DAY_IN_SECONDS));
}
/**
* Activate VIP using calendar duration (zibll month/day).
* time=0 → permanent. Extends from max(now, current_exp).
*/
public function activate_vip_duration(int $user_id, int $level, int $time, string $unit = 'day'): void {
if ($user_id <= 0) {
return;
}
$level = max(1, min(2, $level));
$unit = $unit === 'month' ? 'month' : 'day';
$current_level = (int) get_user_meta($user_id, 'aksrt_vip_level', true);
$new_level = max($current_level, $level);
update_user_meta($user_id, 'aksrt_vip_level', $new_level);
delete_user_meta($user_id, 'aksrt_vip_level_expired');
if ($time <= 0) {
update_user_meta($user_id, 'aksrt_vip_expires', 0);
return;
}
$current_exp = (int) get_user_meta($user_id, 'aksrt_vip_expires', true);
if ($current_exp === 0 && $current_level > 0) {
return; // keep permanent
}
$base = max($current_exp, time());
$modifier = $unit === 'month'
? ('+' . $time . ' months')
: ('+' . $time . ' days');
$new_exp = strtotime($modifier, $base);
if ($new_exp === false) {
$new_exp = $base + ($this->duration_to_days($time, $unit) * DAY_IN_SECONDS);
}
update_user_meta($user_id, 'aksrt_vip_expires', (int) $new_exp);
}
/**