搜索全站
建站 11 分钟阅读

WordPress 会员站续费、到期与权限回收:订阅周期与自动降级

订阅制会员站最容易出事的一天,是到期那天。本文讲清 WP-Cron 的真实执行机制、到期降级的钩子顺序、以及如何用系统 cron 兜住定时任务。

U
UFO_PF 2026-08-04 · 阅读 11 分钟
Wo 建站
阅读提示
本文约 4,282 字,预计阅读 11 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:到期那天,用户和站长都会出问题

订阅制会员站上线三个月后,会迎来第一批到期用户。典型投诉有两种:用户说”我明明续费了却被踢出来了”,站长说”到期了怎么还能看”。前者多为支付通知与站点时钟不一致,后者多为定时任务没跑或者权限判断时机不对。续费与到期是会员站里唯一”有状态迁移”的环节——权限要从 A 变到 B,还必须在正确的时间点变,且只能变一次。这一节讲清楚这条状态机怎么落地。

原理分析:WP-Cron 不是真正的定时器

WP-Cron 的机制是:每次有人访问站点时,WordPress 检查是否有到期的计划任务,有就在后台发起一次 HTTP 请求去执行。这带来两个后果——站点长期没人访问,任务就一直拖着;访问量大时,任务可能被并发触发。

所以会员到期这种”必须在准确时间执行”的任务,正确做法是把 WP-Cron 交给操作系统的 cron:在 wp-config.php 里禁用 DISABLE_WP_CRON,然后由系统每分钟拉一次 wp-cron.php。

状态迁移的时机也要选对。不要在 template_redirect 里改会员等级(会与页面渲染竞争),应该在专用钩子里改,通常是插件的等级变更钩子 pmpro_after_change_membership_level,再配合一次每日扫描兜底。

环节 推荐触发方式 说明
到期扫描 系统 cron 每日执行 兜底,处理漏掉的异步通知
支付成功 网关 Webhook 实时开通
到期提醒 每日扫描时发信 到期前 7 天 / 1 天
权限校验 每次请求实时判断 不依赖缓存中的等级

保姆级实操

步骤 1:把 WP-Cron 交给系统。 先在配置里关掉访问触发,再加一条系统定时任务。

// wp-config.php 中:关闭由页面访问触发的 WP-Cron
define( 'DISABLE_WP_CRON', true );
# 系统 crontab:每分钟由 PHP CLI 触发一次计划任务
* * * * * /usr/bin/php /var/www/html/wp-cron.php doing_wp_cron >/dev/null 2>&1

步骤 2:确认计划任务已注册。 用 WP-CLI 查看,避免插件升级后任务被清掉。

wp cron event list --fields=hook,next_run,recurrence --format=table
wp cron event run pmpro_cron_expire_memberships

步骤 3:注册自己的每日扫描任务。 激活时注册,停用时清理,这是插件开发的基本礼貌。

register_activation_hook( __FILE__, 'mimangfei_schedule_expiry_scan' );
function mimangfei_schedule_expiry_scan() {
    if ( ! wp_next_scheduled( 'mimangfei_daily_expiry_scan' ) ) {
        wp_schedule_event( strtotime( '03:00:00' ), 'daily', 'mimangfei_daily_expiry_scan' );
    }
}

register_deactivation_hook( __FILE__, 'mimangfei_clear_expiry_scan' );
function mimangfei_clear_expiry_scan() {
    $timestamp = wp_next_scheduled( 'mimangfei_daily_expiry_scan' );
    if ( $timestamp ) {
        wp_unschedule_event( $timestamp, 'mimangfei_daily_expiry_scan' );
    }
}

步骤 4:写扫描逻辑。 找出到期用户,先发提醒,再执行降级。注意用 date_default_timezone_set 或统一用 current_time('timestamp'),避免服务器时区与站点时区不一致。

