WooCommerce Subscriptions + HPOS: Understanding Next Steps

In WooCommerce Subscriptions 4.9.0 we declared compatibility with WooCommerce’s new High-Performance Order Storage (HPOS) feature, which means it’s time for extensions that integrate with WooCommerce Subscriptions to update their code in order to be compatible with Subscriptions being stored in the new wc_orders tables.

Getting Started:

Glossary:

  • Subscription or Subscription object – an extension of the WooCommerce Order object with type shop_subscription with additional Subscriptions meta like billing period and interval, next payment dates and related orders.
  • Subscription product – the simple and variable Subscription products when purchased via the checkout turn into a Subscription.
  • WC Subscriptions – this refers to the WooCommerce Subscriptions extension.

Subscriptions in HPOS

When HPOS is enabled, Order data is stored in new, dedicated order database tables. Similarly, because Subscriptions are an extension of the WC_Abstract_Order class, its data is now also stored in the new dedicated order database tables.

With HPOS enabled, Subscription data will be found in the following tables:

  • wp_wc_orders – Where the ID column is the Subscription ID and the type is set to shop_subscription
  • wp_wc_order_addresses – Where the order_id column refers to a Subscription ID
  • wp_wc_order_operational_data – Where the order_id column refers to a subscription ID
  • wp_wc_orders_meta – Where the order_id column refers to a Subscription ID

This has changed from the previous structure where Subscriptions and Subscription metadata were stored in two Post tables:

  • wp_posts – where ID is a Subscription ID and post_type is ‘shop_subscription
  • wp_post_meta – where post_id refers to a Subscription

When it comes to migrating the Subscription data from WP Posts to the new HPOS tables, they are migrated automatically using the WooCommerce Core data synchronization feature.

After your subscriptions have been migrated, you may notice there are WP Posts with post type shop_order_placehold with the same ID as a Subscription ID.

When HPOS is enabled and data syncing is disabled, Subscription post objects will be stored as shop_order_placehold in the wp_posts table. This post is used as a placeholder for the post ID (to ensure we don’t have Subscriptions in the new tables and other posts with the same ID in the posts table).

Changes in WC Subscriptions to Support HPOS

WC Subscriptions relied on a lot of code using WP Post APIs. Therefore, a big portion of our changes to support HPOS was to refactor our functions to use WC CRUD while also maintaining backward compatibility.

Aside from those changes, there were other additions, deprecations, and disabling of old features we had to change in order to fully support Subscriptions and Orders in HPOS, these include:

New functions & Helpers

  • wcs_get_orders_with_meta_query – wrapper for wc_get_orders() to search for orders or Subscriptions with meta query args in a backward compatible way.
  • wcs_is_custom_order_tables_usage_enabled – helper function to determine if HPOS is enabled on the store.
  • wcs_is_custom_order_tables_data_sync_enabled – helper function to determine if HPOS and data synchronization is enabled.

Deprecated/removed functions, hooks and filters

Function/Action/Filter/PropertyStatusTypeDescription
woocommerce_new_subscription_dataDiscouragedFilterThis hook is called with arguments for wp_insert_post. This is no longer used when HPOS based  Subscriptions are created so will not always be called. It will continue to work on stores with HPOS disabled. wcs_create_subscription can be used when a new subscription is created instead.
wcs_{$copy_type}_meta_queryDeprecatedDynamic FilterWhere copy_type is subscription, parent, renewal_order, resubscribe_order.
We’ve maintained backwards compatibility but there’s no direct replacement for this hook. Use wc_subscriptions_copied_data or wc_subscriptions_{$copy_type}_data instead
wcs_{$copy_type}_metaDeprecatedDynamic FilterWhere copy_type is subscription, parent, renewal_order, resubscribe_order.
Replacement use ‘wc_subscriptions_copied_data’ or ‘wc_subscriptions_{$this->copy_type}_data’ instead
wcs_subscriptions_for_{$relation_type}_orderRemovedDynamic FilterWhere relation_type is renewal, switch or resubscribe.
This hook was deprecated in v2.3.2 of WC Subscriptions (released on 10th July, 2018)
woocommerce_subscriptions_admin_related_orders_to_displayDeprecatedFilterThis filter is deprecated in favour of ‘wcs_admin_subscription_related_orders_to_display’. This hook will not work with HPOS, since it accepts the order post object as an argument. It will continue to work on stores with HPOS disabled.
woocommerce_subscriptions_related_orders_meta_box_rowsDeprecatedHookThis action is deprecated in favour of ‘wcs_related_orders_meta_box_rows’. This action will not work with HPOS, since it accepts the order post object as an argument. It will continue to work with CPT.
woocommerce_subscriptions_related_orders_meta_boxDeprecatedHookThis action is deprecated in favour of ‘wcs_related_orders_meta_box’. This action will not work with HPOS, since it accepts the order post object as an argument. It will continue to work with CPT.
woocommerce_subscriptions_related_orders_meta_boxDeprecatedPropertyThis object property is protected, so is only accessible to third-party code that extended the WCS_Post_Meta_Cache_Manager class. They should use the WCS_Post_Meta_Cache_Manager::object_data_cache_manager instead
WC_Subscriptions_Switcher::update_shipping_methods() DeprecatedFunctionNo replacement, also wasn’t used internally.
WC_REST_Subscription_System_Status_Manager::add_subscription_fields_to_reponseDeprecatedFunctionReplaced by `WC_REST_Subscription_System_Status_Manager::add_subscription_fields_to_response()` to support HPOS and correct spelling error
WC_Subscriptions_Cart::get_calculated_shipping_for_packageDeprecatedFunctionThis function was a wrapper for accessing shipping rates stored in a cache. The cache is no longer necesary and was impacting 3rd-party plugins
WC_Subscriptions_Cart::cache_package_ratesDeprecatedFunctionThis function only cached the rates after they were calculated. Given the cache is no longer in use, this function served no purpose. 

