Files
wordpress-theme-aksrt/inc/helpers/aksrt-pay.php
T
kate522 6075028520 fix(pay): 支付方式字样与按钮 UI 三弹窗统一对齐 Zibll
渠道展示名与按钮 UI 与参考主题 zibpay_get_payment_method_args /
zibpay_get_initiate_pay_input 对齐:

- 渠道名:aksrt_list_cash_payment_methods 增加 Zibll 基准展示名映射
  (余额/支付宝/微信/PayPal/Stripe),充值与会员弹窗不再显示网关
  内部名「站内余额」「微信支付」;积分弹窗手写「余额/卡密」与之同源
- 会员按钮字样:cta_label(立即开通/立即续费)→ Zibll 会员弹窗
  initiate_pay_input(4) 默认「立即支付」,与充值/积分弹窗一致
- 无可用支付方式:禁用按钮+自实现文案 → Zibll 同款
  <span class="badg px12 c-yellow-2">暂时无法支付,请与客服联系</span>
- 清理:按钮残留类 aksrt-vip-submit 与对应 CSS,加载/禁用态统一挂
  .initiate-pay(三弹窗按钮共用);renderMethods 空数组重复提示与
  不可达 reason 映射删除
- 契约测试同步锁定(无 cta_label/aksrt-vip-submit、立即支付字样、
  badg 提示、渠道名映射)

弹窗宽度差异(充值/积分 400px modal-mini、会员 600px)为 Zibll
原生设计,已在壳几何对齐时确认一致。
2026-09-18 17:48:54 +08:00

