During a recent project, I had to programmatically create WooCommerce subscriptions and link them to existing customer payment methods. I learned a lot through the process, and I thought I would share my findings in this comprehensive tutorial. Whether you’re migrating subscriptions from another platform, or building a custom subscription creation process, this guide will help you achieve your goal.
In this tutorial, we’ll focus on creating subscriptions programmatically and connecting them to Authorize.net payment tokens. However, the same approach can be adapted for other payment gateways with minimal changes.
Prerequisites
- WooCommerce Subscriptions plugin
- A payment gateway plugin (we’ll use Authorize.net in this example)
- Basic understanding of WooCommerce orders and subscriptions
Understanding Required Data
Before we dive into the code, let’s understand what data we need to create a subscription. At minimum, you’ll need:
- Customer information (ID, billing & shipping details)
- Subscription details (billing interval, period, start date)
- Product information (ID, quantity, pricing)
- Payment method details
The Process
Creating a subscription programmatically involves several steps:
- Create a parent order (required for WooCommerce Subscriptions)
- Create the subscription and link it to the parent order
- Add products and set subscription schedule
- Link payment method (optional but recommended)
Implementation
First, let’s create a class to handle our subscription creation:
class WC_Subscription_Creator {
/**
* Create a new subscription with all required data
*/
public function create_subscription($subscription_data) {
// Create parent order first
$parent_order = wc_create_order([
'customer_id' => $subscription_data['customer_id'],
'created_via' => 'subscription_creator',
]);
if (is_wp_error($parent_order)) {
return $parent_order;
}
// Set addresses on parent order
$parent_order->set_address($subscription_data['billing_info'], 'billing');
$parent_order->set_address($subscription_data['shipping_info'], 'shipping');
// Add products to order
foreach ($subscription_data['products'] as $product_data) {
$product_id = isset($product_data['variation_id']) ?
$product_data['variation_id'] :
$product_data['product_id'];
$product = wc_get_product($product_id);
if (!$product) {
continue;
}
$parent_order->add_product(
$product,
$product_data['quantity'],
[
'total' => $product_data['price'] * $product_data['quantity'],
'subtotal' => $product_data['price'] * $product_data['quantity']
]
);
}
$parent_order->calculate_totals();
$parent_order->save();
// Create the subscription
$subscription = wcs_create_subscription([
'status' => 'pending', // We'll activate it later
'order_id' => $parent_order->get_id(),
'customer_id' => $subscription_data['customer_id'],
'billing_period' => $subscription_data['billing_period'], // day, week, month or year
'billing_interval'=> $subscription_data['billing_interval'], // billing period count
'start_date' => $subscription_data['start_date'],
'created_via' => 'subscription_creator'
]);
if (is_wp_error($subscription)) {
return $subscription;
}
// Set subscription addresses
$subscription->set_address($subscription_data['billing_info'], 'billing');
$subscription->set_address($subscription_data['shipping_info'], 'shipping');
// Add products to subscription
foreach ($subscription_data['products'] as $product_data) {
$product_id = isset($product_data['variation_id']) ?
$product_data['variation_id'] :
$product_data['product_id'];
$product = wc_get_product($product_id);
if (!$product) {
continue;
}
$subscription->add_product(
$product,
$product_data['quantity'],
[
'total' => $product_data['price'] * $product_data['quantity'],
'subtotal' => $product_data['price'] * $product_data['quantity']
]
);
}
// Set subscription dates
if (!empty($subscription_data['next_payment_date'])) {
$subscription->update_dates([
'next_payment' => $subscription_data['next_payment_date']
]);
}
$subscription->calculate_totals();
$subscription->save();
return $subscription;
}
/**
* Get customer's default payment token for Authorize.net
*/
public function get_payment_token($customer_id) {
global $wpdb;
// Check if we're in test mode
$authorize_settings = get_option('woocommerce_authorize_net_cim_credit_card_settings');
$is_test = ($authorize_settings['environment'] ?? '') === 'test';
// Get customer profile ID based on environment
$profile_meta_key = $is_test ?
'wc_authorize_net_cim_customer_profile_id_test' :
'wc_authorize_net_cim_customer_profile_id';
$has_profile = get_user_meta($customer_id, $profile_meta_key, true);
if (!$has_profile) {
return false;
}
// Get default payment token
$token_id = $wpdb->get_var($wpdb->prepare(
"SELECT token_id
FROM {$wpdb->prefix}woocommerce_payment_tokens
WHERE user_id = %d
AND gateway_id = 'authorize_net_cim_credit_card'
AND is_default = 1
LIMIT 1",
$customer_id
));
return $token_id ? WC_Payment_Tokens::get($token_id) : false;
}
/**
* Link payment method to order and subscription
*/
public function link_payment_method($order, $subscription, $customer_id) {
$payment_token = $this->get_payment_token($customer_id);
if (!$payment_token) {
return false;
}
// Get correct profile ID based on environment
$authorize_settings = get_option('woocommerce_authorize_net_cim_credit_card_settings');
$is_test = ($authorize_settings['environment'] ?? '') === 'test';
$profile_meta_key = $is_test ?
'wc_authorize_net_cim_customer_profile_id_test' :
'wc_authorize_net_cim_customer_profile_id';
$customer_profile_id = get_user_meta(
$customer_id,
$profile_meta_key,
true
);
// Set payment method on order
$order->set_payment_method('authorize_net_cim_credit_card');
$order->set_payment_method_title('Credit Card');
$order->update_meta_data('_wc_authorize_net_cim_credit_card_payment_token', $payment_token->get_token());
$order->update_meta_data('_wc_authorize_net_cim_credit_card_customer_id', $customer_profile_id);
$order->save();
// Set payment method on subscription
$subscription->set_payment_method('authorize_net_cim_credit_card');
$subscription->set_payment_method_title('Credit Card');
$subscription->update_meta_data('_wc_authorize_net_cim_credit_card_payment_token', $payment_token->get_token());
$subscription->update_meta_data('_wc_authorize_net_cim_credit_card_customer_id', $customer_profile_id);
$subscription->save();
return true;
}
}
Now that we have our class ready, here’s how to use it:
// Prepare your subscription data
$subscription_data = [
'customer_id' => 123,
'billing_info' => [
'first_name' => 'John',
'last_name' => 'Doe',
'email' => 'john@example.com',
'address_1' => '123 Street',
'city' => 'New York',
'state' => 'NY',
'postcode' => '12345',
'country' => 'US'
],
'shipping_info' => [
// ... shipping details similar to billing
],
'billing_period' => 'month',
'billing_interval' => 1,
'start_date' => date('Y-m-d H:i:s'),
'next_payment_date' => date('Y-m-d H:i:s', strtotime('+1 month')),
'products' => [
[
'product_id' => 456,
'quantity' => 1,
'price' => 29.99
]
]
];
// Create subscription
$creator = new WC_Subscription_Creator();
$subscription = $creator->create_subscription($subscription_data);
if (!is_wp_error($subscription)) {
// Link payment method
$creator->link_payment_method(
wc_get_order($subscription->get_parent_id()),
$subscription,
$subscription_data['customer_id']
);
// Activate the subscription
$subscription->update_status('active');
}
Customer Data Preparation
Before creating subscriptions, you’ll need to ensure your customers exist in WooCommerce. If you’re migrating from another platform, you might need to create customers first or match them with existing ones. The most reliable way to match customers is through their email addresses.
function get_or_create_customer($customer_data) {
// Try to find existing customer by email
$user = get_user_by('email', $customer_data['email']);
if ($user) {
return $user->ID;
}
// Create new customer if not found
$customer_id = wc_create_new_customer(
$customer_data['email'],
$customer_data['email'], // username same as email
wp_generate_password() // random password
);
if (is_wp_error($customer_id)) {
return false;
}
// Update customer meta
update_user_meta($customer_id, 'billing_first_name', $customer_data['first_name']);
update_user_meta($customer_id, 'billing_last_name', $customer_data['last_name']);
// Add other meta as needed
return $customer_id;
}
Important Note
When implementing this functionality, it’s crucial to ensure your code runs after WooCommerce and WooCommerce Subscriptions are fully initialized. The safest approach is to hook into either `init` or `woocommerce_init` with a relatively high priority:
add_action('init', function() {
// Your subscription creation code here
}, 999);
Failing to do this might result in unexpected behavior or errors, as the subscription-related functions and classes might not be available when your code runs.
Additional Considerations
While the above code provides the core functionality, here are some additional aspects you might want to consider:
- Shipping Methods: You can add shipping methods to both the parent order and subscription using
WC_Order_Item_Shipping
. This is particularly useful if you have specific shipping rules. - Error Handling: In a production environment, you’ll want to add proper error handling and perhaps implement a logging system.
- Batching: If you’re importing a large number of subscriptions, consider implementing a batch processing system to avoid timeout issues.
- Payment Gateways: While this example uses Authorize.net, the same principles apply to other payment gateways. You’ll just need to adjust the payment token retrieval and meta key names accordingly.
Example: Adding Shipping Methods
Here’s a quick example of how to add shipping methods:
// Add shipping to order/subscription
$shipping_item = new WC_Order_Item_Shipping();
$shipping_item->set_method_title('Standard Shipping');
$shipping_item->set_method_id('flat_rate');
$shipping_item->set_total(10.00);
$order->add_item($shipping_item);
// Don't forget to recalculate totals
$order->calculate_totals();
That’s it! You now have a solid foundation for programmatically creating WooCommerce subscriptions. This approach can be particularly useful when migrating from another platform or when building custom subscription creation flows.
Remember to test thoroughly in a staging environment first, especially when dealing with payment methods, as incorrect setup could lead to failed payments or other issues down the line.