搜索全站
建站 12 分钟阅读

WordPress 活动报名与票务系统:报名表单、名额控制与支付对接

一百个名额被一百零三人抢完,问题出在"检查余额"和"扣减"不是一步完成。本文用 WooCommerce 库存机制做名额控制,配 Rocket.Forms 表单、电子票与签到核销,搭一套完整报名站。

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

引言:活动现场多了三个人,座位只有一个

线下活动最尴尬的不是没人来,是超额。多人同时在最后几个名额上提交,程序各自读到”还剩 1″,各自写入成功,最后名单比座位多。这类超卖本质上和目标用户并发有关,和用什么插件关系不大;如果你用单选表自己 insert 记录,即便写了判断也会超。其次麻烦的是支付与流水的对应关系:报名表收完了,支付在哪收?付了之后怎么开票、怎么退、怎么改期?把这些串起来才叫活动系统。

原理分析:名额必须用「原子操作」控制,别自己数数

常见的错误做法是先 SELECT count(*) 再 INSERT。两个请求之间的时间窗口足以让数字失效。可靠的做法有三类:

一是数据库层面用唯一约束:orders 表里 (event_id,user_id) 唯一,或者用一个带 AUTO_INCREMENT 上限的票表,靠 INSERT ... SELECT 原子插入。二是借用 WooCommerce 的库存管理:把”票”当成一件库存有限的商品,扣减由 WooCommerce 在 WC_Order 层面完成,天然具备原子性与回滚。三是 Redis 原子自增(INCR)+ 上限判断,适合大流量秒杀。

对普通活动站,方案二性价比最高:支付、邮件、退款、发票全部现成。

方案 原子性 配套能力 适用规模
自建表 + 唯一索引 强 需各自实现 小型免费活动
WooCommerce 商品做票 强 支付/退款/邮件齐全 中大型付费活动
The Events Calendar + Event Tickets 强 日历/参与者管理 需要日历与多场次

保姆级实操

步骤 1:确定票结构。 把每种票种(早鸟、全价、学生)建一个 WooCommerce 产品,勾选“管理库存”,把库存量设为名额,Sold individually 设为 true,并设为 Virtual 商品。还要特别注意“库存保留”设置:WooCommerce 有 woocommerce_hold_stock_minutes 选项,在付款未完成期间保留库存(默认是 60 分钟),报名类活动建议缩短到 10 分钟。

步骤 2:活动页与票关联。 用 The Events Calendar 承载活动时间地点,用其官方扩展 Event Tickets 或 Event Tickets Plus 关联 WooCommerce 票种;免费活动可直接用 Event Tickets 自带的 RSVP。

步骤 3:补一张报名信息 custom 表。 活动特有字段(单位、发票抬头、饮食禁忌)不该塞进 WooCommerce order meta 里几十年不清理:

register_activation_hook( __FILE__, function () {
    global $wpdb;
    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta( "CREATE TABLE {$wpdb->prefix}mf_attendees (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        order_id bigint(20) NOT NULL,
        product_id bigint(20) NOT NULL,
        full_name varchar(100) NOT NULL,
        phone varchar(32) DEFAULT '',
        company varchar(255) DEFAULT '',
        ticket_code varchar(32) NOT NULL,
        checked_in tinyint(1) DEFAULT 0,
        PRIMARY KEY  (id),
        UNIQUE KEY ticket_code (ticket_code),
        KEY order_product (order_id, product_id)
    ) {$wpdb->get_charset_collate()};" );
} );

步骤 4:付款完成写票,超卖在 INSERT 层被拦住:

add_action( 'woocommerce_order_status_processing', function ( $order_id ) {
    $order = wc_get_order( $order_id );
    global $wpdb;
    foreach ( $order->get_items() as $item ) {
        $product = $item->get_product();
        if ( ! $product || ! $product->is_virtual() ) {
            continue;
        }
        for ( $i = 0; $i < $item->get_quantity(); $i++ ) {
            $wpdb->query( $wpdb->prepare(
                "INSERT IGNORE INTO {$wpdb->prefix}mf_attendees
                 (order_id, product_id, full_name, phone, company, ticket_code)
                 VALUES (%d,%d,%s,%s,%s,%s)",
                $order_id,
                $item->get_product_id(),
                $order->get_formatted_billing_full_name(),
                $order->get_billing_phone(),
                $order->get_billing_company(),
                strtoupper( wp_generate_password( 12, false, false ) )
            ) );
            // INSERT IGNORE 若因 ticket_code 重复被忽略,说明需要重试生成
        }
    }
} );

这里的 INSERT IGNORE 只是为了幂等:重复的 order 提交流水不会生成第二张票。真正防止超卖的是 WooCommerce 的商品库存,别指望应用层去重。

步骤 5:生成二维码签到码。 前端可用 qrcodejs(David Shim 的开源库)渲染,认证接口用 REST:

const ticketCode = document.getElementById('ticket-code').dataset.code;
new QRCode(document.getElementById('qr-box'), {
    text: `${window.location.origin}/checkin?code=${encodeURIComponent(ticketCode)}`,
    width: 200,
    height: 200,
    correctLevel: QRCode.CorrectLevel.M
});

步骤 6:签到核销接口。 只允许特定角色调用,避免被人肉核销:

add_action( 'rest_api_init', function () {
    register_rest_route( 'mimangfei/v1', '/checkin', [
        'methods'  => 'POST',
        'permission_callback' => function () {
            return current_user_can( 'edit_posts' ); // 现场工作人员账号用 shop_manager/editor
        },
        'callback' => function ( WP_REST_Request $req ) {
            global $wpdb;
            $code = sanitize_text_field( $req->get_param( 'code' ) );
            $updated = $wpdb->query( $wpdb->prepare(
                "UPDATE {$wpdb->prefix}mf_attendees SET checked_in = 1 WHERE ticket_code = %s AND checked_in = 0",
                $code
            ) );
            return new WP_REST_Response( [ 'ok' => (bool) $updated ], $updated ? 200 : 409 );
        },
    ] );
} );

常见踩坑

  1. 名额用 select count 自己判断,高并发下必然超卖。
  2. 案例: 某沙龙活动主办方把库存保留改成 1440 分钟(一天),未付款订单占位一整天,实际到场率不到六成,多个渠道推广的引流全部作废。
  3. 用同一 Woocommerce 商品做了多个时间场次,早鸟票结束后忘记改库存导致长期超卖。
  4. current_user_can() 在 REST 请求早期执行取不到 cookie(未加 nonce),建议前端调 appearance X-WP-Nonce,否则接口恒返回 401。
  5. 报名 form 用了 mailto 提交,火狐/安全软件拦截后数据全部丢失。
  6. wp_generate_password() 默认含特殊字符,二维码内容需 encodeURIComponent,否则扫码后参数被截断。
  7. 退票没有同时增加 WooCommerce 库存和标记 attendee 记录,二次统计出现虚位。WooCommerce 退款与订单管理

总结

判断一个报名系统是否靠谱的标准:名额会不会超、付了款有没有可核销凭证、退改是否成闭环。行动清单:用 WooCommerce 商品做票 → 库存=名额且缩短保留时长 → attendee 明细独立建表 → 票码 QR + REST 核销 → 退款时同时回滚。对于有大流量预期的活动,再考虑 Redis 预扣减方案。不追求成品插件、完全自建的话,报名校验与防重这块可以看 WordPress 表单提交与安全。

U
UFO_PF作者

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

留下你的想法

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