1689 lines
74 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* AKSRT Pay System — 统一付费内容系统(1:1 对齐 legacy)
*
* 核心机制:
* - 任何 post 通过 _aksrt_pay meta 成为付费文章
* - pay_type 驱动行为(no/reading/download/gallery/video/vip)
* - 统一的价格、VIP 价、下载源、属性、图库、视频管理
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
// ============================================================
// 付费类型常量
// ============================================================
if (!defined('AKSRT_PAY_TYPE_NO')) {
define('AKSRT_PAY_TYPE_NO', \AKSRT\Core\DomainModel::PAY_TYPE_NO);
}
if (!defined('AKSRT_PAY_TYPE_READING')) {
define('AKSRT_PAY_TYPE_READING', \AKSRT\Core\DomainModel::PAY_TYPE_READING);
}
if (!defined('AKSRT_PAY_TYPE_DOWNLOAD')) {
define('AKSRT_PAY_TYPE_DOWNLOAD', \AKSRT\Core\DomainModel::PAY_TYPE_DOWNLOAD);
}
if (!defined('AKSRT_PAY_TYPE_GALLERY')) {
define('AKSRT_PAY_TYPE_GALLERY', \AKSRT\Core\DomainModel::PAY_TYPE_GALLERY);
}
if (!defined('AKSRT_PAY_TYPE_VIDEO')) {
define('AKSRT_PAY_TYPE_VIDEO', \AKSRT\Core\DomainModel::PAY_TYPE_VIDEO);
}
if (!defined('AKSRT_PAY_TYPE_VIP')) {
define('AKSRT_PAY_TYPE_VIP', \AKSRT\Core\DomainModel::PAY_TYPE_VIP);
}
if (!function_exists('aksrt_asset_price_minor')) {
function aksrt_asset_price_minor($value, string $pay_modo): int {
if ($pay_modo === 'points') {
return is_int($value) ? max(0, $value) : 0;
}
if (!is_string($value)) {
return 0;
}
try {
return \AKSRT\Ledger\AssetAmount::balance_minor($value);
} catch (\InvalidArgumentException $e) {
return 0;
}
}
}
if (!function_exists('aksrt_asset_price_value')) {
function aksrt_asset_price_value(int $minor, string $pay_modo) {
return $pay_modo === 'points'
? max(0, $minor)
: \AKSRT\Ledger\AssetAmount::decimal_from_minor(max(0, $minor));
}
}
/**
* 检查用户是否为 VIP 豁免用户(免扣积分下载)。
*
* @param int $user_id
* @return bool
*/
if (!function_exists('aksrt_is_vip_exempt')) {
function aksrt_is_vip_exempt(int $user_id = 0): bool {
if ($user_id <= 0) {
$user_id = get_current_user_id();
}
if ($user_id <= 0) {
return false;
}
// 优先使用 aksrt_is_download_credit_exempt_user(如果已定义)
if (function_exists('aksrt_is_download_credit_exempt_user')) {
return aksrt_is_download_credit_exempt_user($user_id);
}
// 回退:直接检查 VIP 角色
if (function_exists('aksrt_is_vip') && aksrt_is_vip($user_id)) {
return true;
}
return false;
}
}
function aksrt_get_pay_types(): array {
return \AKSRT\Core\DomainModel::pay_type_labels();
}
/**
* Cash payment methods available for content cashiers.
*
* @return array<int, array{id:string,name:string}>
*/
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 [];
}
// 渠道展示名对齐参考主题 zibpay_get_payment_method_args:
// 微信 / 支付宝 / 余额 / 卡密 / PayPal / Stripe(网关 get_name 仅供
// 订单等内部场景,支付方式 UI 统一用本映射)。
$labels = [
'balance' => __('余额', 'aksrt'),
'alipay' => __('支付宝', 'aksrt'),
'epay_alipay' => __('支付宝', 'aksrt'),
'wechatpay' => __('微信', 'aksrt'),
'epay_wxpay' => __('微信', 'aksrt'),
'paypal' => 'PayPal',
'stripe' => 'Stripe',
];
$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' => $labels[$id] ?? (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();
}
}
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 = $pay_meta['download_limit_over_price'] ?? ($pay_modo === 'points' ? 0 : '0.00');
return [
'version' => (string) ($pay_meta['version'] ?? ''),
'size' => (string) ($pay_meta['size'] ?? ''),
'price' => (string) ($pay_meta['price'] ?? '0.00'),
'points_price' => (int) ($pay_meta['points_price'] ?? 0),
'pay_modo' => $pay_modo,
'pay_limit' => $pay_limit,
'original_price' => (string) ($pay_meta['original_price'] ?? '0.00'),
'vip1_price' => (string) ($pay_meta['vip1_price'] ?? '0.00'),
'vip2_price' => (string) ($pay_meta['vip2_price'] ?? '0.00'),
'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),
// legacy: 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<string,mixed>|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 (legacy 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 (legacy 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 (legacy-style).
*
* @param array<string,mixed> $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((string) ($order['amount'] ?? '0.00'))
: (string) ($order['amount'] ?? '0.00');
return $id . '_' . $post_id . '_' . trim($num) . '_' . $amount . '_' . trim($ip);
}
}
/**
* Set guest paid cookies after cash order paid (content).
*
* @param array<string,mixed> $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 (legacy aksrtpay_posts_order_created_set_cookie).
*
* @param array<string,mixed> $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<string,mixed>|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<string,mixed>|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);
}
}
/**
* Resolve the amount a given user should pay for a post.
*
* @param int|WP_Post|null $post
* @param int $user_id
* @return array{amount:int|string,base:int|string,vip_amount:int|string|null,modo:string,currency:string,is_free:bool,pay_limit:int,vip_level:int,original:int|string,vip1:int|string,vip2:int|string}
*/
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 = '¥';
$vip_settings = [];
if (class_exists('\AKSRT\Settings\Settings')) {
$paid = \AKSRT\Settings\Settings::get_paid_content_settings();
$cash_symbol = (string) ($paid['display']['currency_symbol'] ?? '¥');
$vip_settings = \AKSRT\Settings\Settings::get_payment_vip_settings();
}
$vip_levels = is_array($vip_settings['levels'] ?? null) ? $vip_settings['levels'] : [];
if ($pay_modo === 'points') {
$base_minor = max(0, (int) ($meta['points_price'] ?? 0));
$vip1_minor = max(0, (int) ($meta['vip1_points'] ?? 0));
$vip2_minor = max(0, (int) ($meta['vip2_points'] ?? 0));
$currency_label = $currency;
} else {
$parse_cash = static function ($value): int {
if (!is_string($value)) {
return 0;
}
try {
return \AKSRT\Ledger\AssetAmount::balance_minor($value);
} catch (\InvalidArgumentException $e) {
return 0;
}
};
$base_minor = $parse_cash($meta['price'] ?? '0');
$vip1_minor = $parse_cash($meta['vip1_price'] ?? '0');
$vip2_minor = $parse_cash($meta['vip2_price'] ?? '0');
$currency_label = $cash_symbol;
}
$vip_prices = [
1 => $vip1_minor,
2 => $vip2_minor,
];
$vip_minor = null;
for ($level = min(2, $vip_level); $level >= 1; $level--) {
if (empty($vip_levels[$level]['enabled'])) {
continue;
}
$level_price = $vip_prices[$level];
if ($level_price < $base_minor) {
$vip_minor = $level_price;
}
break;
}
$amount_minor = $vip_minor ?? $base_minor;
// Role/VIP free-download exemption (global setting).
if ($amount_minor > 0 && $uid > 0 && function_exists('aksrt_is_download_credit_exempt_user') && aksrt_is_download_credit_exempt_user($uid)) {
$amount_minor = 0;
}
if ($pay_modo === 'points') {
$original = max(0, (int) ($meta['original_price'] ?? 0));
$amount = $amount_minor;
$base = $base_minor;
$vip1 = $vip1_minor;
$vip2 = $vip2_minor;
$vip_amount = $vip_minor;
} else {
$original_minor = $parse_cash($meta['original_price'] ?? '0');
$amount = \AKSRT\Ledger\AssetAmount::decimal_from_minor($amount_minor);
$base = \AKSRT\Ledger\AssetAmount::decimal_from_minor($base_minor);
$vip1 = \AKSRT\Ledger\AssetAmount::decimal_from_minor($vip1_minor);
$vip2 = \AKSRT\Ledger\AssetAmount::decimal_from_minor($vip2_minor);
$vip_amount = $vip_minor === null ? null : \AKSRT\Ledger\AssetAmount::decimal_from_minor($vip_minor);
$original = \AKSRT\Ledger\AssetAmount::decimal_from_minor($original_minor);
}
return [
'amount' => $amount,
'base' => $base,
'vip_amount' => $vip_amount,
'modo' => $pay_modo,
'currency' => $currency_label,
'is_free' => $amount_minor <= 0,
'pay_limit' => $pay_limit,
'vip_level' => $vip_level,
'original' => $original,
'vip1' => $vip1,
'vip2' => $vip2,
];
}
}
/**
* Whether content cashier may accept coupons for this post (legacy 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 aksrtpay_get_post_rebate_discount.
*/
if (!function_exists('aksrt_get_post_rebate_discount')) {
function aksrt_get_post_rebate_discount($post = null, int $user_id = 0): string {
$meta = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($post) : [];
$fixed = is_string($meta['pay_rebate_discount'] ?? null) ? (string) $meta['pay_rebate_discount'] : '0.00';
$fixed_minor = aksrt_asset_price_minor($fixed, '0');
if ($fixed_minor <= 0) {
return '0.00';
}
if (!class_exists('\AKSRT\Settings\Settings') || !method_exists('\AKSRT\Settings\Settings', 'get_payment_affiliate_settings')) {
return '0.00';
}
$aff = \AKSRT\Settings\Settings::get_payment_affiliate_settings();
if (empty($aff['enabled'])) {
return '0.00';
}
$uid = $user_id > 0 ? $user_id : (function_exists('get_current_user_id') ? (int) get_current_user_id() : 0);
$referrer_id = class_exists('\AKSRT\Payment\ReferralAttribution')
? \AKSRT\Payment\ReferralAttribution::get_instance()->resolve($uid)
: 0;
if ($referrer_id <= 0) {
return '0.00';
}
// Need a positive ratio for referrer (VIP tier of referrer preferred).
$ratio = (int) \AKSRT\Settings\Settings::affiliate_ratio_for_user($referrer_id);
if ($ratio <= 0) {
return '0.00';
}
return \AKSRT\Ledger\AssetAmount::decimal_from_minor($fixed_minor);
}
}
/**
* Whether cash checkout may use points deduction (legacy points_deduction).
*
* legacy: aksrtpay_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 legacy 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 legacy 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 (legacy 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;
}
}
/**
* Detect the canonical paid-fragment shortcode in a post's raw content.
*
* The editor writes this exact shortcode format, so this is the single
* source of truth for whether a reading price has any protected fragment to
* apply to. It deliberately does not inspect rendered content.
*/
if (!function_exists('aksrt_post_has_payshow_shortcode')) {
function aksrt_post_has_payshow_shortcode($post = null): bool {
$p = get_post($post);
if (!($p instanceof WP_Post)) {
return false;
}
return preg_match(
'/\[hidecontent\b[^\]]*\btype\s*=\s*(?:"payshow"|\'payshow\'|payshow)[^\]]*\]/i',
(string) $p->post_content
) === 1;
}
}
/**
* A paid fragment is active only for an explicitly priced paid-reading post.
*
* A zero-price reading configuration is intentionally public: authors can
* save content before setting a price without accidentally hiding it.
*/
if (!function_exists('aksrt_is_payshow_purchase_enabled')) {
function aksrt_is_payshow_purchase_enabled($post = null): bool {
if (!aksrt_post_has_payshow_shortcode($post)) {
return false;
}
$meta = aksrt_get_pay_meta($post);
if (($meta['pay_type'] ?? AKSRT_PAY_TYPE_NO) !== AKSRT_PAY_TYPE_READING) {
return false;
}
$pay_modo = ($meta['pay_modo'] ?? '0') === 'points' ? 'points' : '0';
$price = $pay_modo === 'points'
? (int) ($meta['points_price'] ?? 0)
: (string) ($meta['price'] ?? '0.00');
return aksrt_asset_price_minor($price, $pay_modo) > 0;
}
}
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;
$pay_modo = ($meta['pay_modo'] ?? '0') === 'points' ? 'points' : '0';
$price = $pay_modo === 'points' ? (int) ($meta['points_price'] ?? 0) : (string) ($meta['price'] ?? '0.00');
if (aksrt_asset_price_minor($price, $pay_modo) <= 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_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);
if (($meta['pay_modo'] ?? '0') !== 'points') {
return 0;
}
return max(0, (int) ($meta['points_price'] ?? 0));
}
}
/**
* URL of the legacy-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);
}
}
// ============================================================
// 价格显示
// ============================================================
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) ? ($priced['base'] ?? ($pay_modo === 'points' ? 0 : '0.00')) : ($pay_modo === 'points' ? 0 : '0.00'),
'modo' => $pay_modo,
'pay_limit' => $pay_limit,
];
}
$price = is_array($priced)
? ($priced['amount'] ?? ($pay_modo === 'points' ? 0 : '0.00'))
: ($pay_modo === 'points' ? (int) ($meta['points_price'] ?? 0) : (string) ($meta['price'] ?? '0.00'));
$base = is_array($priced) ? ($priced['base'] ?? $price) : $price;
$price_minor = aksrt_asset_price_minor($price, $pay_modo);
if ($price_minor <= 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($price): string {
if (is_int($price)) {
return (string) max(0, $price);
}
if (!is_string($price)) {
return '0';
}
try {
$decimal = \AKSRT\Ledger\AssetAmount::balance($price)->decimal();
} catch (\InvalidArgumentException $e) {
return '0';
}
return str_ends_with($decimal, '.00')
? substr($decimal, 0, -3)
: rtrim($decimal, '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-image'],
AKSRT_PAY_TYPE_VIDEO => ['label' => __('付费视频', 'aksrt'), 'color' => '#8B5CF6', 'icon' => 'fa-play-circle'],
AKSRT_PAY_TYPE_VIP => ['label' => __('VIP 专属', 'aksrt'), 'color' => '#3B82F6', 'icon' => 'fa-certificate'],
];
if (!isset($badges[$pay_type])) return '';
$b = $badges[$pay_type];
return sprintf(
'<span class="aksrt-pay-badge" style="background:%s"><i class="fa %s"></i> %s</span>',
esc_attr($b['color']), esc_attr($b['icon']), esc_html($b['label'])
);
}
}
/**
* legacy aksrtpay_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'] : [];
}
$text = trim((string) ($service['text'] ?? ''));
$value = trim((string) ($service['value'] ?? ''));
$icon = trim((string) ($service['icon'] ?? 'fa fas fa-check-circle'));
if ($icon === '') {
$icon = 'fa fas fa-check-circle';
}
$lines = [];
if ($text !== '') {
$lines = preg_split('/\r\n|\r|\n/', $text) ?: [];
}
if ($value !== '') {
$lines[] = $value;
}
$lines = array_values(array_filter(array_map('trim', $lines)));
$html = '';
foreach ($lines as $line) {
$html .= '<span class="' . esc_attr($class) . '">';
$html .= '<i class="' . esc_attr($icon) . '" aria-hidden="true"></i> ';
$html .= esc_html($line);
$html .= '</span>';
}
return $html;
}
}
/**
* Demo link button (legacy aksrtpay_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(
'<a target="_blank" rel="nofollow noopener" href="%s" class="%s"><i class="fa fas fa-external-link-alt" aria-hidden="true"></i><span>%s</span></a>',
esc_url($url),
esc_attr($class),
esc_html($text)
);
}
}
/**
* Product attributes row (legacy aksrtpay_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 = '<ul class="' . esc_attr($class) . '">';
foreach ($rows as $row) {
$html .= '<li class="' . esc_attr($class) . '-item">';
$html .= '<span class="' . esc_attr($class) . '-name">' . esc_html($row['name']) . '</span>';
$html .= '<span class="' . esc_attr($class) . '-value">' . esc_html($row['value']) . '</span>';
$html .= '</li>';
}
$html .= '</ul>';
return $html;
}
}
/**
* Render the configured icon for a paid-content VIP tier.
*/
if (!function_exists('aksrt_get_pay_vip_icon_html')) {
function aksrt_get_pay_vip_icon_html(array $level_settings, string $class = 'mr3 em14'): string {
$icon_url = esc_url((string) ($level_settings['img_icon'] ?? ''));
if ($icon_url === '') {
return '<i class="fa fas fa-certificate ' . esc_attr($class) . '" aria-hidden="true"></i>';
}
return '<img class="pay-vip-level-icon ' . esc_attr($class) . '" src="' . $icon_url . '" alt="" decoding="async">';
}
}
/**
* Render the reference-theme base-price block for a paid article.
*/
if (!function_exists('aksrt_get_show_pay_price_html')) {
function aksrt_get_show_pay_price_html(array $meta, array $priced, string $class = 'c-red'): string {
$modo = (($priced['modo'] ?? '0') === 'points') ? 'points' : '0';
$base = $priced['base'] ?? ($modo === 'points' ? 0 : '0.00');
$pay_limit = (int) ($priced['pay_limit'] ?? ($meta['pay_limit'] ?? 0));
$cash_symbol = '¥';
$points_name = __('积分', 'aksrt');
$vip_settings = [];
if (class_exists('\AKSRT\Settings\Settings')) {
$paid_settings = \AKSRT\Settings\Settings::get_paid_content_settings();
$cash_symbol = (string) ($paid_settings['display']['currency_symbol'] ?? '¥');
$points_name = \AKSRT\Settings\Settings::get_currency_name();
$vip_settings = \AKSRT\Settings\Settings::get_payment_vip_settings();
}
$vip_levels = is_array($vip_settings['levels'] ?? null) ? $vip_settings['levels'] : [];
if ($pay_limit > 0 && !empty($vip_settings['enabled'])) {
$level_settings = is_array($vip_levels[$pay_limit] ?? null) ? $vip_levels[$pay_limit] : [];
$level_name = (string) ($level_settings['name'] ?? sprintf(__('VIP%d', 'aksrt'), $pay_limit));
$title = $pay_limit === 2
? sprintf(__('仅%s可购买', 'aksrt'), $level_name)
: sprintf(__('%s及以上会员可购买', 'aksrt'), $level_name);
$icon = aksrt_get_pay_vip_icon_html($level_settings);
return '<div class="price-box"><div class="' . esc_attr($class) . ' padding-h10"><b data-toggle="tooltip" title="' . esc_attr($title) . '" class="badg radius jb-vip' . esc_attr((string) $pay_limit) . ' pay-vip-exclusive">' . $icon . esc_html__('会员专属资源', 'aksrt') . '</b></div></div>';
}
if ($modo === 'points') {
return '<div class="price-box"><div class="c-yellow ' . esc_attr($class) . '"><span class="em12 mr3"><i class="fa fas fa-coins" aria-hidden="true"></i></span><b class="em3x">' . esc_html(aksrt_format_price($base)) . '</b><span class="px12 ml3">' . esc_html($points_name) . '</span></div></div>';
}
$mark = '<span class="pay-mark">' . esc_html($cash_symbol) . '</span>';
$base_minor = aksrt_asset_price_minor($base, '0');
$original = $meta['original_price'] ?? '0.00';
$original_minor = aksrt_asset_price_minor($original, '0');
$original_html = '';
if ($original_minor > $base_minor) {
$original_html = '<span class="original-price" title="' . esc_attr(sprintf(__('原价 %s', 'aksrt'), aksrt_format_price($original))) . '">' . $mark . esc_html(aksrt_format_price($original)) . '</span>';
}
$promotion_tag = trim((string) ($meta['promotion_tag'] ?? ''));
if ($promotion_tag !== '' && $original_minor > 0) {
$original_html = '<div class="inline-block ml10 text-left"><badge>' . wp_kses_post($promotion_tag) . '</badge><br>' . $original_html . '</div>';
}
$price = '<div class="' . esc_attr($class) . '"><b class="em3x">' . $mark . esc_html(aksrt_format_price($base)) . '</b>' . $original_html . '</div>';
return '<div class="price-box">' . $price . '</div>';
}
}
/**
* Render all enabled VIP prices for a paid article.
*/
if (!function_exists('aksrt_get_posts_vip_price')) {
function aksrt_get_posts_vip_price(array $meta): string {
if (!class_exists('\AKSRT\Settings\Settings')) {
return '';
}
$vip_settings = \AKSRT\Settings\Settings::get_payment_vip_settings();
if (empty($vip_settings['enabled'])) {
return '';
}
$levels = is_array($vip_settings['levels'] ?? null) ? $vip_settings['levels'] : [];
$modo = (($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0';
$base = $modo === 'points' ? (int) ($meta['points_price'] ?? 0) : (string) ($meta['price'] ?? '0.00');
$base_minor = aksrt_asset_price_minor($base, $modo);
if ($base_minor <= 0) {
return '';
}
$mark = $modo === 'points'
? '<span class="px12"><i class="fa fas fa-coins" aria-hidden="true"></i></span>'
: '<span class="px12">' . esc_html((string) (\AKSRT\Settings\Settings::get_paid_content_settings()['display']['currency_symbol'] ?? '¥')) . '</span>';
$user_id = get_current_user_id();
$user_vip_level = $user_id > 0 && function_exists('aksrt_get_user_vip_level')
? (int) aksrt_get_user_vip_level($user_id)
: 0;
$items = [];
foreach ([1, 2] as $level) {
$level_settings = is_array($levels[$level] ?? null) ? $levels[$level] : [];
if (empty($level_settings['enabled'])) {
continue;
}
$key = $modo === 'points' ? 'vip' . $level . '_points' : 'vip' . $level . '_price';
$value = $meta[$key] ?? ($modo === 'points' ? 0 : '0.00');
$vip_minor = aksrt_asset_price_minor($value, $modo);
if ($vip_minor >= $base_minor) {
continue;
}
$level_name = (string) ($level_settings['name'] ?? sprintf(__('VIP%d', 'aksrt'), $level));
$price = $vip_minor === 0
? esc_html__('免费', 'aksrt')
: $mark . esc_html(aksrt_format_price($value));
$icon = aksrt_get_pay_vip_icon_html($level_settings);
$action_class = $user_id <= 0 ? ' signin-loader' : ($user_vip_level < $level ? ' pay-vip' : '');
$title = sprintf(__('开通%s', 'aksrt'), $level_name);
$title_attr = $action_class !== ''
? ' data-toggle="tooltip" title="' . esc_attr($title) . '"'
: '';
$items[] = '<span class="but vip-price' . $action_class . '" vip-level="' . esc_attr((string) $level) . '" data-vip-level="' . esc_attr((string) $level) . '"' . $title_attr . '>' . $icon . esc_html($level_name) . '<span class="em12 ml3 vip-price-text">' . $price . '</span></span>';
}
$html = implode('', $items);
if (count($items) > 1) {
$html = '<span class="vip-price-buts but-average inline">' . $html . '</span>';
}
return $html;
}
}
// ============================================================
// 付费墙 HTML
// ============================================================
if (!function_exists('aksrt_get_paywall_html')) {
function aksrt_get_paywall_html(WP_Post $post, array $args = []): string {
$is_widget = ($args['layout'] ?? 'article') === 'widget';
$theme = sanitize_html_class((string) ($args['theme'] ?? 'jb-red')) ?: 'jb-red';
$meta = aksrt_get_pay_meta($post);
$pay_type = (string) ($meta['pay_type'] ?? AKSRT_PAY_TYPE_NO);
if ($pay_type === AKSRT_PAY_TYPE_NO) return '';
$priced = aksrt_get_user_pay_price($post);
$modo = (string) ($priced['modo'] ?? '0');
$amount = $priced['amount'] ?? 0;
$amount_minor = aksrt_asset_price_minor($amount, $modo);
$pay_limit = (int) ($priced['pay_limit'] ?? ($meta['pay_limit'] ?? 0));
$vip_level = (int) ($priced['vip_level'] ?? 0);
$pay_vip_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_payment_vip_settings() : [];
$member_blocked = $pay_limit > 0 && !empty($pay_vip_settings['enabled']) && $vip_level < $pay_limit;
$type_labels = [
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'),
];
$type_label = $type_labels[$pay_type] ?? __('付费内容', 'aksrt');
$order_types = [
AKSRT_PAY_TYPE_READING => 1,
AKSRT_PAY_TYPE_DOWNLOAD => 2,
AKSRT_PAY_TYPE_GALLERY => 5,
AKSRT_PAY_TYPE_VIDEO => 6,
AKSRT_PAY_TYPE_VIP => 4,
];
$order_type = (int) ($order_types[$pay_type] ?? 1);
$title = get_the_title($post);
$doc = trim((string) ($args['message'] ?? ''));
if ($doc === '') {
$doc = sprintf(__('此内容为%s,请付费后查看', 'aksrt'), $type_label);
}
$sales = max(0, (int) ($meta['virtual_sales'] ?? 0));
$sales += max(0, (int) get_post_meta($post->ID, \AKSRT\Core\DomainModel::RESOURCE_SALES_VOLUME_KEY, true));
$tag = $is_widget
? '<badge class="pay-tag abs-center">' . esc_html($type_label) . '</badge>'
: '<div class="pay-tag abs-center">' . esc_html($type_label) . '</div>';
$block_class = $is_widget ? ' btn-block' : '';
$button = '';
if ($member_blocked) {
$level_settings = is_array($pay_vip_settings['levels'][$pay_limit] ?? null) ? $pay_vip_settings['levels'][$pay_limit] : [];
$level_name = (string) ($level_settings['name'] ?? sprintf(__('VIP%d', 'aksrt'), $pay_limit));
$vip_action = $vip_level > 0
? sprintf(__('升级%s', 'aksrt'), $level_name)
: __('开通会员', 'aksrt');
$action_class = is_user_logged_in() ? 'pay-vip' : 'signin-loader';
$icon = aksrt_get_pay_vip_icon_html($level_settings);
$button = '<div class="badg c-yellow em09 mb6"><i class="fa fas fa-fw fa-info-circle mr6" aria-hidden="true"></i>' . esc_html(sprintf(__('您暂无购买权限,请先%s', 'aksrt'), $vip_action)) . '</div>';
$button .= '<a href="javascript:;" vip-level="' . esc_attr((string) $pay_limit) . '" data-vip-level="' . esc_attr((string) $pay_limit) . '" class="but btn-block jb-vip' . esc_attr((string) $pay_limit) . ' ' . esc_attr($action_class) . ' padding-lg">' . $icon . esc_html($vip_action) . '</a>';
} elseif (!is_user_logged_in()) {
if ($amount_minor > 0 && $modo === '0' && aksrt_allow_guest_content_pay($post)) {
$button = '<button type="button" class="but jb-red padding-lg cashier-link' . $block_class . '" data-aksrt-pay-content="' . esc_attr((string) $post->ID) . '"><i class="fa fas fa-shopping-cart" aria-hidden="true"></i> ' . esc_html__('立即购买', 'aksrt') . '</button>';
} else {
$button = '<a href="javascript:;" class="but jb-blue signin-loader padding-lg' . $block_class . '"><i class="fa fas fa-sign-in-alt mr10" aria-hidden="true"></i>' . esc_html__('登录购买', 'aksrt') . '</a>';
}
} elseif ($amount_minor > 0) {
$button = '<button type="button" class="but ' . ($modo === 'points' ? 'jb-yellow' : 'jb-red') . ' padding-lg cashier-link' . $block_class . '" data-aksrt-pay-content="' . esc_attr((string) $post->ID) . '"><i class="fa fas fa-shopping-cart" aria-hidden="true"></i> ' . esc_html__('立即购买', 'aksrt') . '</button>';
} else {
$button = '<button type="button" class="but jb-blue padding-lg cashier-link' . $block_class . '" data-aksrt-pay-content="' . esc_attr((string) $post->ID) . '"><i class="fa fas fa-eye" aria-hidden="true"></i> ' . esc_html__('查看内容', 'aksrt') . '</button>';
}
$price = aksrt_get_show_pay_price_html($meta, $priced);
$vip = aksrt_get_posts_vip_price($meta);
$attrs = aksrt_get_pay_attributes_html($post, 'pay-attr');
$count = $sales > 0 ? '<badge class="img-badge hot jb-blue px12">' . esc_html(sprintf(__('已售 %s', 'aksrt'), $sales)) . '</badge>' : '';
$service = aksrt_get_pay_service_html('muted-2-color');
$widget_service = $service !== '' ? '<div class="px12 muted-2-color">' . $service . '</div>' : '';
$article_service = $service !== '' ? '<div class="px12 muted-2-color mt10 text-right">' . $service . '</div>' : '';
$demo = aksrt_get_demo_link_html($post, 'but jb-yellow padding-lg');
$demo_row = $demo !== '' ? '<div class="mt10">' . $demo . '</div>' : '';
$discount = $modo === '0' ? aksrt_get_post_rebate_discount($post) : '0.00';
$discount_text = aksrt_asset_price_minor($discount, '0') > 0
? sprintf(__('推广优惠 %s', 'aksrt'), (string) ($priced['currency'] ?? '¥') . aksrt_format_price($discount))
: '';
$discount_banner = $discount_text !== '' ? '<div class="text-center padding-6 jb-yellow px12">' . esc_html($discount_text) . '</div>' : '';
if ($is_widget && aksrt_has_paid_access($post)) {
$view_labels = [
AKSRT_PAY_TYPE_DOWNLOAD => '<i class="fa fas fa-download fa-fw" aria-hidden="true"></i>' . esc_html__('获取下载地址', 'aksrt'),
AKSRT_PAY_TYPE_GALLERY => '<i class="fa far fa-file-image fa-fw" aria-hidden="true"></i>' . esc_html__('查看图片', 'aksrt'),
AKSRT_PAY_TYPE_VIDEO => '<i class="fa fas fa-play-circle fa-fw" aria-hidden="true"></i>' . esc_html__('查看视频', 'aksrt'),
];
$view_label = $view_labels[$pay_type] ?? '<i class="fa far fa-dot-circle fa-fw" aria-hidden="true"></i>' . esc_html__('查看内容', 'aksrt');
return '<div class="zib-widget pay-box pay-widget paid-box order-type-' . $order_type . '" style="padding:0">' . $tag . '<div class="relative-h ' . esc_attr($theme) . '" style="background-size:120%"><div class="pay-widget__orb pay-widget__orb--right ' . esc_attr($theme) . '"></div><div class="pay-widget__orb pay-widget__orb--left ' . esc_attr($theme) . '"></div><div class="relative box-body"><div class="text-center mt10 box-body"><p><i class="fa fas fa-shopping-bag fa-3x" aria-hidden="true"></i></p><b class="em14">' . esc_html__('已购买', 'aksrt') . '</b></div></div></div><div class="box-body">' . $widget_service . '<div class="mt10"><a href="#posts-pay" class="but padding-lg btn-block ' . esc_attr($theme) . '">' . $view_label . '</a></div>' . $demo_row . $attrs . '</div>' . $count . '</div>';
}
if ($is_widget) {
return '<div class="zib-widget pay-box pay-widget order-type-' . $order_type . '" style="padding:0">' . $tag . '<div class="relative-h ' . esc_attr($theme) . '" style="background-size:120%"><div class="pay-widget__orb pay-widget__orb--right ' . esc_attr($theme) . '"></div><div class="pay-widget__orb pay-widget__orb--left ' . esc_attr($theme) . '"></div><div class="relative box-body"><div class="text-center mt10">' . $price . '</div></div><div class="relative">' . $vip . '</div></div>' . $discount_banner . '<div class="box-body">' . $widget_service . '<div class="mt10">' . $button . '</div>' . $demo_row . $attrs . '</div>' . $count . '</div>';
}
$thumb = get_the_post_thumbnail($post, 'medium', ['class' => 'fit-cover', 'alt' => '']);
if ($thumb === '') {
$thumb = '<div class="pay-thumb-placeholder"><i class="fa fas fa-lock" aria-hidden="true"></i></div>';
}
$graphic_actions = '';
if ($demo !== '' || $discount_text !== '') {
$graphic_actions = '<div class="abs-center text-center left-bottom">';
$graphic_actions .= $demo !== '' ? '<div>' . $demo . '</div>' : '';
$graphic_actions .= $discount_text !== '' ? '<div class="padding-6 jb-red px12 hidden-xs">' . esc_html($discount_text) . '</div>' : '';
$graphic_actions .= '</div>';
}
$discount_mobile = $discount_text !== '' ? '<div class="visible-xs-block badg c-red px12 mb6">' . esc_html($discount_text) . '</div>' : '';
if ($pay_type === AKSRT_PAY_TYPE_GALLERY) {
$gallery_count = count((array) ($meta['gallery'] ?? []));
$graphic = '<div class="flex0 relative mr20 hide-sm pay-thumb"><div class="graphic"><div class="blur-10 absolute">' . $thumb . '</div><div class="absolute graphic-mask" style="opacity:.3"></div><div class="abs-center text-center">' . esc_html(sprintf(__('共%s张图片', 'aksrt'), $gallery_count)) . '<br>' . esc_html__('请付费后查看', 'aksrt') . '</div>' . $graphic_actions . '</div></div>';
} elseif ($pay_type === AKSRT_PAY_TYPE_VIDEO) {
$graphic = '<div class="flex0 relative mr20 hide-sm pay-thumb"><div class="graphic">' . $thumb . '<div class="absolute graphic-mask" style="opacity:.2"></div><div class="abs-center text-center"><i class="fa far fa-play-circle fa-4x opacity8" aria-hidden="true"></i></div>' . $graphic_actions . '</div></div>';
} else {
$graphic = '<div class="flex0 relative mr20 hide-sm pay-thumb"><div class="graphic">' . $thumb . $graphic_actions . '</div></div>';
}
$demo_mobile = $demo !== '' ? '<div class="mt10 visible-xs-block">' . $demo . '</div>' : '';
return '<div class="zib-widget pay-box order-type-' . $order_type . '" id="posts-pay"><div class="flex pay-flexbox">' . $graphic . '<div class="flex-auto-h flex xx jsb"><dt class="text-ellipsis pay-title">' . esc_html($title) . '</dt><div class="mt6 em09 muted-2-color">' . wp_kses_post($doc) . '</div><div>' . $price . '</div>' . $discount_mobile . ($vip !== '' ? '<div>' . $vip . '</div>' : '') . '<div class="text-right mt10">' . $button . '</div></div></div>' . $article_service . $demo_mobile . $attrs . $tag . $count . '</div>';
}
}
/**
* Resolve the paid order that granted access to a post, when access came from
* an actual purchase rather than a free or VIP entitlement.
*
* @return array<string,mixed>|null
*/
if (!function_exists('aksrt_get_current_content_paid_order')) {
function aksrt_get_current_content_paid_order(WP_Post $post): ?array {
if (is_user_logged_in()) {
return aksrt_get_user_content_paid_order($post->ID);
}
return aksrt_get_guest_content_paid_order($post->ID);
}
}
/**
* Reference-theme download actions used inside the purchased article card.
*/
if (!function_exists('aksrt_get_paid_download_actions_html')) {
function aksrt_get_paid_download_actions_html(array $meta): string {
$downloads = is_array($meta['downloads'] ?? null) ? array_values($meta['downloads']) : [];
if ($downloads === []) {
return '<div class="muted-2-color text-center">' . esc_html__('暂无可下载资源', 'aksrt') . '</div>';
}
// 下载总开关关闭 = 直连模式:按钮直接输出原始地址,不走本站中转链接。
$direct_download = class_exists('\AKSRT\Content\Download')
&& !\AKSRT\Content\Download::get_instance()->is_enabled();
$buttons = '';
foreach ($downloads as $index => $download) {
if (!is_array($download)) {
continue;
}
$url = (string) ($download['url'] ?? '');
if ($direct_download && $url === '') {
// 直连模式没有可用的原始地址,空源不渲染。
continue;
}
$label = trim((string) ($download['label'] ?? ''));
$service = function_exists('aksrt_detect_cloud_service')
? aksrt_detect_cloud_service($url)
: ['key' => 'other', 'name' => __('本地下载', 'aksrt')];
if ($label === '') {
$label = (string) ($service['name'] ?? __('本地下载', 'aksrt'));
}
$type = sanitize_key((string) ($download['type'] ?? 'direct'));
$extract_code = trim((string) ($download['extract_code'] ?? ''));
$has_extract = $extract_code !== '';
$service_key = sanitize_html_class((string) ($service['key'] ?? 'other')) ?: 'other';
$button_class = trim((string) ($download['class'] ?? ''));
$button_class = $button_class !== '' ? $button_class : 'b-theme ' . $service_key;
$copy_value = trim((string) ($download['copy_val'] ?? ''));
$copy_key = trim((string) ($download['copy_key'] ?? ''));
$copy_label = $extract_code !== '' ? $extract_code : ($copy_key !== '' ? $copy_key : __('更多内容', 'aksrt'));
$buttons .= '<div class="but-download flex ac">';
if ($direct_download) {
$buttons .= '<a class="mr10 but ' . esc_attr($button_class) . '" href="' . esc_url($url) . '" target="_blank" rel="nofollow noopener">';
$buttons .= '<i class="fa fas fa-download" aria-hidden="true"></i>' . esc_html($label) . '</a>';
} else {
$buttons .= '<button type="button" class="mr10 but ' . esc_attr($button_class) . '" data-action="aksrt-download" data-source-index="' . esc_attr((string) $index) . '" data-source-type="' . esc_attr($type) . '" data-has-extract="' . ($has_extract ? '1' : '0') . '">';
$buttons .= '<i class="fa fas fa-download" aria-hidden="true"></i>' . esc_html($label) . '</button>';
}
if ($copy_value !== '') {
$buttons .= '<button type="button" class="but" data-clipboard-tag="' . esc_attr($copy_key !== '' ? $copy_key : __('内容', 'aksrt')) . '" data-clipboard-text="' . esc_attr($copy_value) . '">' . esc_html($copy_label) . '</button>';
} elseif ($extract_code !== '') {
$buttons .= '<span class="badg">' . esc_html($extract_code) . '</span>';
}
$buttons .= '</div>';
}
return $buttons !== ''
? '<div class="flex ac hh">' . $buttons . '</div>'
: '<div class="muted-2-color text-center">' . esc_html__('暂无可下载资源', 'aksrt') . '</div>';
}
}
/**
* Purchased article card matching Zibll's paid-box structure.
*/
if (!function_exists('aksrt_get_paid_box_html')) {
function aksrt_get_paid_box_html(WP_Post $post): string {
if (!aksrt_has_paid_access($post)) {
return '';
}
$meta = aksrt_get_pay_meta($post);
$pay_type = (string) ($meta['pay_type'] ?? AKSRT_PAY_TYPE_NO);
if ($pay_type === AKSRT_PAY_TYPE_NO) {
return '';
}
$order_types = [
AKSRT_PAY_TYPE_READING => 1,
AKSRT_PAY_TYPE_DOWNLOAD => 2,
AKSRT_PAY_TYPE_GALLERY => 5,
AKSRT_PAY_TYPE_VIDEO => 6,
AKSRT_PAY_TYPE_VIP => 4,
];
$type_labels = [
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'),
];
$order_type = (int) ($order_types[$pay_type] ?? 1);
$type_label = (string) ($type_labels[$pay_type] ?? __('付费内容', 'aksrt'));
$title = get_the_title($post);
$sales = max(0, (int) ($meta['virtual_sales'] ?? 0));
$sales += max(0, (int) get_post_meta($post->ID, \AKSRT\Core\DomainModel::RESOURCE_SALES_VOLUME_KEY, true));
$count = $sales > 0 ? '<badge class="img-badge hot jb-blue px12">' . esc_html(sprintf(__('已售 %s', 'aksrt'), $sales)) . '</badge>' : '';
$order = aksrt_get_current_content_paid_order($post);
$priced = aksrt_get_user_pay_price($post);
$vip_level = max(0, min(2, (int) ($priced['vip_level'] ?? 0)));
$header = '';
$paid_box = '';
if (is_array($order)) {
$order_number = (string) ($order['order_number'] ?? '');
$paid_at = (string) ($order['paid_at'] ?? $order['created_at'] ?? '');
$order_currency = (string) ($order['currency'] ?? '');
$order_amount = $order['amount'] ?? '0.00';
if ($order_currency === 'points') {
$currency_name = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_currency_name() : __('积分', 'aksrt');
$amount_text = aksrt_format_price($order_amount) . ' ' . $currency_name;
} else {
$cash_symbol = '¥';
if (class_exists('\AKSRT\Settings\Settings')) {
$settings = \AKSRT\Settings\Settings::get_paid_content_settings();
$cash_symbol = (string) ($settings['display']['currency_symbol'] ?? '¥');
}
$amount_text = $cash_symbol . aksrt_format_price($order_amount);
}
$paid_info = '<div class="flex jsb"><span>' . esc_html__('订单号', 'aksrt') . '</span><span>' . esc_html($order_number) . '</span></div>';
if ($paid_at !== '') {
$paid_info .= '<div class="flex jsb"><span>' . esc_html__('支付时间', 'aksrt') . '</span><span>' . esc_html($paid_at) . '</span></div>';
}
$paid_info .= '<div class="flex jsb"><span>' . esc_html__('支付金额', 'aksrt') . '</span><span>' . esc_html($amount_text) . '</span></div>';
$paid_box = '<div class="flex ac jb-green padding-10 em09"><div class="text-center flex-auto"><div class="mb6"><i class="fa fas fa-shopping-bag fa-2x" aria-hidden="true"></i></div><b class="em12">' . esc_html__('已购买', 'aksrt') . '</b></div><div class="em09 paid-info flex-auto">' . $paid_info . '</div></div>';
} elseif ($vip_level > 0 || $pay_type === AKSRT_PAY_TYPE_VIP) {
$level = max(1, $vip_level);
$price_label = aksrt_get_pay_price_label($post);
$price = '<div class="price-box"><div class="text-center"><b class="em3x">' . esc_html((string) ($price_label['text'] ?? '')) . '</b></div></div>';
$paid_box = '<div class="flex jsb ac payvip-icon box-body vipbg-v' . esc_attr((string) $level) . '">' . $price . '<div class="flex0"><b class="em12"><i class="fa fas fa-certificate mr10 em12" aria-hidden="true"></i>' . esc_html(sprintf(__('VIP%d 会员免费', 'aksrt'), $level)) . '</b></div></div>';
} else {
$type_label = str_replace(__('付费', 'aksrt'), __('免费', 'aksrt'), $type_label);
}
if ($pay_type === AKSRT_PAY_TYPE_GALLERY) {
$header = aksrt_render_paid_gallery($post);
} elseif ($pay_type === AKSRT_PAY_TYPE_VIDEO) {
$header = aksrt_render_paid_video($post);
}
$download_box = '';
$root_data = '';
$captcha = '';
if ($pay_type === AKSRT_PAY_TYPE_DOWNLOAD) {
$captcha_active = function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('download');
$root_data = ' data-aksrt-download-box data-post-id="' . esc_attr((string) $post->ID) . '" data-captcha="' . ($captcha_active ? '1' : '0') . '"';
$download_box = '<div class="hidden-box show"><div class="hidden-text"><i class="fa fas fa-download mr6" aria-hidden="true"></i>' . esc_html__('资源下载', 'aksrt') . '</div>' . aksrt_get_paid_download_actions_html($meta) . '</div>';
$demo = aksrt_get_demo_link_html($post, 'but jb-yellow padding-lg');
if ($demo !== '') {
$download_box .= '<div class="mt10">' . $demo . '</div>';
}
$download_box .= aksrt_get_pay_attributes_html($post, 'pay-attr');
if ($captcha_active && function_exists('aksrt_render_captcha')) {
ob_start();
aksrt_render_captcha('download');
$captcha = '<div class="resource-download-box__captcha resource-download-box__captcha--hidden" data-aksrt-download-captcha>' . (string) ob_get_clean() . '</div>';
}
}
$service = aksrt_get_pay_service_html('inline-block ml10');
$service = $service !== '' ? '<div class="px12 muted-2-color mt10 text-right">' . $service . '</div>' : '';
$title_style = $count !== '' ? ' style="padding-right:48px"' : '';
$body_class = 'box-body relative' . ($pay_type === AKSRT_PAY_TYPE_VIDEO ? ' dplayer-featured' : '');
return '<div class="pay-box zib-widget paid-box order-type-' . $order_type . '" id="posts-pay"' . $root_data . '>'
. $header
. $paid_box
. '<div class="' . esc_attr($body_class) . '">'
. $count
. '<div' . $title_style . '><span class="badg c-red hollow badg-sm mr6">' . esc_html($type_label) . '</span><b>' . esc_html($title) . '</b></div>'
. $download_box
. $service
. $captcha
. '</div></div>';
}
}
// ============================================================
// 付费图库轮播(Swiper)
// ============================================================
if (!function_exists('aksrt_render_paid_gallery')) {
function aksrt_render_paid_gallery(WP_Post $post): string {
$meta = aksrt_get_pay_meta($post);
if ($meta['pay_type'] !== AKSRT_PAY_TYPE_GALLERY) return '';
$images = $meta['gallery'] ?? [];
if (empty($images)) return '';
if (!aksrt_has_paid_access($post)) return '';
$count = count($images);
$backdrop = (string) end($images);
ob_start();
?>
<div class="relative-h paid-gallery aksrt-paid-gallery" data-post-id="<?php echo esc_attr((string) $post->ID); ?>">
<div class="absolute blur-10 opacity3"><img<?php echo aksrt_get_lazy_attr('lazy_other', $backdrop, 'fit-cover', aksrt_get_lazy_thumb('lg')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> alt=""></div>
<div style="margin-top:-20px" class="relative mb6"><span class="badg b-theme badg-sm"><?php echo esc_html(sprintf(__('共%s张图片', 'aksrt'), $count)); ?></span></div>
<div class="new-swiper swiper-container swiper-scroll aksrt-paid-gallery__swiper" data-loop="true">
<div class="swiper-wrapper">
<?php foreach ($images as $index => $img_url) : ?>
<div class="swiper-slide mr10" style="width:150px">
<div class="graphic" style="padding-bottom:100%!important">
<img<?php echo aksrt_get_lazy_attr('lazy_other', (string) $img_url, 'fit-cover', aksrt_get_lazy_thumb('lg')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> alt="">
<div class="abs-center right-top"><badge class="b-black opacity8 mr6 mt6"><?php echo esc_html(($index + 1) . '/' . $count); ?></badge></div>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
</div>
<?php
return ob_get_clean();
}
}
// ============================================================
// 付费视频播放器
// ============================================================
if (!function_exists('aksrt_render_paid_video')) {
function aksrt_render_paid_video(WP_Post $post): string {
$meta = aksrt_get_pay_meta($post);
if ($meta['pay_type'] !== AKSRT_PAY_TYPE_VIDEO) return '';
$video_url = $meta['video_url'] ?? '';
if (empty($video_url)) return '';
if (!aksrt_has_paid_access($post)) return '';
ob_start();
?>
<div class="aksrt-paid-video" data-post-id="<?php echo esc_attr((string) $post->ID); ?>">
<div class="aksrt-paid-video__player">
<?php if (preg_match('/\.(mp4|webm|ogg)$/i', $video_url)) : ?>
<video controls preload="metadata"<?php echo aksrt_is_lazy('lazy_posts_content') ? ' class="lazyload" src="" data-src="' . esc_url($video_url) . '"' : ' src="' . esc_url($video_url) . '"'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> style="width:100%;border-radius:var(--radius-lg);">
<?php esc_html_e('您的浏览器不支持视频播放。', 'aksrt'); ?>
</video>
<?php else : ?>
<div class="aksrt-paid-video__embed">
<iframe<?php echo aksrt_is_lazy('lazy_posts_content') ? ' class="lazyload" src="" data-src="' . esc_url($video_url) . '"' : ' src="' . esc_url($video_url) . '"'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> frameborder="0" allowfullscreen></iframe>
</div>
<?php endif; ?>
</div>
</div>
<?php
return ob_get_clean();
}
}
// ============================================================
// 内容过滤钩子(注册在 core-hooks.php)
// ============================================================
// 这些函数供 core-hooks.php 中的 the_content 过滤器调用
if (!function_exists('aksrt_paid_content_filter')) {
function aksrt_paid_content_filter(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);
// Reading is protected only by the payshow shortcode. Never replace
// the entire article body with a post-level purchase wall.
if ($pay_type === AKSRT_PAY_TYPE_READING) {
return $content;
}
if (aksrt_can_access_content($post)) {
return $content;
}
if (!in_array($pay_type, [AKSRT_PAY_TYPE_GALLERY, AKSRT_PAY_TYPE_VIDEO], true)) {
return $content;
}
// Only the configured teaser remains in the article body. The single
// template owns the one canonical purchase card for gallery/video.
$preview = trim((string) ($meta['free_preview'] ?? ''));
return $preview !== '' ? '<div class="aksrt-paywall-preview">' . wp_kses_post($preview) . '</div>' : '';
}
}
// ============================================================
// 付费图库/视频样式
// ============================================================
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;
?>
<style>
.aksrt-paid-video{margin:0}
.aksrt-paid-video__embed{position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:var(--radius-lg)}
.aksrt-paid-video__embed iframe{position:absolute;top:0;left:0;width:100%;height:100%;border:none}
</style>
<?php
});