子比主题开发文档
使用指南Codestar Framework主题扩展在线部署AI 功能推荐插件赞助打赏

Zibpay 付费下载与资源权限

梳理子比主题付费资源字段、购买盒子、收银台、订单校验、下载路由、免费次数限制、下载日志和扩展 Hook。

模块边界

付费下载不是普通附件下载。子比主题会把资源信息、付费配置、购买权限、订单状态、VIP 免费、积分购买、游客订单 Cookie、下载次数限制、下载日志和本地文件输出串成一条 Zibpay 链路。

涉及下载资源时,不要直接把真实文件链接输出给未授权用户,也不要只用前端隐藏按钮。正确做法是让用户先经过 zibpay_is_paid() 权限判断,再进入 pay-download 下载路由。

核心源码:

文件作用
zibpay/functions.php注册 pay-download 路由、payshow 短代码和下载模板加载
zibpay/download.php下载请求校验、权限判断、下载次数限制、本地文件输出或远程跳转
zibpay/functions/zibpay-download.php下载按钮、资源数组、免费次数、下载速度、下载日志、远程资源转本地
zibpay/functions/zibpay-post.php付费内容盒子、已购买盒子、收银台入口、演示地址、资源属性和销量展示
zibpay/functions/zibpay-order.php付费资源下单、价格重算、优惠码、推广折扣、分佣和订单创建
zibpay/functions/zibpay-ajax.php发起支付、积分支付、支付轮询
zibpay/functions/ajax.phppay_cashier_modal 收银台弹窗
zibpay/functions/admin/admin.phpposts_zibpay 付费字段、下载资源字段、下载记录后台入口

资源字段

文章付费配置保存在 post meta:

posts_zibpay

后台字段由 CSF::createMetabox('posts_zibpay', ...) 注册,只作用于 post。常见字段:

字段说明
pay_type付费类型,2 表示付费下载
pay_modo支付类型,0 表示现金,points 表示积分
pay_limit购买权限,控制是否仅会员可买
pay_price现金执行价
points_price积分执行价
vip_1_pricevip_2_priceVIP 现金价格,填 0 可作为会员免费
vip_1_pointsvip_2_pointsVIP 积分价格,填 0 可作为会员免费
download_limit_over_price免费资源每日次数超限后的购买价格
pay_download资源下载列表
attributes资源属性,例如版本、大小、格式
demo_link演示地址
pay_title商品标题
pay_doc商品简介
pay_details商品详情
pay_extra_hide付费后额外展示内容

pay_download 是 group 字段。每条资源支持:

字段说明
link下载地址,可上传本地文件或填写远程链接
more按钮旁附加内容,例如提取码、解压密码
copy_key点击复制的名称
copy_val点击复制的内容
icon自定义按钮图标
name自定义按钮文案
class自定义按钮颜色

旧版本可能把 pay_download 存为多行文本。主题会用 zibpay_get_post_down_array() 兼容解析:

下载地址|按钮名称|备注|按钮颜色

扩展字段时要继续写入 posts_zibpay 这一组配置。不要另建一个资源 meta 后只改前端展示,否则下载模板和订单流程读不到同一份资源。

付费类型

文章类付费类型:

类型说明
1付费阅读
2付费资源/付费下载
5付费图片
6付费视频

支付类型:

类型说明
0现金商品,走 Zibpay payment/order 和支付渠道
points积分商品,走 points_initiate_pay 和积分扣减

判断是否积分商品:

$is_points = zibpay_post_is_points_modo($pay_mate, $post_id);

扩展列表筛选、卡片角标或查询条件时,可以读取主题同步保存的:

zibpay_type
zibpay_modo

它们来自 posts_zibpay 的必要挂载,只适合做查询辅助,真实权限仍然要回到 posts_zibpay 和订单状态判断。

购买盒子

文章页会在主题设置的付费盒子位置挂载:

add_action($pay_box_position, 'zibpay_posts_pay_content', 1);

zibpay_posts_pay_content() 的流程:

  1. 读取当前文章 posts_zibpay
  2. pay_type 为空或 no 时不输出。
  3. 先用 zib_get_post_allow_view_data($post) 判断文章基础阅读权限。
  4. 调用 zibpay_is_paid($post->ID) 判断当前用户是否可看。
  5. 已购买或免费时输出 zibpay_posts_paid_box()
  6. 未购买时输出 zibpay_posts_pay_box()

可替换的过滤器:

apply_filters('zibpay_posts_paid_box', '', $pay_mate, $post_id);
apply_filters('zibpay_posts_pay_box', '', $pay_mate, $post_id);

如果只是调整展示样式,优先挂这两个 Filter。不要复制整段 zibpay_posts_pay_content(),否则容易漏掉文章权限、已购买判断和免费会员判断。

权限判断

付费资源最终权限入口:

$paid = zibpay_is_paid($post_id);

它返回 false 或一组已授权数据。常见 paid_type

paid_type来源
paid当前用户或游客 Cookie 找到已支付订单
free商品价格为 0
vip1_freeVIP1 价格为 0
vip2_freeVIP2 价格为 0
trial试用能力

