关于 woocommerce-subscriptions
面向扩展 WooCommerce Subscriptions(官方高级插件)的开发技能,适用于实现周期性收费。内容涵盖订阅生命周期钩子、程序化查询、套餐变更、重试付款及自定义邮件。
主要功能
- 01创建订阅产品
- 02生命周期钩子与状态处理
- 03程序化查询订阅
- 04升级和降级的按比例计费
- 05付款重试与自定义邮件
适用场景
- 01构建周期性收费套餐
- 02根据订阅状态授予访问权限
- 03定制续费和付款失败工作流
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ finsilabs/ awesome-ecommerce-skills --skill woocommerce-subscriptions安装后在 Claude Code 中输入/woocommerce-subscriptions确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
tile.jsonevals/failed-payment-retry-suspension-and-rein/criteria.jsontask.mdevals/plan-switching-with-proration-and-safe-c/criteria.jsontask.mdevals/subscription-product-creation-and-lifecy/criteria.jsontask.md详细介绍
WooCommerce Subscriptions
概述
WooCommerce Subscriptions(官方高级插件)新增了订阅产品类型——简单订阅和可变订阅,并支持可配置的计费周期(每日、每周、每月、每年)、免费试用、注册费,以及按比例计算的升级和降级费用。它可与 Stripe、PayPal Reference Transactions 及其他支持自动定期计费的支付网关集成。你可以通过丰富的 WordPress 操作和筛选器,在订阅生命周期中添加自定义逻辑。
何时使用此技能
- 按定期计费计划销售产品或服务时(SaaS、会员资格、盒装订阅)
- 实现订阅升级、降级或套餐切换时
- 构建自定义续订逻辑,或为支付失败后的重试添加业务规则时
- 将订阅状态与访问控制集成时(例如,限制会员网站内容的访问)
- 使用自定义数据扩展订阅邮件或管理后台报告时
- 创建订阅附加项或按单位缩放数量时
核心说明
-
以编程方式创建订阅产品
<?php // Create a simple subscription product via code (e.g., in a migration script) $product = new WC_Product_Subscription(); $product->set_name('Monthly Pro Plan'); $product->set_status('publish'); $product->set_regular_price('29.99'); // Save first so the product gets an ID $product->save(); // Subscription-specific meta (product must be saved before setting post meta) update_post_meta($product->get_id(), '_subscription_price', '29.99'); update_post_meta($product->get_id(), '_subscription_period', 'month'); update_post_meta($product->get_id(), '_subscription_period_interval', '1'); update_post_meta($product->get_id(), '_subscription_length', '0'); // 0 = forever update_post_meta($product->get_id(), '_subscription_trial_length', '14'); update_post_meta($product->get_id(), '_subscription_trial_period', 'day'); update_post_meta($product->get_id(), '_subscription_sign_up_fee', '0'); -
挂接订阅生命周期事件
WooCommerce Subscriptions 会在每个生命周期阶段触发特定操作:
<?php // When a new subscription is created (after successful first payment) add_action('woocommerce_subscription_status_active', function ($subscription) { $user_id = $subscription->get_user_id(); $plan = $subscription->get_items(); // Array of WC_Order_Item_Product // Grant access — e.g., set user role or update capabilities $user = new WP_User($user_id); $user->add_role('subscriber_member'); // Track in analytics error_log("Subscription {$subscription->get_id()} activated for user {$user_id}"); }); // When a renewal payment succeeds add_action('woocommerce_subscription_renewal_payment_complete', function ($subscription, $last_order) { $user_id = $subscription->get_user_id(); // Extend access, send renewal receipt, update CRM do_action('my_plugin_renewal_processed', $user_id, $subscription); }, 10, 2); // When a renewal payment fails add_action('woocommerce_subscription_payment_failed', function ($subscription, $last_order) { $user_id = $subscription->get_user_id(); $retry_count = $subscription->get_failed_payment_count(); // Notify user and optionally pause access if ($retry_count >= 2) { $user = new WP_User($user_id); $user->remove_role('subscriber_member'); // Send dunning email $subscription->update_status('on-hold'); } }, 10, 2); // When subscription is cancelled add_action('woocommerce_subscription_status_cancelled', function ($subscription) { $user_id = $subscription->get_user_id(); $user = new WP_User($user_id); $user->remove_role('subscriber_member'); }); -
以编程方式查询订阅
<?php // Get all active subscriptions for a user function get_user_active_subscriptions(int $user_id): array { return wcs_get_users_subscriptions($user_id, ['active']); } // Check if a user has an active subscription to a specific product function user_has_active_subscription_to_product(int $user_id, int $product_id): bool { $subscriptions = wcs_get_subscriptions_for_product($product_id, 'any', ['customer_id' => $user_id]); foreach ($subscriptions as $subscription) { if ($subscription->has_status('active')) { return true; } } return false; } // Get subscription by ID function get_subscription_details(int $subscription_id): ?WC_Subscription { $subscription = wcs_get_subscription($subscription_id); if (!$subscription) return null; return $subscription; } // Usage $subscription = get_subscription_details(1234); if ($subscription) { echo $subscription->get_status(); // 'active', 'on-hold', 'cancelled', etc. echo $subscription->get_next_payment_date(); // ISO 8601 date echo $subscription->get_total(); // Current billing amount } -
处理套餐升级和降级
<?php // Add proration logic for plan switches add_filter('woocommerce_subscriptions_switch_proration', function ($proration_amount, $subscription, $new_order, $product, $switch_cart_item) { // Custom proration: charge/credit based on days remaining in billing period $next_payment = strtotime($subscription->get_next_payment_date()); $last_payment = $subscription->get_date('last_payment'); $billing_period_days = ($next_payment - strtotime($last_payment)) / DAY_IN_SECONDS; $days_remaining = ($next_payment - time()) / DAY_IN_SECONDS; $old_daily_rate = (float)$subscription->get_total() / $billing_period_days; $new_product_price = (float)$switch_cart_item['data']->get_price(); $new_daily_rate = $new_product_price / $billing_period_days; // Credit remaining days at old rate, charge at new rate $proration_amount = ($new_daily_rate - $old_daily_rate) * $days_remaining; return round($proration_amount, 2); }, 10, 5); // Trigger a plan switch programmatically function switch_subscription_plan(int $subscription_id, int $new_product_id): bool { $subscription = wcs_get_subscription($subscription_id); if (!$subscription) return false; // Remove existing item and add new product foreach ($subscription->get_items() as $item_id => $item) { $subscription->remove_item($item_id); } $item = new WC_Order_Item_Product(); $item->set_product(wc_get_product($new_product_id)); $item->set_quantity(1); $subscription->add_item($item); // Recalculate totals $subscription->calculate_totals(); $subscription->save(); return true; } -
重试失败的支付并同步计费日期
<?php // Trigger an immediate payment retry (e.g., from an admin action) function retry_failed_subscription_payment(int $subscription_id): bool { $subscription = wcs_get_subscription($subscription_id); if (!$subscription || !$subscription->has_status('on-hold')) { return false; } // Create a renewal order and attempt payment $renewal_order = wcs_create_renewal_order($subscription); if (is_wp_error($renewal_order)) { return false; } // Process payment using the subscription's payment method $payment_gateway = wc_get_payment_gateway_by_order($subscription); if ($payment_gateway && method_exists($payment_gateway, 'scheduled_subscription_payment')) { $payment_gateway->scheduled_subscription_payment( $renewal_order->get_total(), $renewal_order ); } return true; } // Synchronize all active subscriptions to renew on the 1st of the month add_filter('woocommerce_subscriptions_synced_next_payment_date', function ($next_payment_date, $product, $from_timestamp, $trial_end_timestamp) { if ($product->get_meta('_subscription_period') === 'month') { $next_month = date('Y-m-01', strtotime('+1 month')); return strtotime($next_month); } return $next_payment_date; }, 10, 4);
示例
暂停和恢复流程
<?php
// Admin-triggered suspension with reason logging
function suspend_subscription_with_reason(int $subscription_id, string $reason): void {
$subscription = wcs_get_subscription($subscription_id);
if (!$subscription) return;
// Store suspension reason as meta before status change
$subscription->update_meta_data('_suspension_reason', $reason);
$subscription->update_meta_data('_suspension_date', current_time('mysql'));
$subscription->save();
// Change status to on-hold (fires woocommerce_subscription_status_on-hold action)
$subscription->update_status('on-hold', sprintf('Suspended: %s', $reason));
}
// Reinstate and reset payment date
function reinstate_subscription(int $subscription_id): void {
$subscription = wcs_get_subscription($subscription_id);
if (!$subscription || !$subscription->has_status('on-hold')) return;
// Reset next payment date to avoid immediately triggering a renewal
$subscription->set_date('next_payment', strtotime('+1 month'));
// Activate subscription
$subscription->update_status('active', 'Reinstated by admin');
$subscription->save();
}
自定义订阅邮件
<?php
// Add a custom "Renewal Reminder" email 7 days before renewal
add_filter('woocommerce_email_classes', function ($email_classes) {
require_once plugin_dir_path(__FILE__) . 'class-renewal-reminder-email.php';
$email_classes['My_Renewal_Reminder_Email'] = new My_Renewal_Reminder_Email();
return $email_classes;
});
// Schedule the emails via WooCommerce action scheduler
add_action('woocommerce_scheduled_subscription_payment', function ($subscription_id) {
$subscription = wcs_get_subscription($subscription_id);
if (!$subscription) return;
// Schedule reminder 7 days before next payment
$next_payment = strtotime($subscription->get_next_payment_date()) - (7 * DAY_IN_SECONDS);
if ($next_payment > time()) {
as_schedule_single_action(
$next_payment,
'my_plugin_send_renewal_reminder',
[['subscription_id' => $subscription_id]]
);
}
}, 10);
最佳实践
- 所有异步订阅任务都使用 Action Scheduler(与 WooCommerce 捆绑提供)——切勿依赖 WP cron 处理对支付至关重要的操作
- 始终对
wcs_get_subscription()的结果进行空值检查——在某些边缘情况下(手动删除、导入失败),订阅可能已被删除或不存在 - 测试支付网关令牌处理——订阅续订需要已存储的支付令牌;测试网关是否能在续订期间正确扣取存档卡片的款项
- 明确处理支付失败后的催款流程——默认重试计划是在支付失败后的第 1、4 和 7 天;可通过
wcs_retry_rules筛选器自定义,以符合你的业务政策 - 记录所有订阅状态变更——将状态变更历史存储在订单备注或自定义数据表中,以便审计和客户支持
- 套餐切换时遵循按比例计费规则——不要强迫客户为同一计费周期支付双倍费用;使用内置的按比例计费功能或实现自定义逻辑
- 测试升级和降级的边缘情况——尤其是在试用期内,或计费周期发生变化时(从每月改为每年)
- 使用
wcs_user_has_subscription()控制访问权限——这比仅检查用户角色更可靠
常见问题
| 问题 | 解决方案 |
|---|---|
| 续订付款未处理 | 检查支付网关的功能中是否包含 supports[] = subscription;网关必须明确声明支持订阅 |
| 支付成功后订阅状态仍为“pending” | 必须触发 woocommerce_payment_complete 操作——确认支付网关在付款成功时调用了 $order->payment_complete() |
| 按比例计费产生负数费用 | 在按比例计费筛选器的返回值中设置最低值 0.00;负数金额会导致大多数支付处理器出错 |
wcs_get_subscriptions_for_product 返回空结果 | 对于变体,请传入父产品 ID——WCS 在订阅项目上存储的是父产品 ID,而非变体 ID |
| 试用期结束,但未扣取续订费用 | 确保 _subscription_trial_length 元数据已设置为大于 0 的数字,并且已正确存储来自初始订单的支付网关令牌 |
| 管理员更改状态时发送了取消邮件 | 以编程方式取消订阅前,为 woocommerce_subscription_status_cancelled 添加 remove_action;如果需要抑制邮件,取消后再重新添加 |
Related Skills
- @woocommerce-plugin-development
- @woocommerce-rest-api
- @subscription-billing
- @stripe-integration
- @woocommerce-blocks
评论
支持 Markdown