Search

Subscriptions Payment Gateway Integration Guide

This documentation is written for WooCommerce developers who want to extend or integrate with the WooCommerce Subscriptions plugin. To follow this documentation, you will need an advanced understanding of PHP and WordPress development.

If you are looking for a guide to creating and managing subscription products in a WooCommerce store, please refer to the Store Manager Guide instead. If you need to extend WooCommerce Subscriptions and are not a developer, you can find a developer in the Affiliated Woo Worker directory.

Adding subscription support to your existing payment gateway extension is a great way to attract new customers and provide a sought-after feature for your existing customers.

Subscriptions provides a comprehensive API and takes care of much of the subscription management automatically – you just need to add payment processing. This guide outlines the necessary steps for integrating a payment gateway with WooCommerce Subscriptions.

There are 6 steps to add subscription support:

  1. Register Support for Subscriptions
  2. Process Subscription Sign-ups
  3. Manage Subscriptions
  4. Process Failed Payments
  5. Handling Recurring Payment Method Changes
  6. Show your Support

Before continuing with this guide, you should have read the Subscriptions’ API overview. You should also have an in-depth understanding of the WooCommerce Payment Gateway API.

Step 1: Registering Support for Subscriptions ↑ Back to Top

When an order contains a subscription product, the Subscriptions extension filters the available payment gateways to display only those that support recurring payments.

Your gateway must register its support for subscriptions using the WC_Payment_Gateway::supports() API (in WooCommerce 1.5.7 and newer). This is done by setting the supports property of your gateway to an array containing ‘subscriptions’ (and ‘products’ if your gateway also supports product purchases).

For example, the __construct() function of your gateway class should include a line like the following:

class WC_Awesome_Gateway extends WC_Payment_Gateway {
     function __construct() {
          ...
          $this->supports = array( 'subscriptions', 'products' );
          ...
     }
     ...
}

Now, whenever an order contains a subscription, if your gateway is enabled, it will be displayed as a payment option on the checkout page.

Step 1.1: Registering Support for Subscription Management Features

If your payment gateway supports subscription management functions, like cancelling or suspending a subscription, you should also notify Subscriptions that your gateway can handle these functions.

There are a variety of functions Subscriptions will implement if a payment gateway support it, including:

  • subscription cancellation
  • subscription suspension
  • subscription reactivation (after suspending a subscription)
  • subscription amount changes (recurring amount can be changed for the subscription)
  • subscription date changes (next payment date can be changed for the subscription)

To register your gateways support for these features, you add the relevant flag to your gateway’s supports property.

This is an example for a gateway which supports all features:

class WC_Awesome_Gateway extends WC_Payment_Gateway {
     function __construct() {
          ...
          $this->supports = array( 
               'products', 
               'subscriptions',
               'subscription_cancellation', 
               'subscription_suspension', 
               'subscription_reactivation',
               'subscription_amount_changes',
               'subscription_date_changes',
               'subscription_payment_method_change'
          );
          ...
     }

Note: If your payment gateway extension is using token based billing and relying on Subscriptions’ scheduled payment hooks to charge each recurring payment, it can support all of the available features, so add a flag for every feature.

Step 2: Processing a Subscription Sign-Up ↑ Back to Top

Like processing payment for a product, your payment gateway extension will need to process a subscription sign-up in its process_payment() function.

Depending on how you gateway processes subscriptions, you may need to know any of the initial payment amount, the subscription’s sign-up fee, price per period, billing period and duration.

To obtain these details for a normal product, you would use WC_Order methods. Likewise, to obtain these details for an order containing a subscription, use the WC_Subscriptions_Order class methods.

Initial Payment

If your payment gateway requires one upfront amount for the beginning of the subscription, you can uuse

<?php WC_Subscriptions_Order::get_total_initial_payment( $order ) ?>

Where $order is a WC_Order object.

For more details, see the full total initial payment function reference.

Price Per Period

If your payment gateway needs to know when creating the subscription how much to charge for each individual billing period, you can use:

<?php WC_Subscriptions_Order::get_price_per_period( $order ) ?>

For more details, see the full price per period function reference.

Sign-up Fee

If your payment gateway needs a distinct sign-up fee amount for an order, you can call:

<?php WC_Subscriptions_Order::get_sign_up_fee( $order ) ?>

For more details, see the full sign up fee function reference.

Billing Period

To get the subscription period for an order, call:

<?php WC_Subscriptions_Order::get_subscription_period( $order ) ?>

For more details, see the full subscriptions period function reference.

Free Trial Period

Payment gateways typically require one of two methods for setting up a free trial