判断顺序:

  1. 登录用户按用户 ID 查询已支付订单。
  2. 未登录且允许游客购买时,按 zibpay_{post_id} Cookie 查询订单。
  3. 校验游客订单 Cookie 的 token。
  4. 判断现金商品或积分商品是否价格为 0
  5. 判断当前 VIP 等级是否可免费。

游客订单 Cookie 的保留天数来自:

_pz('pay_cookie_day', 15)

并可通过过滤器调整:

apply_filters('tourists_pay_cookie_days', _pz('pay_cookie_day', 15), $post_id);

是否允许游客购买可通过:

apply_filters('tourists_pay_is_allow', (!$user_id && _pz('pay_no_logged_in', true)), $post_id);

下载模板里还提供权限结果过滤器:

apply_filters('pay_download_paid_data', zibpay_is_paid($post_id), $post_id);

这个过滤器适合补充额外授权来源,例如活动赠送或企业授权。返回值必须保持主题能识别的 paid_type 结构,不要直接跳过后续下载校验。

下单流程

未购买时,购买按钮由:

zibpay_get_post_cashier_link($post_id);

生成 RefreshModal 链接,打开:

action = pay_cashier_modal

弹窗服务端入口:

add_action('wp_ajax_pay_cashier_modal', 'zibpay_ajax_pay_cashier_modal');
add_action('wp_ajax_nopriv_pay_cashier_modal', 'zibpay_ajax_pay_cashier_modal');

弹窗会校验:

  1. 文章是否存在。
  2. posts_zibpay.pay_type 是否有效。
  3. 积分商品必须登录。
  4. 会员专享资源必须满足 pay_limit
  5. 最后按支付类型输出现金或积分收银台。

现金商品下单时,zibpay_ajax_submit_order() 会处理 order_type = 2

$pay_mate = get_post_meta($post_id, 'posts_zibpay', true);
$__data['order_type']  = $pay_mate['pay_type'];
$__data['order_price'] = round((float) $pay_mate['pay_price'], 2);

它会重新读取服务端价格、VIP 价格、推广折扣、优惠码、分佣和创作分成。前端传来的价格不可信。

免费资源每日次数超限后,如果 download_limit_over_price 大于 0,主题会用:

zibpay_get_post_cashier_link($post_id, 'but jb-red mt20', __('立即购买', 'zib_language'), array(
    'price_type' => 'download_limit_over',
));

下单时再把价格切换到:

$pay_mate['download_limit_over_price']

下载路由

下载链接由:

$url = zib_pay_get_download_url($post_id);

生成。开启固定链接时格式类似:

/pay-download/{post_id}?key={nonce}&down_id={index}

不开启固定链接时格式类似:

/?pay_download={post_id}&key={nonce}&down_id={index}

相关注册:

入口作用
zib_pay_download_rewrite_rules()添加 pay-download/([0-9]+) rewrite
zib_pay_download_query_vars()注册 pay_download query var
zib_pay_download_load_template()template_redirect 加载 zibpay/download.php

下载模板会校验:

  1. 必须有 post_iddown_id
  2. 如果开启 _pz('pay_type_option', true, 'down_verify_nonce'),必须通过 wp_verify_nonce($_GET['key'], 'pay_down')
  3. 必须通过 zibpay_is_paid()pay_download_paid_data
  4. 免费资源在 pay_free_logged_show 开启时必须登录。
  5. 免费资源必须未超过每日下载次数。
  6. down_id 必须存在于 zibpay_get_post_down_array($post_id)

所以自定义下载按钮时,只能生成主题下载 URL,不要把 pay_download.link 直接放到公开页面。

下载输出

下载模板拿到资源链接后会判断是否为本站本地文件:

$home_url = home_url('/');
if (stripos($file_url, $home_url) === 0) {
    $file_url_local = ABSPATH . rtrim(str_replace($home_url, '', $file_url));
}

本地文件存在时,走:

zib_download_local_file($file_local, $file_local_filename, $download_rate);

下载速度来自:

zibpay_get_user_down_speed($user_id);

它会按普通用户或 VIP 等级读取:

_pz('vip_benefit', 0, 'pay_download_speed')
_pz('vip_benefit', 0, 'pay_download_speed_vip_1')
_pz('vip_benefit', 0, 'pay_download_speed_vip_2')

远程文件默认直接跳转。如果开启“远程文件转本地下载”,zibpay_download_remote_to_local() 会挂到:

add_filter('zibpay_download_file_local', 'zibpay_download_remote_to_local', 10, 2);

它会按配置检查:

配置说明
down_remote_to_local_s是否开启远程转本地
down_remote_to_local_size允许转存的最大文件大小
down_remote_to_local_ext允许转存的文件后缀

符合条件时,远程文件会下载到 ZIB_TEMP_DIR,文件名包含 _paydown_,再按本地文件输出。临时目录由主题上传体系定期清理。

免费次数和日志

免费资源每日下载次数:

函数作用
zibpay_get_user_free_downloaded_number()读取用户当天已下载免费资源数量
zibpay_get_user_free_down_limit()读取普通用户或 VIP 每日免费资源下载上限
zibpay_is_user_free_down_limit()判断是否超限
zibpay_updata_pay_down_mate()下载前记录次数和日志

