__('不付费', 'aksrt'), AKSRT_PAY_TYPE_READING => __('付费阅读', 'aksrt'), AKSRT_PAY_TYPE_DOWNLOAD => __('付费下载', 'aksrt'), AKSRT_PAY_TYPE_GALLERY => __('付费图库', 'aksrt'), AKSRT_PAY_TYPE_VIDEO => __('付费视频', 'aksrt'), AKSRT_PAY_TYPE_VIP => __('VIP 专属', 'aksrt'), ]; } /** * Cash payment methods available for content/topic cashiers. * * @return array */ if (!function_exists('aksrt_list_cash_payment_methods')) { function aksrt_list_cash_payment_methods(bool $logged_in = true): array { $ids = $logged_in ? ['balance', 'alipay', 'wechatpay', 'paypal', 'stripe', 'epay_alipay', 'epay_wxpay'] : ['alipay', 'wechatpay', 'paypal', 'stripe', 'epay_alipay', 'epay_wxpay']; if (!class_exists('\AKSRT\Payment\Gateway')) { // No gateway manager → no real methods (avoid dead balance/alipay stubs). return []; } $methods = []; $gw = \AKSRT\Payment\Gateway::get_instance(); foreach ($ids as $id) { $g = $gw->get_gateway($id); if (!$g) { continue; } if (method_exists($g, 'is_configured') && !$g->is_configured()) { continue; } $methods[] = [ 'id' => $id, 'name' => method_exists($g, 'get_name') ? (string) $g->get_name() : $id, ]; } // Do not inject a fake balance/alipay option when nothing is configured — // callers should surface “暂无可用支付方式” instead of a dead method. return $methods; } } // ============================================================ // 元数据读取 // ============================================================ if (!function_exists('aksrt_get_pay_meta')) { function aksrt_get_pay_meta($post = null): array { $p = get_post($post); if (!($p instanceof WP_Post)) return aksrt_default_pay_meta(); $meta = get_post_meta($p->ID, \AKSRT\Core\DomainModel::PAY_META_KEY, true); if (is_array($meta) && isset($meta['pay_type'])) { return wp_parse_args(\AKSRT\Core\DomainModel::sanitize_pay_meta($meta), aksrt_default_pay_meta()); } return aksrt_default_pay_meta(); } } if (!function_exists('aksrt_default_pay_meta')) { function aksrt_default_pay_meta(): array { return \AKSRT\Core\DomainModel::default_pay_meta(); return [ 'pay_type' => AKSRT_PAY_TYPE_NO, 'price' => 0.0, 'original_price' => 0.0, 'vip1_price' => 0.0, 'vip2_price' => 0.0, 'version' => '', 'size' => '', 'license' => '', 'compat' => '', 'demo_url' => '', 'downloads' => [], 'require_login' => false, 'require_captcha' => false, 'attributes' => [], 'gallery' => [], 'video_url' => '', ]; } } if (!function_exists('aksrt_is_downloadable_content')) { function aksrt_is_downloadable_content($post = null): bool { $p = get_post($post); if (!($p instanceof WP_Post)) { return false; } $meta = aksrt_get_pay_meta($p); if (($meta['pay_type'] ?? AKSRT_PAY_TYPE_NO) !== AKSRT_PAY_TYPE_DOWNLOAD) { return false; } return !empty($meta['downloads']) && is_array($meta['downloads']); } } if (!function_exists('aksrt_get_download_box_meta')) { function aksrt_get_download_box_meta($post = null): array { $p = get_post($post); if (!($p instanceof WP_Post)) { return []; } if (function_exists('aksrt_get_resource_meta') && function_exists('aksrt_is_resource_post') && aksrt_is_resource_post($p)) { $meta = aksrt_get_resource_meta($p); $meta['subject_label'] = __('资源', 'aksrt'); return $meta; } $pay_meta = aksrt_get_pay_meta($p); $download_count = (int) get_post_meta($p->ID, \AKSRT\Core\DomainModel::RESOURCE_DOWNLOAD_COUNT_KEY, true); $view_count = (int) get_post_meta($p->ID, \AKSRT\Core\DomainModel::POST_VIEW_COUNT_KEY, true); $virtual_sales = max(0, (int) ($pay_meta['virtual_sales'] ?? 0)); $sales_volume = (int) get_post_meta($p->ID, \AKSRT\Core\DomainModel::RESOURCE_SALES_VOLUME_KEY, true); $show_sales = !isset($pay_meta['show_sales']) || !empty($pay_meta['show_sales']); $pay_modo = (($pay_meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0'; $pay_limit = (int) ($pay_meta['pay_limit'] ?? 0); if (!in_array($pay_limit, [0, 1, 2], true)) { $pay_limit = 0; } $over_price = max(0.0, (float) ($pay_meta['download_limit_over_price'] ?? 0)); return [ 'version' => (string) ($pay_meta['version'] ?? ''), 'size' => (string) ($pay_meta['size'] ?? ''), 'price' => (float) ($pay_meta['price'] ?? 0), 'points_price' => (int) ($pay_meta['points_price'] ?? 0), 'pay_modo' => $pay_modo, 'pay_limit' => $pay_limit, 'original_price' => (float) ($pay_meta['original_price'] ?? 0), 'vip1_price' => (float) ($pay_meta['vip1_price'] ?? 0), 'vip2_price' => (float) ($pay_meta['vip2_price'] ?? 0), 'vip1_points' => (int) ($pay_meta['vip1_points'] ?? 0), 'vip2_points' => (int) ($pay_meta['vip2_points'] ?? 0), 'pay_type' => (string) ($pay_meta['pay_type'] ?? AKSRT_PAY_TYPE_NO), 'license' => (string) ($pay_meta['license'] ?? ''), 'compat' => (string) ($pay_meta['compat'] ?? ''), 'demo_url' => (string) ($pay_meta['demo_url'] ?? ''), 'downloads' => array_values(is_array($pay_meta['downloads'] ?? null) ? $pay_meta['downloads'] : []), 'download_count' => max(0, $download_count), 'view_count' => max(0, $view_count), 'virtual_sales' => $virtual_sales, 'sales_volume' => max(0, $sales_volume), // Zibll: virtual (pay_cuont) + real sales_volume; fall back to downloads when no sales yet. 'sales_count' => $show_sales ? ($virtual_sales + max(0, $sales_volume, $download_count)) : 0, 'show_sales' => $show_sales, 'download_limit_over_price' => $over_price, 'require_login' => !empty($pay_meta['require_login']), 'require_captcha' => !empty($pay_meta['require_captcha']), 'subject_label' => __('内容', 'aksrt'), ]; } } /** * Latest paid content order for a user/post (resource product_type). * * @return array|null */ if (!function_exists('aksrt_get_user_content_paid_order')) { function aksrt_get_user_content_paid_order(int $post_id, int $user_id = 0): ?array { $uid = $user_id > 0 ? $user_id : get_current_user_id(); if ($uid <= 0 || $post_id <= 0 || !class_exists('\AKSRT\Payment\OrderRepository')) { return null; } $orders = \AKSRT\Payment\OrderRepository::get_instance()->get_user_orders($uid, [ 'product_type' => \AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE, 'status' => '', 'limit' => 20, 'offset' => 0, ]); foreach ($orders as $order) { if ((int) ($order['product_id'] ?? 0) !== $post_id) { continue; } $status = (string) ($order['status'] ?? ''); if (\AKSRT\Core\DomainModel::is_paid_order_status($status)) { return $order; } } return null; } } /** * Increment real sales volume for a paid content post (idempotent per order via order meta note flag optional). */ if (!function_exists('aksrt_bump_content_sales_volume')) { function aksrt_bump_content_sales_volume(int $post_id, int $delta = 1): int { if ($post_id <= 0 || $delta === 0) { return 0; } $key = \AKSRT\Core\DomainModel::RESOURCE_SALES_VOLUME_KEY; $cur = (int) get_post_meta($post_id, $key, true); $next = max(0, $cur + $delta); update_post_meta($post_id, $key, $next); return $next; } } /** * Whether guest cash purchase is allowed (Zibll pay_no_logged_in + pay_limit=0). */ if (!function_exists('aksrt_allow_guest_content_pay')) { function aksrt_allow_guest_content_pay($post = null): bool { $p = get_post($post); if (!($p instanceof WP_Post)) { return false; } $meta = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($p) : []; $pay_limit = (int) ($meta['pay_limit'] ?? 0); if ($pay_limit > 0) { return false; } $pay_modo = (($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0'; if ($pay_modo === 'points') { return false; // points require login } $order = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_order_settings() : []; $paid = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_paid_content_settings() : []; $allow = !empty($order['no_logged_in']) || !empty($paid['no_logged_in']); /** * Filter guest content pay allow. * * @param bool $allow * @param WP_Post $p */ return (bool) apply_filters('aksrt_allow_guest_content_pay', $allow, $p); } } /** * Cookie lifetime days for guest paid content (Zibll pay_cookie_day). */ if (!function_exists('aksrt_guest_pay_cookie_days')) { function aksrt_guest_pay_cookie_days(int $post_id = 0): int { $days = 15; if (class_exists('\AKSRT\Settings\Settings')) { $order = \AKSRT\Settings\Settings::get_order_settings(); $days = max(1, min(31, (int) ($order['cookie_day'] ?? 15))); } /** * @param int $days * @param int $post_id */ return max(1, (int) apply_filters('aksrt_guest_pay_cookie_days', $days, $post_id)); } } /** * Client IP helper for cookie token binding. */ if (!function_exists('aksrt_pay_client_ip')) { function aksrt_pay_client_ip(): string { if (function_exists('aksrt_get_client_ip')) { return (string) aksrt_get_client_ip(); } return (string) ($_SERVER['REMOTE_ADDR'] ?? ''); } } /** * Build token material for guest order cookie (zibll-style). * * @param array $order */ if (!function_exists('aksrt_content_order_cookie_token_key')) { function aksrt_content_order_cookie_token_key(array $order): string { $meta = is_array($order['meta_payload'] ?? null) ? $order['meta_payload'] : []; $ip = (string) ($meta['ip_address'] ?? $order['ip_address'] ?? ''); $id = (string) ($order['id'] ?? $order['order_number'] ?? ''); $post_id = (int) ($order['product_id'] ?? $meta['post_id'] ?? 0); $num = (string) ($order['order_number'] ?? ''); $amount = function_exists('aksrt_format_price') ? aksrt_format_price((float) ($order['amount'] ?? 0)) : (string) round((float) ($order['amount'] ?? 0), 2); return $id . '_' . $post_id . '_' . trim($num) . '_' . $amount . '_' . trim($ip); } } /** * Whether guest cash purchase is allowed for forum topic / plate follow. * Reuses global pay_no_logged_in (same switch as content). * * @param WP_Post|int|null $post Topic or plate post (optional filter context). */ if (!function_exists('aksrt_allow_guest_forum_pay')) { function aksrt_allow_guest_forum_pay($post = null): bool { $order = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_order_settings() : []; $paid = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_paid_content_settings() : []; $allow = !empty($order['no_logged_in']) || !empty($paid['no_logged_in']); /** * @param bool $allow * @param WP_Post|int|null $post */ return (bool) apply_filters('aksrt_allow_guest_forum_pay', $allow, $post); } } /** * Set guest paid cookies after cash order paid (content / topic / plate_follow). * * @param array $order */ if (!function_exists('aksrt_set_guest_pay_cookies')) { function aksrt_set_guest_pay_cookies(array $order): void { $post_id = (int) ($order['product_id'] ?? 0); $order_number = (string) ($order['order_number'] ?? ''); if ($post_id <= 0 || $order_number === '') { return; } $days = aksrt_guest_pay_cookie_days($post_id); $expire = time() + (DAY_IN_SECONDS * $days); $path = defined('COOKIEPATH') && COOKIEPATH ? COOKIEPATH : '/'; $domain = defined('COOKIE_DOMAIN') ? COOKIE_DOMAIN : ''; $secure = is_ssl(); $httponly = true; setcookie('aksrt_pay_' . $post_id, $order_number, $expire, $path, $domain, $secure, $httponly); $_COOKIE['aksrt_pay_' . $post_id] = $order_number; $token = md5(aksrt_content_order_cookie_token_key($order)); $token_name = 'aksrt_pay_token_' . md5($order_number); setcookie($token_name, $token, $expire, $path, $domain, $secure, $httponly); $_COOKIE[$token_name] = $token; } } /** * Set guest paid cookies after content order paid (Zibll zibpay_posts_order_created_set_cookie). * * @param array $order */ if (!function_exists('aksrt_set_guest_content_pay_cookies')) { function aksrt_set_guest_content_pay_cookies(array $order): void { aksrt_set_guest_pay_cookies($order); } } /** * Resolve guest paid order from browser cookie for a product. * * @return array|null */ if (!function_exists('aksrt_get_guest_paid_order')) { function aksrt_get_guest_paid_order(int $product_id, string $product_type = ''): ?array { if ($product_id <= 0 || empty($_COOKIE['aksrt_pay_' . $product_id])) { return null; } if (!class_exists('\AKSRT\Payment\OrderRepository') || !class_exists('\AKSRT\Core\DomainModel')) { return null; } $order_number = sanitize_text_field((string) $_COOKIE['aksrt_pay_' . $product_id]); if ($order_number === '') { return null; } $order = \AKSRT\Payment\OrderRepository::get_instance()->get($order_number); if (!$order) { return null; } $ptype = sanitize_key((string) ($order['product_type'] ?? '')); $want = sanitize_key($product_type); if ($want !== '' && $ptype !== $want) { return null; } if ((int) ($order['product_id'] ?? 0) !== $product_id) { return null; } // Guest orders only (logged-in users use user_id access). if ((int) ($order['user_id'] ?? -1) !== 0) { return null; } $status = (string) ($order['status'] ?? ''); if (!\AKSRT\Core\DomainModel::is_paid_order_status($status)) { return null; } $paid_at = (string) ($order['paid_at'] ?? $order['created_at'] ?? ''); if ($paid_at !== '') { $ts = strtotime($paid_at); $days = aksrt_guest_pay_cookie_days($product_id); if ($ts !== false && ($ts + DAY_IN_SECONDS * $days) < time()) { return null; } } $token_name = 'aksrt_pay_token_' . md5($order_number); $cookie_token = isset($_COOKIE[$token_name]) ? (string) $_COOKIE[$token_name] : ''; $expect = md5(aksrt_content_order_cookie_token_key($order)); if ($cookie_token === '' || !hash_equals($expect, $cookie_token)) { return null; } return $order; } } /** * Resolve guest paid order from browser cookie for a post (content/resource). * * @return array|null */ if (!function_exists('aksrt_get_guest_content_paid_order')) { function aksrt_get_guest_content_paid_order(int $post_id): ?array { $type = class_exists('\AKSRT\Core\DomainModel') ? \AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE : 'resource'; return aksrt_get_guest_paid_order($post_id, $type); } } /** * Whether current request has paid access via guest cookie (content/resource product). */ if (!function_exists('aksrt_guest_has_paid_access')) { function aksrt_guest_has_paid_access(int $post_id): bool { return null !== aksrt_get_guest_content_paid_order($post_id); } } /** * Guest paid plate follow access (cookie). */ if (!function_exists('aksrt_guest_has_plate_follow_access')) { function aksrt_guest_has_plate_follow_access(int $plate_id): bool { $type = class_exists('\AKSRT\Core\DomainModel') ? \AKSRT\Core\DomainModel::ORDER_PRODUCT_PLATE_FOLLOW : 'plate_follow'; return null !== aksrt_get_guest_paid_order($plate_id, $type); } } /** * Resolve the amount a given user should pay for a post (Zibll VIP tier min). * * VIP tier prices only apply when configured as a positive discount (< base). * Full free for VIP uses vip_free_download / exempt roles (handled separately). * * @param int|WP_Post|null $post * @param int $user_id * @return array{amount:float,base:float,vip_amount:float,modo:string,currency:string,is_free:bool,pay_limit:int,vip_level:int,original:float,vip1:float,vip2:float} */ if (!function_exists('aksrt_get_user_pay_price')) { function aksrt_get_user_pay_price($post = null, int $user_id = 0): array { $meta = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($post) : \AKSRT\Core\DomainModel::default_pay_meta(); $pay_modo = (($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0'; $pay_limit = (int) ($meta['pay_limit'] ?? 0); if (!in_array($pay_limit, [0, 1, 2], true)) { $pay_limit = 0; } $uid = $user_id > 0 ? $user_id : (function_exists('get_current_user_id') ? (int) get_current_user_id() : 0); $vip_level = 0; if ($uid > 0 && function_exists('aksrt_get_user_vip_level')) { $vip_level = (int) aksrt_get_user_vip_level($uid); } if ($uid > 0 && function_exists('aksrt_is_vip_exempt') && aksrt_is_vip_exempt($uid)) { $vip_level = max($vip_level, 2); } $currency = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_currency_name() : __('积分', 'aksrt'); $cash_symbol = '¥'; if (class_exists('\AKSRT\Settings\Settings')) { $paid = \AKSRT\Settings\Settings::get_paid_content_settings(); $cash_symbol = (string) ($paid['display']['currency_symbol'] ?? '¥'); } if ($pay_modo === 'points') { $base = (float) ($meta['points_price'] ?? 0); $vip1 = (float) ($meta['vip1_points'] ?? 0); $vip2 = (float) ($meta['vip2_points'] ?? 0); $currency_label = $currency; } else { $base = (float) ($meta['price'] ?? 0); $vip1 = (float) ($meta['vip1_price'] ?? 0); $vip2 = (float) ($meta['vip2_price'] ?? 0); $currency_label = $cash_symbol; } // Positive VIP tier prices only (0 defaults mean "not configured"). $vip_amount = -1.0; if ($vip_level >= 2 && $vip2 > 0) { $vip_amount = $vip2; } elseif ($vip_level >= 1 && $vip1 > 0) { $vip_amount = $vip1; } $amount = $base; if ($vip_amount >= 0 && ($base <= 0 || $vip_amount < $base)) { $amount = $vip_amount; } // Role/VIP free-download exemption (global setting). if ($amount > 0 && $uid > 0 && function_exists('aksrt_is_download_credit_exempt_user') && aksrt_is_download_credit_exempt_user($uid)) { $amount = 0.0; } $original = (float) ($meta['original_price'] ?? 0); return [ 'amount' => max(0.0, (float) $amount), 'base' => max(0.0, (float) $base), 'vip_amount' => $vip_amount >= 0 ? max(0.0, (float) $vip_amount) : -1.0, 'modo' => $pay_modo, 'currency' => $currency_label, 'is_free' => $amount <= 0, 'pay_limit' => $pay_limit, 'vip_level' => $vip_level, 'original' => $original, 'vip1' => max(0.0, (float) $vip1), 'vip2' => max(0.0, (float) $vip2), ]; } } /** * Whether content cashier may accept coupons for this post (zibll coupon_post_s || coupon_s). */ if (!function_exists('aksrt_is_content_coupon_allowed')) { function aksrt_is_content_coupon_allowed($post = null): bool { $meta = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($post) : []; if (!empty($meta['coupon_s'])) { return true; } if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_content_coupon_settings')) { $cfg = \AKSRT\Settings\Settings::get_content_coupon_settings(); return !empty($cfg['enabled']); } if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_paid_content_settings')) { $paid = \AKSRT\Settings\Settings::get_paid_content_settings(); return !empty($paid['coupon_post_s']); } return false; } } /** * Buyer-facing 推广折扣 amount for a post (0 if no eligible referrer). * Cash only; mirrors zibpay_get_post_rebate_discount. */ if (!function_exists('aksrt_get_post_rebate_discount')) { function aksrt_get_post_rebate_discount($post = null, int $user_id = 0): float { $meta = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($post) : []; $fixed = max(0.0, (float) ($meta['pay_rebate_discount'] ?? 0)); if ($fixed <= 0) { return 0.0; } if (!class_exists('\AKSRT\Settings\Settings') || !method_exists('\AKSRT\Settings\Settings', 'get_payment_affiliate_settings')) { return 0.0; } $aff = \AKSRT\Settings\Settings::get_payment_affiliate_settings(); if (empty($aff['enabled'])) { return 0.0; } $uid = $user_id > 0 ? $user_id : (function_exists('get_current_user_id') ? (int) get_current_user_id() : 0); $referrer_id = 0; if (class_exists('\AKSRT\Credits\Invite') && method_exists(\AKSRT\Credits\Invite::get_instance(), 'get_referrer')) { $referrer_id = (int) \AKSRT\Credits\Invite::get_instance()->get_referrer($uid); } if ($referrer_id <= 0) { return 0.0; } // Need a positive ratio for referrer (VIP tier of referrer preferred). $ratio = (int) ($aff['ratio'] ?? 0); $user_ratio = is_array($aff['user_ratio'] ?? null) ? $aff['user_ratio'] : []; $ref_vip = 0; if (function_exists('aksrt_get_user_vip_level')) { $ref_vip = (int) aksrt_get_user_vip_level($referrer_id); } if ($ref_vip >= 2 && !empty($user_ratio['vip2'])) { $ratio = (int) $user_ratio['vip2']; } elseif ($ref_vip >= 1 && !empty($user_ratio['vip1'])) { $ratio = (int) $user_ratio['vip1']; } elseif (isset($user_ratio['user'])) { $ratio = (int) $user_ratio['user']; } if ($ratio <= 0) { return 0.0; } return round($fixed, 2); } } /** * Whether cash checkout may use points deduction (zibll points_deduction). * * zibll: zibpay_is_allow_points_deduction() always returns false ("暂未启用"). * Keep the same contract: schema/UI may exist, runtime is hard-off unless filter overrides. */ if (!function_exists('aksrt_is_allow_points_deduction')) { function aksrt_is_allow_points_deduction(string $context = 'content'): bool { // Mirror zibll hard-off first. $allowed = false; if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_all')) { $all = \AKSRT\Settings\Settings::get_all(); // Even if switch on, default remains false like zibll until product enables it. if (!empty($all['points_deduction_s']) && class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'credits_enabled') && \AKSRT\Settings\Settings::credits_enabled()) { $allowed = false; // still disabled at runtime } } return (bool) apply_filters('aksrt_is_allow_points_deduction', $allowed, $context); } } /** * Content cashier does not accept card_pass payment_method. * Card-pass for balance/VIP/points is user-center redeem (zibll order types 4/8/9), * not content pay types (reading/download/gallery/video). */ if (!function_exists('aksrt_is_content_card_pass_pay_allowed')) { function aksrt_is_content_card_pass_pay_allowed($post = null): bool { return (bool) apply_filters('aksrt_is_content_card_pass_pay_allowed', false, $post); } } // ============================================================ // 状态判断 // ============================================================ if (!function_exists('aksrt_is_paid_post')) { function aksrt_is_paid_post($post = null): bool { return aksrt_get_pay_meta($post)['pay_type'] !== AKSRT_PAY_TYPE_NO; } } if (!function_exists('aksrt_is_paid_reading')) { function aksrt_is_paid_reading($post = null): bool { return aksrt_get_pay_meta($post)['pay_type'] === AKSRT_PAY_TYPE_READING; } } if (!function_exists('aksrt_is_paid_download')) { function aksrt_is_paid_download($post = null): bool { return aksrt_get_pay_meta($post)['pay_type'] === AKSRT_PAY_TYPE_DOWNLOAD; } } if (!function_exists('aksrt_is_paid_gallery')) { function aksrt_is_paid_gallery($post = null): bool { return aksrt_get_pay_meta($post)['pay_type'] === AKSRT_PAY_TYPE_GALLERY; } } if (!function_exists('aksrt_is_paid_video')) { function aksrt_is_paid_video($post = null): bool { return aksrt_get_pay_meta($post)['pay_type'] === AKSRT_PAY_TYPE_VIDEO; } } if (!function_exists('aksrt_is_vip_only')) { function aksrt_is_vip_only($post = null): bool { return aksrt_get_pay_meta($post)['pay_type'] === AKSRT_PAY_TYPE_VIP; } } // ============================================================ // 付费状态检查 // ============================================================ if (!function_exists('aksrt_has_paid_access')) { function aksrt_has_paid_access($post = null, int $user_id = 0): bool { $p = get_post($post); if (!($p instanceof WP_Post)) return true; // 单一真相:付费访问由 \AKSRT\Content\Visibility(基于订单 + VIP)裁决。 if (class_exists('\AKSRT\Content\Visibility')) { $state = \AKSRT\Content\Visibility::get_post_access_state($p, $user_id); return !empty($state['has_paid_access']); } // 兜底:仅在 service 不可用时使用,避免离线环境直接拒绝。 $meta = aksrt_get_pay_meta($p); if ($meta['pay_type'] === AKSRT_PAY_TYPE_NO) return true; if ((float) $meta['price'] <= 0 && $meta['pay_type'] !== AKSRT_PAY_TYPE_VIP) return true; $uid = $user_id > 0 ? $user_id : get_current_user_id(); if ($uid > 0 && function_exists('aksrt_is_vip_exempt') && aksrt_is_vip_exempt($uid)) return true; if ($uid > 0 && get_user_meta($uid, '_aksrt_paid_' . $p->ID, true)) return true; return false; } } if (!function_exists('aksrt_can_access_content')) { function aksrt_can_access_content($post = null): bool { $p = get_post($post); if (!($p instanceof WP_Post)) return true; if (class_exists('\AKSRT\Content\Visibility')) { return \AKSRT\Content\Visibility::can_view_paid_content($p); } $pay_type = aksrt_get_pay_meta($p)['pay_type']; if ($pay_type === AKSRT_PAY_TYPE_NO) return true; // 只有 reading/gallery/video 需要隐藏内容 if (!in_array($pay_type, [AKSRT_PAY_TYPE_READING, AKSRT_PAY_TYPE_GALLERY, AKSRT_PAY_TYPE_VIDEO], true)) { return true; } return aksrt_has_paid_access($p); } } if (!function_exists('aksrt_can_download')) { /** * 下载授权门禁 —— 单一合同。 * * 实际判定交给 \AKSRT\Content\Visibility::can_download_resource()。 * 不再使用 download_credit 做按次扣积分门禁。 * * @param int|WP_Post|null $post * @param int $user_id 0 表示当前用户 * @return true|WP_Error */ function aksrt_can_download($post = null, int $user_id = 0) { if (class_exists('\AKSRT\Content\Visibility')) { return \AKSRT\Content\Visibility::can_download_resource($post, $user_id); } $p = get_post($post); if (!($p instanceof WP_Post)) { return new WP_Error('aksrt_invalid', __('无效内容。', 'aksrt')); } $meta = aksrt_get_pay_meta($p); if (($meta['pay_type'] ?? '') !== AKSRT_PAY_TYPE_DOWNLOAD) { return new WP_Error('aksrt_not_downloadable', __('该内容不是可下载资源。', 'aksrt')); } $paid_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_paid_content_settings() : ['download_login' => true]; $uid = $user_id > 0 ? $user_id : get_current_user_id(); if (!empty($paid_settings['download_login']) && $uid <= 0) { $login_url = wp_login_url(get_permalink($p)); return new WP_Error('aksrt_need_login', __('请先登录。', 'aksrt'), ['login_url' => $login_url]); } if ($uid > 0 && function_exists('aksrt_has_paid_access') && aksrt_has_paid_access($p, $uid)) { return true; } if ($uid > 0 && function_exists('aksrt_is_vip_exempt') && aksrt_is_vip_exempt($uid)) { return true; } // Fallback without Content_Visibility: require purchase when priced. $cost = function_exists('aksrt_get_download_cost') ? aksrt_get_download_cost($p) : 0; if ($cost > 0) { return new WP_Error('aksrt_need_purchase', __('请先购买后再下载。', 'aksrt'), [ 'status' => 402, 'cost' => $cost, 'post_id'=> (int) $p->ID, ]); } return true; } } if (!function_exists('aksrt_get_download_cost')) { function aksrt_get_download_cost($post = null): int { if (function_exists('aksrt_resource_download_cost')) { return (int) aksrt_resource_download_cost($post); } $meta = aksrt_get_pay_meta($post); return (int) ceil((float) $meta['price']); } } /** * URL of the Zibll-style single-post download page (page template + ?post=ID). */ if (!function_exists('aksrt_get_single_download_page_url')) { function aksrt_get_single_download_page_url($post = null): string { $p = get_post($post); if (!($p instanceof WP_Post)) { return ''; } $page_url = ''; if (function_exists('aksrt_find_template_page')) { $page = aksrt_find_template_page('templates/pages/page-download-file.php'); if ($page instanceof WP_Post) { $page_url = get_permalink($page); } } if ($page_url === '') { // Fallback: look for any page using the template via slug convention. $page_url = home_url('/download-file/'); } return add_query_arg('post', (int) $p->ID, $page_url); } } /** * Whether independent download page mode is enabled (Zibll down_alone_page). */ if (!function_exists('aksrt_is_download_alone_page_enabled')) { function aksrt_is_download_alone_page_enabled(): bool { if (!class_exists('\AKSRT\Settings\Settings')) { return false; } $paid = \AKSRT\Settings\Settings::get_paid_content_settings(); return !empty($paid['down_alone_page']) || !empty($paid['download']['alone_page']); } } // ============================================================ // 价格显示 // ============================================================ if (!function_exists('aksrt_get_pay_price_label')) { function aksrt_get_pay_price_label($post = null): array { $meta = aksrt_get_pay_meta($post); $priced = function_exists('aksrt_get_user_pay_price') ? aksrt_get_user_pay_price($post) : null; $pay_modo = is_array($priced) ? ((($priced['modo'] ?? '0') === 'points') ? 'points' : '0') : ((($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0'); $pay_limit = is_array($priced) ? (int) ($priced['pay_limit'] ?? 0) : (int) ($meta['pay_limit'] ?? 0); $vip_level = is_array($priced) ? (int) ($priced['vip_level'] ?? 0) : 0; $currency = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_currency_name() : __('积分', 'aksrt'); $cash_symbol = '¥'; if (class_exists('\AKSRT\Settings\Settings')) { $paid = \AKSRT\Settings\Settings::get_paid_content_settings(); $cash_symbol = (string) ($paid['display']['currency_symbol'] ?? '¥'); } // pay_type VIP-only or pay_limit not met → member-exclusive badge. $member_blocked = ($meta['pay_type'] === AKSRT_PAY_TYPE_VIP) || ($pay_limit > 0 && $vip_level < $pay_limit); if ($member_blocked) { return [ 'text' => __('会员专属', 'aksrt'), 'free' => false, 'amount' => 0, 'base' => is_array($priced) ? (float) ($priced['base'] ?? 0) : 0, 'modo' => $pay_modo, 'pay_limit' => $pay_limit, ]; } $price = is_array($priced) ? (float) ($priced['amount'] ?? 0) : ($pay_modo === 'points' ? (float) ($meta['points_price'] ?? 0) : (float) ($meta['price'] ?? 0)); $base = is_array($priced) ? (float) ($priced['base'] ?? $price) : $price; if ($price <= 0) { return [ 'text' => __('免费', 'aksrt'), 'free' => true, 'amount' => 0, 'base' => $base, 'modo' => $pay_modo, 'pay_limit' => $pay_limit, ]; } if ($pay_modo === 'points') { return [ 'text' => sprintf(__('%s %s', 'aksrt'), aksrt_format_price($price), $currency), 'free' => false, 'amount' => $price, 'base' => $base, 'modo' => 'points', 'pay_limit' => $pay_limit, ]; } return [ 'text' => $cash_symbol . aksrt_format_price($price), 'free' => false, 'amount' => $price, 'base' => $base, 'modo' => '0', 'pay_limit' => $pay_limit, ]; } } if (!function_exists('aksrt_format_price')) { function aksrt_format_price(float $price): string { if (fmod($price, 1.0) === 0.0) return (string) (int) $price; return rtrim(rtrim(number_format($price, 2, '.', ''), '0'), '.'); } } // ============================================================ // 付费类型徽章 // ============================================================ if (!function_exists('aksrt_get_pay_type_badge')) { function aksrt_get_pay_type_badge(string $pay_type): string { $badges = [ AKSRT_PAY_TYPE_READING => ['label' => __('付费阅读', 'aksrt'), 'color' => '#EF4444', 'icon' => 'fa-lock'], AKSRT_PAY_TYPE_DOWNLOAD => ['label' => __('付费下载', 'aksrt'), 'color' => '#F97316', 'icon' => 'fa-download'], AKSRT_PAY_TYPE_GALLERY => ['label' => __('付费图库', 'aksrt'), 'color' => '#10B981', 'icon' => 'fa-images'], AKSRT_PAY_TYPE_VIDEO => ['label' => __('付费视频', 'aksrt'), 'color' => '#8B5CF6', 'icon' => 'fa-play-circle'], AKSRT_PAY_TYPE_VIP => ['label' => __('VIP 专属', 'aksrt'), 'color' => '#3B82F6', 'icon' => 'fa-crown'], ]; if (!isset($badges[$pay_type])) return ''; $b = $badges[$pay_type]; return sprintf( ' %s', esc_attr($b['color']), esc_attr($b['icon']), esc_html($b['label']) ); } } /** * Zibll zibpay_get_service() — service bullets under pay/download box. * * @return string HTML list of service items. */ if (!function_exists('aksrt_get_pay_service_html')) { function aksrt_get_pay_service_html(string $class = 'resource-download-box__guarantee'): string { $service = []; if (class_exists('\AKSRT\Settings\Settings')) { $order = \AKSRT\Settings\Settings::get_order_settings(); $service = is_array($order['service'] ?? null) ? $order['service'] : []; } $items = []; $text = trim((string) ($service['text'] ?? '')); $value = trim((string) ($service['value'] ?? '')); $icon = trim((string) ($service['icon'] ?? 'fas fa-check-circle')); if ($icon === '') { $icon = 'fas fa-check-circle'; } // Support multi-line pay_service textarea (one bullet per line). $lines = []; if ($text !== '') { $lines = preg_split('/\r\n|\r|\n/', $text) ?: []; } if ($value !== '') { $lines[] = $value; } $lines = array_values(array_filter(array_map('trim', $lines))); if ($lines === []) { // Defaults aligned with previous hardcoded guarantees. $lines = [ __('安全检测', 'aksrt'), __('支持 VIP 权益', 'aksrt'), __('支持积分结算', 'aksrt'), ]; } $html = ''; foreach ($lines as $line) { $html .= ''; $html .= ' '; $html .= esc_html($line); $html .= ''; } return $html; } } /** * Demo link button (Zibll zibpay_get_demo_link). */ if (!function_exists('aksrt_get_demo_link_html')) { function aksrt_get_demo_link_html($post = null, string $class = 'resource-download-box__btn btn-outline resource-download-box__btn--demo'): string { $p = get_post($post); if (!($p instanceof WP_Post)) { return ''; } $meta = aksrt_get_pay_meta($p); if (($meta['pay_type'] ?? '') !== AKSRT_PAY_TYPE_DOWNLOAD) { return ''; } $url = trim((string) ($meta['demo_url'] ?? '')); if ($url === '') { return ''; } $text = __('查看演示', 'aksrt'); return sprintf( '%s', esc_url($url), esc_attr($class), esc_html($text) ); } } /** * Product attributes row (Zibll zibpay_get_product_attributes). */ if (!function_exists('aksrt_get_pay_attributes_html')) { function aksrt_get_pay_attributes_html($post = null, string $class = 'resource-download-box__attrs'): string { $p = get_post($post); if (!($p instanceof WP_Post)) { return ''; } $meta = aksrt_get_pay_meta($p); $attrs = is_array($meta['attributes'] ?? null) ? $meta['attributes'] : []; // Built-in version/size/license/compat as attributes when set. $builtin = [ 'version' => __('版本', 'aksrt'), 'size' => __('大小', 'aksrt'), 'license' => __('授权', 'aksrt'), 'compat' => __('兼容', 'aksrt'), ]; $rows = []; foreach ($builtin as $key => $label) { $val = trim((string) ($meta[$key] ?? '')); if ($val !== '') { $rows[] = ['name' => $label, 'value' => $val]; } } foreach ($attrs as $row) { if (!is_array($row)) { continue; } $name = trim((string) ($row['name'] ?? $row['key'] ?? $row['label'] ?? '')); $value = trim((string) ($row['value'] ?? $row['val'] ?? '')); if ($name === '' || $value === '') { continue; } $rows[] = ['name' => $name, 'value' => $value]; } if ($rows === []) { return ''; } $html = '
    '; foreach ($rows as $row) { $html .= '
  • '; $html .= '' . esc_html($row['name']) . ''; $html .= '' . esc_html($row['value']) . ''; $html .= '
  • '; } $html .= '
'; return $html; } } // ============================================================ // 付费墙 HTML // ============================================================ if (!function_exists('aksrt_get_paywall_html')) { function aksrt_get_paywall_html(WP_Post $post): string { $meta = aksrt_get_pay_meta($post); $pay_type = $meta['pay_type']; $priced = function_exists('aksrt_get_user_pay_price') ? aksrt_get_user_pay_price($post) : null; $pay_modo = is_array($priced) ? ((($priced['modo'] ?? '0') === 'points') ? 'points' : '0') : ((($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0'); $price = is_array($priced) ? (float) ($priced['amount'] ?? 0) : (float) ($meta['price'] ?? 0); $vip_level = is_array($priced) ? (int) ($priced['vip_level'] ?? 0) : 0; $currency = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_currency_name() : __('积分', 'aksrt'); $cash_symbol = '¥'; if (class_exists('\AKSRT\Settings\Settings')) { $paid = \AKSRT\Settings\Settings::get_paid_content_settings(); $cash_symbol = (string) ($paid['display']['currency_symbol'] ?? '¥'); } $price_text = $pay_modo === 'points' ? (aksrt_format_price($price) . ' ' . $currency) : ($cash_symbol . aksrt_format_price($price)); $type_labels = [ AKSRT_PAY_TYPE_READING => ['icon' => 'fa-lock', 'label' => __('付费阅读', 'aksrt'), 'desc' => __('付费后即可查看完整内容', 'aksrt')], AKSRT_PAY_TYPE_GALLERY => ['icon' => 'fa-images', 'label' => __('付费图库', 'aksrt'), 'desc' => __('付费后即可查看全部图片', 'aksrt')], AKSRT_PAY_TYPE_VIDEO => ['icon' => 'fa-play-circle', 'label' => __('付费视频', 'aksrt'), 'desc' => __('付费后即可观看完整视频', 'aksrt')], ]; $type_info = $type_labels[$pay_type] ?? $type_labels[AKSRT_PAY_TYPE_READING]; $login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(get_permalink($post)); $is_logged_in = is_user_logged_in(); $original_price = (float) ($meta['original_price'] ?? 0); $has_original = $original_price > 0 && $original_price > $price; $pay_limit = (int) ($meta['pay_limit'] ?? 0); $member_blocked = $pay_limit > 0 && $vip_level < $pay_limit; $vip_url = function_exists('aksrt_get_user_center_tab_url') ? aksrt_get_user_center_tab_url('credits') : (function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url() : home_url('/')); ob_start(); ?>

0) : ?>
0 && $pay_modo !== 'points' && function_exists('aksrt_allow_guest_content_pay') && aksrt_allow_guest_content_pay($post)) : ?>

