搜索全站
安装部署 13 分钟阅读

用 WooCommerce 搭建数字商品售卖站:自动发货与授权码机制

卖模板、插件、网课、电子书,最麻烦的是发货和授权。本文对比 WooCommerce 虚拟商品与 Easy Digital Downloads 两条路线,给出自动发货、下载链接保护、授权码生成与接口校验的完整实现。

U
UFO_PF 2018-04-21 · 阅读 13 分钟
用 安装部署
阅读提示
本文约 5,159 字,预计阅读 13 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:半夜三点的人工发货,撑不过第一个爆款

做 WordPress 主题或插件变现的独立开发者,最初的套路几乎都一样:PayPal 收款后手动打包发送。前几十单还能撑住,一旦内容被推到首页或被某个社群传播,半夜里订单蜂拥而至,你还得爬起来回邮件。更糟的是下载链接发出去就等于公开——网络上两天后就出现”破解版”。数字商品站真正需要的是三件事:付款即自动发货、下载链接有时间与次数限制、软件本身能校验用户是否真的买过。

原理分析:两条成熟路线的取舍

路线一是在 WooCommerce 里把商品勾选 Virtual(虚拟)+ Downloadable(可下载),WooCommerce 自带 Download limit 与 Download expiry 字段,权限记录在下载权限表里并与订单绑定,发货逻辑由订单状态驱动:支付完成 → 授予下载权限 → 触发邮件。

路线二是用 Easy Digital Downloads(EDD),它天生为虚拟商品设计,有 download_history、file delivery method、以及官方扩展 Software Licensing 来做激活与校验。缺点是模板与支付网关生态要重新熟悉。

维度 WooCommerce 虚拟商品 Easy Digital Downloads
上手成本 低,已有 Woo 直接改 中,需另行搭建商品页
授权功能 需第三方商业扩展 官方有收费扩展 Software Licensing
与博客共存 天然兼容 需处理两套 URL 结构
适合人群 以内容为主、顺带卖书 以数字产品为主业

下载保护层面,Nginx 下可以用内部重定向(X-Accel-Redirect)让真实文件不暴露路径:location 设为 internal;,PHP 侧返回 X-Accel-Redirect 响应头即可让 Nginx 接管发送。

保姆级实操

步骤 1:建立商品。 WooCommerce → 商品 → 新建,在「商品数据」下拉中选择 Virtual + Downloadable,上传文件到 Downloads 标签页,填入 Download limit(留空表示不限次数)与 Download expiry(过期天数,留空表示永不过期)。

步骤 2:开启自动完成。 WooCommerce → 设置 → 商品 → Virtual → 勾上 “Virtual” 商品订单在付款后自动转为 Completed,权限即刻生效,邮件自动带走下载链接。

步骤 3:锁住上传目录,仅允许经脚本取文件:

# 真实文件不允许直接 URL 访问,由 PHP 验权后通过内部重定向输出
location /wp-content/uploads/protected/ {
    internal;
    alias /var/www/html/wp-content/uploads/protected/;
}

步骤 4:自建 REST 接口做授权校验。 下面是一个真实可用的注册端点和校验逻辑:

add_action( 'rest_api_init', function () {
    register_rest_route( 'mimangfei/v1', '/license/activate', [
        'methods'             => 'POST',
        'permission_callback' => '__return_true',
        'callback'            => function ( WP_REST_Request $req ) {
            $license = sanitize_text_field( $req->get_param( 'license_key' ) );
            $domain  = sanitize_text_field( $req->get_param( 'domain' ) );
            global $wpdb;
            $row = $wpdb->get_row( $wpdb->prepare(
                "SELECT * FROM {$wpdb->prefix}mf_licenses WHERE license_key = %s",
                $license
            ) );
            if ( ! $row ) {
                return new WP_REST_Response( [ 'valid' => false, 'code' => 'LICENSE_NOT_FOUND' ], 404 );
            }
            if ( time() > strtotime( $row->expires_at ) ) {
                return new WP_REST_Response( [ 'valid' => false, 'code' => 'LICENSE_EXPIRED' ], 403 );
            }
            $sites = array_filter( explode( ',', $row->activated_domains ) );
            if ( count( $sites ) >= (int) $row->limit && ! in_array( $domain, $sites, true ) ) {
                return new WP_REST_Response( [ 'valid' => false, 'code' => 'LICENSE_LIMIT_EXCEEDED' ], 409 );
            }
            $sites[]     = $domain;
            $wpdb->update(
                "{$wpdb->prefix}mf_licenses",
                [ 'activated_domains' => implode( ',', array_unique( $sites ) ) ],
                [ 'license_key' => $license ]
            );
            return new WP_REST_Response( [ 'valid' => true, 'expires_at' => $row->expires_at ], 200 );
        },
    ] );
} );

// 付款成功时生成授权码并写表(演示:真实项目请改用更安全的随机源)
add_action( 'woocommerce_order_status_completed', function ( $order_id ) {
    $order = wc_get_order( $order_id );
    foreach ( $order->get_items() as $item ) {
        if ( ! wc_get_product( $item->get_product_id() )->is_virtual() ) {
            continue;
        }
        $key = strtoupper( wp_generate_password( 16, false, false ) );
        global $wpdb;
        $wpdb->insert(
            "{$wpdb->prefix}mf_licenses",
            [
                'license_key'       => implode( '-', str_split( $key, 4 ) ),
                'order_id'          => $order_id,
                'limit'             => 3,
                'expires_at'        => gmdate( 'Y-m-d H:i:s', time() + YEAR_IN_SECONDS ),
                'activated_domains' => '',
            ]
        );
    }
} );

步骤 5:配套一张表。 插件启用时用 dbDelta 建表,注意字段之间必须两个空格:

register_activation_hook( __FILE__, function () {
    global $wpdb;
    $charset = $wpdb->get_charset_collate();
    $sql = "CREATE TABLE {$wpdb->prefix}mf_licenses (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        license_key varchar(64) NOT NULL,
        order_id bigint(20) NOT NULL,
        limit int(11) DEFAULT 3,
        activated_domains text,
        expires_at datetime NOT NULL,
        PRIMARY KEY  (id),
        UNIQUE KEY license_key (license_key)
    ) $charset;";
    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta( $sql );
} );

步骤 6:把授权码写进邮件。 使用 woocommerce_email_order_meta 输出刚才生成的 key,用户下单后无需人工介入。

常见踩坑

  1. 只勾了 Virtual 忘了 Downloadable,商品页不出现 Downloads 标签,压根没有下载权限。
  2. 案例: 某开发者把下载文件放在公共 uploads 目录,也没有任何限制,搜索引擎第三方爬虫抓取了 PDF,一个月后盗版文档在盗盘论坛传播,退款率激增。
  3. dbDelta() 的 SQL 字段间只有一个空格,表结构更新被静默忽略。
  4. Nginx alias 路径末尾少了斜杠,Nginx 报 403 Forbidden,但 WooCommerce 侧只表现为”点了下载没反应”。
  5. 开了 CDN 且未排除下载路径,CDN 节点缓存了带鉴权的响应,非付费用户也能撞缓存下载。
  6. 授权校验接口没做频率限制,被脚本轮询爆破——至少加个 Transients 限流。WordPress 权限管理与安全加固
  7. 未设置产品退款政策,数码商品被滥用退款。WooCommerce 退款与售后流程

总结

数字商品站的判断标准是:发货是否全自动、文件是否无法被直接列举、授权是否可校验。行动清单:确定 WooCommerce 还是 EDD → 打通付款到 Completed 的自动转换 → 把上传目录设为内部访问 → 自建或用商业 License 扩展做校验 → 邮件注入授权信息。别急着商业化的细节,先保证”付费即交付”这一环绝对可靠。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。