  1. Passing the free trial period and length, like PayPal; or
  2. Setting the start date for the subscription, like WorldPay.

To use the first method, get a subscriptions trial details with the following two functions:

<?php 

WC_Subscriptions_Order::get_subscription_trial_period( $order );

WC_Subscriptions_Order::get_subscription_trial_length( $order );

?>

To use the second method and set the start date, use the following function:

<?php WC_Subscriptions_Product::get_trial_expiration_date( $product_id, $order_start_date ); ?>

Subscription Length

To get the subscription duration for an order, call:

<?php WC_Subscriptions_Order::get_subscription_length( $order ) ?>

For more details, see the full subscriptions length function reference.

Putting it all Together

The paypal_standard_subscription_args() function within the bundled WC_PayPal_Standard_Subscriptions class provides an example of accessing subscription details and combining them into a payment gateway request.

extract( self::get_order_id_and_key( $paypal_args ) );

if ( WC_Subscriptions_Order::order_contains_subscription( $order_id ) ) {

	$order = new WC_Order( $order_id );

	$order_items = $order-&gt;get_items();

	// Only one subscription allowed in the cart when PayPal Standard is active
	$product = $order-&gt;get_product_from_item( $order_items[0] );

	// It's a subscription
	$paypal_args['cmd'] = '_xclick-subscriptions';

	if ( count( $order-&gt;get_items() ) &gt; 1 ) {

		foreach ( $order-&gt;get_items() as $item ) {
			if ( $item['qty'] &gt; 1 )
				$item_names[] = $item['qty'] . ' x ' . $item['name'];
			else if ( $item['qty'] &gt; 0 )
				$item_names[] = $item['name'];
		}

		$paypal_args['item_name'] = sprintf( __( 'Order %s', WC_Subscriptions::$text_domain ), $order-&gt;get_order_number() );

	} else {

		$paypal_args['item_name'] = $product-&gt;get_title();

	}

	$unconverted_periods = array(
		'billing_period' =&gt; WC_Subscriptions_Order::get_subscription_period( $order ),
		'trial_period'   =&gt; WC_Subscriptions_Order::get_subscription_trial_period( $order )
	);

	$converted_periods = array();

	// Convert period strings into PayPay's format
	foreach ( $unconverted_periods as $key =&gt; $period ) {
		switch( strtolower( $period ) ) {
			case 'day':
				$converted_periods[$key] = 'D';
				break;
			case 'week':
				$converted_periods[$key] = 'W';
				break;
			case 'year':
				$converted_periods[$key] = 'Y';
				break;
			case 'month':
			default:
				$converted_periods[$key] = 'M';
				break;
		}
	}

	$sign_up_fee = WC_Subscriptions_Order::get_sign_up_fee( $order );

	$initial_payment = WC_Subscriptions_Order::get_total_initial_payment( $order );

	$price_per_period = WC_Subscriptions_Order::get_recurring_total( $order );

	$subscription_interval = WC_Subscriptions_Order::get_subscription_interval( $order );

	$subscription_installments = WC_Subscriptions_Order::get_subscription_length( $order ) / $subscription_interval;

	$subscription_trial_length = WC_Subscriptions_Order::get_subscription_trial_length( $order );

	if ( $subscription_trial_length &gt; 0 ) { // Specify a free trial period

		$paypal_args['a1'] = ( $sign_up_fee &gt; 0 ) ? $sign_up_fee : 0; // Maybe add the sign up fee to the free trial period

		// Trial period length
		$paypal_args['p1'] = $subscription_trial_length;

		// Trial period
		$paypal_args['t1'] = $converted_periods['trial_period'];

	} elseif ( $sign_up_fee &gt; 0 ) { // No trial period, so charge sign up fee and per period price for the first period

		if ( $subscription_installments == 1 )
			$param_number = 3;
		else
			$param_number = 1;

		$paypal_args['a'.$param_number] = $initial_payment;

		// Sign Up interval
		$paypal_args['p'.$param_number] = $subscription_interval;

		// Sign Up unit of duration
		$paypal_args['t'.$param_number] = $converted_periods['billing_period'];

	}

	// We have a recurring payment
	if ( ! isset( $param_number ) || $param_number == 1 ) {

		// Subscription price
		$paypal_args['a3'] = $price_per_period;

		// Subscription duration
		$paypal_args['p3'] = $subscription_interval;

		// Subscription period
		$paypal_args['t3'] = $converted_periods['billing_period'];

	}

	// Recurring payments
	if ( $subscription_installments == 1 || ( $sign_up_fee &gt; 0 &amp;&amp; $subscription_trial_length == 0 &amp;&amp; $subscription_installments == 2 ) ) {

		// Non-recurring payments
		$paypal_args['src'] = 0;

	} else {

		$paypal_args['src'] = 1;

		if ( $subscription_installments &gt; 0 ) {
			if ( $sign_up_fee &gt; 0 &amp;&amp; $subscription_trial_length == 0 ) // An initial period is being used to charge a sign-up fee
				$subscription_installments--;

			$paypal_args['srt'] = $subscription_installments;

		}
	}

	// Force return URL so that order description &amp; instructions display
	$paypal_args['rm'] = 2;

}

Step 3: Subscription Management ↑ Back to Top

After a subscription has been purchased with your extension, certain aspects of the subscription are managed automatically while others need to be managed by your gateway extension.

Order Status & Subscription Status Binding

Subscription status is bound to order status changes, so using the WooCommerce Payment Gateway API to set an order’s status will automatically set the status of a subscription. When an order status changes to processing or complete, a subscription purchased in the order is activated automatically. When an order is cancelled, refunded or marked as failed, the status of a subscription in that order will also be updated to cancelled or failed.

As a result, the bare minimum required to manage a subscription is to use the WooCommerce Payment Gateway API to manage an order’s status.

Subscription Payment & Status Management

There is no automatic handling of subscription payments. Either your gateway or your extension will need to handle these payments.

Recurring Payments Processed by the Gateway

If your payment gateway can automatically charge recurring payments, like PayPal, your extension can set up the subscription with the gateway and then use the WC_Subscriptions_Manager::process_subscription_payments_on_order() function to keep a record of each payment once the gateway has processed the payment.

If your payment gateway also manages the billing schedule, then you should also add the 'gateway_scheduled_payments' flag when registering support for Subscriptions. Not adding this flag can cause subscriptions to be incorrectly suspended when the gateway’s schedule does not precede the WooCommerce schedule.

Recurring Payments Processed by your Extension

Some payment gateways may have little or no support for recurring payments, but may allow you to charge a stored credit card. These gateways can still be integrated with WC Subscriptions to offer automatic recurring billing.

In fact, this is how the Stripe extension adds support for Subscriptions, even though the Stripe payment gateway can automatically charge recurring payments.

For each subscription, a 'scheduled_subscription_payment_{payment_gateway_id}' hook is fired whenever a payment is due for a specific gateway. This hook includes the amount due on the subscription (including outstanding payments, if any) as well as the order and product ID of the subscription. You can use this hook to process a subscription payment.

For example, the Stripe extension hooks its `scheduled_subscription_payment()` function to the 'scheduled_subscription_payment_stripe' hook to process the payment for each billing period:

function scheduled_subscription_payment( $amount_to_charge, $order, $product_id ) {

	$result = $this->process_subscription_payment( $order, $amount_to_charge );

	if ( is_wp_error( $result ) ) {
		WC_Subscriptions_Manager::process_subscription_payment_failure_on_order( $order, $product_id );
	} else {
		WC_Subscriptions_Manager::process_subscription_payments_on_order( $order );
	}
}

This is also a good example of using the API functions for success and failure of the payment. More on failed payments later.

Charging payments on the 'scheduled_subscription_payment_{payment_gateway_id}' hook is only required if your gateway does not automatically charge recurring payments. However, this method can be used in-lieu of your payment gateways recurring billing solution because doing so has a few advantages:

  1. You need less code to support subscription payments & subscription features like changing the next payment date because WC Subscriptions core will take care of all of this for you and you just need to hook to 'scheduled_subscription_payment_{payment_gateway_id}';
  2. You have complete flexibility in the billing interval and period, e.g., some payment gateways, like Authorize.net ARB, only support a free trial period of the same billing period as the recurring payments, that is, Authorize.net ARB does not allow a 2 week trial period for a subscription billing every month;
  3. You can support all Subscription features, regardless of those which your payment gateway’s recurring billing solution provides.

Activating a Subscription

<?php WC_Subscriptions_Manager::activate_subscriptions_for_order( $order ) ?&gt;

As the name suggests, the activate_subscriptions_for_order() can be used to mark all subscriptions in an order as active.

This function is fired automatically when an order’s status changes to completed or processing, so if you are correctly updating an order’s status, you do not need to call this function.

The function calls the WC_Subscriptions_Manager::activate_subscription() function for each subscription item in an order, which for now, will only be one item, but in future, this may be more than one item.

Cancelling a Subscription

If your gateway provides an API for cancelling a subscription (or you are manually charging recurring payments), you can add support for subscriptions to be cancelled in the store.

Just like registering support for subscriptions, you notify Subscriptions of your cancellation capability by including a flag in your extensions supports() property.

For example, the following is an excerpt from the PayPal Digital Goods gateway extension:

function __construct() { 

	// ...

	$this->supports = array( 'products', 'subscriptions', 'subscription_cancellation' );

	// ...
}

Note the 'subscription_cancellation' flag.

Including this flag will expose the Cancel action link on a subscription purchased with your gateway to store managers and subscribers. When this action link is clicked, Subscriptions will cancel the subscription in the store and you need to cancel the subscription with your gateway.

Subscriptions fires a 'cancelled_subscription' hook whenever a subscription is cancelled, but a better action to hook to use is 'cancelled_subscription_{payment_gateway_id}'. This hook passes the WC_Order object and WC_Product ID of the subscription product being cancelled. You can use these details to cancel the subscription with your gateway.

Cancellations at the Gateway

If a user can cancel a subscription with the payment gateway directly, you must reflect this change in the WooCommerce store by calling:

<?php WC_Subscriptions_Manager::cancel_subscriptions_for_order( $order ) ?>

This function is automatically called when an order’s status is changed to cancelled, failed or refunded order.

Internally, the function acts as a convenience wrapper function for the WC_Subscriptions_Manager::cancel_subscription() function. If you need to cancel just one subscription and no its associated order, you can call the cancel subscription function directly:

<?php WC_Subscriptions_Order::cancel_subscription( $user_id, $subscription_key ) ?>

Where $subscription_key is the key derived from the concatenation of the order ID, an underscore and the subscription product’s ID e.g. 153_12 for product ID 12 purchased on order 153.

Expiring a Subscription

The Subscriptions extension schedules a WP-Cron job set a subscription’s status to expired when its expiration date arrives, so you do not need to manually expire a subscription.

However, if your payment gateway provides an expiration notification and you like the security of redundancy, you can call the WC_Subscriptions_Manager function directly.

To expire any subscriptions on an order call:

<?php WC_Subscriptions_Manager::expire_subscriptions_for_order( $order ) ?>

To expire an individual subscription call:

<?php WC_Subscriptions_Manager::expire_subscription( $user_id, $subscription_key ) ?>

If you’re charging recurring payments manually, you do not need to expire a subscription because the scheduled payment hooks will no longer fire once a subscriptions is cancelled.

Recording Payments

A subscription’s status does not change when each payment is received (except potentially for the first payment), however, Subscriptions provides an API to record payments. If your gateway provides a notification system, like the PayPal IPN, it’s a good idea to call the process_subscription_payments_on_order() function to keep a record of the payment on the order.

<?php WC_Subscriptions_Manager::process_subscription_payments_on_order( $order ) ?>

Recording Failed Payments

A subscription’s status does not change when a payment fails (unless the total number of failed payments exceeds the number the administrator has set for the store). However, you should still record failed payments. If your gateway provides a notification system, like the PayPal IPN, it’s a good idea to call the process_subscription_payment_failure_on_order() function to keep a record of the payment on the order.

<?php WC_Subscriptions_Manager::process_subscription_payment_failure_on_order( $order ) ?>

Order API vs Individual Subscription API

You may have noticed a trend in the API. Subscriptions includes functions for operating both on an order and on an individual subscription. In most cases, you will find it easier to use the function that operates on an order. It is also better to operate on an order other than an individual subscription, because you will need to use less code within your extension.

Subscription Management Example

The process_paypal_ipn_request() function in the bundled WC_PayPal_Standard_Subscriptions class provides an example of manually, and redundantly calling subscription management functions.

This function is hooked to PayPal IPN requests and checks if the request relates to a subscription. The switch statement, included below, performs subscription management tasks for any subscription related transactions.

switch( $transaction_details['txn_type'] ) {
	case 'subscr_signup':

		// Store PayPal Details
		update_post_meta( $order_id, 'Payer PayPal address', $transaction_details['payer_email']);
		update_post_meta( $order_id, 'Payer PayPal first name', $transaction_details['first_name']);
		update_post_meta( $order_id, 'Payer PayPal last name', $transaction_details['last_name']);
		update_post_meta( $order_id, 'PayPal Subscriber ID', $transaction_details['subscr_id']);

		// Payment completed
		$order->add_order_note( __( 'IPN subscription sign up completed.', WC_Subscriptions::$text_domain ) );

		if ( self::$debug )
			self::$log->add( 'paypal', 'IPN subscription sign up completed for order ' . $order_id );

		break;

	case 'subscr_payment':

		if ( 'completed' == strtolower( $transaction_details['payment_status'] ) ) {
			// Store PayPal Details
			update_post_meta( $order_id, 'PayPal Transaction ID', $transaction_details['txn_id'] );
			update_post_meta( $order_id, 'Payer PayPal first name', $transaction_details['first_name'] );
			update_post_meta( $order_id, 'Payer PayPal last name', $transaction_details['last_name'] );
			update_post_meta( $order_id, 'PayPal Payment type', $transaction_details['payment_type'] ); 

			// Subscription Payment completed
			$order->add_order_note( __( 'IPN subscription payment completed.', WC_Subscriptions::$text_domain ) );

			if ( self::$debug ) 
				self::$log->add( 'paypal', 'IPN subscription payment completed for order ' . $order_id );

			$subscriptions_in_order = WC_Subscriptions_Order::get_recurring_items( $order );
			$subscription_item      = array_pop( $subscriptions_in_order );
			$subscription_key       = WC_Subscriptions_Manager::get_subscription_key( $order->id, $subscription_item['id'] );
			$subscription           = WC_Subscriptions_Manager::get_subscription( $subscription_key, $order->customer_user );

			// First payment on order, process payment & activate subscription
			if ( empty( $subscription['completed_payments'] ) ) {

				$order->payment_complete();

				WC_Subscriptions_Manager::activate_subscriptions_for_order( $order );

			} else {

				WC_Subscriptions_Manager::process_subscription_payments_on_order( $order );

			}

		} elseif ( 'failed' == strtolower( $transaction_details['payment_status'] ) ) {

			// Subscription Payment completed
			$order->add_order_note( __( 'IPN subscription payment failed.', WC_Subscriptions::$text_domain ) );

			if ( self::$debug ) 
				self::$log->add( 'paypal', 'IPN subscription payment failed for order ' . $order_id );

			WC_Subscriptions_Manager::process_subscription_payment_failure_on_order( $order );

		} else {

			if ( self::$debug ) 
				self::$log->add( 'paypal', 'IPN subscription payment notification received for order ' . $order_id  . ' with status ' . $transaction_details['payment_status'] );

		}

		break;

	case 'subscr_cancel':

		if ( self::$debug ) 
			self::$log->add( 'paypal', 'IPN subscription cancelled for order ' . $order_id );

		// Subscription Payment completed
		$order->add_order_note( __( 'IPN subscription cancelled for order.', WC_Subscriptions::$text_domain ) );

		WC_Subscriptions_Manager::cancel_subscriptions_for_order( $order );

		break;

	case 'subscr_eot': // Subscription ended, either due to failed payments or expiration

		// PayPal fires the 'subscr_eot' notice immediately if a subscription is only for one billing period, so ignore the request when we only have one billing period
		if ( 1 != WC_Subscriptions_Order::get_subscription_length( $order ) ) {

			if ( self::$debug ) 
				self::$log->add( 'paypal', 'IPN subscription end-of-term for order ' . $order_id );

			// Record subscription ended
			$order->add_order_note( __( 'IPN subscription end-of-term for order.', WC_Subscriptions::$text_domain ) );

			// Ended due to failed payments so cancel the subscription
			if ( time() < strtotime( WC_Subscriptions_Manager::get_subscription_expiration_date( WC_Subscriptions_Manager::get_subscription_key( $order->id ), $order->customer_user ) ) )
				WC_Subscriptions_Manager::cancel_subscriptions_for_order( $order );
			else
				WC_Subscriptions_Manager::expire_subscriptions_for_order( $order );
		}
		break;

	case 'subscr_failed': // Subscription sign up failed

		if ( self::$debug ) 
			self::$log->add( 'paypal', 'IPN subscription sign up failure for order ' . $order_id );

		// Subscription Payment completed
		$order->add_order_note( __( 'IPN subscription sign up failure.', WC_Subscriptions::$text_domain ) );

		WC_Subscriptions_Manager::failed_subscription_sign_ups_for_order( $order );

		break;
}

Step 4: Failed Payments ↑ Back to Top

Subscriptions also provides an API for handling failed payments, which your extension may or may not need to use depending on whether your payment gateway will process failed payments.

There are two functions available:

<?php WC_Subscriptions_Manager::process_subscription_payment_failure_on_order( $order, $product_id ) ?>
You should pass the order ID or order object for the order used to purchase the subscription (the parent order). The status of this order won’t be changed, instead it will be used to look up the subscription and create a renewal order with the “failed” status.
<?php WC_Subscriptions_Manager::process_subscription_payment_failure( $user_id, $subscription_key ) ?>

The first of these is a convenience wrapper for the second, so you can use either, but not both.

When Subscriptions processes a failed payment, it will put the subscription on-hold until the customer has logged in to manually complete payment for the renewal period. The payment method used for making that payment will also be used for future recurring payments, as long you handle recurring payment method changes, as covered in the next section.

If your gateway does not manage failed payments for you, you must use one of the failed payments API functions.

Step 5: Recurring Payment Method Changes ↑ Back to Top

Subscriptions 1.4 introduced a way for customers to change the payment method used for future payments on their subscription. It also uses this method to update the payment method on a subscription when a recurring payment failed.

To handle recurring payment method changes, your gateway needs to:

  1. add 'subscription_payment_method_change' supports flag so that your gateway is presented as a payment option when the customer is changing the payment; and
  2. hook to the 'woocommerce_subscriptions_changed_failing_payment_method_{your-gateway}' action to update the payment method when a customer is making a payment in lieu of an automatic renewal payment that previously failed.

5.1: Supporting Subscriber Payment Method Changes

To support customer initiated payment method changes, your extension will only need to be able to process subscription orders with a $0 initial total. Subscriptions creates a mock checkout using the original order details and overriding the total to be $0. If your payment gateway extension correctly handles a $0 initial total, as it will need to do to process free trial periods correctly, then it shouldn’t need any additional code to handle payment method changes.

The only time it will need additional code, is if the billing schedule is managed by the Payment Gateway, not by Subscriptions. In this case, you may also need to set the correct first payment date (in a similar fashion to the way you set a free trial on a standard subscription sign-up).

5.2: Updating the Payment Method After a Failure

If a subscriber’s automatic payment fails, her subscription will be put on-hold until she logs in to complete the payment.

As of Subscriptions 1.4, the recurring payment method use for future payments will be updated to the payment method used to complete this payment. As a result, you will need to update any meta data on the original subscription that is required by your payment gateway to handle future automatic payments. You must update the meta data on the 'woocommerce_subscriptions_changed_failing_payment_method_{your-gateway}' hook, if you do not, all future automatic payment will continue to fail.

You do not need to update the the payment method or anything else on the original order, Subscriptions will handle that, simply make sure the original order has whatever meta data is required to correctly handle future payments and manage the subscription.

Below is some example code similar to that used in Stripe and Authorize.net CIM to fulfill this requirement. It updates the customer token on the original subscription order by accessing the customer token from the new renewal order.

/**
 * Update the customer token IDs for a subscription after a customer used the gateway to successfully complete the payment
 * for an automatic renewal payment which had previously failed.
 *
 * @param WC_Order $original_order The original order in which the subscription was purchased.
 * @param WC_Order $renewal_order The order which recorded the successful payment (to make up for the failed automatic payment).
 * @return void
 */
function yg_update_failing_payment_method( $original_order, $new_renewal_order ) {
	update_post_meta( $original_order->id, '_your_gateway_customer_token_id', get_post_meta( $new_renewal_order->id, '_your_gateway_customer_token_id', true ) );
}
add_action( 'woocommerce_subscriptions_changed_failing_payment_method_your_gateway', 'yg_failing_payment_method', 10, 2 );
For payment gateway changes to work, your extension will also need to be able to process subscription orders with a $0 initial total

Step 6: Testing Renewal Payments ↑ Back to Top

Once you have everything set-up and working, you will need to test recurring payments. You don’t have to wait 24 hours for an automatic payment for testing.

To trigger an off-schedule renewal and therefore trigger each step in the renewal process like processing payment and sending renewal order emails, follow these steps:

  1. Purchase a test subscription using your payment gateway. Take note of the order number for the subscription.
  2. Add a WCS_DEBUG constant to your site with a value of true. Much like the WP_DEBUG constant, this is most easily set by adding define('WCS_DEBUG', true); to your wp-config.php file.
  3. Install & activate the WP-Crontrol plugin.
  4. Go to the Tools > Crontrol administration screen.
  5. Find the WP-Cron job with 'scheduled_subscription_payment' as the Hook Name and the order ID at the start of the “subscription_key” field of the Arguments. For example, the Arguments for the subscription purchased in order 1150 might look like: {"user_id":26,"subscription_key":"1150_45"}
  6. Click the Run Now link next to the appropriate WP-Cron job.
Crontrol Administration Screen

Example Crontrol Administration Screen

You can also use this method to test payment failures. To do so, delete or modify the meta data that is used to process the payment. For example, you could change the '_stripe_customer_id' for Stripe or '_wc_authorize_net_cim_payment_profile_id' for Authorize.net CIM as stored in the post meta table against the original subscription order. When the payment is processed, it will fail as the customer token is invalid, and therefore, trigger the failed renewal process.

Much like its WordPress counterpart (WP_DEBUG), the WCS_DEBUG constant should not be set permanently on a live site. It is designed to help testing on staging and development sites.

FAQs ↑ Back to Top

How can I show my extension supports Subscriptions?

Supports Subscription Badge Once your gateway includes support for Subscriptions, you can highlight this feature to by including the Supports Subscription badge on your extension’s page in the WooCommerce Extensions marketplace. Please also reach out to Support to request your extension be added to the official Subscription Payment Gateways document.

How can I debug issues with renewal orders?

If a renewal payment is being processed correctly at the payment gateway but the renewal order’s status is not being set to processing or completed, then it is likely a PHP fatal error is occurring during the renewal process. This error may be caused by custom code, plugin conflicts or other server related issues.

To diagnose these issues, it is essential to review the PHP error logs for your website. Unfortunately, there is no standard location for the PHP error log, and it can be set to be stored in a different location depending on your host. To find the PHP error log, you can:

  1. Create a file name phpinfo.php in the root of your WordPress’s directory
  2. Open the phpinfo.php file in a text editor
  3. Insert the following code into the file: <?php phpinfo(); ?>
  4. Open the file on your site, for example, if your site’s URL is example.com, you can open the file by visiting http://example.com/phpinfo.php
  5. Search the page for the error_log value. The file path listed here is the absolute file path of the PHP error log – visit that address on your server and you should find the PHP error log. If the value is empty, then you need to set a value to log errors on your site.

Once you have a copy of your PHP error log, you can look for entries beginning PHP Fatal error around the time of renewal. The error listed here will indicate the cause of the issue.

Example PHP Info Output

Example PHP Info Output

Back to the top