$config * @return array> */ private function build_upgrade_items(int $user_id, int $expires, bool $is_permanent, array $config): array { $items = []; $now = time(); // 月费会员升级月费会员:按剩余天数计价,会员有效期不变(仅提升等级)。 if (!$is_permanent && $expires > $now && !empty($config['month_to_month_s'])) { $daily_minor = $this->parse_vip_money_minor((string) ($config['unit_price'] ?? '')); if ($daily_minor > 0) { $remaining = (int) ceil(max(1, ($expires - $now) / DAY_IN_SECONDS)); if ($daily_minor <= intdiv(PHP_INT_MAX, $remaining)) { $price = \AKSRT\Ledger\AssetAmount::decimal_from_minor($remaining * $daily_minor); $items[] = [ 'package_id' => $this->encode_package_id(2, 902), 'level' => 2, 'index' => 902, 'action' => 'upgrade', 'price' => $price, 'base_price' => $price, 'show_price' => '0.00', 'discount_tag' => (string) ($config['unit_tag'] ?? ''), 'tag_class' => (string) ($config['unit_tag_class'] ?? ''), 'days' => $remaining, 'title' => sprintf(__('按剩余 %d 天升级', 'aksrt'), $remaining), 'price_label' => $this->format_cash_label($price), 'show_price_label' => '', 'days_label' => sprintf(__('%d 天', 'aksrt'), $remaining), 'cta_label' => __('立即升级', 'aksrt'), 'is_upgrade_product' => true, 'upgrade_mode' => 'unit', ]; } } } // 月费会员升级永久会员:固定价格直接转为永久二级会员。 if (!$is_permanent && !empty($config['jump_s'])) { $price_minor = $this->parse_vip_money_minor((string) ($config['jump_price'] ?? '')); if ($price_minor > 0) { $price = \AKSRT\Ledger\AssetAmount::decimal_from_minor($price_minor); $show_minor = $this->parse_vip_money_minor((string) ($config['jump_show_price'] ?? '')); $show = \AKSRT\Ledger\AssetAmount::decimal_from_minor($show_minor); $items[] = [ 'package_id' => $this->encode_package_id(2, 903), 'level' => 2, 'index' => 903, 'action' => 'upgrade', 'price' => $price, 'base_price' => $price, 'show_price' => $show, 'discount_tag' => (string) ($config['jump_tag'] ?? ''), 'tag_class' => (string) ($config['jump_tag_class'] ?? ''), 'days' => 0, 'title' => __('升级为永久会员', 'aksrt'), 'price_label' => $this->format_cash_label($price), 'show_price_label' => $show_minor > 0 ? $this->format_cash_label($show) : '', 'days_label' => __('永久', 'aksrt'), 'cta_label' => __('立即升级', 'aksrt'), 'is_upgrade_product' => true, 'upgrade_mode' => 'jump', ]; } } // 永久会员升级永久会员。 if ($is_permanent) { $price_minor = $this->parse_vip_money_minor((string) ($config['permanent_price'] ?? '')); if ($price_minor > 0) { $price = \AKSRT\Ledger\AssetAmount::decimal_from_minor($price_minor); $show_minor = $this->parse_vip_money_minor((string) ($config['permanent_show_price'] ?? '')); $show = \AKSRT\Ledger\AssetAmount::decimal_from_minor($show_minor); $items[] = [ 'package_id' => $this->encode_package_id(2, 901), 'level' => 2, 'index' => 901, 'action' => 'upgrade', 'price' => $price, 'base_price' => $price, 'show_price' => $show, 'discount_tag' => (string) ($config['permanent_tag'] ?? ''), 'tag_class' => (string) ($config['permanent_tag_class'] ?? ''), 'days' => 0, 'title' => __('永久会员升级', 'aksrt'), 'price_label' => $this->format_cash_label($price), 'show_price_label' => $show_minor > 0 ? $this->format_cash_label($show) : '', 'days_label' => __('永久', 'aksrt'), 'cta_label' => __('立即升级', 'aksrt'), 'is_upgrade_product' => true, 'upgrade_mode' => 'permanent', ]; } } return $items; } /** * Points exchange packages from the structured 积分兑换明细 group. * * Each row is points / level / time / unit, where time is the literal * string "Permanent" for a permanent grant (reference theme contract). * * @param array $vip * @param array $levels * @return array> */ public function get_points_exchange_packages(array $vip, array $levels): array { $points_ex = is_array($vip['points_exchange'] ?? null) ? $vip['points_exchange'] : []; $rows = isset($points_ex['packages']) && is_array($points_ex['packages']) ? $points_ex['packages'] : []; $currency = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_currency_name() : __('积分', 'aksrt'); $out = []; $i = 0; foreach ($rows as $row) { if (!is_array($row)) { continue; } $lv = (int) ($row['level'] ?? 0); $pts = max(0, (int) ($row['points'] ?? 0)); if ($lv < 1 || $lv > 2 || $pts <= 0) { continue; } if (empty($levels[$lv]['enabled'])) { continue; } $is_permanent = !empty($row['is_permanent']); $time = max(0, (int) ($row['time'] ?? 0)); $unit = ((string) ($row['unit'] ?? 'day')) === 'month' ? 'month' : 'day'; $days = $is_permanent ? 0 : max(0, (int) ($row['days'] ?? 0)); if (!$is_permanent && $time <= 0) { continue; } $i++; $lv_name = (string) ($levels[$lv]['name'] ?? ('VIP' . $lv)); $out[] = [ 'package_id' => 5000 + $i, 'level' => $lv, 'index' => $i, 'points' => $pts, 'days' => $days, 'time' => $time, 'unit' => $unit, 'is_permanent' => $is_permanent, 'title' => $is_permanent ? sprintf(__('%s · 永久', 'aksrt'), $lv_name) : sprintf(__('%1$s · %2$d%3$s', 'aksrt'), $lv_name, $time, $unit === 'month' ? __('个月', 'aksrt') : __('天', 'aksrt')), 'price_label' => $pts . ' ' . $currency, 'days_label' => $is_permanent ? __('永久', 'aksrt') : ($unit === 'month' ? sprintf(__('%d 个月', 'aksrt'), $time) : sprintf(__('%d 天', 'aksrt'), $time)), 'duration_label' => $is_permanent ? __('永久', 'aksrt') : ($unit === 'month' ? sprintf(__('%d 个月', 'aksrt'), $time) : sprintf(__('%d 天', 'aksrt'), $time)), 'cta_label' => __('立即兑换', 'aksrt'), ]; } return $out; } /** * Apply renew discount/reduce to same-level packages. * * `customize` reuses the dedicated renew product's own price/show_price/tag, * so the caller passes that row's whole item in as $pkg. * * @param array $renew * @param array $upgrade * @return array{price:string,show_price:string,tag:string,tag_class:string}|null */ private function apply_action_price( array $pkg, string $action, array $renew, array $upgrade, int $target_level, int $user_level, int $expires, bool $is_permanent, int $days ): ?array { $base_price = (string) ($pkg['price'] ?? ''); $base_minor = $this->parse_vip_money_minor($base_price); if ($base_minor <= 0) { return null; } if ($action === 'renew' && $is_permanent) { return null; } if ($action !== 'renew') { return [ 'price' => \AKSRT\Ledger\AssetAmount::decimal_from_minor($base_minor), 'show_price' => '0.00', 'tag' => '', 'tag_class' => '', ]; } $type = sanitize_key((string) ($renew['price_type'] ?? 'discount')); // discount/reduce 的标签由优惠幅度计算得出,若实际没有优惠则属于虚假折扣。 $computed_tag = in_array($type, ['discount', 'reduce'], true); $price_minor = $base_minor; $show_minor = $base_minor; $tag = ''; $tag_class = ''; if ($type === 'discount') { $discount = (float) ($renew['discount'] ?? 8); if ($discount <= 0) { $discount = 8; } // 折值:8 = 八折(参考主题 slider 0.1–9.9 语义)。 $discount = max(0.1, min(9.9, $discount)); $price_minor = \AKSRT\Ledger\AssetAmount::percentage($base_minor, $discount * 10); $tag = sprintf( /* translators: %s: discount like 8 */ __('%s折优惠', 'aksrt'), rtrim(rtrim(number_format($discount, 1, '.', ''), '0'), '.') ?: '0' ); } elseif ($type === 'reduce') { $reduce_minor = $this->parse_vip_money_minor($renew['reduce'] ?? '20'); $price_minor = max(1, $base_minor - $reduce_minor); $tag = sprintf( /* translators: %s: amount */ __('续费立减%s', 'aksrt'), $this->format_cash_label(\AKSRT\Ledger\AssetAmount::decimal_from_minor($reduce_minor)) ); } elseif ($type === 'customize') { // 自定义商品:沿用商品自身的原价、促销标签与标签颜色。 $show_minor = $this->parse_vip_money_minor((string) ($pkg['show_price'] ?? '')); $tag = (string) ($pkg['tag'] ?? ''); $tag_class = (string) ($pkg['tag_class'] ?? ''); } else { $show_minor = 0; $tag = __('续费同价', 'aksrt'); } if ($price_minor < 1) { $price_minor = 1; } if ($show_minor > 0 && $price_minor >= $show_minor) { // 参考主题契约:原价仅在低于执行价时展示。 $show_minor = 0; if ($computed_tag) { $tag = __('续费', 'aksrt'); } } return [ 'price' => \AKSRT\Ledger\AssetAmount::decimal_from_minor($price_minor), 'show_price' => \AKSRT\Ledger\AssetAmount::decimal_from_minor($show_minor > $price_minor ? $show_minor : 0), 'tag' => $tag, 'tag_class' => $tag_class, ]; } public function encode_package_id(int $level, int $index): int { return (max(1, min(2, $level)) * 1000) + max(1, $index); } /** * @return array{level:int,index:int}|null */ public function decode_package_id(int $package_id): ?array { if ($package_id < 1001) { return null; } $level = intdiv($package_id, 1000); $index = $package_id % 1000; if ($level < 1 || $level > 2 || $index < 1) { return null; } return ['level' => $level, 'index' => $index]; } /** * @return array */ private function available_payment_methods(): array { $methods = function_exists('aksrt_list_cash_payment_methods') ? aksrt_list_cash_payment_methods(true) : []; // 卡密兑换会员渠道(后台 会员购买 → 启用卡密兑换会员功能): // 提交走 /pay/card-pass/redeem type=vip,不创建支付订单。 $card_pass = $this->vip_card_pass_context(); if (!empty($card_pass['enabled'])) { $methods[] = ['id' => 'card_pass', 'name' => __('卡密', 'aksrt')]; } // 卡密渠道追加后需按「收款接口显示顺序」重新定位(含 card_pass 槽位)。 return function_exists('aksrt_sort_payment_methods') ? aksrt_sort_payment_methods($methods) : $methods; } /** * 卡密兑换会员渠道上下文(ChargeCard vip 公开选项)。 * * @return array{enabled:bool,desc:string,only_password:bool} */ private function vip_card_pass_context(): array { $empty = ['enabled' => false, 'desc' => '', 'only_password' => false]; if (!class_exists('\AKSRT\Payment\ChargeCard')) { return $empty; } $opts = \AKSRT\Payment\ChargeCard::get_instance()->get_public_options(); return is_array($opts['vip'] ?? null) ? array_merge($empty, $opts['vip']) : $empty; } private function format_cash_label(string $amount): string { $symbol = '¥'; if (method_exists($this, 'get_paid_display_context')) { $paid = $this->get_paid_display_context(); $symbol = (string) ($paid['display']['currency_symbol'] ?? '¥'); } elseif (class_exists('\AKSRT\Settings\Settings')) { $paid = \AKSRT\Settings\Settings::get_paid_content_settings(); $symbol = (string) ($paid['display']['currency_symbol'] ?? '¥'); } $n = function_exists('aksrt_format_price') ? aksrt_format_price($amount) : (string) $amount; return $symbol . $n; } private function parse_vip_money_minor($value): int { if (!is_string($value)) { return 0; } try { return \AKSRT\Ledger\AssetAmount::balance_minor($value); } catch (\InvalidArgumentException $e) { return 0; } } private function format_expires_label(int $expires, int $level): string { if ($level <= 0) { return __('未开通', 'aksrt'); } if ($expires <= 0) { return __('永久有效', 'aksrt'); } if ($expires < time()) { return __('已过期', 'aksrt'); } return sprintf( /* translators: %s: date */ __('到期时间:%s', 'aksrt'), wp_date('Y-m-d H:i', $expires) ); } }