feat(pay): zibll-style pay_modo/points/pay_limit, VIP quota, and local speed stream

Expand post pay meta with cash/points modes and member purchase limits,
enforce free-download daily quotas, and stream local files with VIP speed
caps while keeping remote links on redirect.
This commit is contained in:
2026-07-14 01:11:33 +08:00
parent 8b50687e9d
commit 1c8e4d3919
6 changed files with 311 additions and 33 deletions
+48 -2
View File
@@ -72,6 +72,16 @@ class AKSRT_Content_Visibility {
}
$has_free_access = $price <= 0 && $pay_type !== AKSRT_Domain_Model::PAY_TYPE_VIP;
// Prefer points_price when pay_modo=points.
if (is_array($pay_meta) && (($pay_meta['pay_modo'] ?? '0') === 'points')) {
$price = (float) ($pay_meta['points_price'] ?? $price);
$state['price'] = $price;
$has_free_access = $price <= 0 && $pay_type !== AKSRT_Domain_Model::PAY_TYPE_VIP;
}
$pay_limit = (int) ($pay_meta['pay_limit'] ?? 0);
if (!in_array($pay_limit, [0, 1, 2], true)) {
$pay_limit = 0;
}
$has_paid_order = false;
if ($uid > 0 && class_exists('AKSRT_Order')) {
@@ -83,14 +93,50 @@ class AKSRT_Content_Visibility {
}
$has_vip_access = false;
$user_vip_level = 0;
if ($uid > 0) {
if (function_exists('aksrt_get_user_vip_level')) {
$user_vip_level = aksrt_get_user_vip_level($uid);
}
if (function_exists('aksrt_is_vip_exempt') && aksrt_is_vip_exempt($uid)) {
$has_vip_access = true;
} elseif (function_exists('aksrt_is_vip') && $pay_type === AKSRT_Domain_Model::PAY_TYPE_VIP) {
$has_vip_access = aksrt_is_vip($uid);
$user_vip_level = max($user_vip_level, 2);
} elseif (function_exists('aksrt_is_vip') && ($pay_type === AKSRT_Domain_Model::PAY_TYPE_VIP || $pay_limit > 0)) {
$has_vip_access = $user_vip_level >= max(1, $pay_limit);
} elseif ($user_vip_level > 0) {
$has_vip_access = $pay_limit <= 0 || $user_vip_level >= $pay_limit;
}
}
// Member-only purchase restriction (Zibll pay_limit).
if ($pay_limit > 0 && $pay_type !== AKSRT_Domain_Model::PAY_TYPE_NO) {
$state['need_vip'] = true;
$state['pay_limit'] = $pay_limit;
$allowed_member = $has_vip_access && $user_vip_level >= $pay_limit;
if ($pay_type === AKSRT_Domain_Model::PAY_TYPE_DOWNLOAD) {
$download_cost = function_exists('aksrt_resource_download_cost')
? (int) aksrt_resource_download_cost($resource_post)
: (int) ceil($price);
$state['download_cost'] = max(0, $download_cost);
$state['need_login'] = $uid <= 0;
$state['need_purchase'] = $allowed_member && $state['download_cost'] > 0 && !$has_paid_order;
$state['has_paid_access'] = $allowed_member && ($has_paid_order || $state['download_cost'] <= 0 || $has_vip_access);
$state['can_view'] = true;
$state['can_download'] = $allowed_member && ($uid > 0) && ($has_paid_order || $state['download_cost'] <= 0 || $has_vip_access);
if (!$allowed_member) {
$state['can_download'] = false;
$state['has_paid_access'] = false;
}
return $state;
}
$state['has_paid_access'] = $allowed_member || $has_paid_order;
$state['can_view'] = $state['has_paid_access'];
$state['can_download'] = false;
$state['need_purchase'] = !$state['has_paid_access'];
return $state;
}
if ($pay_type === AKSRT_Domain_Model::PAY_TYPE_DOWNLOAD) {
$download_cost = function_exists('aksrt_resource_download_cost')
? (int) aksrt_resource_download_cost($resource_post)
+91 -1
View File
@@ -284,11 +284,101 @@ class AKSRT_Download {
update_post_meta((int) $row['post_id'], '_aksrt_res_download_count', $count + 1);
}
$target_url = esc_url_raw((string) $meta['downloads'][$idx]['url']);
$speed_kb = 0;
$uid_speed = (int) ($row['user_id'] ?? 0);
if ($uid_speed > 0 && function_exists('aksrt_get_user_download_speed')) {
$speed_kb = aksrt_get_user_download_speed($uid_speed);
}
// Local attachment / uploads file: optional throttled stream (Zibll speed limit).
$local_path = $this->resolve_local_download_path($target_url);
if ($local_path !== '' && is_readable($local_path)) {
$this->stream_local_file($local_path, $speed_kb);
exit;
}
nocache_headers();
wp_redirect(esc_url_raw((string) $meta['downloads'][$idx]['url']), 302);
wp_redirect($target_url, 302);
exit;
}
/**
* Map a site-local URL to a filesystem path under uploads (or ABSPATH).
*/
private function resolve_local_download_path(string $url): string {
if ($url === '') {
return '';
}
$uploads = wp_upload_dir();
$baseurl = trailingslashit((string) ($uploads['baseurl'] ?? ''));
$basedir = trailingslashit((string) ($uploads['basedir'] ?? ''));
if ($baseurl !== '' && str_starts_with($url, $baseurl)) {
$rel = substr($url, strlen($baseurl));
$path = $basedir . ltrim(rawurldecode($rel), '/\\');
$real = realpath($path);
$root = realpath($basedir);
if ($real && $root && str_starts_with($real, $root) && is_file($real)) {
return $real;
}
}
// Same-host path under ABSPATH.
$home = trailingslashit(home_url('/'));
if (str_starts_with($url, $home)) {
$rel = substr($url, strlen($home));
$path = ABSPATH . ltrim(rawurldecode($rel), '/\\');
$real = realpath($path);
$root = realpath(ABSPATH);
if ($real && $root && str_starts_with($real, $root) && is_file($real)) {
return $real;
}
}
return '';
}
/**
* Stream a local file with optional KB/s throttle.
*/
private function stream_local_file(string $path, int $speed_kb = 0): void {
$filename = basename($path);
$size = filesize($path);
if ($size === false) {
$this->fail_410();
}
nocache_headers();
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . rawurlencode($filename) . '"');
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . (string) $size);
$chunk = 1024 * 64; // 64KB
if ($speed_kb > 0) {
$chunk = max(1024, min($chunk, $speed_kb * 1024));
}
$fh = fopen($path, 'rb');
if (!$fh) {
$this->fail_410();
}
while (!feof($fh)) {
$buf = fread($fh, $chunk);
if ($buf === false) {
break;
}
echo $buf;
if (function_exists('flush')) {
flush();
}
if ($speed_kb > 0) {
// Approximate throttle: sleep 1s per speed_kb window when chunk ~= speed_kb KB.
usleep(200000); // 0.2s per chunk for smoother throttle
}
}
fclose($fh);
}
private function fail_410(): void {
status_header(410);
nocache_headers();
+77 -10
View File
@@ -101,17 +101,48 @@ class AKSRT_Resource_Meta_Box {
<div class="aksrt-pmb__section-title"><?php esc_html_e('价格设置', 'aksrt'); ?></div>
<div class="aksrt-pmb__row">
<label for="aksrt_pay_price"><?php esc_html_e('价格(积分)', 'aksrt'); ?></label>
<label for="aksrt_pay_modo"><?php esc_html_e('支付方式', 'aksrt'); ?></label>
<div>
<input type="number" id="aksrt_pay_price" name="aksrt_pay_price" value="<?php echo esc_attr((string) $meta['price']); ?>" min="0" step="1">
<p class="aksrt-pmb__hint"><?php esc_html_e('0 = 免费;> 0 = 用户需支付对应积分。', 'aksrt'); ?></p>
<select id="aksrt_pay_modo" name="aksrt_pay_modo" style="min-width:160px;">
<option value="0" <?php selected((string) ($meta['pay_modo'] ?? '0'), '0'); ?>><?php esc_html_e('现金', 'aksrt'); ?></option>
<option value="points" <?php selected((string) ($meta['pay_modo'] ?? '0'), 'points'); ?>><?php esc_html_e('积分', 'aksrt'); ?></option>
</select>
<p class="aksrt-pmb__hint"><?php esc_html_e('对齐 zibll pay_modo:现金价或积分价结算。', 'aksrt'); ?></p>
</div>
</div>
<div class="aksrt-pmb__row">
<label for="aksrt_pay_price"><?php esc_html_e('现金价格', 'aksrt'); ?></label>
<div>
<input type="number" id="aksrt_pay_price" name="aksrt_pay_price" value="<?php echo esc_attr((string) $meta['price']); ?>" min="0" step="0.01">
<p class="aksrt-pmb__hint"><?php esc_html_e('支付方式为「现金」时使用。0 = 免费。', 'aksrt'); ?></p>
</div>
</div>
<div class="aksrt-pmb__row">
<label for="aksrt_pay_points_price"><?php esc_html_e('积分价格', 'aksrt'); ?></label>
<div>
<input type="number" id="aksrt_pay_points_price" name="aksrt_pay_points_price" value="<?php echo esc_attr((string) ($meta['points_price'] ?? 0)); ?>" min="0" step="1">
<p class="aksrt-pmb__hint"><?php esc_html_e('支付方式为「积分」时使用。0 = 免费。', 'aksrt'); ?></p>
</div>
</div>
<div class="aksrt-pmb__row">
<label for="aksrt_pay_original_price"><?php esc_html_e('原价(划线价)', 'aksrt'); ?></label>
<div>
<input type="number" id="aksrt_pay_original_price" name="aksrt_pay_original_price" value="<?php echo esc_attr((string) ($meta['original_price'] ?? '')); ?>" min="0" step="1" placeholder="<?php esc_attr_e('可选,显示划线效果', 'aksrt'); ?>">
<input type="number" id="aksrt_pay_original_price" name="aksrt_pay_original_price" value="<?php echo esc_attr((string) ($meta['original_price'] ?? '')); ?>" min="0" step="0.01" placeholder="<?php esc_attr_e('可选,显示划线效果', 'aksrt'); ?>">
</div>
</div>
<div class="aksrt-pmb__row">
<label for="aksrt_pay_limit"><?php esc_html_e('购买限制', 'aksrt'); ?></label>
<div>
<select id="aksrt_pay_limit" name="aksrt_pay_limit" style="min-width:200px;">
<option value="0" <?php selected((int) ($meta['pay_limit'] ?? 0), 0); ?>><?php esc_html_e('所有用户可购买', 'aksrt'); ?></option>
<option value="1" <?php selected((int) ($meta['pay_limit'] ?? 0), 1); ?>><?php esc_html_e('仅 VIP1 及以上', 'aksrt'); ?></option>
<option value="2" <?php selected((int) ($meta['pay_limit'] ?? 0), 2); ?>><?php esc_html_e('仅 VIP2', 'aksrt'); ?></option>
</select>
<p class="aksrt-pmb__hint"><?php esc_html_e('对齐 zibll pay_limit:限制购买人群(会员专属资源)。', 'aksrt'); ?></p>
</div>
</div>
@@ -120,15 +151,23 @@ class AKSRT_Resource_Meta_Box {
<div>
<div class="aksrt-pmb__vip-row">
<div>
<label for="aksrt_pay_vip1_price"><?php esc_html_e('VIP1 价格', 'aksrt'); ?></label>
<input type="number" id="aksrt_pay_vip1_price" name="aksrt_pay_vip1_price" value="<?php echo esc_attr((string) ($meta['vip1_price'] ?? '')); ?>" min="0" step="1" placeholder="0">
<label for="aksrt_pay_vip1_price"><?php esc_html_e('VIP1 现金价', 'aksrt'); ?></label>
<input type="number" id="aksrt_pay_vip1_price" name="aksrt_pay_vip1_price" value="<?php echo esc_attr((string) ($meta['vip1_price'] ?? '')); ?>" min="0" step="0.01" placeholder="0">
</div>
<div>
<label for="aksrt_pay_vip2_price"><?php esc_html_e('VIP2 价格', 'aksrt'); ?></label>
<input type="number" id="aksrt_pay_vip2_price" name="aksrt_pay_vip2_price" value="<?php echo esc_attr((string) ($meta['vip2_price'] ?? '')); ?>" min="0" step="1" placeholder="0">
<label for="aksrt_pay_vip2_price"><?php esc_html_e('VIP2 现金价', 'aksrt'); ?></label>
<input type="number" id="aksrt_pay_vip2_price" name="aksrt_pay_vip2_price" value="<?php echo esc_attr((string) ($meta['vip2_price'] ?? '')); ?>" min="0" step="0.01" placeholder="0">
</div>
<div>
<label for="aksrt_pay_vip1_points"><?php esc_html_e('VIP1 积分价', 'aksrt'); ?></label>
<input type="number" id="aksrt_pay_vip1_points" name="aksrt_pay_vip1_points" value="<?php echo esc_attr((string) ($meta['vip1_points'] ?? '')); ?>" min="0" step="1" placeholder="0">
</div>
<div>
<label for="aksrt_pay_vip2_points"><?php esc_html_e('VIP2 积分价', 'aksrt'); ?></label>
<input type="number" id="aksrt_pay_vip2_points" name="aksrt_pay_vip2_points" value="<?php echo esc_attr((string) ($meta['vip2_points'] ?? '')); ?>" min="0" step="1" placeholder="0">
</div>
</div>
<p class="aksrt-pmb__hint"><?php esc_html_e('VIP 用户可以专属价格购买。留空或 0 表示不启用。', 'aksrt'); ?></p>
<p class="aksrt-pmb__hint"><?php esc_html_e('VIP 用户专属价。留空或 0 表示不启用该档位优惠。', 'aksrt'); ?></p>
</div>
</div>
</div>
@@ -383,10 +422,28 @@ class AKSRT_Resource_Meta_Box {
}
// 价格字段
$pay_modo = sanitize_key((string) ($_POST['aksrt_pay_modo'] ?? '0'));
if ($pay_modo !== 'points') {
$pay_modo = '0';
}
$price = max(0, (float) ($_POST['aksrt_pay_price'] ?? 0));
$points_price = max(0, (int) ($_POST['aksrt_pay_points_price'] ?? 0));
// Keep legacy single-price editors usable: if points field empty and modo points, mirror price.
if ($pay_modo === 'points' && $points_price <= 0 && $price > 0) {
$points_price = (int) ceil($price);
}
if ($pay_modo !== 'points' && $price <= 0 && $points_price > 0) {
$price = (float) $points_price;
}
$original_price = max(0, (float) ($_POST['aksrt_pay_original_price'] ?? 0));
$vip1_price = max(0, (float) ($_POST['aksrt_pay_vip1_price'] ?? 0));
$vip2_price = max(0, (float) ($_POST['aksrt_pay_vip2_price'] ?? 0));
$vip1_points = max(0, (int) ($_POST['aksrt_pay_vip1_points'] ?? 0));
$vip2_points = max(0, (int) ($_POST['aksrt_pay_vip2_points'] ?? 0));
$pay_limit = (int) ($_POST['aksrt_pay_limit'] ?? 0);
if (!in_array($pay_limit, [0, 1, 2], true)) {
$pay_limit = 0;
}
// 资源信息
$version = sanitize_text_field((string) ($_POST['aksrt_pay_version'] ?? ''));
@@ -413,10 +470,15 @@ class AKSRT_Resource_Meta_Box {
// 保存统一元数据
$pay_meta = AKSRT_Domain_Model::sanitize_pay_meta([
'pay_type' => $pay_type,
'pay_modo' => $pay_modo,
'price' => $price,
'points_price' => $points_price,
'original_price' => $original_price,
'vip1_price' => $vip1_price,
'vip2_price' => $vip2_price,
'vip1_points' => $vip1_points,
'vip2_points' => $vip2_points,
'pay_limit' => $pay_limit,
'version' => $version,
'size' => $size,
'compat' => $compat,
@@ -441,7 +503,12 @@ class AKSRT_Resource_Meta_Box {
// 同步标量字段用于查询(模仿 zibll 的 zibpay_type 同步)
update_post_meta($post_id, AKSRT_Domain_Model::PAY_TYPE_KEY, (string) $pay_meta['pay_type']);
update_post_meta($post_id, AKSRT_Domain_Model::PAY_PRICE_KEY, (float) $pay_meta['price']);
$query_price = ($pay_meta['pay_modo'] === 'points')
? (float) $pay_meta['points_price']
: (float) $pay_meta['price'];
update_post_meta($post_id, AKSRT_Domain_Model::PAY_PRICE_KEY, $query_price);
update_post_meta($post_id, '_aksrt_pay_modo', (string) $pay_meta['pay_modo']);
update_post_meta($post_id, '_aksrt_pay_limit', (int) $pay_meta['pay_limit']);
// 清理旧的 _aksrt_is_resource 标记
update_post_meta(
+12
View File
@@ -107,10 +107,15 @@ class AKSRT_Domain_Model {
public static function default_pay_meta(): array {
return [
'pay_type' => self::PAY_TYPE_NO,
'pay_modo' => '0', // '0' cash | 'points' credits (Zibll)
'price' => 0.0,
'points_price' => 0,
'original_price' => 0.0,
'vip1_price' => 0.0,
'vip2_price' => 0.0,
'vip1_points' => 0,
'vip2_points' => 0,
'pay_limit' => 0, // 0 all | 1 VIP1+ | 2 VIP2 only (Zibll)
'version' => '',
'size' => '',
'license' => '',
@@ -298,10 +303,17 @@ class AKSRT_Domain_Model {
public static function sanitize_pay_meta(array $raw): array {
$meta = self::default_pay_meta();
$meta['pay_type'] = self::normalize_pay_type((string) ($raw['pay_type'] ?? $meta['pay_type']));
$pay_modo = sanitize_key((string) ($raw['pay_modo'] ?? '0'));
$meta['pay_modo'] = $pay_modo === 'points' ? 'points' : '0';
$meta['price'] = max(0, (float) ($raw['price'] ?? 0));
$meta['points_price'] = max(0, (int) ($raw['points_price'] ?? $raw['price'] ?? 0));
$meta['original_price'] = max(0, (float) ($raw['original_price'] ?? 0));
$meta['vip1_price'] = max(0, (float) ($raw['vip1_price'] ?? 0));
$meta['vip2_price'] = max(0, (float) ($raw['vip2_price'] ?? 0));
$meta['vip1_points'] = max(0, (int) ($raw['vip1_points'] ?? $raw['vip1_price'] ?? 0));
$meta['vip2_points'] = max(0, (int) ($raw['vip2_points'] ?? $raw['vip2_price'] ?? 0));
$pay_limit = (int) ($raw['pay_limit'] ?? 0);
$meta['pay_limit'] = in_array($pay_limit, [0, 1, 2], true) ? $pay_limit : 0;
$meta['version'] = sanitize_text_field((string) ($raw['version'] ?? ''));
$meta['size'] = sanitize_text_field((string) ($raw['size'] ?? ''));
$meta['license'] = sanitize_text_field((string) ($raw['license'] ?? ''));
+33 -7
View File
@@ -374,19 +374,45 @@ if (!function_exists('aksrt_is_download_alone_page_enabled')) {
if (!function_exists('aksrt_get_pay_price_label')) {
function aksrt_get_pay_price_label($post = null): array {
$meta = aksrt_get_pay_meta($post);
$price = (float) $meta['price'];
$pay_modo = (($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0';
$pay_limit = (int) ($meta['pay_limit'] ?? 0);
$currency = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name() : __('积分', 'aksrt');
if ($meta['pay_type'] === AKSRT_PAY_TYPE_VIP) {
return ['text' => __('VIP 专属', 'aksrt'), 'free' => false, 'amount' => 0];
$cash_symbol = '¥';
if (class_exists('AKSRT_Settings')) {
$paid = AKSRT_Settings::get_paid_content_settings();
$cash_symbol = (string) ($paid['display']['currency_symbol'] ?? '¥');
}
if ($meta['pay_type'] === AKSRT_PAY_TYPE_VIP || $pay_limit > 0) {
return ['text' => __('会员专属', 'aksrt'), 'free' => false, 'amount' => 0, 'modo' => $pay_modo, 'pay_limit' => $pay_limit];
}
if ($pay_modo === 'points') {
$price = (int) ($meta['points_price'] ?? 0);
if ($price <= 0) {
return ['text' => __('免费', 'aksrt'), 'free' => true, 'amount' => 0, 'modo' => 'points', 'pay_limit' => 0];
}
return [
'text' => sprintf(__('%s %s', 'aksrt'), aksrt_format_price((float) $price), $currency),
'free' => false,
'amount' => (float) $price,
'modo' => 'points',
'pay_limit' => 0,
];
}
$price = (float) $meta['price'];
if ($price <= 0) {
return ['text' => __('免费', 'aksrt'), 'free' => true, 'amount' => 0];
return ['text' => __('免费', 'aksrt'), 'free' => true, 'amount' => 0, 'modo' => '0', 'pay_limit' => 0];
}
$amount = aksrt_format_price($price);
return ['text' => sprintf(__('%s %s', 'aksrt'), $amount, $currency), 'free' => false, 'amount' => $price];
return [
'text' => $cash_symbol . aksrt_format_price($price),
'free' => false,
'amount' => $price,
'modo' => '0',
'pay_limit' => 0,
];
}
}
+50 -13
View File
@@ -169,30 +169,44 @@ if (!function_exists('aksrt_resource_card_badge_text')) {
return '';
}
$price = (float) ($meta['price'] ?? 0);
$pay_modo = (($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0';
$pay_limit = (int) ($meta['pay_limit'] ?? 0);
$price = $pay_modo === 'points'
? (float) ($meta['points_price'] ?? 0)
: (float) ($meta['price'] ?? 0);
$type_labels = function_exists('aksrt_get_pay_types') ? aksrt_get_pay_types() : [];
$type_name = $type_labels[$pay_type] ?? __('付费内容', 'aksrt');
// VIP-only posts.
if ($pay_type === 'vip') {
// VIP-only / purchase limit (Zibll pay_limit).
if ($pay_type === 'vip' || $pay_limit > 0) {
return __('会员专属', 'aksrt');
}
// Free paid-type (price 0): Zibll shows "免费资源" / free type name.
// Free paid-type (price 0).
if ($price <= 0) {
if ($pay_type === 'download') {
return __('免费资源', 'aksrt');
}
// e.g. 付费阅读 → 免费阅读
return str_replace(__('付费', 'aksrt'), __('免费', 'aksrt'), (string) $type_name);
}
$currency = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name() : __('积分', 'aksrt');
$amount = function_exists('aksrt_resource_price_amount')
? aksrt_resource_price_amount($price)
: (string) $price;
if ($pay_modo === 'points') {
$currency = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name() : __('积分', 'aksrt');
$amount = function_exists('aksrt_resource_price_amount')
? aksrt_resource_price_amount($price)
: (string) (int) $price;
return sprintf('%s %s%s', $type_name, $amount, $currency);
}
return sprintf('%s %s%s', $type_name, $amount, $currency);
$symbol = '¥';
if (class_exists('AKSRT_Settings')) {
$paid = AKSRT_Settings::get_paid_content_settings();
$symbol = (string) ($paid['display']['currency_symbol'] ?? '¥');
}
$amount = function_exists('aksrt_format_price')
? aksrt_format_price($price)
: (string) $price;
return sprintf('%s %s%s', $type_name, $symbol, $amount);
}
}
@@ -417,21 +431,44 @@ if (!function_exists('aksrt_resource_download_cost')) {
*/
function aksrt_resource_download_cost($post = null): int {
$meta = aksrt_get_resource_meta($post);
// Prefer unified pay meta when available (pay_modo / points_price).
if (function_exists('aksrt_get_pay_meta')) {
$pay = aksrt_get_pay_meta($post);
if (is_array($pay) && (($pay['pay_type'] ?? '') === 'download' || !empty($pay['downloads']))) {
$meta = array_merge($meta, $pay);
}
}
$paid_settings = class_exists('AKSRT_Settings')
? AKSRT_Settings::get_paid_content_settings()
: [
'download_credit' => true,
'default_cost' => 0,
];
if (empty($paid_settings['download_credit'])) return 0;
$pay_modo = (($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0';
// Cash mode: do not deduct credits for download unlock (order/cash checkout path).
// Points mode or legacy credit gate: use points_price / price.
if ($pay_modo !== 'points' && empty($paid_settings['download_credit'])) {
return 0;
}
if ($pay_modo === '0' && empty($paid_settings['download_credit'])) {
// Cash purchase path — cost handled by orders, not credit deduct.
return 0;
}
if (function_exists('get_current_user_id') && aksrt_is_download_credit_exempt_user((int) get_current_user_id())) {
return 0;
}
$price = (float) $meta['price'];
if ($pay_modo === 'points') {
$price = (float) ($meta['points_price'] ?? $meta['price'] ?? 0);
} else {
// When download_credit is on, treat price as credit cost (legacy AKSRT).
$price = (float) ($meta['price'] ?? 0);
}
if ($price > 0) {
return (int) ceil($price);
}
// 单条未设价格 → 回退到全局默认价
return max(0, (int) ($paid_settings['default_cost'] ?? 0));
}
}