WooCommerce Subscriptions exposes a rich set of hooks and helper functions that let you modify the renewal process without touching core files. Common customisations include adding a custom fee on renewal, pausing renewal when a user meta flag is set, and sending a tailored reminder email before the next payment date.
Problem: WooCommerce Subscriptions provides hooks for renewal events, but the default renewal logic does not accommodate custom business rules — pausing a subscription based on account balance, applying loyalty discounts, or routing renewals to a different payment gateway.
Solution: Hook into wcs_create_renewal_order to modify the renewal order before payment, woocommerce_scheduled_subscription_payment to intercept the payment attempt, and woocommerce_subscription_status_updated to react to status changes. Use $subscription->update_meta_data() to persist custom state between renewals.
The snippets below use wcs_get_subscriptions() to query active subscriptions, hook into woocommerce_subscription_renewal_payment_complete for post-payment logic, and attach a custom action to woocommerce_scheduled_subscription_payment to add a dynamic renewal fee.
<?php
// 1. Query all active subscriptions for a specific product
function my_get_active_subs_for_product( int $product_id ): array {
return wcs_get_subscriptions( [
'subscription_status' => 'active',
'product_id' => $product_id,
'subscriptions_per_page' => -1,
] );
}
// 2. After a renewal payment, record the timestamp in user meta
add_action( 'woocommerce_subscription_renewal_payment_complete',
function ( WC_Subscription $subscription, WC_Order $renewal_order ) {
$user_id = $subscription->get_user_id();
update_user_meta( $user_id, '_last_renewal_ts', time() );
$subscription->add_order_note(
sprintf( 'Renewal #%d completed at %s.',
$renewal_order->get_id(),
current_time( 'mysql' )
)
);
}, 10, 2
);
// 3. Add a $2.50 processing fee to every renewal order
add_action( 'woocommerce_checkout_order_processed',
function ( int $order_id, array $posted, WC_Order $order ) {
if ( wcs_order_contains_renewal( $order ) ) {
$fee = new WC_Order_Item_Fee();
$fee->set_name( 'Renewal Processing Fee' );
$fee->set_amount( 2.50 );
$fee->set_tax_status( 'none' );
$fee->set_total( 2.50 );
$order->add_item( $fee );
$order->calculate_totals();
$order->save();
}
}, 10, 3
);
// 4. Pause renewal if user has a ban flag
add_filter( 'woocommerce_subscription_is_failed_renewal_order',
function ( bool $is_failed, WC_Order $renewal_order ) {
$user_id = $renewal_order->get_user_id();
if ( get_user_meta( $user_id, '_renewal_paused', true ) ) {
$renewal_order->update_status( 'on-hold', 'Renewal paused by admin flag.' );
return true;
}
return $is_failed;
}, 10, 2
);
NOTE: Always call $order->calculate_totals() and $order->save() after programmatically adding fees; skipping either step results in the order total being out of sync with its line items in the WooCommerce admin.