0) : ?>

'']; ?>

' . wp_kses_post($preview) . ''; } $out .= aksrt_get_paywall_html($post); if ($pay_type === AKSRT_PAY_TYPE_GALLERY) { $out .= aksrt_render_paid_gallery($post); } elseif ($pay_type === AKSRT_PAY_TYPE_VIDEO) { $out .= aksrt_render_paid_video($post); } return $out; } } // 已付费的内容增强 if (!function_exists('aksrt_paid_content_enhance')) { function aksrt_paid_content_enhance(string $content): string { if (is_admin() || !is_singular() || !in_the_loop() || !is_main_query()) { return $content; } global $post; if (!($post instanceof WP_Post)) { return $content; } $meta = aksrt_get_pay_meta($post); $pay_type = (string) ($meta['pay_type'] ?? AKSRT_PAY_TYPE_NO); if ($pay_type === AKSRT_PAY_TYPE_NO) { return $content; } if (!aksrt_has_paid_access($post)) { return $content; } if ($pay_type === AKSRT_PAY_TYPE_GALLERY && strpos($content, 'aksrt-paid-gallery') === false) { $gallery_html = aksrt_render_paid_gallery($post); if ($gallery_html) { $content .= $gallery_html; } } if ($pay_type === AKSRT_PAY_TYPE_VIDEO && strpos($content, 'aksrt-paid-video') === false) { $video_html = aksrt_render_paid_video($post); if ($video_html) { $content .= $video_html; } } return $content; } } // ============================================================ // 付费图库/视频样式 // ============================================================ add_action('wp_head', function (): void { if (is_admin() || !is_singular()) return; global $post; if (!($post instanceof WP_Post)) return; $meta = aksrt_get_pay_meta($post); if (!in_array($meta['pay_type'], [AKSRT_PAY_TYPE_GALLERY, AKSRT_PAY_TYPE_VIDEO], true)) return; ?>