下载前 Hook:

do_action('zibpay_download_before', $post_id, $down_id, $paid, $file_url, $file_local);

主题默认监听:

add_action('zibpay_download_before', 'zibpay_updata_pay_down_mate', 10, 3);

记录内容:

位置meta key说明
user metapay_down_number按日期记录免费资源下载次数
user metapay_down_log用户下载日志
post metapay_down_log文章资源下载日志

日志保留条数来自:

_pz('pay_type_option', 0, 'down_user_log')
_pz('pay_type_option', 0, 'down_post_log')

后台下载记录入口使用:

zibpay_get_paydown_log_admin_link($type, $id);

示例:生成安全下载按钮

function zib_docs_get_paid_download_button($post_id, $down_id = 0)
{
    $post_id = (int) $post_id;
    $down_id = (int) $down_id;

    if (!$post_id || !function_exists('zib_pay_get_download_url')) {
        return '';
    }

    $paid = function_exists('zibpay_is_paid') ? zibpay_is_paid($post_id) : false;
    if (!$paid) {
        return '';
    }

    $down = function_exists('zibpay_get_post_down_array') ? zibpay_get_post_down_array($post_id) : array();
    if (empty($down[$down_id]['link'])) {
        return '';
    }

    $url = add_query_arg(array(
        'down_id' => $down_id,
    ), zib_pay_get_download_url($post_id));

    return '<a target="_blank" href="' . esc_url($url) . '" class="but jb-blue"><i class="fa fa-download mr6" aria-hidden="true"></i>' . esc_html__('资源下载', 'zib_language') . '</a>';
}

这个示例仍然只输出 pay-download 路由,不输出真实资源链接。真正下载时,主题模板还会再次校验 nonce、权限、次数和资源 ID。

示例:监听下载并写审计

add_action('zibpay_download_before', 'zib_docs_log_paid_download', 20, 5);

function zib_docs_log_paid_download($post_id, $down_id, $paid, $file_url, $file_local)
{
    $user_id = get_current_user_id();
    if (!$post_id || empty($paid['paid_type'])) {
        return;
    }

    $log = array(
        'user_id'   => $user_id,
        'post_id'   => (int) $post_id,
        'down_id'   => (int) $down_id,
        'paid_type' => $paid['paid_type'],
        'order_num' => !empty($paid['order_num']) ? $paid['order_num'] : '',
        'time'      => current_time('mysql'),
    );

    update_post_meta((int) $post_id, 'docs_last_download_audit', $log);
}

不要在这个 Hook 里把 $file_url 原样发到公开通知、日志页面或前端接口。远程链接可能包含临时签名、本地路径映射或网盘提取信息。

示例:补充企业授权

add_filter('pay_download_paid_data', 'zib_docs_company_download_paid_data', 20, 2);

function zib_docs_company_download_paid_data($paid, $post_id)
{
    if ($paid) {
        return $paid;
    }

    $user_id = get_current_user_id();
    if (!$user_id) {
        return $paid;
    }

    $company_posts = zib_get_user_meta($user_id, 'company_paid_posts', true);
    if (!is_array($company_posts) || !in_array((int) $post_id, $company_posts, true)) {
        return $paid;
    }

    return array(
        'paid_type' => 'paid',
        'order_num' => 'company_' . $user_id . '_' . (int) $post_id,
    );
}

这类扩展只补充“授权结果”,仍然保留下载模板里的 down_id、nonce、次数限制和日志记录。

add_filter('tourists_pay_cookie_days', 'zib_docs_tourists_pay_cookie_days', 10, 2);

function zib_docs_tourists_pay_cookie_days($days, $post_id)
{
    $pay_mate = get_post_meta($post_id, 'posts_zibpay', true);
    if (!empty($pay_mate['pay_type']) && (int) $pay_mate['pay_type'] === 2) {
        return 7;
    }

    return $days;
}

游客订单依赖浏览器 Cookie,不适合承载长期授权、跨设备授权或企业授权。需要长期权限时,应要求登录购买或把授权写入用户体系。

风险清单

不要这样做推荐做法
未授权时直接输出 pay_download.link输出 zib_pay_get_download_url() 生成的下载路由
只靠前端隐藏下载按钮下载模板里用 zibpay_is_paid() 重算权限
自己拼价格提交支付zibpay_ajax_submit_order()posts_zibpay 读取价格
给免费资源绕过登录和次数限制保留 pay_free_logged_showzibpay_is_user_free_down_limit()
把本地文件路径、临时文件路径返回前端zib_download_local_file() 输出文件
在日志里公开远程下载原始 URL只记录必要的用户、文章、资源 ID 和订单号
自建下载路由绕过 zibpay_download_before复用主题下载路由和 Hook
download_file 附件 Ajax 发付费资源付费资源走 Zibpay 下载权限链路

付费下载的关键不是“按钮能点”,而是每次下载都能在服务端重新证明:用户是谁、资源是哪一条、权限从哪里来、是否超限、是否要记录日志,以及最终输出的是本地文件还是远程跳转。

On this page