add_action( 'mimangfei_daily_expiry_scan', 'mimangfei_handle_expirations' );
function mimangfei_handle_expirations() {
    global $wpdb;
    $now     = current_time( 'mysql' );
    $soon    = date( 'Y-m-d H:i:s', current_time( 'timestamp' ) + 7 * DAY_IN_SECONDS );

    // 1. 到期前 7 天提醒
    $remind = $wpdb->get_col( $wpdb->prepare(
        "SELECT user_id FROM {$wpdb->usermeta}
         WHERE meta_key = '_expires_at'
           AND meta_value > %s AND meta_value <= %s",
        $now, $soon
    ) );
    foreach ( $remind as $user_id ) {
        $user = get_userdata( $user_id );
        wp_mail(
            $user->user_email,
            '您的会员即将到期',
            '您的会员将在 7 天内到期,续费可保留原有权益。'
        );
    }

    // 2. 已到期:执行降级
    $expired = $wpdb->get_col( $wpdb->prepare(
        "SELECT user_id FROM {$wpdb->usermeta}
         WHERE meta_key = '_expires_at' AND meta_value <= %s",
        $now
    ) );
    foreach ( $expired as $user_id ) {
        mimangfei_downgrade_user( $user_id );
    }
}

function mimangfei_downgrade_user( $user_id ) {
    if ( function_exists( 'pmpro_changeMembershipLevel' ) ) {
        pmpro_changeMembershipLevel( 0, $user_id ); // 0 表示取消会员
    }
    delete_user_meta( $user_id, '_expires_at' );
    update_user_meta( $user_id, '_last_downgraded', current_time( 'mysql' ) );
}

步骤 5:用等级变更钩子做收尾。 降级后清缓存、记日志、发通知,都挂在插件的等级变更钩子上。

add_action( 'pmpro_after_change_membership_level', 'mimangfei_on_level_change', 10, 2 );
function mimangfei_on_level_change( $level_id, $user_id ) {
    clean_user_cache( $user_id );
    if ( 0 === (int) $level_id ) {
        $user = get_userdata( $user_id );
        wp_mail( $user->user_email, '会员已到期', '您的会员权限已回收,续费后可立即恢复。' );
    }
}

步骤 6:处理”续费后立刻恢复”。 支付网关的 Webhook 到达时,先更新到期时间,再调用 pmpro_changeMembershipLevel() 升级。注意幂等:同一个 Webhook 可能重复投递,要用订单号去重。

步骤 7:给宽限期。 到期后留 3 天宽限,宽限期内保留权限但页面顶部提示,能显著降低”忘了续费”导致的流失。

常见踩坑

  1. 站点没人访问导致任务不跑。 典型症状是会员到期后仍能看好几天,因为 WP-Cron 一直没被触发。
  2. 案例: 某站把到期任务挂在默认的 wp_schedule_event 上,站点凌晨无访问,任务全部堆积到早上第一波访问时集中执行,导致数据库连接数打满、首页 502。解决方式就是改用系统 cron 并把扫描时间挪到凌晨三点。
  3. 服务器时区与站点时区不一致。 用 current_time('timestamp') 而不是 time()。
  4. Webhook 重复投递造成重复续期。 必须用订单号做幂等判断。
  5. 降级了但页面还显示旧权限。 页面缓存与用户缓存都要清,clean_user_cache() 别忘了。
  6. 宽限期与到期判断互相打架。 宽限状态应单独存一个 meta,不要覆盖到期时间。
  7. 停用插件没清理计划任务。 留下孤儿任务,升级插件后重复执行。

总结

到期处理的合格标准:任务由系统 cron 触发、时间统一用站点时区、Webhook 幂等、降级后有宽限期与通知。上线前用 wp cron event run 手动跑一次,确认邮件与权限都按预期变化。会员等级怎么设计见付费会员社区权限方案;交易邮件送达率怎么保障见WordPress SMTP 邮件配置。

U
UFO_PF作者

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

留下你的想法

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