Other low-level changes

  • New Subscription data store – WCS_Orders_Table_Subscription_Data_Store class. An instance of this class is returned by calling WC_Data_Store::load( 'subscription' )
  • New Object Meta Cache Manager – this is to replace our existing WCS_Post_Meta_Cache_Manager and is used when the store has HPOS enabled. This class is used to track metadata changes on orders and Subscriptions and trigger updated/deleted hooks to assist with keeping our cached data up-to-date.

Disabling of legacy features and code

Legacy Subscription reports have been disabled on stores that have HPOS without data syncing turned on. Subscription Reports currently contain complex direct DB queries which won’t work in HPOS environments unless the Subscription data is synced to posts meta tables. Rather than delaying our HPOS compatibility release, we decided to temporarily disable our reports while we work on updating them to support the new HPOS feature.

Old upgrade and repair scripts won’t work. WC Subscriptions has some old upgrade and repair scripts that use direct database queries and WP Post APIs to fetch and update Subscription data. Rather than upgrading and testing all of these old repair scripts to work in HPOS, we decided to disable them. This change will only affect stores that are updating from a very old version of Subscriptions (5+ years old) and have already migrated Subscriptions data to the new HPOS tables (extremely unlikely, if not impossible).

How to Update Your Subscription Integration Code

To support Subscriptions in HPOS tables, you will need to audit your Subscription integrations code for any direct DB queries or usage of WordPress APIs and replace them with the equivalent WC CRUD method or Data Store method

Most common WC CRUD and Data Store methods were introcued in WooCommerce Core 3.0 and do not need to be surrounded by any conditional version checks.

Follow the steps in the Auditing the code base for direct DB access usage section of the High Performance Order Storage Upgrade Recipe Book to find instances of direct DB queries or usage of WordPress APIs.

Example code changes:

Updating WP Post API functions with WC CRUD methods

get_post_meta( $subscription_id, '_billing_period', true );

Replacement:

// get an instance of the subscription if you don't already have one.

$subscription = wcs_get_subscription( $subscription_id ); 

$subscription->get_meta( '_billing_period', true );

update_post_meta( $subscription_id, '_my_gateway_source_id', '_source_1234' );

Replacement:

// get an instance of the subscription if you don't already have one.

$subscription = wcs_get_subscription( $subscription_id ); 

$subscription->update_meta_data( '_my_gateway_source_id', '_source_1234' );

$subscription->save();

get_edit_post_link( $subscription_id )

Replacement:

// get an instance of the subscription if you don't already have one.

$subscription = wcs_get_subscription( $subscription_id );

$subscription->get_edit_order_url();

Updating WP Post Type checks with using the Data Store

'shop_subscription' === get_post_type( subscription_id ) 

// or

'shop_subscription' === $post->post_type

Replacement:

'shop_subscription' === WC_Data_Store::load( 'subscription' )->get_order_type( $object_id ).

For more examples, check out: APIs for getting/setting posts and postmeta.

Tip: a Subscription object is returned when you call wc_get_order( $subscription_id ) because a Subscription is a custom order type.

What code doesn’t need updating?

  • Subscription simple and variable products
  • Subscription order items 

While updating your Subscriptions integration code, you may come across WordPress APIs being used on Subscription products or Subscription order items. Since products are remaining in the Posts table and orderitems already have their own tables, these areas of code should not need to be updated to support HPOS.

If in doubt, update the code to use WC CRUD methods, as this approach is data store agnostic and will future-proof your code for any upcoming database changes.


Leave a Reply

Your email address will not be published. Required fields are marked *