<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Developer Docs - CODIBU</title>
	<atom:link href="https://help.codibu.com/kbtopic/developer-docs/feed/" rel="self" type="application/rss+xml" />
	<link>https://help.codibu.com</link>
	<description>Hosting &#38; Domain,  Development &#38; Design, SEO &#38; Marketing, 2300+ Themes &#38; Plugins, Free SEO analysis &#38; tools</description>
	<lastBuildDate>Sun, 08 Nov 2020 15:16:15 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://help.codibu.com/wp-content/uploads/2022/07/favicon.png</url>
	<title>Developer Docs - CODIBU</title>
	<link>https://help.codibu.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>How Does Subscriptions Handle Staging Sites and Migrations?</title>
		<link>https://help.codibu.com/blog/how-does-subscriptions-handle-staging-sites-and-migrations/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=how-does-subscriptions-handle-staging-sites-and-migrations</link>
					<comments>https://help.codibu.com/blog/how-does-subscriptions-handle-staging-sites-and-migrations/#respond</comments>
		
		<dc:creator><![CDATA[JN C]]></dc:creator>
		<pubDate>Sun, 08 Nov 2020 15:16:15 +0000</pubDate>
				<guid isPermaLink="false">https://help.codibu.com/kb/how-does-subscriptions-handle-staging-sites-and-migrations/</guid>

					<description><![CDATA[<p>WooCommerce Subscriptions can handle staging sites and migrations. Some hosts, like SiteGround, provide an easy way to create a clone of your website for testing changes.<span class="excerpt-hellip"> […]</span></p>
<p>The post <a href="https://help.codibu.com/blog/how-does-subscriptions-handle-staging-sites-and-migrations/">How Does Subscriptions Handle Staging Sites and Migrations?</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>WooCommerce Subscriptions can handle staging sites and migrations. Some hosts, like <b>SiteGround</b>, provide an easy way to create a clone of your website for testing changes. This is called a <b>staging site</b>.</p>
<p>To prevent duplicate payments from a clone of your site, <b>Subscriptions will disable automatic payments and subscription-related emails whenever the current site’s URL differs to the URL of the site where Subscriptions was first activated</b>.</p>
<h2 id="section-1">Duplicate Site Warning</h2>
<p>You will know when the site is in staging mode because Subscriptions will display a warning in the administration area of your site. This notice also shows which URL Subscriptions considers to be the live site.</p>
<figure id="attachment_1360303" class="wp-caption aligncenter" aria-describedby="caption-attachment-1360303"><a href="https://help.codibu.com/wp-content/uploads/2020/11/staging_site_notice.png"><img fetchpriority="high" decoding="async" class="aligncenter size-full wp-image-10739" src="https://help.codibu.com/wp-content/uploads/2020/11/staging_site_notice.png" alt="" width="2048" height="152" /></a><br /><figcaption id="caption-attachment-1360303" class="wp-caption-text">Staging Site Notice</figcaption></figure>
<p>Subscriptions will also display a smaller warning next to the <strong>WooCommerce &gt; Subscriptions</strong> menu item when a store is operating in Staging Mode.</p>
<p><a href="https://help.codibu.com/wp-content/uploads/2020/11/subscriptions-duplicate-site-menu-item-badge.png"><img decoding="async" class="aligncenter size-full wp-image-10744" src="https://help.codibu.com/wp-content/uploads/2020/11/subscriptions-duplicate-site-menu-item-badge.png" alt="" width="420" height="507" /></a></p>
<p>You can still test subscription renewals on a test site, but Subscriptions will use the <a href="https://help.codibu.com/blog/kb/subscriptions-store-manager-guide/"><b>manual renewal process</b></a> and will not send any <a href="https://help.codibu.com/blog/kb/subscriptions-store-manager-guide/#section-18"><b>subscription-related emails</b></a>.</p>
<p>It is possible to <a href="https://help.codibu.com/blog/kb/how-does-subscriptions-handle-staging-sites-and-migrations/#section-4">enable automatic payments</a> and to <a href="https://help.codibu.com/blog/kb/how-does-subscriptions-handle-staging-sites-and-migrations/#section-8">send subscription-relation emails</a> on a staging site.</p>
<p id="stop-emails"><strong>Please note:</strong> it is only <a href="https://help.codibu.com/blog/kb/subscriptions-store-manager-guide/#section-19">emails sent by Subscriptions</a> that will be blocked by Subscriptions in staging mode, <strong>any emails sent or managed by WordPress, WooCommerce or another plugin may still be sent from your staging site.</strong> To block all emails from your site, install a plugin like Disable Emails.</p>
<h2 id="section-2">What does Subscriptions consider a Staging Site?</h2>
<p>Subscriptions will keep a record of the URL of the site where it is first activated. It considers this to be the <em>live site</em> and will run in live mode. If the site’s URL then changes, it considers the new site to be a <em>staging site</em> and it will run in staging mode.</p>
<p>This means if you first activate Subscriptions on a staging site, <strong>Subscriptions will run in live mode on the staging site because it considers this site to actually be your live site</strong>.</p>
<p>Because of this, if you want to test Subscriptions on a staging site before running it on your life site, we recommend <strong>first activating it on the live site and then creating the staging site from that site’s database</strong>.</p>
<p><strong>Developer Note:</strong> Subscriptions stores a clone of WordPress’ <code>siteurl</code> value in an option with the name <code>wc_subscriptions_siteurl</code>. It compares this value with the <code>siteurl</code> value to see if it has changed.</p>
<h2 id="section-3">Check if Staging Mode is Active</h2>
<p>If you are unsure whether you or another administrator on the site has dismissed the notice and want to check if the store is running in Staging mode, you can:</p>
<ol>
<li>Go To <strong>WooCommerce</strong></li>
<li>Check the Subscriptions menu item. If a red area is shown with the word <strong>Staging</strong>, the site is in <strong>Staging Mode</strong>. If nothing is displayed except “Subscriptions”, the site is in <strong>Live Mode</strong>.</li>
</ol>
<p><a href="https://help.codibu.com/wp-content/uploads/2020/11/subscriptions-duplicate-site-menu-item-badge.png"><img decoding="async" class="aligncenter size-full wp-image-10744" src="https://help.codibu.com/wp-content/uploads/2020/11/subscriptions-duplicate-site-menu-item-badge.png" alt="" width="420" height="507" /></a></p>
<p>Whether the store is running in Staging or Live mode is also displayed on the Status screen. To check it there, you can:</p>
<ol>
<li>Go To <strong>WooCommerce &gt; Status</strong></li>
<li>Scroll down to <strong>Subscriptions Mode</strong></li>
<li>Check the value. If a red cross is shown with the word <strong>Staging</strong>, the site is in <strong>Staging Mode</strong>. If a tick mark is shown with the word <strong>Live</strong>, the site is in <strong>Live Mode</strong>.</li>
</ol>
<p><a href="https://help.codibu.com/wp-content/uploads/2020/11/system-status-entry-for-a-site-in-staging-mode.png"><img decoding="async" class="aligncenter size-full wp-image-10761" src="https://help.codibu.com/wp-content/uploads/2020/11/system-status-entry-for-a-site-in-staging-mode.png" alt="" width="653" height="529" /></a></p>
<p>Below this section will be a row labeled <strong>Subscriptions Live URL</strong>. This shows the URL that Subscriptions considers to be the live site.</p>
<figure id="attachment_1360327" class="wp-caption aligncenter" aria-describedby="caption-attachment-1360327"><a href="https://help.codibu.com/wp-content/uploads/2020/11/staging_site_status_url.png"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-10740" src="https://help.codibu.com/wp-content/uploads/2020/11/staging_site_status_url.png" alt="" width="2048" height="586" /></a><figcaption id="caption-attachment-1360327" class="wp-caption-text">Subscriptions Live Site URL</figcaption></figure>
<h2 id="section-4">Enabling Automatic Payments on a Staging Site</h2>
<p>To switch your site from <em>Staging Mode</em> to <em>Live Mode</em>:</p>
<ol>
<li>Go to any administrator page on your site, e.g. visit <code>example.com/wp-admin/</code></li>
<li>Click the <strong>Enable Automatic Payments</strong> button displayed in the staging site notice.</li>
</ol>
<p>If you chose the <strong>Quit Nagging Me</strong> option, but then later find you want to <em>enable automatic payments</em> on the staging site, you can:</p>
<ol>
<li>Go to <code>example.com/wp-admin/?wcs_display_staging_notice=true</code> while logged in as an administrator, replacing <code>example.com</code> with your site’s URL</li>
</ol>
<p>Alternatively, you can:</p>
<ol>
<li>Access your database via PhpMyAdmin if your host provides it (or install a plugin like WP phpMyAdmin if your host does not provide phpMyAdmin access)</li>
<li>Search the <code>wp_options</code> table for an option with the <code>option_name</code> of <code>'wcs_ignore_duplicate_siteurl_notice'</code></li>
<li>Change the option value of the <code>'wcs_ignore_duplicate_siteurl_notice'</code> option to anything other than its current value, <strong>“false”</strong> would be a good choice.</li>
<li>Save the changes to the option value</li>
</ol>
<p>The staging site notice should reappear and you can choose to <em>Enable automatic payments</em>.</p>
<h2 id="section-5">Database Migration</h2>
<p>When migrating your site’s database from one server to another server, there are additional considerations.</p>
<p>If you have migrated your site’s database and the <em>domain name has not changed</em>, the staging mode will not be triggered on the new site. This means renewal payments will be processed in live mode. If you want to make sure payments are not processed, even temporarily, you need to:</p>
<ul>
<li>Change the site URL for it, this will switch that site into staging mode; or</li>
<li><a href="https://help.codibu.com/blog/kb/how-does-subscriptions-handle-staging-sites-and-migrations/#section-7">Disable Action Scheduler’s default queue runner</a> to ensure Subscriptions doesn’t trigger renewal events.</li>
</ul>
<h3 id="section-6">Decommission the old site</h3>
<p>To avoid any issues with duplicate payments or emails, you should decommission the old site as soon as possible.</p>
<p>However, if you need to continue to allow the old site to run after migrating the database to a different server, you must also:</p>
<ul>
<li>Change the site URL for it, this will switch that site into staging mode. In some cases, <code>wc_subscriptions_siteurl</code> value might need to be updated as well, the option can be found under <code>https://YOUR-DOMAIN.com/wp-admin/options.php</code> (replace YOUR-DOMAIN.com with your own domain URL); or</li>
<li><a href="https://help.codibu.com/blog/kb/how-does-subscriptions-handle-staging-sites-and-migrations/#section-7">Disable Action Scheduler’s default queue runner</a> to ensure Subscriptions doesn’t trigger renewal events.</li>
</ul>
<p>Without completing one of these steps, your old site will continue to process real recurring payments.</p>
<p>We also suggest you <strong>block all emails from your duplicate site</strong> to prevent customers receiving any emails from this old site. To do this, you can use a plugin like Disable Emails (this plugin is not officially endorsed by WooCommerce.com).</p>
<h2 id="section-7">Disabling All Scheduled Events</h2>
<p>By default, <a href="https://help.codibu.com/blog/kb/subscription-renewal-process/">scheduled renewals</a> and other scheduled events, like expiration, will still be triggered when Subscriptions is in staging mode. No payments will be attempted, or emails sent, for these events, but the event will still be initiated as it would be on the live site.</p>
<p>If you wish to stop these events being triggered, install and activate the free <strong>Action Scheduler – Disable Default Runner</strong> plugin.</p>
<p>This plugin disables all events in the scheduling library WooCommerce Subscriptions uses. This library is also used by other extensions, like <a href="https://help.codibu.com/blog/kb/woocommerce-memberships-2/">WooCommerce Memberships</a>, so will also disable any scheduled events for those plugins.</p>
<div class="woo-sc-box note   ">The <strong>Action Scheduler – Disable Default Runner</strong> plugin does not rely on the <a href="https://help.codibu.com/blog/kb/how-does-subscriptions-handle-staging-sites-and-migrations/#section-2">site URL check</a>. Once active, it will block scheduled events. As a result, it can also be used on a live site to temporarily disable renewals payments and other scheduled events.</div>
<h2 id="section-8">Enabling Subscriptions Emails on a Staging Site</h2>
<p>If you would like to enable subscription-related emails on a staging site, you can define the <code>WCS_FORCE_EMAIL</code> constant.</p>
<p>In order to enable this constant:</p>
<ol>
<li>Go to your site’s <code>wp-config.php</code> file</li>
<li>Above the line that says <code>/* That's all, stop editing! Happy blogging. */</code>, add:
<p>&nbsp;</p>
<pre>if ( ! defined( ‘WCS_FORCE_EMAIL’ ) {
      define( ‘WCS_FORCE_EMAIL’, true );
}</pre>
</li>
<li>Save file</li>
</ol>
<h2 id="section-9">FAQ</h2>
<h3 id="section-10">Why are renewal payments not processing on the live site?</h3>
<p>When a renewal is processed in Staging mode, the following note will be added to its order notes:</p>
<blockquote>
<p>Payment processing skipped – renewal order created on staging site under staging site lock. Live site is at https://example.com/</p>
</blockquote>
<p>On occasion, these notes may appear on renewal orders created on the live site. This means the live site is accessible via more than one URL. For example, the site may be accessible via both:</p>
<ul>
<li>https://www.example.com</li>
<li>https://example.com</li>
</ul>
<p>To process renewals correctly, <strong>Subscriptions requires your WordPress site to be accessible via only one URL</strong>.</p>
<p>To ensure your site is accessible via only one the URL, you can use <a href="https://help.codibu.com/blog/kb/editing-wp-config-php/">the WordPress Site URL constants</a> in your <code>wp-config.php</code> file to set the URL for the site. For example:</p>
<pre>define( 'WP_SITEURL', 'https://example.com' );
define( 'WP_HOME', 'https://example.com' );</pre>
<p>You should also make sure your web server software, like Apache, is configured to redirect any requests to other URLs to the canonical URL. WordPress has a guide on setting up <code>.htaccess</code> file correctly. If you need additional help, please contact your web host.</p>
<p>Serving your site under a single URL is good practice as it has other benefits, most notably for SEO. As Moz explains:</p>
<blockquote>
<p>Duplicate content is content that appears on the Internet in more than one place. That “one place” is defined as a location with a unique website address (URL)</p>
<p>While not technically a penalty, duplicate content can still sometimes impact search engine rankings. When there are multiple pieces of, as Google calls it, “appreciably similar” content in more than one location on the Internet, it can be difficult for search engines to decide which version is more relevant to a given search query.</p>
</blockquote>
<h3 id="section-11">Why are emails still being sent on my staging site?</h3>
<p>WooCommerce Subscriptions will only block <a href="https://help.codibu.com/blog/kb/subscriptions-store-manager-guide/#section-19">emails sent by it</a> when in staging mode. Emails sent or managed by WordPress, WooCommerce or another plugin may still be sent from your staging site.</p>
<p>To block all emails from your site, install a plugin like Disable Emails.</p><p>The post <a href="https://help.codibu.com/blog/how-does-subscriptions-handle-staging-sites-and-migrations/">How Does Subscriptions Handle Staging Sites and Migrations?</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://help.codibu.com/blog/how-does-subscriptions-handle-staging-sites-and-migrations/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Subscriptions PayPal IPN Issues</title>
		<link>https://help.codibu.com/blog/subscriptions-paypal-ipn-issues/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=subscriptions-paypal-ipn-issues</link>
					<comments>https://help.codibu.com/blog/subscriptions-paypal-ipn-issues/#respond</comments>
		
		<dc:creator><![CDATA[JN C]]></dc:creator>
		<pubDate>Sun, 08 Nov 2020 15:15:18 +0000</pubDate>
				<guid isPermaLink="false">https://help.codibu.com/kb/subscriptions-paypal-ipn-issues/</guid>

					<description><![CDATA[<p>WooCommerce Subscriptions uses PayPal’s IPN (Instant Payment Notification) system to keep data in sync with PayPal. When PayPal processes a payment or other subscription event, like a cancellation,<span class="excerpt-hellip"> […]</span></p>
<p>The post <a href="https://help.codibu.com/blog/subscriptions-paypal-ipn-issues/">Subscriptions PayPal IPN Issues</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>WooCommerce Subscriptions uses PayPal’s IPN (Instant Payment Notification) system to keep data in sync with PayPal.</p>
<p>When PayPal processes a payment or other subscription event, like a cancellation, it sends an IPN to the site. Subscriptions processes this IPN and performs the appropriate actions concerning the related orders and subscriptions.</p>
<p>For example, when a recurring payment is due, payment occurs via PayPal and an IPN is sent confirming that the payment was processed. The site receives this message, creates an order to record the transaction and sets the status of the corresponding order and subscription to reflect the processed payment.</p>
<p>On occasion, errors occur when processing an IPN. The reasons for the errors vary. A couple of the most common reasons include IPN’s for orders that don’t exist in the store and third-party plugin or custom code conflicts.</p>
<p>IPN issues also appear with varied symptoms, from a renewal order not getting generated even though PayPal charged a customer to multiple renewal orders being created when PayPal only charged once or PayPal and Subscriptions disagreeing on how much the customer was / should have been charged.</p>
<p>This guide explained how to find information used to debug issues with WooCommerce Subscriptions the PayPal Standard’s IPN system.</p>
<h2 id="section-1">IPN Processing Error Notice</h2>
<p>When an error occurs while processing an IPN, WooCommerce Subscriptions will display a notice such as the following at the top of your store’s WordPress administration dashboard.</p>
<div class="wp-block-image">
<figure class="aligncenter"><a href="https://help.codibu.com/wp-content/uploads/2020/11/subscription-paypal-ipn-error.png"><img loading="lazy" decoding="async" class="wp-image-1223239" src="https://help.codibu.com/wp-content/uploads/2020/11/subscription-paypal-ipn-error.png?w=950" sizes="(max-width: 1999px) 100vw, 1999px" srcset="https://help.codibu.com/wp-content/uploads/2020/11/subscription-paypal-ipn-error.png 1999w, https://help.codibu.com/wp-content/uploads/2020/11/subscription-paypal-ipn-error.png?resize=550,253 550w, https://help.codibu.com/wp-content/uploads/2020/11/subscription-paypal-ipn-error.png?resize=768,353 768w, https://help.codibu.com/wp-content/uploads/2020/11/subscription-paypal-ipn-error.png?resize=950,437 950w, https://help.codibu.com/wp-content/uploads/2020/11/subscription-paypal-ipn-error.png?resize=1536,706 1536w" alt="Subscriptions PayPal IPN Error screenshot" width="1999" height="919" /></a><figcaption>Subscriptions PayPal IPN Error</figcaption></figure>
</div>
<p>When this notice is displayed, it’s because the exact nature of the error cannot be automatically pinpointed. It will require further investigation by the support team to find the error and its causes. Additionally, to make sure it will not happen again, some form of intervention will likely be necessary to address the error.</p>
<p>When <a href="https://woocommerce.com/my-account/marketplace-ticket-form/">opening a ticket</a> to report this error, there are a few pieces of information that will help the support team provide a smoother experience.</p>
<p>Please make sure to include a copy of the <a href="https://help.codibu.com/blog/kb/understanding-the-woocommerce-system-status-report/#section-1">system status report</a>. The system status report provides a snapshot of the site’s code and data, such as WooCommerce version and which plugins are installed. This type of information helps the support team to better understand the context of the error.</p>
<p>Finally, if some of the information below can be included, that can help to diagnose the error.</p>
<h3 id="section-2">PayPal Subscription Details Page</h3>
<p>PayPal provides information about subscriptions via the Subscription Details Page.</p>
<p>This page will look something like the picture below – PayPal change their interface from time-to-time and display different interfaces for different countries and account types, your page may appear differently.</p>
<p>A screenshot of this screen for a problematic subscription on your site would be very helpful. You can match them up by the profile ID or the customer.</p>
<div class="wp-block-image">
<figure class="aligncenter"><a href="https://help.codibu.com/wp-content/uploads/2020/11/paypal-subscription-details-screenshot.png"><img loading="lazy" decoding="async" class="wp-image-166200" src="https://help.codibu.com/wp-content/uploads/2020/11/paypal-subscription-details-screenshot.png?w=950" sizes="(max-width: 995px) 100vw, 995px" srcset="https://help.codibu.com/wp-content/uploads/2020/11/paypal-subscription-details-screenshot.png 995w, https://help.codibu.com/wp-content/uploads/2020/11/paypal-subscription-details-screenshot.png?resize=550,438 550w, https://help.codibu.com/wp-content/uploads/2020/11/paypal-subscription-details-screenshot.png?resize=768,612 768w, https://help.codibu.com/wp-content/uploads/2020/11/paypal-subscription-details-screenshot.png?resize=950,757 950w" alt="PayPal Subscription Details Page Screenshot" width="995" height="793" /></a><figcaption>PayPal Subscription Details Page Screenshot</figcaption></figure>
</div>
<p>This page holds information about what PayPal knows about the subscription. If there are differences between these details and the ones WooCommerce and Subscriptions have then that’s a clue.</p>
<h3 id="section-3">PayPal Profile Status History page</h3>
<p>Found by clicking <strong>View History</strong> button on the subscription page (near the top, under Subscription details). It looks similar to this:</p>
<figure class="wp-block-image"><a href="https://help.codibu.com/wp-content/uploads/2020/11/paypalhistory.png" rel="prettyPhoto"><img loading="lazy" decoding="async" class="wp-image-160595" src="https://help.codibu.com/wp-content/uploads/2020/11/paypalhistory.png" sizes="(max-width: 792px) 100vw, 792px" srcset="https://help.codibu.com/wp-content/uploads/2020/11/paypalhistory.png 792w, https://help.codibu.com/wp-content/uploads/2020/11/paypalhistory.png?resize=550,251 550w, https://help.codibu.com/wp-content/uploads/2020/11/paypalhistory.png?resize=768,351 768w" alt="PayPal History" width="792" height="362" /></a></figure>
<p>When Subscriptions sends any data to PayPal, it will be recorded here. When it was created, when it was suspended or reactivated, when it was cancelled. If Subscriptions claims that it suspended the subscription, but PayPal did not register that change, that’s a clue.</p>
<h3 id="section-4">IPN History for Problematic Subscriptions</h3>
<p>PayPal keeps a record of IPN messages. These are also accessible via your PayPal account. These can be very valuable when diagnosing IPN errors. For example, if the was retried IPN, we’ll see that so we know the retries might cause multiple orders to be created, but something’s wrong because it had to retry.</p>
<p>To find the IPN history:</p>
<ul>
<li>Search by profile ID for the last payment transaction on this page (<strong>History &gt; Find a transaction</strong>):<a href="https://help.codibu.com/wp-content/uploads/2020/11/paypalfindtransaction.png" rel="prettyPhoto"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-160596" src="https://help.codibu.com/wp-content/uploads/2020/11/paypalfindtransaction.png" sizes="(max-width: 1024px) 100vw, 1024px" srcset="https://help.codibu.com/wp-content/uploads/2020/11/paypalfindtransaction.png 1026w, https://help.codibu.com/wp-content/uploads/2020/11/paypalfindtransaction.png?resize=550,380 550w, https://help.codibu.com/wp-content/uploads/2020/11/paypalfindtransaction.png?resize=768,531 768w, https://help.codibu.com/wp-content/uploads/2020/11/paypalfindtransaction.png?resize=950,656 950w" alt="paypalfindtransaction" width="1024" height="708" /></a></li>
<li>View the transaction details and get the ID from there: <a href="https://help.codibu.com/wp-content/uploads/2020/11/paypaltransactiondetails.png" rel="prettyPhoto"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-160597" src="https://help.codibu.com/wp-content/uploads/2020/11/paypaltransactiondetails.png" sizes="(max-width: 864px) 100vw, 864px" srcset="https://help.codibu.com/wp-content/uploads/2020/11/paypaltransactiondetails.png 864w, https://help.codibu.com/wp-content/uploads/2020/11/paypaltransactiondetails.png?resize=509,550 509w, https://help.codibu.com/wp-content/uploads/2020/11/paypaltransactiondetails.png?resize=768,830 768w" alt="paypaltransactiondetails" width="864" height="934" /></a></li>
<li>Put the ID into the IPN History screen to search: <a href="https://help.codibu.com/wp-content/uploads/2020/11/ipnhistory.png" rel="prettyPhoto"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-160598" src="https://help.codibu.com/wp-content/uploads/2020/11/ipnhistory.png" sizes="(max-width: 1024px) 100vw, 1024px" srcset="https://help.codibu.com/wp-content/uploads/2020/11/ipnhistory.png 1586w, https://help.codibu.com/wp-content/uploads/2020/11/ipnhistory.png?resize=550,422 550w, https://help.codibu.com/wp-content/uploads/2020/11/ipnhistory.png?resize=768,589 768w, https://help.codibu.com/wp-content/uploads/2020/11/ipnhistory.png?resize=950,728 950w, https://help.codibu.com/wp-content/uploads/2020/11/ipnhistory.png?resize=1536,1178 1536w" alt="ipnhistory" width="1024" height="785" /></a></li>
<li>Click on the message ID link, and grab that screenshot too:<a href="https://help.codibu.com/wp-content/uploads/2020/11/ipndetails.png" rel="prettyPhoto"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-160599" src="https://help.codibu.com/wp-content/uploads/2020/11/ipndetails.png" sizes="(max-width: 1024px) 100vw, 1024px" srcset="https://help.codibu.com/wp-content/uploads/2020/11/ipndetails.png 1610w, https://help.codibu.com/wp-content/uploads/2020/11/ipndetails.png?resize=550,448 550w, https://help.codibu.com/wp-content/uploads/2020/11/ipndetails.png?resize=768,625 768w, https://help.codibu.com/wp-content/uploads/2020/11/ipndetails.png?resize=950,773 950w, https://help.codibu.com/wp-content/uploads/2020/11/ipndetails.png?resize=1536,1250 1536w" alt="ipndetails" width="1024" height="833" /></a></li>
</ul>
<p>These are all information we’ll need alongside the usual WooCommerce ticket requirements: System Status Report, FTP details, and sometimes database access details.</p><p>The post <a href="https://help.codibu.com/blog/subscriptions-paypal-ipn-issues/">Subscriptions PayPal IPN Issues</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://help.codibu.com/blog/subscriptions-paypal-ipn-issues/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Subscriptions 2.0 Deprecated Hooks &#038; Query Monitor Warning</title>
		<link>https://help.codibu.com/blog/subscriptions-2-0-deprecated-hooks-query-monitor-warning/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=subscriptions-2-0-deprecated-hooks-query-monitor-warning</link>
					<comments>https://help.codibu.com/blog/subscriptions-2-0-deprecated-hooks-query-monitor-warning/#respond</comments>
		
		<dc:creator><![CDATA[JN C]]></dc:creator>
		<pubDate>Sun, 08 Nov 2020 15:14:37 +0000</pubDate>
				<guid isPermaLink="false">https://help.codibu.com/kb/subscriptions-2-0-deprecated-hooks-query-monitor-warning/</guid>

					<description><![CDATA[<p>Subscriptions 2.0 changed the way data was stored and instantiated. Because of this, it needed to deprecate a large number of existing hooks. Some of these hooks had dynamic<span class="excerpt-hellip"> […]</span></p>
<p>The post <a href="https://help.codibu.com/blog/subscriptions-2-0-deprecated-hooks-query-monitor-warning/">Subscriptions 2.0 Deprecated Hooks & Query Monitor Warning</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Subscriptions 2.0 changed the way data was stored and instantiated. Because of this, it needed to deprecate a large number of existing hooks.</p>
<p>Some of these hooks had dynamic names, i.e. actions/filters where we don’t know what the hook actually is at run-time because it includes a payment gateway ID or another piece of dynamic data we don’t know in advance. Because of this, there was no choice but to either break backwards compatibility completely for those hooks (which would be severe in the case of scheduled subscription payment hooks) or add a small function to the <code>'all'</code> hook to check if it’s a hook we have deprecated and maintain backwards compatibility.</p>
<p>The latter option was chosen as far better, but it may cause performance issues on some sites. We have, however, attempted to mitigate performance issues and also made sure there is a way to remove this code if your site is negatively affected and you know you have no deprecated code running on your site.</p>
<div class="woo-sc-box note   "><strong>Note:</strong> Deprecation handlers are no longer loaded by default with <a href="https://help.codibu.com/blog/kb/whats-new-in-subscriptions-2-1/#deprecated-hooks-are-no-longer-called">Subscriptions 2.1 and newer</a>. If you need to continue to call deprecated hooks, install WooCommerce Subscriptions Load Deprecation Handlers.</div>
<h2 id="section-1">Mitigating performance issues</h2>
<p>Functions attached to <code>'all'</code>, while running many times each request, are minor. They simply check the name of the hook to see if they start with 13 known hook prefixes.</p>
<p>This was done to mitigate any major performance impact on your site. However, depending on the number of plugins running on your site, you may find that running this tiny piece of logic on all hooks markedly reduces site performance. If this is the case, consider removing deprecated handling as outlined below.</p>
<h2 id="section-2">Removing deprecated hook handling</h2>
<p>If you know your site is not running outdated code, you can avoid having to load both this and other depreciation handling code to improve performance with Subscriptions 2.0.</p>
<p>The snippet below tells Subscriptions not to load deprecated handlers, including classes that attach to the <code>'all'</code> hook to handle dynamic hooks:</p>
<div id="gist26964114" class="gist">
<div class="gist-file">
<div class="gist-data">
<div class="js-gist-file-update-container js-task-list-container file-box">
<div id="file-wcs-remove-deprecated-handlers-php" class="file my-2">
<div class="Box-body p-0 blob-wrapper data type-php  ">
<table class="highlight tab-size js-file-line-container" data-tab-size="8" data-paste-markdown-skip="">
<tbody>
<tr>
<td id="file-wcs-remove-deprecated-handlers-php-L1" class="blob-num js-line-number" data-line-number="1"> </td>
<td id="file-wcs-remove-deprecated-handlers-php-LC1" class="blob-code blob-code-inner js-file-line"><span class="pl-ent">&lt;?php</span></td>
</tr>
<tr>
<td id="file-wcs-remove-deprecated-handlers-php-L2" class="blob-num js-line-number" data-line-number="2"> </td>
<td id="file-wcs-remove-deprecated-handlers-php-LC2" class="blob-code blob-code-inner js-file-line"><span class="pl-c">/**</span></td>
</tr>
<tr>
<td id="file-wcs-remove-deprecated-handlers-php-L3" class="blob-num js-line-number" data-line-number="3"> </td>
<td id="file-wcs-remove-deprecated-handlers-php-LC3" class="blob-code blob-code-inner js-file-line"><span class="pl-c"> * Plugin Name: WooCommerce Subscriptions Remove Deprecation Handlers</span></td>
</tr>
<tr>
<td id="file-wcs-remove-deprecated-handlers-php-L4" class="blob-num js-line-number" data-line-number="4"> </td>
<td id="file-wcs-remove-deprecated-handlers-php-LC4" class="blob-code blob-code-inner js-file-line"><span class="pl-c"> * Plugin URI: https://support.woothemes.com/hc/en-us/articles/205214466</span></td>
</tr>
<tr>
<td id="file-wcs-remove-deprecated-handlers-php-L5" class="blob-num js-line-number" data-line-number="5"> </td>
<td id="file-wcs-remove-deprecated-handlers-php-LC5" class="blob-code blob-code-inner js-file-line"><span class="pl-c"> * Description: Do not load backward compatibility support in Subscriptions 2.0.</span></td>
</tr>
<tr>
<td id="file-wcs-remove-deprecated-handlers-php-L6" class="blob-num js-line-number" data-line-number="6"> </td>
<td id="file-wcs-remove-deprecated-handlers-php-LC6" class="blob-code blob-code-inner js-file-line"><span class="pl-c"> * Author: Prospress Inc.</span></td>
</tr>
<tr>
<td id="file-wcs-remove-deprecated-handlers-php-L7" class="blob-num js-line-number" data-line-number="7"> </td>
<td id="file-wcs-remove-deprecated-handlers-php-LC7" class="blob-code blob-code-inner js-file-line"><span class="pl-c"> * Version: 1.0</span></td>
</tr>
<tr>
<td id="file-wcs-remove-deprecated-handlers-php-L8" class="blob-num js-line-number" data-line-number="8"> </td>
<td id="file-wcs-remove-deprecated-handlers-php-LC8" class="blob-code blob-code-inner js-file-line"><span class="pl-c"> * Author URI: http://prospress.com</span></td>
</tr>
<tr>
<td id="file-wcs-remove-deprecated-handlers-php-L9" class="blob-num js-line-number" data-line-number="9"> </td>
<td id="file-wcs-remove-deprecated-handlers-php-LC9" class="blob-code blob-code-inner js-file-line"><span class="pl-c"> */</span></td>
</tr>
<tr>
<td id="file-wcs-remove-deprecated-handlers-php-L10" class="blob-num js-line-number" data-line-number="10"> </td>
<td id="file-wcs-remove-deprecated-handlers-php-LC10" class="blob-code blob-code-inner js-file-line"> </td>
</tr>
<tr>
<td id="file-wcs-remove-deprecated-handlers-php-L11" class="blob-num js-line-number" data-line-number="11"> </td>
<td id="file-wcs-remove-deprecated-handlers-php-LC11" class="blob-code blob-code-inner js-file-line"><span class="pl-k">function</span> <span class="pl-en">wcs_remove_deprecation_handlers</span>() {</td>
</tr>
<tr>
<td id="file-wcs-remove-deprecated-handlers-php-L12" class="blob-num js-line-number" data-line-number="12"> </td>
<td id="file-wcs-remove-deprecated-handlers-php-LC12" class="blob-code blob-code-inner js-file-line"><span class="pl-en">add_filter</span>( <span class="pl-s">&#8216;woocommerce_subscriptions_load_deprecation_handlers&#8217;</span>, <span class="pl-s">&#8216;__return_false&#8217;</span> );</td>
</tr>
<tr>
<td id="file-wcs-remove-deprecated-handlers-php-L13" class="blob-num js-line-number" data-line-number="13"> </td>
<td id="file-wcs-remove-deprecated-handlers-php-LC13" class="blob-code blob-code-inner js-file-line">}</td>
</tr>
<tr>
<td id="file-wcs-remove-deprecated-handlers-php-L14" class="blob-num js-line-number" data-line-number="14"> </td>
<td id="file-wcs-remove-deprecated-handlers-php-LC14" class="blob-code blob-code-inner js-file-line"><span class="pl-en">add_action</span>( <span class="pl-s">&#8216;plugins_loaded&#8217;</span>, <span class="pl-s">&#8216;wcs_remove_deprecation_handlers&#8217;</span>, <span class="pl-c1">0</span> );</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<p>&nbsp;</p>
</div>
</div>
<p>This snippet can also be downloaded and installed as a plugin.</p>
<div class="woo-sc-box note   "><strong>Note:</strong> Deprecation handlers are no longer loaded by default with <a href="https://help.codibu.com/blog/kb/whats-new-in-subscriptions-2-1/#deprecated-hooks-are-no-longer-called">Subscriptions 2.1 and newer</a>. If you need to continue to call deprecated hooks, install WooCommerce Subscriptions Load Deprecation Handlers.</div>
<h2 id="section-3">Warning</h2>
<p>You must be certain that no third-party or custom code is running on your site that is dependent on deprecated hooks before disabling this deprecated hook handling. Disabling will break backwards compatibility with all old hooks and can, among other things:</p>
<ul>
<li>Prevent recurring payments being processed</li>
<li>Break synchronization of subscription status between the payment gateway and store</li>
<li>Break updating of a failing payment method</li>
</ul>
<h2 id="section-4">List of Deprecated Hooks</h2>
<p>This gist provides a complete list of deprecated hooks, and their new counterpart, formatted as PHP arrays. The new hook is listed as the array key, and the deprecated hook as the value or values.</p>
<div id="gist40368083" class="gist">
<div class="gist-file">
<div class="gist-data">
<div class="js-gist-file-update-container js-task-list-container file-box">
<div id="file-wcs-deprecated-hook-list-php" class="file my-2">
<div class="Box-body p-0 blob-wrapper data type-php  ">
<table class="highlight tab-size js-file-line-container" data-tab-size="8" data-paste-markdown-skip="">
<tbody>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L1" class="blob-num js-line-number" data-line-number="1"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC1" class="blob-code blob-code-inner js-file-line"><span class="pl-ent">&lt;?php</span></td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L2" class="blob-num js-line-number" data-line-number="2"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC2" class="blob-code blob-code-inner js-file-line"> </td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L3" class="blob-num js-line-number" data-line-number="3"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC3" class="blob-code blob-code-inner js-file-line"><span class="pl-s1"><span class="pl-c1">$</span>deprecated_actions</span> = <span class="pl-en">array</span>(</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L4" class="blob-num js-line-number" data-line-number="4"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC4" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_scheduled_subscription_payment&#8217;</span> =&gt; <span class="pl-s">&#8216;scheduled_subscription_payment&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L5" class="blob-num js-line-number" data-line-number="5"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC5" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_payment_complete&#8217;</span> =&gt; <span class="pl-s">&#8216;processed_subscription_payment&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L6" class="blob-num js-line-number" data-line-number="6"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC6" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_renewal_payment_complete&#8217;</span> =&gt; <span class="pl-s">&#8216;processed_subscription_renewal_payment&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L7" class="blob-num js-line-number" data-line-number="7"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC7" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscriptions_paid_for_failed_renewal_order&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_processed_failed_renewal_order_payment&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L8" class="blob-num js-line-number" data-line-number="8"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC8" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscriptions_pre_update_payment_method&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_pre_update_recurring_payment_method&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L9" class="blob-num js-line-number" data-line-number="9"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC9" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_payment_method_updated&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_updated_recurring_payment_method&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L10" class="blob-num js-line-number" data-line-number="10"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC10" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_failing_payment_method_updated&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_changed_failing_payment_method&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L11" class="blob-num js-line-number" data-line-number="11"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC11" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_payment_failed&#8217;</span> =&gt; <span class="pl-s">&#8216;processed_subscription_payment_failure&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L12" class="blob-num js-line-number" data-line-number="12"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC12" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_change_payment_method_via_pay_shortcode&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_change_payment_method_via_pay_shortcode&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L13" class="blob-num js-line-number" data-line-number="13"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC13" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;subscriptions_put_on_hold_for_order&#8217;</span> =&gt; <span class="pl-s">&#8216;subscriptions_suspended_for_order&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L14" class="blob-num js-line-number" data-line-number="14"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC14" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_status_active&#8217;</span> =&gt; <span class="pl-s">&#8216;activated_subscription&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L15" class="blob-num js-line-number" data-line-number="15"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC15" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_status_on-hold&#8217;</span> =&gt; <span class="pl-en">array</span>( <span class="pl-s">&#8216;suspended_subscription&#8217;</span>, <span class="pl-s">&#8216;subscription_put_on-hold&#8217;</span> ),</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L16" class="blob-num js-line-number" data-line-number="16"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC16" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_status_cancelled&#8217;</span> =&gt; <span class="pl-s">&#8216;cancelled_subscription&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L17" class="blob-num js-line-number" data-line-number="17"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC17" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_status_on-hold_to_active&#8217;</span> =&gt; <span class="pl-s">&#8216;reactivated_subscription&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L18" class="blob-num js-line-number" data-line-number="18"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC18" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_status_expired&#8217;</span> =&gt; <span class="pl-s">&#8216;subscription_expired&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L19" class="blob-num js-line-number" data-line-number="19"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC19" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_scheduled_subscription_trial_end&#8217;</span> =&gt; <span class="pl-s">&#8216;subscription_trial_end&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L20" class="blob-num js-line-number" data-line-number="20"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC20" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_scheduled_subscription_end_of_prepaid_term&#8217;</span> =&gt; <span class="pl-s">&#8216;subscription_end_of_prepaid_term&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L21" class="blob-num js-line-number" data-line-number="21"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC21" class="blob-code blob-code-inner js-file-line">);</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L22" class="blob-num js-line-number" data-line-number="22"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC22" class="blob-code blob-code-inner js-file-line"> </td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L23" class="blob-num js-line-number" data-line-number="23"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC23" class="blob-code blob-code-inner js-file-line"><span class="pl-s1"><span class="pl-c1">$</span>deprecated_filters</span> = <span class="pl-en">array</span>(</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L24" class="blob-num js-line-number" data-line-number="24"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC24" class="blob-code blob-code-inner js-file-line"> </td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L25" class="blob-num js-line-number" data-line-number="25"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC25" class="blob-code blob-code-inner js-file-line"><span class="pl-c">// Subscription Meta Filters</span></td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L26" class="blob-num js-line-number" data-line-number="26"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC26" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_payment_failed_count&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscription_failed_payment_count&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L27" class="blob-num js-line-number" data-line-number="27"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC27" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_payment_completed_count&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscription_completed_payment_count&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L28" class="blob-num js-line-number" data-line-number="28"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC28" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_get_end_date&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscription_expiration_date&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L29" class="blob-num js-line-number" data-line-number="29"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC29" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_get_trial_end_date&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscription_trial_expiration_date&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L30" class="blob-num js-line-number" data-line-number="30"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC30" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_date_updated&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_set_expiration_date&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L31" class="blob-num js-line-number" data-line-number="31"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC31" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscriptions_product_expiration_date&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscription_calculated_expiration_date&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L32" class="blob-num js-line-number" data-line-number="32"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC32" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_date_updated&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscription_set_next_payment_date&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L33" class="blob-num js-line-number" data-line-number="33"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC33" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_get_last_payment_date&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscription_last_payment_date&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L34" class="blob-num js-line-number" data-line-number="34"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC34" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_calculated_next_payment_date&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_calculated_next_payment_date&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L35" class="blob-num js-line-number" data-line-number="35"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC35" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_date_updated&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_set_trial_expiration_date&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L36" class="blob-num js-line-number" data-line-number="36"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC36" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;wcs_subscription_statuses&#8217;</span> =&gt; <span class="pl-en">array</span>(</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L37" class="blob-num js-line-number" data-line-number="37"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC37" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscriptions_custom_status_string&#8217;</span>, <span class="pl-c">//no replacement as Subscriptions now uses wcs_get_subscription_statuses() for everything (the deprecator could use &#8216;wc_subscription_statuses&#8217; and loop over all statuses to set it in the returned value)</span></td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L38" class="blob-num js-line-number" data-line-number="38"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC38" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscriptions_status_string&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L39" class="blob-num js-line-number" data-line-number="39"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC39" class="blob-code blob-code-inner js-file-line">),</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L40" class="blob-num js-line-number" data-line-number="40"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC40" class="blob-code blob-code-inner js-file-line"> </td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L41" class="blob-num js-line-number" data-line-number="41"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC41" class="blob-code blob-code-inner js-file-line"><span class="pl-c">// Renewal Filters</span></td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L42" class="blob-num js-line-number" data-line-number="42"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC42" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;wcs_renewal_order_items&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_renewal_order_items&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L43" class="blob-num js-line-number" data-line-number="43"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC43" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;wcs_renewal_order_meta_query&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_renewal_order_meta_query&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L44" class="blob-num js-line-number" data-line-number="44"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC44" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;wcs_renewal_order_meta&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_renewal_order_meta&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L45" class="blob-num js-line-number" data-line-number="45"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC45" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;wcs_renewal_order_item_name&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_renewal_order_item_name&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L46" class="blob-num js-line-number" data-line-number="46"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC46" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;wcs_users_resubscribe_link&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_users_renewal_link&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L47" class="blob-num js-line-number" data-line-number="47"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC47" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;wcs_can_user_resubscribe_to_subscription&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_can_subscription_be_renewed&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L48" class="blob-num js-line-number" data-line-number="48"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC48" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;wcs_renewal_order_created&#8217;</span> =&gt; <span class="pl-en">array</span>(</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L49" class="blob-num js-line-number" data-line-number="49"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC49" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscriptions_renewal_order_created&#8217;</span>, <span class="pl-c">// Even though &#8216;woocommerce_subscriptions_renewal_order_created&#8217; is an action, as it is attached to a filter, we need to handle it in here</span></td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L50" class="blob-num js-line-number" data-line-number="50"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC50" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscriptions_renewal_order_id&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L51" class="blob-num js-line-number" data-line-number="51"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC51" class="blob-code blob-code-inner js-file-line">),</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L52" class="blob-num js-line-number" data-line-number="52"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC52" class="blob-code blob-code-inner js-file-line"> </td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L53" class="blob-num js-line-number" data-line-number="53"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC53" class="blob-code blob-code-inner js-file-line"><span class="pl-c">// List Table Filters</span></td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L54" class="blob-num js-line-number" data-line-number="54"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC54" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_list_table_actions&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_list_table_actions&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L55" class="blob-num js-line-number" data-line-number="55"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC55" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_list_table_column_status_content&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_list_table_column_status_content&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L56" class="blob-num js-line-number" data-line-number="56"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC56" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_list_table_column_content&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_list_table_column_content&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L57" class="blob-num js-line-number" data-line-number="57"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC57" class="blob-code blob-code-inner js-file-line"> </td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L58" class="blob-num js-line-number" data-line-number="58"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC58" class="blob-code blob-code-inner js-file-line"><span class="pl-c">// User Filters</span></td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L59" class="blob-num js-line-number" data-line-number="59"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC59" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;wcs_can_user_put_subscription_on_hold&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_can_current_user_suspend&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L60" class="blob-num js-line-number" data-line-number="60"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC60" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;wcs_view_subscription_actions&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_my_account_my_subscriptions_actions&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L61" class="blob-num js-line-number" data-line-number="61"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC61" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;wcs_get_users_subscriptions&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_users_subscriptions&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L62" class="blob-num js-line-number" data-line-number="62"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC62" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;wcs_users_change_status_link&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_users_action_link&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L63" class="blob-num js-line-number" data-line-number="63"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC63" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;wcs_user_has_subscription&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_user_has_subscription&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L64" class="blob-num js-line-number" data-line-number="64"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC64" class="blob-code blob-code-inner js-file-line"> </td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L65" class="blob-num js-line-number" data-line-number="65"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC65" class="blob-code blob-code-inner js-file-line"><span class="pl-c">// Misc Filters</span></td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L66" class="blob-num js-line-number" data-line-number="66"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC66" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_max_failed_payments_exceeded&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_max_failed_payments_exceeded&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L67" class="blob-num js-line-number" data-line-number="67"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC67" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_my_subscriptions_payment_method&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_my_subscriptions_recurring_payment_method&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L68" class="blob-num js-line-number" data-line-number="68"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC68" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscriptions_update_payment_via_pay_shortcode&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_update_recurring_payment_via_pay_shortcode&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L69" class="blob-num js-line-number" data-line-number="69"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC69" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_can_subscription_be_updated_to&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_can_subscription_be_changed_to&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L70" class="blob-num js-line-number" data-line-number="70"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC70" class="blob-code blob-code-inner js-file-line">);</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L71" class="blob-num js-line-number" data-line-number="71"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC71" class="blob-code blob-code-inner js-file-line"> </td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L72" class="blob-num js-line-number" data-line-number="72"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC72" class="blob-code blob-code-inner js-file-line"><span class="pl-c">/**</span></td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L73" class="blob-num js-line-number" data-line-number="73"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC73" class="blob-code blob-code-inner js-file-line"><span class="pl-c"> * The following hooks are prefixes only, the are used a deprecated hooks. Refer to the source code for the suffix on these hooks.</span></td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L74" class="blob-num js-line-number" data-line-number="74"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC74" class="blob-code blob-code-inner js-file-line"><span class="pl-c"> */</span></td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L75" class="blob-num js-line-number" data-line-number="75"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC75" class="blob-code blob-code-inner js-file-line"> </td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L76" class="blob-num js-line-number" data-line-number="76"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC76" class="blob-code blob-code-inner js-file-line"><span class="pl-s1"><span class="pl-c1">$</span>deprecated_hook_prefixes</span> = <span class="pl-en">array</span>(</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L77" class="blob-num js-line-number" data-line-number="77"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC77" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_admin_changed_subscription_to_&#8217;</span> =&gt; <span class="pl-s">&#8216;admin_changed_subscription_to_&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L78" class="blob-num js-line-number" data-line-number="78"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC78" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_scheduled_subscription_payment_&#8217;</span> =&gt; <span class="pl-s">&#8216;scheduled_subscription_payment_&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L79" class="blob-num js-line-number" data-line-number="79"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC79" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_customer_changed_subscription_to_&#8217;</span> =&gt; <span class="pl-s">&#8216;customer_changed_subscription_to_&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L80" class="blob-num js-line-number" data-line-number="80"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC80" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_payment_method_updated_to_&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_updated_recurring_payment_method_to_&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L81" class="blob-num js-line-number" data-line-number="81"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC81" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_payment_method_updated_from_&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_updated_recurring_payment_method_from_&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L82" class="blob-num js-line-number" data-line-number="82"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC82" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_failing_payment_method_updated_&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscriptions_changed_failing_payment_method_&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L83" class="blob-num js-line-number" data-line-number="83"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC83" class="blob-code blob-code-inner js-file-line"> </td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L84" class="blob-num js-line-number" data-line-number="84"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC84" class="blob-code blob-code-inner js-file-line"><span class="pl-c">// Gateway status change hooks</span></td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L85" class="blob-num js-line-number" data-line-number="85"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC85" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_activated_&#8217;</span> =&gt; <span class="pl-en">array</span>(</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L86" class="blob-num js-line-number" data-line-number="86"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC86" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;activated_subscription_&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L87" class="blob-num js-line-number" data-line-number="87"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC87" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;reactivated_subscription_&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L88" class="blob-num js-line-number" data-line-number="88"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC88" class="blob-code blob-code-inner js-file-line">),</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L89" class="blob-num js-line-number" data-line-number="89"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC89" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_on-hold_&#8217;</span> =&gt; <span class="pl-s">&#8216;subscription_put_on-hold_&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L90" class="blob-num js-line-number" data-line-number="90"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC90" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_cancelled_&#8217;</span> =&gt; <span class="pl-s">&#8216;cancelled_subscription_&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L91" class="blob-num js-line-number" data-line-number="91"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC91" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_subscription_expired_&#8217;</span> =&gt; <span class="pl-s">&#8216;subscription_expired_&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L92" class="blob-num js-line-number" data-line-number="92"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC92" class="blob-code blob-code-inner js-file-line"> </td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L93" class="blob-num js-line-number" data-line-number="93"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC93" class="blob-code blob-code-inner js-file-line"><span class="pl-c">// This is a filter, not an action</span></td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L94" class="blob-num js-line-number" data-line-number="94"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC94" class="blob-code blob-code-inner js-file-line"><span class="pl-s">&#8216;woocommerce_can_subscription_be_updated_to_&#8217;</span> =&gt; <span class="pl-s">&#8216;woocommerce_subscription_can_be_changed_to_&#8217;</span>,</td>
</tr>
<tr>
<td id="file-wcs-deprecated-hook-list-php-L95" class="blob-num js-line-number" data-line-number="95"> </td>
<td id="file-wcs-deprecated-hook-list-php-LC95" class="blob-code blob-code-inner js-file-line">);</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="woo-sc-box note   ">In almost all cases, the new hooks pass different parameters to callbacks and expect a different return value. Therefore, to update your code, you cannot simply replace the old hook with the new one.</div><p>The post <a href="https://help.codibu.com/blog/subscriptions-2-0-deprecated-hooks-query-monitor-warning/">Subscriptions 2.0 Deprecated Hooks & Query Monitor Warning</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://help.codibu.com/blog/subscriptions-2-0-deprecated-hooks-query-monitor-warning/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Subscriptions v2.0: Payment Gateway Upgrade Guide</title>
		<link>https://help.codibu.com/blog/subscriptions-v2-0-payment-gateway-upgrade-guide/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=subscriptions-v2-0-payment-gateway-upgrade-guide</link>
					<comments>https://help.codibu.com/blog/subscriptions-v2-0-payment-gateway-upgrade-guide/#respond</comments>
		
		<dc:creator><![CDATA[JN C]]></dc:creator>
		<pubDate>Sun, 08 Nov 2020 15:13:55 +0000</pubDate>
				<guid isPermaLink="false">https://help.codibu.com/kb/subscriptions-v2-0-payment-gateway-upgrade-guide/</guid>

					<description><![CDATA[<p>Although backward compatibility has been preserved for payment gateway APIs, Subscriptions v2.0 introduces a number of changes that relate to payment related meta data and how<span class="excerpt-hellip"> […]</span></p>
<p>The post <a href="https://help.codibu.com/blog/subscriptions-v2-0-payment-gateway-upgrade-guide/">Subscriptions v2.0: Payment Gateway Upgrade Guide</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Although backward compatibility has been preserved for payment gateway APIs, Subscriptions v2.0 introduces a number of changes that relate to payment related meta data and how recurring payments are handled.</p>
<p>&nbsp;</p>
<p>There are a number of updates you can make to your gateway extension’s code to support these changes and avoid deprecated notices on your customer’s sites.</p>
<p>If you have not already, you should read the <a href="https://help.codibu.com/blog/kb/whats-new-in-woocommerce-subscriptions-v2-0/">What’s New in Subscriptions v2.0</a> document and <a href="https://help.codibu.com/blog/kb/subscriptions-v2-0-architectural-changes/">Overview of Subscriptions v2.0 Architectural Changes</a> before continuing with this document.</p>
<h2 id="section-1">Items to Update</h2>
<p>To be fully compatible with Subscriptions v2.0, you need to:</p>
<ul>
<li>update use of deprecated hooks</li>
<li>update the location of your meta data</li>
<li>add support for multiple subscriptions</li>
<li>add support for admin payment method changes</li>
<li>update customer facing change payment method support (if you already support it)</li>
<li>use renewal orders for processing renewal payments</li>
<li>make sure relevant meta data is copied from the original order to the subscription for existing orders/subscriptions on upgrade. All post meta on the original order will be copied by default by the <a href="https://help.codibu.com/blog/kb/subscriptions-v2-0-database-upgrade-process-for-v1-5-to-v2-0/">Subscriptions upgrader</a>, so you only need to copy meta data stored elsewhere or <a href="https://help.codibu.com/blog/kb/subscriptions-v2-0-database-upgrade-process-for-v1-5-to-v2-0/#section-1">exclude post meta data you do not want copied</a>, if any</li>
</ul>
<h2 id="section-2">Example Code: Simplify Commerce</h2>
<p>For example code of the patches required to make a token based payment gateway fully compatible with Subscriptions v2.0, see the Simplify Commerce Subscriptions v2.0 Pull Request for WooCommerce Core.</p>
<p>This pull request includes annotated commits for all of the patches required by a token based payment gateway to be fully compatible with Subscriptions v2.0.</p>
<h2 id="section-3">Store Meta Data on the Subscription not the Original Order</h2>
<p>As discussed in the <a href="https://help.codibu.com/blog/kb/subscriptions-v2-0-architectural-changes/">overview of Subscriptions v2.0’s Architectural Changes</a>, a subscription’s meta data is no longer stored against the order created to record the purchase of that subscription. It is now stored on a separate <code>'shop_subscription'</code> post type.</p>
<p>As a result, if your gateway stores payment related meta data, like credit card or customer tokens, it should store this against the subscription or subscriptions created during checkout and not the original order (as was previously the case).</p>
<p>This is also essential for adding support for the new <a href="https://help.codibu.com/blog/kb/admin-change-payment-method-integration-guide/">Admin Payment Method Changes</a> feature.</p>
<p>As Subscriptions will copy all post meta data stored against the <code>'shop_subscription'</code> post to renewal orders (i.e. <code>'shop_order'</code> posts), your gateway’s meta data will be copied to renewal orders and you can then access it on the renewal order to process the renewal payments via the <code>'woocommerce_scheduled_subscription_payment_{$gateway_id}'</code> hook.</p>
<p>A note on upgrading: as mentioned above, all post meta on the original order will be copied by default by the <a href="https://help.codibu.com/blog/kb/subscriptions-v2-0-database-upgrade-process-for-v1-5-to-v2-0/">Subscriptions upgrader</a>. So migrating your code to use post meta on the subscription will work for both old and new subscriptions.</p>
<h2 id="section-4">Update Deprecated Hooks</h2>
<p>A number of hooks have been deprecated in Subscriptions v2.0, mainly due to the <a href="https://help.codibu.com/blog/kb/subscriptions-v2-0-architectural-changes/">architectural changes</a>.</p>
<p>Specifically, the following hooks have been changed in order to change the parameters passed to callbacks:</p>
<ul>
<li><code>'scheduled_subscription_payment_{$gateway_id}</code> has been replaced with <code>'woocommerce_scheduled_subscription_payment_{$gateway_id}'</code></li>
<li><code>'woocommerce_my_subscriptions_recurring_payment_method'</code> has been replaced with <code>'woocommerce_my_subscriptions_payment_method'</code></li>
<li><code>'woocommerce_subscriptions_renewal_order_meta_query'</code> has been replaced with <code>'wcs_resubscribe_order_created'</code> in the case of what were previously called “parent” renewal orders and <code>'wcs_renewal_order_created'</code> for orders that were previously called “child” renewal orders; and</li>
<li><code>'woocommerce_subscriptions_changed_failing_payment_method_{$gateway_id}'</code> has been replaced with <code>'woocommerce_subscription_failing_payment_method_updated_{$gateway_id}'</code></li>
</ul>
<p>You should update your use of these deprecated hooks to avoid notices and also take advantage of the extra features provided by receiving a <a href="https://help.codibu.com/blog/kb/subscriptions-v2-0-architectural-changes/#the-wc_subscription-object"><code>WC_Subscription</code> as a parameter</a>.</p>
<h3 id="section-5">Hook for Recurring Payment Method Display</h3>
<p>By default, Subscriptions displays your payment gateway extension’s name as the payment method for a subscription; however, Subscriptions also provides an API for displaying a custom label. This can be used to provide a gateway specific and more descriptive label on the payment method. For example, the Stripe extension uses it to display the last 4 digits of the credit card used for recurring payments instead of simply <em>Credit Card</em>.</p>
<p>In Subscriptions v1.5, the filter for displaying a custom label was: <code>'woocommerce_my_subscriptions_recurring_payment_method'</code>.</p>
<p>In Subscriptions v2.0, this has been changed to <code>'woocommerce_my_subscriptions_payment_method'</code>.</p>
<p>The new filter passes the string displayed to describe the payment method (the same as the old filter), and then an instance of a <code>WC_Subscription</code> for which the string relates. This can be used to access meta data specific to that subscription.</p>
<h2 id="section-6">Support for Multiple Subscriptions</h2>
<p>Most modern payment gateways should be able to support Subscriptions v2.0’s new multiple subscriptions feature.</p>
<p>Specifically, if your payment gateway uses customer or credit card tokens for processing renewals, you will be able to support multiple subscriptions. Furthermore, if your payment gateway stores the meta data it uses to process automatic payments in post meta and/or user meta, adding support will be as simple as adding a <code>'multiple_subscriptions'</code> value to your extension’s <code>$supports</code> property.</p>
<p>To learn more about how multiple subscriptions is implemented in Subscriptions v2.0 and to understand whether your payment gateway will be able to support it, refer to the <a href="https://help.codibu.com/blog/kb/store-manager-guide-to-multiple-subscriptions/">overview of multiple subscriptions</a>.</p>
<h2 id="section-7">Support Admin Payment Method Changes</h2>
<p>Subscriptions v2.0 introduces a new feature to allow store managers to change the payment method used on a subscription. To learn how to add support for this feature in your extension, refer to the <a href="https://help.codibu.com/blog/kb/admin-change-payment-method-integration-guide/">Admin Change Payment Method Integration Guide</a>.</p>
<h2 id="section-8">Update Customer Facing Change Payment Method Support</h2>
<p>Subscriptions v1.5 provided a customer facing process for <a href="https://help.codibu.com/blog/kb/subscriptions-payment-gateway-integration-guide/#section-5)%20for%20a%20subscription%20(especially%20useful%20for%20updating%20a%20failing%20payment%20method">changing the payment method</a>.</p>
<p>Because it is now possible to <a href="https://help.codibu.com/blog/kb/subscriptions-v2-0-architectural-changes/">store a subscription’s payment method meta data on the subscription</a> instead of the original order, this process has been updated for v2.0.</p>
<p>To update your process, you need to:</p>
<ol>
<li>declare support for the new feature, which is <code>'subscription_payment_method_change_customer'</code>: a new feature name has been chosen to both require an update and to differentiate between the new admin payment method change feature and the customer facing feature.</li>
<li>update your payment method’s meta data using the new <code>'woocommerce_subscription_failing_payment_method_updated_{$gateway_id}'</code> hook, which passes your gateway the <code>WC_Subscription</code> object, instead of the old <code>'woocommerce_subscriptions_changed_failing_payment_method_{$gateway_id}'</code> hook, which passed the <code>WC_Order</code> of the original purchase.</li>
</ol>
<p>The code required to update Simplify Commerce can be seen in this commit to WooCommerce core on GitHub.</p>
<h2 id="section-9">Use Renewal Order for Processing Scheduled Payment</h2>
<p>As discussed in the <a href="https://help.codibu.com/blog/kb/subscriptions-v2-0-architectural-changes//#renewal-order-creation">overview of renewal order creation changes</a>, Subscriptions v2.0 now creates the renewal order for recurring payment <em>before</em> triggering the scheduled subscription payment hook.</p>
<p>This makes it possible to use the same code for processing an initial payment as for processing a recurring payment (as the renewal order contains all essential information about the payment). It also makes it possible to record the transaction ID of the recurring payment on the renewal order by calling <code>$renewal_order-&gt;payment_complete( $transaction_id )</code>.</p>
<p>To use the renewal order for processing scheduled payments, update the code attached to the deprecated <code>'scheduled_subscription_payment_{$gateway_id}'</code> action to use the new hook <code>'woocommerce_scheduled_subscription_renewal_{$gateway_id}'</code> instead.</p>
<p>This hook will pass callbacks:</p>
<ol>
<li>the amount to charge (the same as the first parameter in Subscriptions v1.5)</li>
<li>an instance of a <code>WC_Order</code>, which represents the pending renewal order for this payment</li>
</ol>
<p>You can also process the renewal payment without having to worry about the associated subscription by calling <code>$order-&gt;payment_complete( $transaction_id )</code> where <code>$order</code> is the renewal order, just as you would with a normal payment.</p>
<h3 id="section-10">A Note on Tokens and Renewal Order Meta Data</h3>
<p>You may be wondering why the <code>'woocommerce_scheduled_subscription_payment_{$gateway_id}'</code> only passes callbacks the amount and a single order as a parameter, but not the <code>WC_Subscription</code> for which the order relates. This is to lay the foundation for batch processing renewal payments for multiple subscriptions in a single order. More generally, it also helps provide APIs that only require an understanding of WooCommerce primatives, like <code>WC_Order</code>, instead of Subscriptions specific objects, like <code>WC_Subscription</code>.</p>
<p>When a renewal order is generated, via <code>wcs_create_renewal_order()</code>, Subscriptions copies all post meta data rows stored against the <code>'shop_subscription'</code> object to the new renewal order object. It is possible for extensions to opt-out their data from this process using the <code>'wcs_renewal_order_meta'</code> or <code>'wcs_renewal_order_meta_query'</code> hook, but it is recommended that you let your payment gateway’s token data be copied to renewal orders. This makes it possible to use only the parameters passed along with <code>'woocommerce_scheduled_subscription_payment_{$gateway_id}'</code>, instead of also having to use <code>wcs_get_subscription_for_renewal_order()</code> to access the data on the subscription.</p>
<h2 id="section-11">Handling $0 Renewals</h2>
<p>You may also (optionally) remove any code your gateway has to process $0 renewal orders/payments. Subscriptions v1.5 still called the payment gateway hook if a $0 amount was due; however, in v2.0, it no longer involves the payment gateway, and instead generates the order and processes it internally.</p>
<h2 id="section-12">Test Cases</h2>
<p>After you have updated your integration, you can confirm everything is working by running through all of the following test cases using your payment gateway as the payment method.</p>
<h3 id="section-13">Tests for Subscriptions v1.5 and v2.0</h3>
<ul>
<li>checkout with only a simple product in the cart</li>
<li>checkout with only a simple subscription product in the cart</li>
<li>checkout with only a simple subscription product with a sign-up fee in the cart</li>
<li>checkout with only a simple subscription product with a free trial in the cart</li>
<li>checkout with only a simple subscription product with a sign-up fee and free trial in the cart</li>
<li>checkout with only a simple subscription product <a href="https://help.codibu.com/blog/kb/subscription-renewal-synchronization-guide/">synchronized</a> to a day in the future in the cart</li>
<li>checkout with only a simple subscription product with a sign-up fee and <a href="https://help.codibu.com/blog/kb/subscription-renewal-synchronization-guide/">synchronized</a> to a day in the future in the cart</li>
<li>checkout with a simple product <em>and</em> simple subscription product in the cart</li>
<li><a href="https://help.codibu.com/blog/kb/testing-subscription-renewal-payments/">trigger an automatic renewal</a> for a subscription with valid payment gateway meta data to test automatic payment success</li>
<li><a href="https://help.codibu.com/blog/kb/testing-subscription-renewal-payments/">trigger an automatic renewal</a> for a subscription with invalid payment gateway meta data to test automatic payment failure</li>
<li>complete the <a href="https://help.codibu.com/blog/kb/subscribers-view/#section-3">manual payment process for the failed renewal</a> and confirm that payment gateway meta data is updated correctly. Then trigger an automatic renewal after changing the payment method to ensure future renewals use the new payment gateway meta data</li>
<li>test the <strong><a href="https://help.codibu.com/blog/kb/subscribers-view/#section-5">Change Payment Method</a></strong> process on a subscription and confirm that payment gateway meta data is updated correctly. Then trigger an automatic renewal to ensure future renewals use the new payment gateway meta data.</li>
</ul>
<h3 id="section-14">Test for Subscriptions v2.0 Specific Functionality</h3>
<ul>
<li>checkout with <a href="https://help.codibu.com/blog/kb/store-manager-guide-to-multiple-subscriptions/">multiple simple subscription products</a> with different billing periods in the cart (e.g. one subscription product renewing weekly and another renewing monthly)</li>
<li>trigger an automatic renewal with working payment gateway meta data for a subscription with multiple simple subscription product line items</li>
<li>checkout with a simple product <em>and</em> multiple simple subscription products in the cart</li>
<li>(optional) if you added support for <a href="https://help.codibu.com/blog/kb/admin-change-payment-method-integration-guide/">administrator payment method changes</a>, change payment method on a subscription from the admin then trigger an automatic renewal to ensure future renewals use the new payment gateway meta data.</li>
</ul><p>The post <a href="https://help.codibu.com/blog/subscriptions-v2-0-payment-gateway-upgrade-guide/">Subscriptions v2.0: Payment Gateway Upgrade Guide</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://help.codibu.com/blog/subscriptions-v2-0-payment-gateway-upgrade-guide/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Subscriptions v2.0: Database Upgrade Process for v1.5 to v2.0</title>
		<link>https://help.codibu.com/blog/subscriptions-v2-0-database-upgrade-process-for-v1-5-to-v2-0/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=subscriptions-v2-0-database-upgrade-process-for-v1-5-to-v2-0</link>
					<comments>https://help.codibu.com/blog/subscriptions-v2-0-database-upgrade-process-for-v1-5-to-v2-0/#respond</comments>
		
		<dc:creator><![CDATA[JN C]]></dc:creator>
		<pubDate>Sun, 08 Nov 2020 15:13:09 +0000</pubDate>
				<guid isPermaLink="false">https://help.codibu.com/kb/subscriptions-v2-0-database-upgrade-process-for-v1-5-to-v2-0/</guid>

					<description><![CDATA[<p>To migrate stores using Subscriptions v1.5 to the new architecture of v2.0, Subscriptions uses a database upgrade script. This script will create a new 'shop_subscription' post for each subscription<span class="excerpt-hellip"> […]</span></p>
<p>The post <a href="https://help.codibu.com/blog/subscriptions-v2-0-database-upgrade-process-for-v1-5-to-v2-0/">Subscriptions v2.0: Database Upgrade Process for v1.5 to v2.0</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>To migrate stores using Subscriptions v1.5 to the <a href="https://help.codibu.com/blog/kb/subscriptions-v2-0-architectural-changes/">new architecture of v2.0</a>, Subscriptions uses a database upgrade script.</p>
<p>This script will create a new <code>'shop_subscription'</code> post for each subscription on the site and then migrate all meta data relating to a subscription from the original order used to purchase the subscription to the new <code>'shop_subscription'</code> post.</p>
<p>As seen the interface screenshot below, this script upgrades all subscriptions on the site in batches of 50 subscriptions at a time via series of synchronous Ajax requests to avoid memory exhaustion or script timeouts on stores with large numbers of subscriptions.</p>
<figure id="attachment_158241" class="wp-caption aligncenter" aria-describedby="caption-attachment-158241"><a href="https://help.codibu.com/wp-content/uploads/2020/11/woocommerce-subscriptions-database-upgrade-screenshot.png"><img loading="lazy" decoding="async" class="size-full wp-image-158241" src="https://help.codibu.com/wp-content/uploads/2020/11/woocommerce-subscriptions-database-upgrade-screenshot.png" sizes="(max-width: 1081px) 100vw, 1081px" srcset="https://help.codibu.com/wp-content/uploads/2020/11/woocommerce-subscriptions-database-upgrade-screenshot.png 1081w, https://help.codibu.com/wp-content/uploads/2020/11/woocommerce-subscriptions-database-upgrade-screenshot.png?resize=550,317 550w, https://help.codibu.com/wp-content/uploads/2020/11/woocommerce-subscriptions-database-upgrade-screenshot.png?resize=768,443 768w, https://help.codibu.com/wp-content/uploads/2020/11/woocommerce-subscriptions-database-upgrade-screenshot.png?resize=950,548 950w" alt="WooCommerce Subscriptions Database Upgrade Process" width="1081" height="623" /></a><figcaption id="caption-attachment-158241" class="wp-caption-text">WooCommerce Subscriptions Database Upgrade Process</figcaption></figure>
<p>The data migrated includes:</p>
<ul>
<li>Billing schedule, including recurring interval and period, and trial end, next payment and end dates</li>
<li>Subscription product line item</li>
<li>Download permissions for the subscription product</li>
<li>Order notes which contain any of the words <code>subscription</code>, <code>recurring</code> or <code>renewal</code>, as these are deemed to relate to the subscription not the original order</li>
<li>Recurring tax, shipping and coupon order items and their associated meta data</li>
<li>Switch, renewal and resubscribe meta data linking switch, renewal and resubscribe orders to the new subscription instead of the original order</li>
<li>All post meta data not excluding with the <code>'wcs_upgrade_subscription_meta_to_copy'</code> filter</li>
</ul>
<p>Specific details of the data upgraded during the process can be seen in the <code>WCS_Upgrade_2_0</code> class found in <code>/includes/upgrades/class-wcs-upgrade-2-0.php</code> file.</p>
<h2 id="section-1">Excluding of Post Meta Data</h2>
<p>Subscriptions uses an opt-out approach to migrating meta data to the new subscriptions during the upgrade. This is to achieve compatibility by default rather than requiring all developers to opt-in to have their code be compatible.</p>
<p>This is especially important for payment gateways which historically attached billing data, like customer tokens, to the original order. It ensures that this data is copied to the new subscription, which is where we recommend developers store it from v2.0 onwards.</p>
<p>It also allows the data to be relied upon to exist on the subscription for all old and new subscriptions, instead of just new subscriptions. As well as saving <code>if/else</code> statements, this ensures the payment method meta data can be <a href="https://help.codibu.com/blog/kb/admin-change-payment-method-integration-guide/">changed by store managers</a> on both old and new subscriptions if the extension supports this new feature.</p>
<p>However, if your extension attaches meta data to an order that should not be copied to <code>'shop_subscription'</code> objects, you can use the <code>'wcs_upgrade_subscription_meta_to_copy'</code> filter to prevent it being copied to the subscription. An example of the kind of data may not need to be copied to new subscriptions is a shipment tracking number specific to the original order.</p>
<p>The example below shows how to make sure meta data with the meta key of <code>'_my_shipment_tracking_code'</code> is not copied to new subscription on upgrade.</p>
<pre>function eg_do_not_copy_tracking_code( $order_meta ) {
    if ( isset( $order_meta ) ) {
        unset( $order_meta['_my_shipment_tracking_code'] );
    }
    return $order_meta;
}
add_filter( 'wcs_upgrade_subscription_meta_to_copy', 'eg_do_not_cop</pre><p>The post <a href="https://help.codibu.com/blog/subscriptions-v2-0-database-upgrade-process-for-v1-5-to-v2-0/">Subscriptions v2.0: Database Upgrade Process for v1.5 to v2.0</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://help.codibu.com/blog/subscriptions-v2-0-database-upgrade-process-for-v1-5-to-v2-0/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Subscriptions v2.0 Architectural Changes</title>
		<link>https://help.codibu.com/blog/subscriptions-v2-0-architectural-changes/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=subscriptions-v2-0-architectural-changes</link>
					<comments>https://help.codibu.com/blog/subscriptions-v2-0-architectural-changes/#respond</comments>
		
		<dc:creator><![CDATA[JN C]]></dc:creator>
		<pubDate>Sun, 08 Nov 2020 15:12:25 +0000</pubDate>
				<guid isPermaLink="false">https://help.codibu.com/kb/subscriptions-v2-0-architectural-changes/</guid>

					<description><![CDATA[<p>WooCommerce Subscriptions v2.0 introduced a major architectural change relating to how subscription data is stored and accessed. This was the most important change in Subscriptions’ code<span class="excerpt-hellip"> […]</span></p>
<p>The post <a href="https://help.codibu.com/blog/subscriptions-v2-0-architectural-changes/">Subscriptions v2.0 Architectural Changes</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>WooCommerce Subscriptions v2.0 introduced a major architectural change relating to how subscription data is stored and accessed.</p>
<p>This was the most important change in Subscriptions’ code base since it was first developed more than 3 years prior to the version 2.0 release. It was also the first time a major breaking change to Subscriptions’ code base was introduced.</p>
<p>While all reasonable efforts were undertaken to maintain backward compatibility with API functions and existing actions/filters, custom code written against the Subscriptions v1.5 code base may not be compatible with the v2.0. In particular, if code accesses a subscription’s meta data directly in the database, or against order (i.e. <code>WC_Order</code>) or cart (i.e. <code>WC()-&gt;cart</code> or <code>WC_Cart</code>) objects, it will most likely be broken.</p>
<p>This guide explains the history behind the old architecture and the reason changes were necessary. It then provides an <a href="https://help.codibu.com/blog/kb/subscriptions-v2-0-architectural-changes/#section-3">overview of the changes</a> to help understand how to update code written to integrate with Subscriptions.</p>
<p>If you have not already, you may wish to read the <a href="https://help.codibu.com/blog/kb/subscriptions-v2-0-architectural-changes">What’s New in Subscriptions v2.0</a> and <a href="https://help.codibu.com/blog/kb/store-manager-guide-to-multiple-subscriptions/">Store Manager Guide to Multiple Subscriptions </a>documents to get a non-technical overview of the changes introduced by Subscriptions v2.0.</p>
<div class="woo-sc-box info   ">Need to upgrade a payment gateway for version 2.0? Check out the <a href="https://help.codibu.com/blog/kb/subscriptions-v2-0-payment-gateway-upgrade-guide/">Payment Gateway Upgrade Guide</a>.</div>
<h2 id="section-1">A History of Subscriptions</h2>
<p>Almost 3 years from the time of writing, Subscriptions v1.0 was first being designed. It was being created to achieve one simple goal – connect WooCommerce with PayPal Standard’s Subscriptions.</p>
<p>Because PayPal offered off-site payment processing and subscription storage, little consideration was given to how to store and manage a subscription after it was purchased within WooCommerce. The most important aspect of the design was how to create a subscription product in WooCommerce and then how to pass the details of that product to PayPal on checkout. It’s no coincidence that the product components of Subscriptions’ architecture are not changing in v2.0. A subscription is still a custom product type that extends from a base WooCommerce product class.</p>
<p>However, because of the focus on PayPal and the features it provided, the original architecture had some major short-comings:</p>
<ol>
<li>it was not possible to purchase multiple different subscription products on different billing schedules in the same transaction. PayPal requires a customer to sign up for each subscription individually, so to purchase an annual, monthly and weekly subscription in the same transaction and use PayPal as the payment method, the customer would need to complete checkout with PayPal 3 times.</li>
<li>Subscriptions only needed to know the PayPal Profile ID for the subscription and handle IPN messages in order to handle recurring payments. Because PayPal took care of all of this, the method by which subscription meta data (such as recurring totals, completed renewal payments etc.) were stored in WooCommerce was an afterthought. It was such an after thought, that renewal orders, now the backbone of a subscriptions record keeping and inventory management, did not exist until version v1.2.</li>
<li>subscription management within the store, like modifying a subscription’s billing schedule, shipping, taxes or line items was not possible, because PayPal didn’t allow any of these details to be changed. As a result, when interfaces for these tasks were introduced in later versions of Subscriptions, they were also an afterthought that needed to be retrofitted to the original architecture.</li>
</ol>
<h3 id="section-2">Subscriptions v1.5 Architecture</h3>
<h4 id="v15-data-storage-the-order">v1.5 Data Storage – the Order</h4>
<p>Because Subscriptions was first designed to simply link an order in a WooCommerce store with PayPal, Subscriptions v1.5 stored all meta data relating to a subscription on the original order created to record the purchase of a subscription product.</p>
<p>Because much of a subscription’s data was not even stored in v1.0, this meant that as newer versions of Subscriptions required new meta data to add new features with other payment gateways (like changing payment method or billing/shipping address), this data was retrofitted to the order. As a result, data was often stored in unexpected and disjointed places.</p>
<p>For example, a subscription’s data in v1.5 was stored in the following database tables:</p>
<ul>
<li><code>wp_postmeta</code>: some values designed to mirror the original order’s meta data on the subscription were stored in the post meta table against the original order, like <code>_recurring_payment_method</code> (which mirrored the original order’s <code>_payment_method</code> and was the payment method used to <a href="https://help.codibu.com/blog/kb/subscription-renewal-process/">process renewals</a>) or <code>_recurring_order_total</code> (which mirrored the original order’s <code>_order_total</code> and was the amount charged for renewal payments)</li>
<li><code>wp_woocommerce_order_items</code>: some values mirroring the original order’s items were stored in the order item table, like <code>recurring_tax</code> item/s (which mirrored the original order’s <code>tax</code> item/s for recurring amounts) or <code>recurring_shipping</code> item/s (which mirrored the original order’s <code>shipping</code> item/s for recurring amounts)</li>
<li><code>wp_woocommerce_order_itemmeta</code>: some values relating to the subscription product’s billing terms were stored in the order item meta table against the subscription product’s line item. For example, a subscription’s billing schedule (<code>_subscription_period</code> and <code>_subscription_interval</code>), status (<code>_subscription_status</code>) and important dates (<code>_subscription_start_date</code>, <code>_subscription_expiry_date</code>, <code>_subscription_trial_expiry_date</code> and <code>_subscription_completed_payments</code>) were all stored in the order item meta table.</li>
</ul>
<p>This storage system was:</p>
<ul>
<li><strong>inefficient</strong>: querying subscriptions almost always required a number of <code>JOIN</code> statements and queries on columns of a generic <code>varchar</code> type (the type used for meta values). For example, a query to get subscriptions by start date required joining 3 tables: <code>wp_posts</code>, <code>wp_woocommerce_order_items</code> and <code>wp_woocommerce_order_itemmeta</code> to search for a MySQL formatted date stored as a <code>varchar</code>. This ultimately led to serious scaling issues once a store started to enjoy more than 20,000 subscribers. In v2.0, the same query is on a single table (<code>wp_posts</code>) in a column with a more accurate type (<code>datetime</code>) and it uses a schema proven to scale to hundreds of thousands of rows.</li>
<li><strong>cumbersome</strong>: to find out any details on a subscription, you needed to know the order ID and often either the user ID and/or product ID for that subscription. Each subscription did not have a simple, unique ID which could be used to access it and pass around its data.</li>
<li><strong>unintuitive</strong>: as a subscription has evolved into an important object, new developers coming to Subscriptions now expect to find a discrete subscription object, not a collection of data attached to an order.</li>
<li><strong>repetitive</strong>: because this schema was completely unique to Subscriptions, it was not possible to take advantage of code already written in WordPress or WooCommerce for querying data. This makes simple feature requests, like improved reporting, difficult to implement because it requires custom database queries compared with using well known WordPress API functions like <code>get_posts()</code>.</li>
</ul>
<h4 id="v15-data-structure-the-array">v1.5 Data Structure – the Array</h4>
<p>As a subscription had very little data in v1.0, and that data rarely needed to be accessed or modified, the data structure chosen for accessing a subscription’s details at the application level was the most basic object available: an <code>array</code>.</p>
<p>As new versions of Subscriptions began to provide features for modifying a subscription, the array structure was maintained and separate functions were implemented to act on the subscription’s data, including the most primitive WordPress functions for acting on data like <code>update_post_meta()</code> or subscription specific functions like <code>WC_Subscriptions_Manager::update_subscription()</code>.</p>
<p>Because an array can not have methods, this was the only approach available, despite native methods on the object offering a much more natural approach (e.g. <code>$subscription-&gt;update_status()</code>) and also providing many of the benefits of object oriented programming, like abstraction and encapsulation.</p>
<h2 id="section-3">Evolution of the WooCommerce Ecosystem</h2>
<p>In addition to the issues outlined above with the existing data storage and structure used for a subscription, the payment gateway and WooCommerce landscape evolved dramatically in the time since Subscriptions 1.0 was designed.</p>
<h4 id="uptake-of-modern-payment-gateway-apis">Uptake of Modern Payment Gateway APIs</h4>
<p>In the 3 years since first designing Subscriptions, modern payment gateways that provide credit card tokens, like Stripe and Authorize.net CIM, have become more available to non-US markets and easier to access by non-technical store owners. This development has created an opportunity for Subscriptions’ feature set to be designed for these modern, more flexible payment methods, rather than the out-dated APIs offered by PayPal Standard.</p>
<p>Feedback on the Subscriptions feature set has consistently demanded more control over subscription data from within the WooCommerce store. This is possible with these modern payment gateways (but not PayPal Standard).</p>
<p>With Subscriptions 2.0 therefore, there was an opportunity and a need to design a system for these gateways that provides more control to store owners. Fortunately, there was also a recent development in WooCommerce that made such a change possible.</p>
<h4 id="woocommerce-v22-and-custom-order-types">WooCommerce v2.2 and Custom Order Types</h4>
<p>WooCommerce v2.2 quietly introduced a new API that could have great implications for plugins like Subscriptions – the custom order types API.</p>
<p>This API allows for more than just the standard <code>simple</code> order type to exist in a WooCommerce store.</p>
<p>An order is WooCommerce’s snapshot of history. In the case of a <code>simple</code> order, it is used to record a purchase transaction. In the case of a <code>refund</code>, it records the return of some or all of an order’s amount to the customer.</p>
<p>Unlike an order, a subscription is not a record of what <em>has</em> happened; instead, it is an agreement for what <em>should</em> happen. However, an order’s meta data, like customer billing and shipping details, product line items, taxes, fees and totals perfectly align with the details required for such an agreement. Therefore, a custom order type is the perfect data store for a subscription.</p>
<h4 id="store-manager-feedback">Store Manager Feedback</h4>
<p>The final and most important driving force behind the change was the same factor that drives all of development on Subscriptions – customer feedback.</p>
<p>In addition to technical and market changes, our understanding of what store managers expect from a subscription management plugin evolved in the 3 years since releasing version 1.0.</p>
<p>Although no store manager has ever asked for a subscription to be stored as a custom order type, to support the numerous and diverse requests for all manner of features to be built on top of Subscriptions, the architecture needed to evolve. Many requested features, like upgrades/downgrades, updating payment method, changing shipping address, were implemented, painstakingly in some cases, on the Subscriptions 1.5 architecture.</p>
<p>However, many features could not be feasibly implemented with that architecture, such as the ability for customers to purchase different subscription products in a single transaction. Even simple requests, like gifting subscriptions, improved reporting or having shipping charged only on the initial order were extremely difficult to achieve with the v1.5 data scheme and structure.</p>
<p>The new architecture consolidates 3 years of feedback into a design that can support every feature request and behaviour change we’ve received in that time. Even if these do not make it into Subscriptions core, it will be possible to write them as mini-extensions with significantly less developer time than previously required.</p>
<h2 id="section-4">Introducing the Subscription Order Type</h2>
<p>The backbone of the v2.0 changes is a new subscription order type.</p>
<p>In much the same way as an order is stored as a <code>'shop_order'</code> post type and instantiated as an instance of a <code>WC_Order</code> class, the subscription order type consists of a <code>'shop_subscription'</code> post type and <code>WC_Subscription</code> class.</p>
<h3 id="section-5">The <code>'shop_subscription'</code> Post Type</h3>
<p>The <code>'shop_subscription'</code> post type is a WordPress custom post type created by <code>wc_register_order_type()</code> to be used for the storage of subscription data.</p>
<p>As this post type is an extension of the <code>'shop_order'</code> post type, a subscription’s meta is stored in the same locations as an order’s data, making it possible for developers to learn where an order’s data is stored, and then instantly understand where a subscription’s data will be stored. For example, after being purchased in an order, a subscription product becomes a line item on both that order and a subscription, meaning its data is consistently stored as a <code>line_item</code> in <code>wp_woocommerce_order_items</code> table for both the <code>'shop_order'</code> and <code>'shop_subscription'</code>.</p>
<h3 id="section-6">The <code>WC_Subscription</code> Class</h3>
<p>The <code>WC_Subscription</code> class extends <code>WC_Order</code> to provide an object for interacting with a subscription’s data.</p>
<p>The object inherits all of <a href="http://docs.woocommerce.com/wc-apidocs/class-WC_Order.html">the properties and methods of the <code>WC_Order</code> class</a> (which also includes those of its parent – <code><a href="http://docs.woocommerce.com/wc-apidocs/class-WC_Abstract_Order.html">WC_Abstract_Order</a>)</code>. These properties and methods make it significantly more intuitive to work with a subscription’s data. For example, in v2.0, changing a subscription’s status can be done with the <code>WC_Abstract_Order-&gt;update_status()</code> method (e.g. <code>$subscription-&gt;update_status()</code>.</p>
<p>In addition, the <code>WC_Subscription</code> class also adds new properties for storing a subscription’s meta data, mainly relating to billing schedule and important dates, like the next payment date, and new methods for interacting with this data, like <code>WC_Subscription-&gt;update_dates()</code>.</p>
<h3 id="section-7">Benefits of the Subscription Order Type</h3>
<p>The primary benefits of the subscription order type are:</p>
<ul>
<li>WordPress’s post list table: as a subscription is now a custom post type, the custom list table used on the <strong>Manage Subscriptions</strong> screen has been replaced by a WordPress post list table. This saved hundreds of lines of code and helps provide better future proofing of the list table for updates to WordPress and WooCommerce markup and CSS.</li>
<li>Scalable storage: now that a subscription’s data is stored in the same way as other WordPress custom post types and WooCommerce orders, Subscriptions can take advantage of trusted API functions, like <code>get_posts()</code>, to work with subscription data and be confident that queries will scale.</li>
<li>DRY: by using a custom order type, Subscriptions can take advantage of WooCommerce code to add new features more quickly and with less code. An example of this in v2.0, beyond the obvious example of extending <code>WC_Order</code>, is the introduction of API endpoints for subscriptions. This is done by introducing a new <code>WC_API_Subscriptions</code> class which extends <code>WC_API_Orders</code> and benefits from much of the code in that class.</li>
<li>Learn once: developers familiar with working with WooCommerce’s orders can now transfer that knowledge entirely to working with subscriptions. Instead of having to learn an entirely new set of API functions and database schema, developers now need to only learn where the extra meta data is stored and how to access it.</li>
</ul>
<h3 id="section-8">Implications of the Subscription Order Type</h3>
<p>By changing the database schema and application level data structure of a subscription, code handling everything from the initial purchase through to renewal and management of a subscription has had to be updated.</p>
<p>This is because old functions expected some combination of an order ID or <code>WC_Order</code>, subscription key (consisting of the order ID and product ID, not a unique integer ID for that object), user ID and/or product ID. Similarly, the hooks used for existing actions and filters would pass this data to callbacks; where as in v2.0, those callbacks need the <code>WC_Subscriptions</code> or ID of the <code>'shop_subscription'</code> to take advantage of the new architecture.</p>
<h3 id="section-9">Backward Compatibility</h3>
<p>All reasonable steps have been taken to ensure existing functions and actions/filters will continue to work. However, use of deprecated functions, actions and filters will throw deprecated notices and they will be removed in future versions.</p>
<p>Furthermore, some hooks may have been called on events which no longer occur, like creating a pending subscription by adding an order added via the administration screen.</p>
<p>Finally, any code directly querying the database to read or write subscription data will no longer be compatible with the new schema in Subscriptions v2.0.</p>
<h2 id="section-10">Other Changes</h2>
<p>This guide focused on the introduction of the <code>'shop_subscription'</code> post type and corresponding <code>WC_Subscription</code> class; however, there are a number of other notable changes introduced in Subscriptions v2.0 that related to developers.</p>
<h3 id="renewal-order-relationship">Renewal Order Relationship Change</h3>
<p>In Subscriptions v1.5, a subscription was connected to a renewal order using the renewal order’s <code>post_parent</code> column in the <code>wp_posts</code> database table.</p>
<p>Specifically, the <code>post_parent</code> was set to the post ID of the original order used to purchase a subscription (because a subscription in v1.5 did not have its own unique ID).</p>
<p>In v2.0, this relation was changed to use post meta on the subscription post type. A <code>meta_key</code> of <code>_subscription_renewal</code> storing a <code>meta_value</code> of the subscription’s post ID is now used to record this relationship.</p>
<p>This change makes it possible to have a many-to-one relationship between subscriptions and renewal orders. That is to say, a single renewal order can now be related to multiple subscriptions; where as in v1.5, a renewal order could only relate to a single subscription. Although this new capability isn’t being used in any meaningful way with the release of Subscriptions v2.0, it was introduced with v2.0 because the relationship needed to change to use the ID of a <code>'shop_subscription'</code> post, not the ID of a <code>'shop_order'</code> post.</p>
<p>As the relationship needed to change in v2.0 anyway, this change was implemented now to avoid another breaking change in the future version.</p>
<p>The many-to-one relationship makes it possible to implement advanced features in the future, like batch processing renewal payments for different subscriptions in a single renewal order. Such a feature would save store owners fees as most payment gateways charge a per transaction fee, like Stripe’s $0.20.</p>
<h3 id="renewal-order-creation">Renewal Order Creation Before Scheduled Payment Hook</h3>
<p>Subscriptions v1.0 did not create renewal orders because PayPal recorded transaction details and sent store owners and customers the details of those transactions.</p>
<p>Accordingly, when renewal orders were introduced in version v1.2, renewal order creation was retrofitted to the existing codebase and therefore, renewal orders were only created after the payment gateway processed the payment.</p>
<p>This design had a few significant drawbacks:</p>
<ul>
<li>payment gateways had to use special Subscription API functions and write special code for handling subscription renewal payments, instead of being able to use code written for WooCommerce’s order object that worked for all payments.</li>
<li>when WooCommerce v2.2 introduced the <code>_transaction_id</code> API for storing a payment gateway’s transaction on a renewal order, it was not possible for payment gateways to add the transaction ID for renewal orders without hacks.</li>
<li>if an unrecoverable error occurred when attempting to process the renewal payment, the renewal order would not be created. This also meant sites which had an unrecoverable error needed to run special code in order to generate missing orders.</li>
</ul>
<p>Because of these drawbacks, Subscriptions v2.0 changes the flow of operations to create the renewal order <em>before</em> the scheduled subscription payment hook is triggered.</p>
<p>To learn how to take advantage of this change and use the renewal order for processing renewal payments with your payment gateway extension/s, refer to the <a href="https://help.codibu.com/blog/kb/subscriptions-v2-0-payment-gateway-upgrade-guide/">Payment Gateway Upgrade Guide</a>.</p>
<h3 id="changes-to-switching">Changes to the Switching Process</h3>
<p>If you are not familiar with the switching feature of Subscriptions (i.e. upgrading/downgrading), please read the <a href="https://help.codibu.com/blog/kb/subscription-switching-guide/">switching guide</a> before continuing with this section.</p>
<p>In Subscriptions v1.5, switching a subscription from one variation or grouped product to another always created a new subscription and set the status of the old subscription to <code>switched</code>. This was to ensure compatibility of the switching feature with PayPal, and to always avoid rewriting history and changing subscription related meta data on the original order used to purchase the subscription.</p>
<p>However, due to the architectural changes in v2.0, it is now possible to simply change the details of the subscription object without losing the history of the original order.</p>
<p>Furthermore, because a subscription may now contain multiple line items, switching needed to be updated to be <a href="https://help.codibu.com/blog/kb/whats-new-in-woocommerce-subscriptions-v2-0/#section-6">per item instead of per subscription</a>.</p>
<p>In v2.0, the switching process now:</p>
<ul>
<li>removes the line item from the old subscription</li>
<li>adds the new item as a line item on the existing <code>'shop_subscription'</code> if the new item is:
<ul>
<li>on the same billing schedule as the old subscription; or</li>
<li>on a different billing schedule, but is the only line item on the subscription, and therefore, the subscription’s billing schedule can be changed.</li>
</ul>
</li>
<li>creates a new <code>'shop_subscription'</code> if the new item is on a different billing schedule and the old subscription had more than one line item.</li>
</ul>
<p>The <a href="https://help.codibu.com/blog/kb/subscription-switching-guide/">switching process</a> still uses the cart and checkout as it did in v1.5. A switch order is also still created to record the switch. This order is created regardless of whether proration is enabled and a prorated amount is charged for the switch or the switching cost is $0. Multiple switches can also be completed at the same time, for multiple line items on the same or different subscriptions.</p>
<h3 id="pending-cancellation-status">New Pending Cancellation Status</h3>
<p>Subscriptions v2.0 introduced a new subscription status: <em>Pending Cancellation</em>.</p>
<p>This status, internally stored as <code>wc-pending-cancel</code> (because <code>wc-pending-cancellation</code> is too long for the <code>post_status</code> column), is given to a subscription to signify that the customer or store manager has cancelled the subscription, but the customer is still entitled to a pre-paid term.</p>
<p>The status will be applied to a subscription whenever a customer or store manage cancels a subscription. It is set automatically when calling <code>WC_Subscription::cancel_order()</code>, but not when calling <code>WC_Susbcription::update_status()</code> to provide an API for developers that immediately transitions a subscription to <em>cancelled</em> status (the <code>wc-cancelled</code> status).</p>
<p>Prior to Subscriptions v2.0, the pre-paid term was signified only by an action triggered at the end of the pre-paid term (<code>'subscription_end_of_prepaid_term'</code>). It was left up to developers to use it as required, and was not used internally by Subscriptions.</p>
<h4 id="example-of-pending-cancellation">Example of Pending Cancellation</h4>
<p>If a customer buys a monthly subscription on the 1st January, then cancels the subscription on 15th January, in Subscriptions v1.5, its status would immediately be changed to <code>wc-cancelled</code>, the customer’s role would immediately be transitioned to the default inactive role and then on the 1st February, a <code>'subscription_end_of_prepaid_term'</code> hook would be triggered.</p>
<p>With v2.0, that subscription’s status will be immediately set to <code>wc-pending-cancel</code> and the customer will keep the default active subscriber role. On the 1st February, the subscription’s status will be changed to <code>wc-cancelled</code> and the customer’s role will be changed to the inactive role. The end of prepaid term hook will also be triggered.</p>
<h3 id="section-15">Store Manager Guide to v2.0 Changes</h3>
<p>A summary of all the changes in relation to how a store manager interacts with the Subscriptions extension can also be found in the <a href="https://help.codibu.com/blog/kb/whats-new-in-woocommerce-subscriptions-v2-0/">Overview of Subscriptions v2.0’s Changes</a> document.</p><p>The post <a href="https://help.codibu.com/blog/subscriptions-v2-0-architectural-changes/">Subscriptions v2.0 Architectural Changes</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://help.codibu.com/blog/subscriptions-v2-0-architectural-changes/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Subscriptions Payment Gateway Integration Guide</title>
		<link>https://help.codibu.com/blog/subscriptions-payment-gateway-integration-guide/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=subscriptions-payment-gateway-integration-guide</link>
					<comments>https://help.codibu.com/blog/subscriptions-payment-gateway-integration-guide/#respond</comments>
		
		<dc:creator><![CDATA[JN C]]></dc:creator>
		<pubDate>Sun, 08 Nov 2020 15:10:57 +0000</pubDate>
				<guid isPermaLink="false">https://help.codibu.com/kb/subscriptions-payment-gateway-integration-guide/</guid>

					<description><![CDATA[<p>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.<span class="excerpt-hellip"> […]</span></p>
<p>The post <a href="https://help.codibu.com/blog/subscriptions-payment-gateway-integration-guide/">Subscriptions Payment Gateway Integration Guide</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>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.</p>
<p>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.</p>
<p>There are 6 steps to add subscription support:</p>
<ol>
<li>Register Support for Subscriptions</li>
<li>Process Subscription Sign-ups</li>
<li>Manage Subscriptions</li>
<li>Process Failed Payments</li>
<li>Handling Recurring Payment Method Changes</li>
<li>Show your Support</li>
</ol>
<p>Before continuing with this guide, you should have read the <a href="https://help.codibu.com/blog/kb/introduction-to-subscriptions-developer-documentation/">Subscriptions’ API overview</a>. You should also have an in-depth understanding of the <a href="https://help.codibu.com/blog/kb/payment-gateway-api/">WooCommerce Payment Gateway API</a>.</p>
<p class="woo-sc-box info">Need to upgrade a payment gateway for WooCommerce Subscriptions version 2.0? Check out the <a href="https://help.codibu.com/blog/kb/subscriptions-v2-0-payment-gateway-upgrade-guide/">Payment Gateway Upgrade Guide</a>.</p>
<h2 id="section-1">Step 1: Registering Support for Subscriptions</h2>
<p>When an order contains a subscription product, the Subscriptions extension filters the available payment gateways to display only those that support recurring payments.</p>
<p>Your gateway must register its support for subscriptions using the <code>WC_Payment_Gateway::supports()</code> API (in WooCommerce 1.5.7 and newer). This is done by setting the <code>supports</code> property of your gateway to an array containing <em>‘subscriptions’</em> (and <em>‘products’</em> if your gateway also supports product purchases).</p>
<p>For example, the <code>__construct()</code> function of your gateway class should include a line like the following:</p>
<pre class="wp-block-preformatted">class WC_Awesome_Gateway extends WC_Payment_Gateway {
     function __construct() {
          ...
          $this-&gt;supports = array( 'subscriptions', 'products' );
          ...
     }
     ...
}</pre>
<p>Now, whenever an order contains a subscription, if your gateway is enabled, it will be displayed as a payment option on the checkout page.</p>
<h3 id="register-support">Step 1.1: Registering Support for Subscription Management Features</h3>
<p>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.</p>
<p>There are a variety of functions Subscriptions will implement if a payment gateway support it, including:</p>
<ul>
<li>subscription cancellation</li>
<li>subscription suspension</li>
<li>subscription reactivation (after suspending a subscription)</li>
<li>subscription amount changes (recurring amount can be changed for the subscription)</li>
<li>subscription date changes (next payment date can be changed for the subscription)</li>
</ul>
<p>To register your gateways support for these features, you add the relevant flag to your gateway’s <code>supports</code> property.</p>
<p>This is an example for a gateway which supports all features:</p>
<pre class="wp-block-preformatted">class WC_Awesome_Gateway extends WC_Payment_Gateway {
     function __construct() {
          ...
          $this-&gt;supports = array( 
               'products', 
               'subscriptions',
               'subscription_cancellation', 
               'subscription_suspension', 
               'subscription_reactivation',
               'subscription_amount_changes',
               'subscription_date_changes',
               'subscription_payment_method_change'
               'subscription_payment_method_change_customer',
               'subscription_payment_method_change_admin',
               'multiple_subscriptions',
          );
          ...
     }</pre>
<p><strong>Note:</strong> 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 <em>all</em> of the available features, so add a flag for every feature.</p>
<h2 id="section-3">Step 2: Processing a Subscription Sign-Up</h2>
<p>Like processing payment for a product, your payment gateway extension will need to process a subscription sign-up in its <code>process_payment()</code> function.</p>
<p>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 at outset of the subscription.</p>
<p>To obtain these details for an order containing a subscription, use the <code>WC_Subscriptions_Order</code> class methods.</p>
<h3 id="section-4">Initial Payment</h3>
<p>If your payment gateway requires one upfront amount for the beginning of the subscription, you can use</p>
<pre class="wp-block-preformatted">&lt;?php WC_Subscriptions_Order::get_total_initial_payment( $order ) ?&gt;</pre>
<p>Where <code>$order</code> is a <code>WC_Order</code> object.</p>
<p>For more details, see the full <a href="https://help.codibu.com/blog/kb/subscription-order-cart-function-reference/#section-9">total initial payment function reference</a>.</p>
<h3 id="section-5">Price Per Period</h3>
<p>If your payment gateway needs to know when creating the subscription how much to charge for each individual billing period, you can use:</p>
<pre class="wp-block-preformatted">&lt;?php WC_Subscriptions_Order::get_price_per_period( $order ) ?&gt;</pre>
<p>For more details, see the full <a href="https://help.codibu.com/blog/kb/subscription-order-cart-function-reference/#section-13">price per period function reference</a>.</p>
<h3 id="section-6">Sign-up Fee</h3>
<p>If your payment gateway needs a distinct sign-up fee amount for an order, you can call:</p>
<pre class="wp-block-preformatted">&lt;?php WC_Subscriptions_Order::get_sign_up_fee( $order ) ?&gt;</pre>
<p>For more details, see the full <a href="https://help.codibu.com/blog/kb/subscription-order-cart-function-reference/#section-17">sign up fee function reference</a>.</p>
<h3 id="section-7">Billing Period</h3>
<p>To get the subscription period for an order, call:</p>
<pre class="wp-block-preformatted">&lt;?php WC_Subscriptions_Order::get_subscription_period( $order ) ?&gt;</pre>
<p>For more details, see the full <a href="https://help.codibu.com/blog/kb/subscription-order-cart-function-reference/#section-21">subscriptions period function reference</a>.</p>
<h3 id="section-8">Free Trial Period</h3>
<p>Payment gateways typically require one of two methods for setting up a free trial</p>
<ol>
<li>Passing the free trial period and length, like PayPal; or</li>
<li>Setting the start date for the subscription, like WorldPay.</li>
</ol>
<p>To use the first method, get a subscriptions trial details with the following two functions:</p>
<pre class="wp-block-preformatted">&lt;?php 

WC_Subscriptions_Order::get_subscription_trial_period( $order );

WC_Subscriptions_Order::get_subscription_trial_length( $order );

?&gt;</pre>
<p>To use the second method and set the start date, use the following function:</p>
<pre class="wp-block-preformatted">&lt;?php WC_Subscriptions_Product::get_trial_expiration_date( $product_id, $order_start_date ); ?&gt;</pre>
<h3 id="section-9">Subscription Length</h3>
<p>To get the subscription duration for an order, call:</p>
<pre class="wp-block-preformatted">&lt;?php WC_Subscriptions_Order::get_subscription_length( $order ) ?&gt;</pre>
<p>For more details, see the full <a href="https://help.codibu.com/blog/kb/subscription-order-cart-function-reference/#section-29">subscriptions length function reference</a>.</p>
<h3 id="section-10">Putting it all Together</h3>
<p>The <code>paypal_standard_subscription_args()</code> function within the bundled <code>WC_PayPal_Standard_Subscriptions</code> class provides an example of accessing subscription details and combining them into a payment gateway request.</p>
<pre class="wp-block-preformatted">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-&amp;gt;get_items();

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

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

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

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

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

    } else {

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

    }

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

    $converted_periods = array();

    // Convert period strings into PayPay's format
    foreach ( $unconverted_periods as $key =&amp;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 &amp;gt; 0 ) { // Specify a free trial period

        $paypal_args['a1'] = ( $sign_up_fee &amp;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 &amp;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 &amp;gt; 0 &amp;amp;&amp;amp; $subscription_trial_length == 0 &amp;amp;&amp;amp; $subscription_installments == 2 ) ) {

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

    } else {

        $paypal_args['src'] = 1;

        if ( $subscription_installments &amp;gt; 0 ) {
            if ( $sign_up_fee &amp;gt; 0 &amp;amp;&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;amp; instructions display
    $paypal_args['rm'] = 2;

}</pre>
<h2 id="section-11">Step 3: Subscription Management</h2>
<p>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.</p>
<h3 id="section-12">Order Status &amp; Subscription Status Binding</h3>
<p>Subscription status is bound to order status changes, so using the <a href="https://help.codibu.com/blog/kb/subscriptions-payment-gateway-integration-guide/">WooCommerce Payment Gateway API</a> 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.</p>
<p>As a result, the bare minimum required to manage a subscription is to use the <a href="https://help.codibu.com/blog/kb/subscriptions-payment-gateway-integration-guide/">WooCommerce Payment Gateway API</a> to manage an order’s status.</p>
<h3 id="section-13">Subscription Payment &amp; Status Management</h3>
<p>There is no automatic handling of subscription payments. Either your gateway or your extension will need to handle these payments.</p>
<h4 id="gateway-scheduled-payments">Recurring Payments Processed by the Gateway</h4>
<p>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 <code>WC_Subscriptions_Manager::process_subscription_payments_on_order()</code> function to keep a record of each payment once the gateway has processed the payment.</p>
<p>If your payment gateway also manages the billing schedule, then you should also add the <code>'gateway_scheduled_payments'</code> flag when <a href="https://help.codibu.com/blog/kb/subscriptions-payment-gateway-integration-guide/#section-1">registering support for Subscriptions</a>. Not adding this flag can cause subscriptions to be incorrectly suspended when the gateway’s schedule does not precede the WooCommerce schedule.</p>
<h4 id="recurring-payments-by-extension">Recurring Payments Processed by your Extension</h4>
<p>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.</p>
<p>In fact, this is how the <a href="https://help.codibu.com/blog/kb/stripe/">Stripe extension</a> adds support for Subscriptions, even though the Stripe payment gateway can automatically charge recurring payments.</p>
<p>For each subscription, a <code>'scheduled_subscription_payment_{payment_gateway_id}'</code> 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.</p>
<p>For example, the Stripe extension hooks its <code>`scheduled_subscription_payment()`</code> function to the <code>'scheduled_subscription_payment_stripe'</code> hook to process the payment for each billing period:</p>
<pre class="wp-block-preformatted">function scheduled_subscription_payment( $amount_to_charge, $order, $product_id ) {

    $result = $this-&gt;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 );
    }
}</pre>
<p>This is also a good example of using the API functions for success and failure of the payment. More on failed payments later.</p>
<p>Charging payments on the <code>'scheduled_subscription_payment_{payment_gateway_id}'</code> 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:</p>
<ol>
<li>You need less code to support subscription payments &amp; 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 <code>'scheduled_subscription_payment_{payment_gateway_id}'</code>;</li>
<li>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;</li>
<li>You can support <em>all</em> Subscription features, regardless of those which your payment gateway’s recurring billing solution provides.</li>
</ol>
<h4 id="activating-a-subscription">Activating a Subscription</h4>
<pre class="wp-block-preformatted">&lt;?php WC_Subscriptions_Manager::activate_subscriptions_for_order( $order ) ?&amp;gt;</pre>
<p>As the name suggests, the <code>activate_subscriptions_for_order()</code> can be used to mark all subscriptions in an order as <em>active</em>.</p>
<p>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.</p>
<p>The function calls the <code>WC_Subscriptions_Manager::activate_subscription()</code> 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.</p>
<h4 id="cancelling-a-subscription">Cancelling a Subscription</h4>
<p>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.</p>
<p>Just like registering support for subscriptions, you notify Subscriptions of your cancellation capability by including a flag in your extensions <code>supports()</code> property.</p>
<p>For example, the following is an excerpt from the PayPal Digital Goods gateway extension:</p>
<pre class="wp-block-preformatted">function __construct() { 

    // ...

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

    // ...
}</pre>
<p>Note the <code>'subscription_cancellation'</code> flag.</p>
<p>Including this flag will expose the <em>Cancel</em> 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.</p>
<p>Subscriptions fires a <code>'cancelled_subscription'</code> hook whenever a subscription is cancelled, but a better action to hook to use is <code>'woocommerce_subscription_cancelled_{payment_gateway_id}'</code>. This hook passes the <code>WC_Order</code> object and <code>WC_Product</code> ID of the subscription product being cancelled. You can use these details to cancel the subscription with your gateway.</p>
<h6 id="cancellations-at-the-gateway">Cancellations at the Gateway</h6>
<p>If a user can cancel a subscription with the payment gateway directly, you <strong>must</strong> reflect this change in the WooCommerce store by calling:</p>
<pre class="wp-block-preformatted">&lt;?php WC_Subscriptions_Manager::cancel_subscriptions_for_order( $order ) ?&gt;</pre>
<p>This function is automatically called when an order’s status is changed to cancelled, failed or refunded order.</p>
<p>Internally, the function acts as a convenience wrapper function for the <code>WC_Subscriptions_Manager::cancel_subscription()</code> function. If you need to cancel just one subscription and no its associated order, you can call the cancel subscription function directly:</p>
<pre class="wp-block-preformatted">&lt;?php WC_Subscriptions_Order::cancel_subscription( $user_id, $subscription_key ) ?&gt;</pre>
<p>Where <code>$subscription_key</code> 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.</p>
<h4 id="expiring-a-subscription">Expiring a Subscription</h4>
<p>The Subscriptions extension schedules a action to set a subscription’s status to <em>expired</em> when its expiration date arrives, so you do not need to manually expire a subscription.</p>
<p>However, if your payment gateway provides an expiration notification and you like the security of redundancy, you can call the <code>WC_Subscriptions_Manager</code> function directly.</p>
<p>To expire any subscriptions on an order call:</p>
<pre class="wp-block-preformatted">&lt;?php WC_Subscriptions_Manager::expire_subscriptions_for_order( $order ) ?&gt;</pre>
<p>To expire an individual subscription call:</p>
<pre class="wp-block-preformatted">&lt;?php WC_Subscriptions_Manager::expire_subscription( $user_id, $subscription_key ) ?&gt;</pre>
<p>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.</p>
<h4 id="recording-payments">Recording Payments</h4>
<p>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 <code>process_subscription_payments_on_order()</code> function to keep a record of the payment on the order.</p>
<pre class="wp-block-preformatted">&lt;?php WC_Subscriptions_Manager::process_subscription_payments_on_order( $order ) ?&gt;</pre>
<h4 id="recording-failed-payments">Recording Failed Payments</h4>
<p>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 <code>process_subscription_payment_failure_on_order()</code> function to keep a record of the payment on the order.</p>
<pre class="wp-block-preformatted">&lt;?php WC_Subscriptions_Manager::process_subscription_payment_failure_on_order( $order ) ?&gt;</pre>
<h3 id="section-14">Order API vs Individual Subscription API</h3>
<p>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.</p>
<h3 id="section-15">Subscription Management Example</h3>
<p>The <code>process_paypal_ipn_request()</code> function in the bundled <code>WC_PayPal_Standard_Subscriptions</code> class provides an example of manually, and redundantly calling subscription management functions.</p>
<p>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.</p>
<pre class="wp-block-preformatted">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-&gt;add_order_note( __( 'IPN subscription sign up completed.', WC_Subscriptions::$text_domain ) );

        if ( self::$debug )
            self::$log-&gt;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-&gt;add_order_note( __( 'IPN subscription payment completed.', WC_Subscriptions::$text_domain ) );

            if ( self::$debug ) 
                self::$log-&gt;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-&gt;id, $subscription_item['id'] );
            $subscription           = WC_Subscriptions_Manager::get_subscription( $subscription_key, $order-&gt;customer_user );

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

                $order-&gt;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-&gt;add_order_note( __( 'IPN subscription payment failed.', WC_Subscriptions::$text_domain ) );

            if ( self::$debug ) 
                self::$log-&gt;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-&gt;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-&gt;add( 'paypal', 'IPN subscription cancelled for order ' . $order_id );

        // Subscription Payment completed
        $order-&gt;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-&gt;add( 'paypal', 'IPN subscription end-of-term for order ' . $order_id );

            // Record subscription ended
            $order-&gt;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() &lt; strtotime( WC_Subscriptions_Manager::get_subscription_expiration_date( WC_Subscriptions_Manager::get_subscription_key( $order-&gt;id ), $order-&gt;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-&gt;add( 'paypal', 'IPN subscription sign up failure for order ' . $order_id );

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

        WC_Subscriptions_Manager::failed_subscription_sign_ups_for_order( $order );

        break;
}</pre>
<h2 id="section-16">Step 4: Failed Payments</h2>
<p>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.</p>
<p>There are two functions available:</p>
<pre class="wp-block-preformatted">&lt;?php WC_Subscriptions_Manager::process_subscription_payment_failure_on_order( $order, $product_id ) ?&gt;</pre>
<p class="woo-sc-box note">You should pass the order ID or order object for the order used to purchase the subscription (the <em>parent</em> 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 <em>“failed”</em> status.</p>
<pre class="wp-block-preformatted">&lt;?php WC_Subscriptions_Manager::process_subscription_payment_failure( $user_id, $subscription_key ) ?&gt;</pre>
<p>The first of these is a convenience wrapper for the second, so you can use either, but not both.</p>
<p>When Subscriptions processes a failed payment, it will put the subscription <strong>on-hold</strong> 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.</p>
<p class="woo-sc-box note">If your gateway does not manage failed payments for you, you <strong>must</strong> use one of the failed payments API functions.</p>
<h2 id="section-17">Step 5: Recurring Payment Method Changes</h2>
<p>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.</p>
<p>To handle recurring payment method changes, your gateway needs to:</p>
<ol>
<li>add <code>'subscription_payment_method_change'</code> supports flag so that your gateway is presented as a payment option when the customer is changing the payment; and</li>
<li>hook to the <code>'woocommerce_subscription_failing_payment_method_updated</code>_<code>{your-gateway}'</code> action to update the payment method when a customer is making a payment in lieu of an automatic renewal payment that previously failed.</li>
</ol>
<h3 id="section-18">5.1: Supporting Subscriber Payment Method Changes</h3>
<p>To support <a href="https://help.codibu.com/blog/kb/subscribers-view/#section-11">customer initiated payment method changes</a>, 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.</p>
<p>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).</p>
<p>In these cases, you can check if the <code>$order_id</code> parameter passed to your gateway’s <code>process_payment()</code> method is for a subscription using <code>wcs_is_subscription( $order_id )</code>. When your gateway’s <code>process_payment()</code> is called with the ID of a subscription, it means the request is to change the payment method on the subscription.</p>
<h3 id="section-19">5.2: Updating the Payment Method After a Failure</h3>
<p>If a subscriber’s automatic payment <a href="https://help.codibu.com/blog/kb/subscription-renewal-process/#section-6">fails</a>, her subscription will be put on-hold until she logs in to complete the payment.</p>
<p>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. <strong>You must update the meta data on the <code>'woocommerce_subscriptions_changed_failing_payment_method_{your-gateway}'</code> hook</strong>, if you do not, all future automatic payment will continue to fail.</p>
<p>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.</p>
<p>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.</p>
<pre class="wp-block-preformatted">/**
 * 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-&gt;id, '_your_gateway_customer_token_id', get_post_meta( $new_renewal_order-&gt;id, '_your_gateway_customer_token_id', true ) );
}
add_action( 'woocommerce_subscriptions_changed_failing_payment_method_your_gateway', 'yg_failing_payment_method', 10, 2 );
</pre>
<p class="woo-sc-box note">For payment gateway changes to work, your extension will also need to be able to process subscription orders with a $0 initial total</p>
<h2 id="section-20">Step 6: Testing Renewal Payments</h2>
<p>Once you have everything set-up and working, you may want to test recurring payments.</p>
<p>To test recurring payments, following the instructions in the guide to <a href="https://help.codibu.com/blog/kb/testing-subscription-renewal-payments/">triggering subscription renewals</a>.</p>
<p>You can also use this method to test <em>payment failures</em>. To do so, delete or modify the meta data that is used to process the payment. For example, you could change the <code>'_stripe_customer_id'</code> for Stripe or <code>'_wc_authorize_net_cim_payment_profile_id'</code> for <a href="https://help.codibu.com/blog/kb/authorize-net/">Authorize.net CIM</a> as stored in the post meta table against the <strong>original subscription order</strong>. When the payment is processed, it will fail as the customer token is invalid, and therefore, trigger the failed renewal process.</p>
<h2 id="section-21">FAQs</h2>
<h3 id="error-log">How can I debug issues with renewal orders?</h3>
<p>If a renewal payment is being processed correctly at the payment gateway but the renewal order’s status is not being set to <strong>processing</strong> or <strong>completed</strong>, 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.</p>
<p>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:</p>
<ol>
<li>Create a file name <code>phpinfo.php</code> in the root of your WordPress’s directory</li>
<li>Open the <code>phpinfo.php</code> file in a text editor</li>
<li>Insert the following code into the file: <code>&lt;?php phpinfo(); ?&gt;</code></li>
<li>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</li>
<li>Search the page for the <code>error_log</code> 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.</li>
</ol>
<p>Once you have a copy of your PHP error log, you can look for entries beginning <code>PHP Fatal error</code> around the time of renewal. The error listed here will indicate the cause of the issue.</p>
<div class="wp-block-image">
<figure class="aligncenter"><a href="https://help.codibu.com/wp-content/uploads/2012/06/example-php-info-output.png"><img loading="lazy" decoding="async" class="wp-image-153224" src="https://help.codibu.com/wp-content/uploads/2012/06/example-php-info-output.png" sizes="(max-width: 755px) 100vw, 755px" srcset="https://help.codibu.com/wp-content/uploads/2012/06/example-php-info-output.png 755w, https://help.codibu.com/wp-content/uploads/2012/06/example-php-info-output.png?resize=534,550 534w" alt="Example PHP Info Output" width="755" height="778" /></a><figcaption>Example PHP Info Output</figcaption></figure>
</div><p>The post <a href="https://help.codibu.com/blog/subscriptions-payment-gateway-integration-guide/">Subscriptions Payment Gateway Integration Guide</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://help.codibu.com/blog/subscriptions-payment-gateway-integration-guide/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Developer Guide to Cart and Recurring Cart Fees</title>
		<link>https://help.codibu.com/blog/developer-guide-to-cart-and-recurring-cart-fees/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=developer-guide-to-cart-and-recurring-cart-fees</link>
					<comments>https://help.codibu.com/blog/developer-guide-to-cart-and-recurring-cart-fees/#respond</comments>
		
		<dc:creator><![CDATA[JN C]]></dc:creator>
		<pubDate>Sun, 08 Nov 2020 15:09:58 +0000</pubDate>
				<guid isPermaLink="false">https://help.codibu.com/kb/developer-guide-to-cart-and-recurring-cart-fees/</guid>

					<description><![CDATA[<p>This page is written for WooCommerce developers who want to extend or integrate with the WooCommerce Subscriptions plugin. You need an advanced understanding of PHP and WordPress development. The<span class="excerpt-hellip"> […]</span></p>
<p>The post <a href="https://help.codibu.com/blog/developer-guide-to-cart-and-recurring-cart-fees/">Developer Guide to Cart and Recurring Cart Fees</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></description>
										<content:encoded><![CDATA[<div class="woo-sc-box note   ">This page is written for WooCommerce developers who want to extend or integrate with the WooCommerce Subscriptions plugin. You need an advanced understanding of PHP and WordPress development.</div>
<p>The following guide is written for developers who want to add fees to the WooCommerce cart and would like to learn about how these fees interact with <a href="https://help.codibu.com/blog/kb/woocommerce-subscriptions/">WooCommerce Subscriptions</a>.</p>
<p>The guide explains how fees added to the cart act by default, and how they can be customized to be applied only to the initial order or recurring orders.</p>
<h2 id="section-1">Understanding Cart Fees</h2>
<p>Unlike cart items, cart fees are non-persistent, this means third-party plugins which add fees need to be constantly hooked into the cart calculations, adding their fees each time the cart totals are calculated.</p>
<p>To do that WooCommerce provides the <code>woocommerce_cart_calculate_fees</code> hook, which third parties can use to add their fees.</p>
<p>For example, to add a $10 engraving fee to the cart, you could use the following code snippet:</p>
<pre>add_filter( 'woocommerce_cart_calculate_fees', 'add_engraving_fees', 10, 1 );

function add_engraving_fees( $cart ) {
     $cart-&gt;add_fee( 'Engraving', '10' );
}
</pre>
<p>This fee will appear like this in the cart totals section of the cart page:</p>
<figure id="attachment_786850" class="wp-caption aligncenter" aria-describedby="caption-attachment-786850"><a href="https://help.codibu.com/wp-content/uploads/2020/11/cart-fees.png"><img loading="lazy" decoding="async" class="size-full wp-image-786850" src="https://help.codibu.com/wp-content/uploads/2020/11/cart-fees.png" alt="Cart fees displayed on Cart Page" width="473" height="285" /></a><figcaption id="caption-attachment-786850" class="wp-caption-text">Cart fees displayed in Edit Totals section of the Cart Page</figcaption></figure>
<p>WooCommerce Subscriptions’ recurring carts use the same cart calculations and so the code snippet above also applies the fees to any recurring cart. That is to say, <strong>by default, any cart fees added using the <code>woocommerce_cart_calculate_fees</code> hook will be applied both to both the initial order, and any subscriptions created for that transaction</strong>. Those fees are then also applied to subsequent transactions.</p>
<p>So for example, using the same code snippet above, customers purchasing subscriptions would see the following cart totals:</p>
<figure id="attachment_786853" class="wp-caption aligncenter" aria-describedby="caption-attachment-786853"><a href="https://help.codibu.com/wp-content/uploads/2020/11/recurring-cart-fees.png"><img loading="lazy" decoding="async" class="size-full wp-image-786853" src="https://help.codibu.com/wp-content/uploads/2020/11/recurring-cart-fees.png" alt="Recurring Cart Fee shown in Recurring Totals Section of Cart Page" width="483" height="514" /></a><figcaption id="caption-attachment-786853" class="wp-caption-text">Recurring Cart Fee shown in Recurring Totals Section of Cart Page</figcaption></figure>
<h2 id="section-2">Recurring Only Fees</h2>
<p>As mentioned above, by default, fees applied to the cart are applied to be the initial and recurring carts.</p>
<p>In some cases, you may <strong>require the fee to only apply to the on-going recurring payment for any subscriptions</strong>. To achieve this, you need to tweak the code snippet from above and add a condition to check whether the cart being passed into your callback is a recurring cart, or the standard WooCommerce cart.</p>
<p>Recurring carts have a <code>$recurring_cart_key</code> property we can use to determine if the cart is a recurring cart.</p>
<p>For example:</p>
<pre>add_filter( 'woocommerce_cart_calculate_fees', 'add_recurring_postage_fees', 10, 1 );

function add_recurring_postage_fees( $cart ) {
    if ( ! empty( $cart-&gt;recurring_cart_key ) ) {
        $cart-&gt;add_fee( 'Postage', 5 );
    }
}
</pre>
<figure id="attachment_786860" class="wp-caption aligncenter" aria-describedby="caption-attachment-786860"><a href="https://help.codibu.com/wp-content/uploads/2020/11/recurring-only-cart-fees.png"><img loading="lazy" decoding="async" class="size-full wp-image-786860" src="https://help.codibu.com/wp-content/uploads/2020/11/recurring-only-cart-fees.png" alt="Recurring Only Cart Fees displayed in Cart Totals" width="475" height="461" /></a><figcaption id="caption-attachment-786860" class="wp-caption-text">Recurring Only Cart Fees displayed in Cart Totals</figcaption></figure>
<div class="woo-sc-box note   ">There was a bug with WooCommerce 3.2 where fees were displayed on the initial cart totals even though they were not included in the order’s total. This issue has been fixed with WooCommerce 3.3 via this pull request. If you encounter this issue, please update WooCommerce to the latest version.</div>
<h2 id="section-3">Initial Order Only Fees</h2>
<p>For situations where fees should only be charged on the initial order when the customer signs up, there are two options to add a fee only to the initial order:</p>
<p><strong>Option 1: Invert check for recurring cart<br />
</strong></p>
<pre>add_filter( 'woocommerce_cart_calculate_fees', 'add_administration_fees', 10, 1 );

function add_administration_fees( $cart ) {
    if ( empty( $cart-&gt;recurring_cart_key ) ) {
        $cart-&gt;add_fee( 'Processing Fee', 2.5 );
    }
}
</pre>
<p>In this example, we’ve reversed the logic from the previous code example. This code now only applies fees to the initial purchase cart.</p>
<p><strong>Option 2: Apply fee to global cart<br />
</strong></p>
<pre>add_filter( 'woocommerce_cart_calculate_fees', 'add_administration_fees', 10 );

function add_administration_fees() {
    WC()-&gt;cart-&gt;add_fee( 'Processing Fee', 2.5 );
}
</pre>
<p>In this example, we’re applying the fee to the global WooCommerce cart object. This is the default cart object and so only applies fees to the initial purchase.</p>
<figure id="attachment_786867" class="wp-caption aligncenter" aria-describedby="caption-attachment-786867"><a href="https://help.codibu.com/wp-content/uploads/2020/11/fees-for-initial-order-only.png"><img loading="lazy" decoding="async" class="size-full wp-image-786867" src="https://help.codibu.com/wp-content/uploads/2020/11/fees-for-initial-order-only.png" alt="Fee for Initial Order Only Displayed in Cart Totals" width="477" height="385" /></a><figcaption id="caption-attachment-786867" class="wp-caption-text">Fee for Initial Order Only Displayed in Cart Totals</figcaption></figure>
<div class="woo-sc-box note   ">There was a bug with WooCommerce 3.2 where fees were the fee was applied to both the initial purchase’s order and recurring orders. This issue has been fixed with WooCommerce 3.3 via this pull request. If you encounter this issue, please update WooCommerce to the latest version.</div>
<h2 id="section-4">The Recurring Fee Filter</h2>
<p>WooCommerce Subscriptions 2.2.16 introduced the <code>woocommerce_subscriptions_is_recurring_fee</code> filter. This filter allows developers who have applied fees to the initial cart to also apply the fees to the recurring cart.</p>
<p>For example, if the cart fees have been applied using <code>WC()-&gt;cart-&gt;add_fee( 'Fee', '10' );</code>, you could use this filter to also apply it to all recurring carts.</p>
<p>By default, the return value of this filter is set to <code>false</code>, so that all fees applied using <code>WC()-&gt;cart</code> are non-recurring fees by default.</p>
<pre>// All fees are recurring
add_filter( 'woocommerce_subscriptions_is_recurring_fee', '__return_true' );

add_filter( 'woocommerce_cart_calculate_fees', 'add_fees', 10 );

function add_fees() {
    WC()-&gt;cart-&gt;add_fee( 'Fee', '10' );
}
</pre>
<p>If only certain fees are recurring or you would like to apply more complex conditions, you can use the <code>$fee</code> and <code>$cart</code> filter arguments.<br />
For example:</p>
<pre>add_filter( 'woocommerce_cart_calculate_fees', 'add_fees', 10 );

function add_fees() {
    WC()-&gt;cart-&gt;add_fee( 'Personal Engraving', 10 );
    WC()-&gt;cart-&gt;add_fee( 'Postage', 5 );
}

add_filter( 'woocommerce_subscriptions_is_recurring_fee', 'apply_recurring_fees', 10, 3 );

function apply_recurring_fees( $is_recurring, $fee, $cart ) {

    // Personal engraving fees should be charged on a recurring basis
    if ( 'personal-engraving' === $fee-&gt;id ) {
        $is_recurring = true;
    // Subscriptions with a recurring total less than $30 are required to pay on-going postage charges
    } elseif ( 'postage' === $fee-&gt;id &amp;&amp; 30 &gt; $cart-&gt;get_subtotal() ) {
        $is_recurring = true;
    }

    return $is_recurring;
}</pre><p>The post <a href="https://help.codibu.com/blog/developer-guide-to-cart-and-recurring-cart-fees/">Developer Guide to Cart and Recurring Cart Fees</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://help.codibu.com/blog/developer-guide-to-cart-and-recurring-cart-fees/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Developer Guide to Failed Recurring Payment Retry System</title>
		<link>https://help.codibu.com/blog/developer-guide-to-failed-recurring-payment-retry-system/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=developer-guide-to-failed-recurring-payment-retry-system</link>
					<comments>https://help.codibu.com/blog/developer-guide-to-failed-recurring-payment-retry-system/#respond</comments>
		
		<dc:creator><![CDATA[JN C]]></dc:creator>
		<pubDate>Sun, 08 Nov 2020 15:09:22 +0000</pubDate>
				<guid isPermaLink="false">https://help.codibu.com/kb/developer-guide-to-failed-recurring-payment-retry-system/</guid>

					<description><![CDATA[<p>This page is written for WooCommerce developers who want to extend or integrate with the WooCommerce Subscriptions plugin. You need an advanced understanding of PHP and WordPress development. WooCommerce Subscriptions is<span class="excerpt-hellip"> […]</span></p>
<p>The post <a href="https://help.codibu.com/blog/developer-guide-to-failed-recurring-payment-retry-system/">Developer Guide to Failed Recurring Payment Retry System</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></description>
										<content:encoded><![CDATA[<div class="woo-sc-box note   ">This page is written for WooCommerce developers who want to extend or integrate with the WooCommerce Subscriptions plugin. You need an advanced understanding of PHP and WordPress development.</div>
<p><a href="https://help.codibu.com/blog/kb/woocommerce-subscriptions/">WooCommerce Subscriptions</a> is a premium plugin, and <a href="https://help.codibu.com/blog/kb/whats-new-in-subscriptions-2-1/">version 2.1</a> introduced a new system to automatically retry a recurring payment that previously failed.</p>
<p>This guide provides a technical overview of the Failed Recurring Payment Retry system and is intended for developers looking to customize or otherwise interact with the retry system.</p>
<p>We recommend reading the <a href="https://help.codibu.com/blog/kb/developer-guide-to-failed-recurring-payment-retry-system/">Store Owner Guide to the Failed Payment Retry System</a> for a non-technical introduction to the retry system.</p>
<div class="woo-sc-box note   "><b>Note:</b> This is a <b>Developer level</b> doc. If you are unfamiliar with code/templates and resolving potential conflicts, select a <a href="https://woocommerce.com/customizations/"><span class="s2">WooExpert or Developer</span></a> for assistance. We are unable to provide support for customizations under our <a href="https://help.codibu.com/blog/kb/support-policy/"><span class="s2">Support Policy</span></a>.</div>
<p>&nbsp;</p>
<h2 id="section-1">Retry System Components</h2>
<p>The retry system is made up of a number of different components, each of which implements a distinct aspect of the retry system. These components are:</p>
<ul>
<li><strong><code>WCS_Retry_Manager</code></strong>: Manages the entire retry system, from loading all components to hooking into the normal failed payment flow, checking retry rules and applying a rule to retry a failed renewal payment when required.</li>
<li><strong><code>WCS_Retry_Rules</code></strong>: Sets up the default store-wide rules for retrying failed automatic renewal payments and provides methods for working with rules, like <code>get_rule()</code>.</li>
<li><strong><code>WCS_Retry_Rule</code></strong>: Represents instance of a retry rule and provides methods for retrieving and checking a rule’s properties. Used by <code>WCS_Retry_Rules-&gt;get_rule()</code> and <code>WCS_Retry-&gt;get_rule()</code>.</li>
<li><strong><code>WCS_Retry</code></strong>: Represents instance of a retry and provides methods for retrieving and checking properties on a retry, like <code>get_order_id()</code> and <code>get_rule()</code>.</li>
<li><strong><code>WCS_Retry_Store</code></strong>: Provides an extensible interface to store a retry in the database.</li>
<li><strong><code>WCS_Retry_Post_Store</code></strong>: Implements <code>WCS_Retry_Store</code> to store retry details in the WordPress posts table as a <a href="https://codex.wordpress.org/Post_Types">custom post type</a>.</li>
<li><strong><code>WCS_Retry_Database_Store</code></strong>: Implements <code>WCS_Retry_Store</code> to store retry details in the <code>wcs_payment_retries</code> custom table.</li>
<li><strong><code>WCS_Retry_Hybrid_Store</code></strong>: The hybrid store acts as a bridge between the two default stores and migrates retries from the post store to the database store when a retry is retrieved from the post store. This store also extends <code>WCS_Retry_Store</code>.</li>
<li><code><strong>WCS_Retry_Background_Migrator</strong></code>: This class extends the <code>WCS_Background_Upgrader</code> class and handles the migration of retries using <code>WCS_Retry_Migrator</code> in the background via an Action Scheduler action.</li>
<li><code><strong>WCS_Retry_Migrator</strong></code>: The retry migrator contains the logic behind the migration from the post store to the database store.</li>
<li><strong><code>WCS_Retry_Stores</code></strong>: Managers the two default store interfaces and contains functions to access them. Use <code>WCS_Retry_Stores::get_post_store()</code> or <code>WCS_Retry_Stores::get_database_store()</code> to get the stores.</li>
<li><strong><code>WCS_Retry_Email</code></strong>: Manages emails sent as part of the retry process by registering the custom retry email classes and sending emails on relevant hooks.</li>
<li><strong><code>WCS_Email_Payment_Retry</code></strong>: Controls the email template sent to store owners when an attempt to automatically process a subscription renewal payment has failed and a retry rule has been applied to retry payment in the future.</li>
<li><strong><code>WCS_Email_Customer_Payment_Retry</code></strong>: Controls the email template sent to the customer/subscriber when an attempt to automatically process a recurring payment has failed and a retry rule has been applied to retry payment in the future.</li>
<li><strong><code>WCS_Retry_Admin</code></strong>: Sets up the administration UI elements, including the <a href="https://help.codibu.com/blog/kb/developer-guide-to-failed-recurring-payment-retry-system/#42-automatic-failed-payment-retries-metabox"><strong>Automatic Failed Payment Retries</strong> meta box</a> and the setting to <a href="https://help.codibu.com/blog/kb/developer-guide-to-failed-recurring-payment-retry-system/#section-1">Enable the Retry System</a>.</li>
<li><code><strong>WCS_Retry_Table_Maker</strong></code>: Extends <code>WCS_Table_Maker</code>, and defines the version and table definition for the retries custom database.</li>
</ul>
<h2 id="section-2">Retry Process Flow</h2>
<p>The Store Owner Guide provides a non-technical overview of the <a href="https://help.codibu.com/blog/kb/developer-guide-to-failed-recurring-payment-retry-system//#section-2">Retry Process</a>. This section provides a technical guide, including details of hooks involved in the process.</p>
<p>How general retry process proceeds:</p>
<ol>
<li>The <code>'woocommerce_subscription_renewal_payment_failed'</code> action is triggered in <code>WC_Subscription-&gt;payment_failed()</code> after a renewal payment fails.</li>
<li>The <code>WCS_Renewal_Retry_Manager::maybe_apply_retry_rule()</code> is called, as it’s attached to <code>'woocommerce_subscription_renewal_payment_failed'</code>.</li>
<li><code>WCS_Renewal_Retry_Manager::maybe_apply_retry_rule()</code> checks:
<ul>
<li>the subscription is manual: <code>$subscription-&gt;is_manual()</code>.</li>
<li>automatic retry is possible with the payment method on the subscription: <code>$subscription-&gt;payment_method_supports( 'gateway_scheduled_payments' )</code>.</li>
<li>the last order is a renewal: <code>wcs_order_contains_renewal( $last_order )</code>.</li>
<li>there is a retry rule for this stage of the retry process and for this specific order: <code>WCS_Renewal_Retry_Manager::rules()-&gt;has_rule( WCS_Renewal_Retry_Manager::store()-&gt;get_retry_count_for_order( $renewal_order-&gt;id ), $renewal_order-&gt;id )</code>.</li>
</ul>
</li>
<li>If all these conditions pass:
<ul>
<li>A new pending retry is saved to correspond to the rule: <code>WCS_Renewal_Retry_Manager::store()-&gt;save( $retry )</code>.</li>
<li>Status of the renewal order is updated according to the rule: <code>$order-&gt;update_status( $new_status )</code>.</li>
<li>Status of the subscription is updated according to the rule: <code>$subscription-&gt;update_status( $new_status )</code>.</li>
<li>Interval time defined by the retry rule is then used to set the retry date/time on the subscription: <code>$subscription-&gt;update_dates( array( 'payment_retry' =&gt; gmdate( 'Y-m-d H:i:s', gmdate( 'U' ) + $retry_rule-&gt;get_retry_interval( $retry_count ) ) ) )</code>.</li>
</ul>
</li>
<li>When the scheduled time for the retry event arrives, the <code>'woocommerce_scheduled_subscription_payment_retry'</code> action will be triggered passing callbacks the ID of the renewal order to which the retry relates.</li>
<li>The <code>WCS_Renewal_Retry_Manager::maybe_retry_payment()</code> is called, as it is hooked to <code>'woocommerce_scheduled_subscription_payment_retry'</code>.</li>
<li><code>WCS_Renewal_Retry_Manager::maybe_retry_payment()</code> checks:
<ul>
<li>Retry still has <code>pending</code> status</li>
<li>Last order still needs payment: <code>$last_order-&gt;needs_payment()</code></li>
</ul>
</li>
<li>If these checks fail, the status of the retry is transitioned to <code>'cancelled'</code>.</li>
<li>If they pass, <code>WCS_Renewal_Retry_Manager::maybe_retry_payment()</code> will transition the retry to the <code>'processing'</code> status and then check:
<ul>
<li>Last order still has the status defined by the retry rule applied to it (if a status was defined): <code>$last_order-&gt;has_status( $last_retry-&gt;get_rule()-&gt;get_status_to_apply( 'order' ) )</code>.</li>
<li>Subscription still has the status defined by the retry rule applied to it (if a status was defined): <code>$subscription-&gt;has_status( $last_retry-&gt;get_rule()-&gt;get_status_to_apply( 'subscription' ) )</code>.</li>
</ul>
</li>
<li>If these additional checks fail, the status of the retry will be transitioned to <code>'cancelled'</code></li>
<li>If they pass, <code>WCS_Renewal_Retry_Manager::maybe_retry_payment()</code> will then:
<ul>
<li>Update the subscription status to <code>on-hold</code> in preparation for payment (Subscriptions uses this status immediately before payment regardless of status defined by the retry rule to avoid compatibility issues with payment gateways that expect the subscription to have <em>on-hold </em>status, as it would for normal recurring payments): <code>$subscription-&gt;update_status( 'on-hold' )</code>.</li>
<li>Tell the payment gateway on the subscription to process payment for the last order: <code>WC_Subscriptions_Payment_Gateways::gateway_scheduled_subscription_payment( $subscription )</code>.</li>
</ul>
</li>
<li><code>WCS_Renewal_Retry_Manager::maybe_retry_payment()</code> will then check the order again and if it does not need payment, the status of retry is transitioned to <code>'complete'</code> status as the last payment must have been processed correctly.</li>
<li>If the last order still needs payment, the retry is transitioned to <code>'failed'</code> status as the last payment did not process correctly.</li>
<li>If payment failed, <code>WC_Subscription-&gt;payment_failed()</code> will trigger <code>'woocommerce_subscription_renewal_payment_failed'</code> again and the process repeats from step 1.</li>
</ol>
<h2 id="section-3">Customizing the Retry Process</h2>
<p>The retry process is based on a set of retry rules, as explained in the <a href="hhttps://help.codibu.com/blog/kb/developer-guide-to-failed-recurring-payment-retry-system//#section-3">Store Owner Guide to the Failed Payment Retry System</a>.</p>
<p>These rules can be customized by changing both the default rule applied to all failed payments in the store and/or one specific rule applied to a given order.</p>
<div class="woo-sc-box note   "><b>Please Note:</b> avoid adding rules that trigger a large number of retry attempts, like dozens or more, or attempt retries very frequently, like multiple times per day, or every day for weeks or months. Some payment gateways may flag your account if they see this type of activity. It can also affect your reputation with the particular bank of the customer’s card. Use automatic retries sparingly and notify the customer of most, if not all retry attempts.</div>
<h3 id="section-4">Rule Data Structure</h3>
<p>Retry rule data can take two forms:</p>
<ul>
<li>A <em>raw</em> retry rule is an array. This is the form used to store the default rule set in the protected <code>WCS_Retry_Rules-&gt;default_retry_rules</code> property and to store the rule applied for a specific retry in the database.</li>
<li>An instance of <code>WCS_Retry_Rule</code> (or child class of <code>WCS_Retry_Rule</code>). This is the form used to work with a specific rule.</li>
</ul>
<h4 id="raw-rule-data-structure">Raw Rule Data Structure</h4>
<p>The raw rule format is an array with the following values:</p>
<ul>
<li><code>retry_after_interval</code>: Amount of time, in seconds, between when the rule is applied and when to reattempt payment. Note: This interval accumulates between rules. For example, consider a store with two rules, the first with an interval of 24 hours and the second with an interval of 48 hours. If the 1st retry attempt fails, the 2nd retry attempt will be run 48 hours after it fails, not 48 hours after the first payment failed. This is 72 hours after first payment failure.</li>
<li><code>email_template_customer</code>: Class name of the email template to send the customer when the retry rule is applied (i.e. the payment fails). If empty, no email is sent.</li>
<li><code>email_template_admin</code>: Class name of the email template to send the store owner when the retry rule is applied (i.e. the payment fails). If empty, no email is sent.</li>
<li><code>status_to_apply_to_order</code>: Status to apply to the renewal order between when payment fails and when it is attempted again. This is <strong>not the status applied after the payment attempt for this retry rule fails</strong>. It is the status applied at the time the retry rule is applied, which happens when the previous payment attempt failed. This can be either the full, internal status name, e.g. <code>wc-pending</code> or shorthand status name, e.g. <code>pending</code>.</li>
<li><code>status_to_apply_to_subscription</code>: Status to apply to the subscription between when payment fails and when it is attempted again. This is <strong>not the status applied after the payment attempt for this retry rule fails</strong>. It is the status applied at the time the retry rule is applied, which happens when the previous payment attempt failed. This can be either the full, internal status name, e.g. <code>wc-on-hold</code> or shorthand status name, e.g. <code>on-hold</code>.</li>
</ul>
<p>This snippet provides an example of rule in the raw, array data structure.</p>
<pre>array(
    'retry_after_interval'            =&gt; DAY_IN_SECONDS,
    'email_template_customer'         =&gt; 'WCS_Email_Customer_Payment_Retry',
    'email_template_admin'            =&gt; 'WCS_Email_Payment_Retry',
    'status_to_apply_to_order'        =&gt; 'pending',
    'status_to_apply_to_subscription' =&gt; 'on-hold',
),
</pre>
<h4 id="rule-class-data-structure">Rule Class Data Structure</h4>
<p>The <code>WCS_Retry_Rule</code> class is used by default to instantiate retry rule data into the object used in <code>WCS_Retry_Manager</code> and elsewhere.</p>
<p>The class used to instantiate rule data can also be customized with the <code>'wcs_retry_rule_class'</code> filter. In most cases, this is unnecessary. It is only necessary to achieve behavior not customizable via custom rule filters detailed below.</p>
<p>This snippet provides an example of using a custom rule class.</p>
<pre>function eg_my_custom_retry_rule_class( $default_retry_class ) {
    return 'EG_Retry_Rule';
}
add_filter( 'wcs_retry_rule_class', 'eg_my_custom_retry_rule_class' );
</pre>
<div class="woo-sc-box note   ">If implementing a custom rule class, either extend WCS_Retry_Rule or be sure to implement all of the methods in WCS_Retry_Rule to avoid errors.</div>
<h3 id="section-5">Custom Storewide Rules</h3>
<p>The retry system uses a default set of rules for managing all failed payments in the store. These rules are defined in <code>WCS_Retry_Rules::__construct()</code> and accessed via <code>WCS_Retry_Rules::get_rule()</code>.</p>
<p>Customizing the default retry rules makes it possible to apply a new set of rules that are better suited to unique requirements for your store. For example, if a store only sells annual subscriptions, you may wish to use retry rules that continue retrying for up to 30 days after the initial payment failed, rather than the much shorter default.</p>
<p>The <code>'wcs_default_retry_rules'</code> filter makes it possible to customize the default rules. This filter passes callbacks an array of rules in the raw array rule data structure, and expects to receive the same from callbacks.</p>
<p><strong>Note:</strong> To apply code to the <code>'wcs_default_retry_rules'</code> filter, your <code>add_filter()</code> call will need to occur before <code>WCS_Retry_Rules</code> is first instantiated, which is attached to the <code>'woocommerce_subscription_renewal_payment_failed'</code> and <code>'woocommerce_scheduled_subscription_payment_retry'</code> hooks.</p>
<h4 id="example-custom-default-rules">Example Custom Default Rules</h4>
<p>This snippet provides a complete set of custom rules that will:</p>
<ul>
<li>Retry a payment 5 times over the course of a month, instead of the default 7 days</li>
<li>Implement more advanced dunning than the default rules, by sending three different emails to the customer</li>
<li>Only notify the store owner of the failed payment via email on the first and last retry attempt</li>
<li>Leave the subscription active for the first 7 days to provide a grace period before blocking access to virtual content linked to the subscription</li>
</ul>
<pre>function eg_my_custom_retry_rules( $default_retry_rules_array ) {
    return array(
            array(
                'retry_after_interval'            =&gt; 3 * DAY_IN_SECONDS,
                'email_template_customer'         =&gt; '',
                'email_template_admin'            =&gt; 'WCS_Email_Payment_Retry',
                'status_to_apply_to_order'        =&gt; 'pending',
                'status_to_apply_to_subscription' =&gt; 'active',
            ),
            array(
                'retry_after_interval'            =&gt; 4 * DAY_IN_SECONDS,
                'email_template_customer'         =&gt; 'EG_Email_Customer_Payment_Retry_First_Nag', // custom email
                'email_template_admin'            =&gt; '',
                'status_to_apply_to_order'        =&gt; 'pending',
                'status_to_apply_to_subscription' =&gt; 'active',
            ),
            array(
                'retry_after_interval'            =&gt; WEEK_IN_SECONDS,
                'email_template_customer'         =&gt; '', // avoid spamming the customer by not sending them an email this time either
                'email_template_admin'            =&gt; '',
                'status_to_apply_to_order'        =&gt; 'pending',
                'status_to_apply_to_subscription' =&gt; 'on-hold',
            ),
            array(
                'retry_after_interval'            =&gt; WEEK_IN_SECONDS,
                'email_template_customer'         =&gt; 'EG_Email_Customer_Payment_Retry_Second_Nag', // custom email
                'email_template_admin'            =&gt; '',
                'status_to_apply_to_order'        =&gt; 'pending',
                'status_to_apply_to_subscription' =&gt; 'on-hold',
            ),
            array(
                'retry_after_interval'            =&gt; WEEK_IN_SECONDS,
                'email_template_customer'         =&gt; 'EG_Email_Customer_Payment_Retry_Final_Nag', // custom email
                'email_template_admin'            =&gt; 'WCS_Email_Payment_Retry',
                'status_to_apply_to_order'        =&gt; 'pending',
                'status_to_apply_to_subscription' =&gt; 'on-hold',
            ),
        );
}
add_filter( 'wcs_default_retry_rules', 'eg_my_custom_retry_rules' );
</pre>
<h3 id="section-6">Custom Individual Rule</h3>
<p>You may wish to apply different retry rules for different products, billing schedules, payment amounts or other conditions. To do this, you can customize a specific retry rule based on the order ID and its position in the retry queue.</p>
<p>An individual rule can be customized in two ways, depending on the data structure of the rule.</p>
<p>To customize the raw rule in array format, use the <code>'wcs_get_retry_rule_raw'</code> filter. To customize the instantiated rule object, use the <code>'wcs_get_retry_rule'</code> filter.</p>
<p>Callbacks on both of these filters receive 3 parameters:</p>
<ol>
<li><code>$rule</code>: this will be:
<ul>
<li>an array representing the rule with the array keys described above for <code>'wcs_get_retry_rule_raw'</code>.</li>
<li>an instance of <code>WCS_Retry_Rule</code> for <code>'wcs_get_retry_rule'</code>.</li>
<li><code>null</code> if there is no rule for this <code>$retry_number</code> and <code>$order_id</code>.</li>
</ul>
</li>
<li><code>$retry_number</code>: the position in the retry queue, starting at 0. For example, after the first payment failure, there have been no retries, so the <code>$retry_number</code> would be <code>0</code>. If the retry fails after applying this first rule, to get the rule for the 2nd retry, the <code>$retry_number</code> would then be <code>1</code>.</li>
<li><code>$order_id</code>: the ID of the order for which this rule relates.</li>
</ol>
<h4 id="example-custom-individual-raw-rule">Example Custom Individual Raw Rule</h4>
<p>This snippet changes the email template sent to customers for a product with ID 30.</p>
<pre>function eg_my_custom_retry_rule( $rule_raw, $retry_number, $order_id ) {

    $order       = wc_get_order( $order_id );
    $has_product = false;

    foreach ( $order-&gt;get_items() as $line_item ) {
        if ( $line_item['product_id'] == 30 ) {
            $has_product = true;
            break;
        }
    }

    if ( $has_product &amp;&amp; ! empty( $rule_raw['email_template_customer'] ) ) {
        $rule_raw['email_template_customer'] = 'EG_Email_Customer_Payment_Retry_Product_Thirty';
    }

    return $rule_raw;
}
add_filter( 'wcs_get_retry_rule_raw', 'eg_my_custom_retry_rule' );
</pre>
<h4 id="example-custom-individual-rule-object">Example Custom Individual Rule Object</h4>
<p>This snippet uses a custom retry rule class and interval for annual subscriptions.</p>
<pre>function eg_my_custom_retry_rule( $rule, $retry_number, $order_id ) {

    $subscription = wcs_get_subscriptions_for_order( $order, array( 'order_type' =&gt; 'renewal' ) );

    if ( ! empty( $subscription ) &amp;&amp; 'year' === $subscription-&gt;billing_period ) {

        $existing_rule_raw = $rule-&gt;get_raw_data();

        if ( ! empty( $existing_rule_raw['retry_after_interval'] ) ) {
            $existing_rule_raw['retry_after_interval'] = WEEK_IN_SECONDS;
            $rule = new EG_Retry_Rule( $rule-&gt;get_rule_raw() );
        }
    }

    return $rule;
}
add_filter( 'wcs_get_retry_rule', 'eg_my_custom_retry_rule' );
</pre>
<h2 id="section-7">Testing the Retry System</h2>
<p>After creating custom retry rules, or to check whether your gateway is compatible with the retry system, you can test the retry system with the following process.</p>
<h3 id="section-8">Step 1: Enable the Retry System</h3>
<p>Before testing, ensure that you have <a href="https://help.codibu.com/blog/kb/developer-guide-to-failed-recurring-payment-retry-system/#section-1">enabled the Retry System</a> in your store.</p>
<h3 id="section-9">Step 2: Trigger a Failed Recurring Payment</h3>
<p>Depending on your gateway, the way to trigger a recurring payment failure will differ. For <a href="https://woocommerce.com/products/stripe/">Stripe</a> and other payment gateways that support <a href="https://help.codibu.com/blog/kb/subscription-payment-methods-gateways/#advanced-features">admin payment method changes</a> you can:</p>
<ol>
<li>Purchase a subscription using a payment method which supports <a href="https://help.codibu.com/blog/kb/subscription-payment-methods-gateways/#advanced-features">payment date changes</a></li>
<li>Go to: <strong>WooCommerce &gt; Edit Subscription</strong> for that subscription</li>
<li>Click the pencil icon next to <strong>Billing Details</strong></li>
<li>Enter dummy payment token meta data so that the payment will fail</li>
<li>Click <strong>Save Subscription</strong></li>
<li>After the page has reloaded, click the <strong>Actions</strong> select box in the <strong>Subscription Actions</strong> metabox</li>
<li>Click <strong>Process Renewal</strong></li>
<li>Click <strong>Save Subscription</strong></li>
</ol>
<p>This creates a renewal order, records the failed payment on that renewal and applies the first retry rule to that order.</p>
<h3 id="section-10">Step 3: Monitor Retries</h3>
<p>At this stage, you can view the details of the <em>Pending</em> retry via the interfaces detailed in the <a href="https://help.codibu.com/blog/kb/developer-guide-to-failed-recurring-payment-retry-system/#section-4">Store Manager guide to Monitoring Retries</a>.</p>
<p>If your retry rule worked, you should now be able to see:</p>
<ul>
<li>A <em>Renewal Payment Retry</em> date on the <a href="https://help.codibu.com/blog/kb/developer-guide-to-failed-recurring-payment-retry-system/#section-8"><strong>WooCommerce &gt; Edit Subscription</strong> administration screen</a></li>
<li>A <em>Pending</em> retry in the <a href="https://help.codibu.com/blog/kb/developer-guide-to-failed-recurring-payment-retry-system/#section-9"><strong>Automatic Failed Payment Retries</strong> metabox</a></li>
</ul>
<h3 id="section-11">Step 4: Trigger the Retry (Optional)</h3>
<p>If you do not want to wait until the retry is triggered automatically, you can trigger the retry immediately.</p>
<p>To trigger a scheduled payment retry hook immediately:</p>
<ol>
<li>Make sure your store is running in debug mode by setting the <code>WP_DEBUG</code> constant</li>
<li>Visit your WordPress administration dashboard</li>
<li>Go to: <strong>Tools &gt; Scheduled Actions</strong></li>
<li>In the search box, enter the ID of your test order</li>
<li>Find the row with the hook <code>'scheduled_subscription_payment_retry'</code> and the status <em>pending</em></li>
<li>Hover over the row and click <strong>Run</strong></li>
</ol>
<p>This immediately triggers the <code>'scheduled_subscription_payment_retry'</code> hook.</p>
<div class="woo-sc-box info   ">Subscriptions uses Action Scheduler for managing the scheduled retry dates.</div>
<figure id="attachment_165234" class="wp-caption aligncenter" aria-describedby="caption-attachment-165234"><a href="https://help.codibu.com/wp-content/uploads/2020/11/retry-scheduled-action.png"><img loading="lazy" decoding="async" class="size-large wp-image-165234" src="https://help.codibu.com/wp-content/uploads/2020/11/retry-scheduled-action.png?w=950" sizes="(max-width: 950px) 100vw, 950px" srcset="https://help.codibu.com/wp-content/uploads/2020/11/retry-scheduled-action.png 1915w, https://help.codibu.com/wp-content/uploads/2020/11/retry-scheduled-action.png?resize=550,253 550w, https://help.codibu.com/wp-content/uploads/2020/11/retry-scheduled-action.png?resize=768,353 768w, https://help.codibu.com/wp-content/uploads/2020/11/retry-scheduled-action.png?resize=950,437 950w, https://help.codibu.com/wp-content/uploads/2020/11/retry-scheduled-action.png?resize=1536,706 1536w" alt="Scheduled Failed Recurring Payment Retry Action Screenshot" width="950" height="437" /></a><figcaption id="caption-attachment-165234" class="wp-caption-text">Scheduled Failed Recurring Payment Retry Action</figcaption></figure>
<div class="woo-sc-box normal   "><a title="WooCommerce Subscriptions Developer Documentation Overview" href="https://help.codibu.com/blog/kb/introduction-to-subscriptions-developer-documentation/">← WooCommerce Subscriptions Developer Documentation Overview</a></div>
<h2 id="section-12">Retry Migration to Custom Table</h2>
<p>When the retry system was introduced in 2.1, retries were a custom post type and were stored in the WordPress posts and postmeta tables. To improve the performance of the retry system, in version 2.4 we introduced a custom table to store the retry data. With the introduction of this custom table, we needed to migrate the existing data.</p>
<p>Retries are migrated to the custom table in 2 ways:</p>
<ol>
<li><strong>On the fly</strong>: while retries still exist in the posts table, the <code>WCS_Retry_Hybrid_Store</code> is used to act as a bridge between the two data stores. When a retry object is requested (e.g. via <code>WCS_Retry_Manager::store()-&gt;get_retry( $retry_id )</code>), the hybrid store will first check if the retry exists in the post table and if it does, it will migrate the retry data to the new custom table before returning the retry object.</li>
<li><strong>In the background</strong>: when the store upgrades to 2.4, or any version after 2.4, a migration action will be scheduled via the Action Scheduler library. When this action is triggered, the <code>WCS_Retry_Background_Migrator</code> class will migrate retries in batches, rescheduling itself every 60 seconds (by default) until all retries have been migrated. <code>WCS_Retry_Background_Migrator</code> is initialized by and stored on <code>WCS_Retry_Manager</code> as protected variable.</li>
</ol>
<h3 id="section-13">How do I track the progress of the retry data migration?</h3>
<p>The status of the retry data migration is displayed in the System Status.</p>
<ol>
<li>Go to <strong>WooCommerce &gt; Status</strong>.</li>
<li>Scroll down to <strong>Retries Migration Status</strong>.</li>
</ol>
<p><a href="https://help.codibu.com/wp-content/uploads/2020/11/retry-migration-status.png" rel="prettyPhoto"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1392055" src="https://help.codibu.com/wp-content/uploads/2020/11/retry-migration-status.png" sizes="(max-width: 706px) 100vw, 706px" srcset="https://help.codibu.com/wp-content/uploads/2020/11/retry-migration-status.png 706w, https://help.codibu.com/wp-content/uploads/2020/11/retry-migration-status.png?resize=550,495 550w" alt="" width="706" height="636" /></a></p>
<h3 id="section-14">How can I tell how many retries still need to be migrated?</h3>
<p>To see how many retries you still have stored in the posts table:</p>
<ol>
<li>Go to <strong>WooCommerce &gt; Status</strong> and scroll down to the <strong>Post Type Counts</strong> section.</li>
<li>The number next to <code>payment_retry</code> is the number of retries which haven’t been mirgrated.</li>
</ol>
<p>If there isn’t any <code>payment_retry</code> row displayed in this table, there aren’t any retries still stored in the posts table.</p>
<p><a href="https://help.codibu.com/wp-content/uploads/2020/11/payment-retry-post-count.png" rel="prettyPhoto"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1392093" src="https://help.codibu.com/wp-content/uploads/2020/11/payment-retry-post-count.png" sizes="(max-width: 790px) 100vw, 790px" srcset="https://help.codibu.com/wp-content/uploads/2020/11/payment-retry-post-count.png 790w, https://help.codibu.com/wp-content/uploads/2020/11/payment-retry-post-count.png?resize=550,443 550w, https://help.codibu.com/wp-content/uploads/2020/11/payment-retry-post-count.png?resize=768,618 768w" alt="" width="790" height="636" /></a></p><p>The post <a href="https://help.codibu.com/blog/developer-guide-to-failed-recurring-payment-retry-system/">Developer Guide to Failed Recurring Payment Retry System</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://help.codibu.com/blog/developer-guide-to-failed-recurring-payment-retry-system/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Admin Change Payment Method Integration Guide</title>
		<link>https://help.codibu.com/blog/admin-change-payment-method-integration-guide/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=admin-change-payment-method-integration-guide</link>
					<comments>https://help.codibu.com/blog/admin-change-payment-method-integration-guide/#respond</comments>
		
		<dc:creator><![CDATA[JN C]]></dc:creator>
		<pubDate>Sun, 08 Nov 2020 15:08:22 +0000</pubDate>
				<guid isPermaLink="false">https://help.codibu.com/kb/admin-change-payment-method-integration-guide/</guid>

					<description><![CDATA[<p>This document is written for WooCommerce developers who want to extend or integrate with the WooCommerce Subscriptions plugin. To follow this documentation, you will need an<span class="excerpt-hellip"> […]</span></p>
<p>The post <a href="https://help.codibu.com/blog/admin-change-payment-method-integration-guide/">Admin Change Payment Method Integration Guide</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></description>
										<content:encoded><![CDATA[<div class="woo-sc-box note   ">This document 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 subscriptions in your store, refer to the <a title="Subscriptions Store Manager Guide" href="https://help.codibu.com/blog/kb/subscriptions-store-manager-guide/">Store Manager Guide</a>.</div>
<p>
This article provides developer documentation for adding support to a payment gateway extension for Subscription v2.0’s <a href="https://help.codibu.com/blog/kb/whats-new-in-woocommerce-subscriptions-v2-0/#change-payment-method-admin">new <em>Change Payment Method</em> section</a> of the <a href="https://help.codibu.com/blog/kb/manually-add-or-edit-a-subscription/"><strong>Edit Subscription</strong> administration screen</a>.</p>
<p>&nbsp;</p>
<p>If you are a developer of a payment gateway and have not yet provided support for processing subscriptions, please read the <a href="https://help.codibu.com/blog/kb/subscriptions-payment-gateway-integration-guide/">Subscriptions Payment Gateway Integration Guide</a>.</p>
<div class="woo-sc-box info   ">Need to upgrade a payment gateway for version 2.0? Check out the <a href="https://help.codibu.com/blog/kb/subscriptions-v2-0-payment-gateway-upgrade-guide/">Payment Gateway Upgrade Guide</a>.</div>
<h2 id="section-1">Background</h2>
<p>A new feature added in Subscriptions v2.0 provides a way for payment gateways to allow store managers to add or edit a subscription’s payment method and meta data on the new <strong>Edit Subscription</strong> administration screen.</p>
<figure id="attachment_158225" class="wp-caption aligncenter" aria-describedby="caption-attachment-158225"><a href="https://help.codibu.com/wp-content/uploads/2020/11/change-recurring-payment-method-screenshot.png"><img loading="lazy" decoding="async" class="size-full wp-image-158225" src="https://help.codibu.com/wp-content/uploads/2020/11/change-recurring-payment-method-screenshot.png" sizes="(max-width: 2260px) 100vw, 2260px" srcset="https://help.codibu.com/wp-content/uploads/2020/11/change-recurring-payment-method-screenshot.png 2260w, https://help.codibu.com/wp-content/uploads/2020/11/change-recurring-payment-method-screenshot.png?resize=550,376 550w, https://help.codibu.com/wp-content/uploads/2020/11/change-recurring-payment-method-screenshot.png?resize=768,525 768w, https://help.codibu.com/wp-content/uploads/2020/11/change-recurring-payment-method-screenshot.png?resize=950,649 950w, https://help.codibu.com/wp-content/uploads/2020/11/change-recurring-payment-method-screenshot.png?resize=1536,1049 1536w, https://help.codibu.com/wp-content/uploads/2020/11/change-recurring-payment-method-screenshot.png?resize=2048,1399 2048w" alt="Change Recurring Payment Method" width="2260" height="1544" /></a><figcaption id="caption-attachment-158225" class="wp-caption-text">Change Recurring Payment Method Fields</figcaption></figure>
<p>Adding support for this feature can be achieved with any payment gateway that uses customer or credit card tokens with the following steps:</p>
<ol>
<li>Add the support flag to your gateway to declare support for store managers to change the payment methods</li>
<li>Allow store managers to change the payment meta data</li>
<li>Validate the new meta data before saving the subscription.</li>
</ol>
<h2 id="section-2">Step 1. Declare Support for Changing Payment Methods</h2>
<p>By default, Subscriptions will not allow store managers to change or modify the payment gateway on the <strong>Edit Subscription</strong> screen to anything other than the built-in <strong>Manual Renewals</strong> gateway.</p>
<p>To add the support for this feature, you will need to declare support by adding <code>'subscription_payment_method_change_admin'</code> to your gateway’s <code>$this-&gt;supports</code> property.</p>
<p>For example, if Stripe wanted to allow administrators to change the payment method, they could achieve this by adding the <code>'subscription_payment_method_change_admin'</code> to their <code>WC_Payment_Gateway-&gt;supports</code> array as follows:</p>
<pre>$this-&gt;supports = array(
    // Other support flags here
    'subscription_payment_method_change_admin',
    // Other support flags here
);
</pre>
<p>Once your gateway declares support, it will be included as an option on the <strong>Edit Subscription</strong> page.</p>
<figure id="attachment_158240" class="wp-caption aligncenter" aria-describedby="caption-attachment-158240"><a href="https://help.codibu.com/wp-content/uploads/2020/11/choose-payment-method-for-subscription-screenshot.png"><img loading="lazy" decoding="async" class="size-full wp-image-158240" src="https://help.codibu.com/wp-content/uploads/2020/11/choose-payment-method-for-subscription-screenshot.png" sizes="(max-width: 2260px) 100vw, 2260px" srcset="https://help.codibu.com/wp-content/uploads/2020/11/choose-payment-method-for-subscription-screenshot.png 2260w, https://help.codibu.com/wp-content/uploads/2020/11/choose-payment-method-for-subscription-screenshot.png?resize=550,376 550w, https://help.codibu.com/wp-content/uploads/2020/11/choose-payment-method-for-subscription-screenshot.png?resize=768,525 768w, https://help.codibu.com/wp-content/uploads/2020/11/choose-payment-method-for-subscription-screenshot.png?resize=950,649 950w, https://help.codibu.com/wp-content/uploads/2020/11/choose-payment-method-for-subscription-screenshot.png?resize=1536,1049 1536w, https://help.codibu.com/wp-content/uploads/2020/11/choose-payment-method-for-subscription-screenshot.png?resize=2048,1399 2048w" alt="Choose Payment Method for Subscription" width="2260" height="1544" /></a><figcaption id="caption-attachment-158240" class="wp-caption-text">Choose Payment Method for Subscription</figcaption></figure>
<h2 id="section-3">Step 2: Declare your Payment Gateway’s Meta Data</h2>
<p>To allow store managers to correctly configure automatic payments with your payment gateway via the <strong>Edit Subscription</strong> screen, you also need to tell Subscriptions about the meta data required by your payment gateway in order to process automatic payments. This is done using the filter: <code>'woocommerce_subscription_payment_meta'</code>.</p>
<p>Callbacks on this hook are passed an array of meta data for all payment gateways in the store and an instance of the subscription (a <code>WC_Subscription</code>) to which the meta data relates.</p>
<p>To use this filter correctly, you need to insert information about your payment details using your payment gateway’s ID as the key on the array, i.e. <code>payment_meta[ YOUR_GATEWAY_ID ]</code>. You should also populate the meta data with the current or default value for the given subscription, which is passed to your callback as the 2nd parameter.</p>
<p>The full structure needs to meet the following format:</p>
<pre>$payment_meta[ YOUR_GATEWAY_ID ]  = array (
    'table_name' =&gt; array (
        'meta_key_1' =&gt; array (
            'value' =&gt; get_post_meta( $subscription-&gt;id, ..., true ),
            'label' =&gt; 'Front-end Label to display',
        ),
        'meta_key_2' =&gt; array (
            'value' =&gt; get_user_meta( $subscription-&gt;customer_user, ..., true ),
            'label' =&gt; 'Front-end Label to display',
        )
    ),
);
</pre>
<p>Subscriptions uses this information in two ways:</p>
<ul>
<li>to display the appropriate fields on the <strong>Edit Subscription</strong> screen, which is why a <code>'label'</code> field is required; and</li>
<li>to save the meta data in the database when the <strong>Edit Subscription</strong> screen is saved, which is why the meta key and table name fields are required.</li>
</ul>
<p>The <code>'table_name'</code> string tells Subscriptions where to store and retrieve the meta data. If the value is either <code>'post_meta'</code>, <code>'user_meta'</code> or <code>'options'</code>, subscriptions will automatically save the new data to those respective tables with the site’s database prefix (i.e. <code>wpdb-&gt;prefix</code>). If the default table names are not suitable for storing your data, please read the FAQ section for details of alternatives.</p>
<p>Specifically:</p>
<ul>
<li>Any data found in <code>payment_meta[ YOUR_GATEWAY_ID ]['post_meta']</code> will be stored in the post meta table using <code>update_post_meta( $subscription, 'meta_key', $new_data)</code>;</li>
<li>Data found in <code>payment_meta[ YOUR_GATEWAY_ID ]['user_meta']</code> will be saved as <code>update_user_meta( $subscription-&gt;customer_user, 'meta_key', $new_data );</code>;</li>
<li>The data listed under <code>payment_meta[ YOUR_GATEWAY_ID ]['options']</code> will be saved to options table using <code>update_option( 'meta_key', $new_data )</code>.</li>
</ul>
<h3 id="section-4">Example: Stripe</h3>
<p>Here’s an example of how the Stripe payment gateway uses this filter to allow administrators to change the <code>'_stripe_customer_id'</code> meta data found in the post meta table and similarly the <code>'_stripe_card_id'</code> meta data.</p>
<pre>class WC_Gateway_Stripe_Addons extends WC_Gateway_Stripe {

    ...

    /**
     * Include the payment meta data required to process automatic recurring payments so that store managers can
     * manually set up automatic recurring payments for a customer via the Edit Subscriptions screen in 2.0+.
     *
     * @since 2.5
     * @param array $payment_meta associative array of meta data required for automatic payments
     * @param WC_Subscription $subscription An instance of a subscription object
     * @return array
     */
    public function add_subscription_payment_meta( $payment_meta, $subscription ) {

        $payment_meta[ $this-&gt;id ] = array(
            'post_meta' =&gt; array(
                '_stripe_customer_id' =&gt; array(
                    'value' =&gt; get_post_meta( $subscription-&gt;id, '_stripe_customer_id', true ),
                    'label' =&gt; 'Stripe Customer ID',
                ),
                '_stripe_card_id' =&gt; array(
                    'value' =&gt; get_post_meta( $subscription-&gt;id, '_stripe_card_id', true ),
                    'label' =&gt; 'Stripe Card ID',
                ),
            ),
        );

        return $payment_meta;
    }

    ...
}
</pre>
<h2 id="section-5">Step 3: Validate Meta Data</h2>
<p>To ensure store managers are inputting correct data, an optional but highly recommended feature you can implement is validation of the data before it is saved to the subscription.</p>
<p>The <code>'woocommerce_subscription_validate_payment_meta'</code> filter is available for this purpose. This filter passes <code>payment_method_id</code> and <code>payment_meta</code> array parameters to callbacks. The <code>payment_meta</code> contains all the user’s inputted data in the same format as described in Step 2. Attach a validation function to this filter to validate meta data entered by the store manager.</p>
<p>Any exceptions thrown in your validation function will be caught and the exception message will be displayed on the <strong>Edit Subscription</strong> in an administration notices section (see below). If an exception is caught, all new modifications before saving the subscription will be ignored.</p>
<pre>function my_validation_function( $payment_method_id, $payment_meta ) {

    if ( 'YOUR_GATEWAY_ID' === $payment_method_id ) {
        if ( 'example_throw' == $payment_meta['YOUR_GATEWAY_ID']['post_meta']['example_meta_key'] ) {
            throw new Exception( 'Error to appear on Edit Subscription in the case of an invalid input' );
        }   
    }
}
add_action( 'woocommerce_subscription_validate_payment_meta', 'my_validation_function', 10, 2 );
</pre>
<h3 id="section-4">Example: Stripe</h3>
<p>The following codes demonstrate the validation function used by the Stripe Payment Gateway to validate its data.</p>
<pre>class WC_Gateway_Stripe_Addons extends WC_Gateway_Stripe {

    ...

    /**
     * Validate the payment meta data required to process automatic recurring payments so that store managers can
     * manually set up automatic recurring payments for a customer via the Edit Subscriptions screen in 2.0+.
     *
     * @since 2.5
     * @param string $payment_method_id The ID of the payment method to validate
     * @param array $payment_meta associative array of meta data required for automatic payments
     * @return array
     */
    public function validate_subscription_payment_meta( $payment_method_id, $payment_meta ) {

        if ( $this-&gt;id === $payment_method_id ) {

            if ( ! isset( $payment_meta['post_meta']['_stripe_customer_id']['value'] ) || empty( $payment_meta['post_meta']['_stripe_customer_id']['value'] ) ) {
                throw new Exception( 'A "_stripe_customer_id" value is required.' );
            } elseif ( 0 !== strpos( $payment_meta['post_meta']['_stripe_customer_id']['value'], 'cus_' ) ) {
                throw new Exception( 'Invalid customer ID. A valid "_stripe_customer_id" must begin with "cus_".' );
            }

            if ( ! isset( $payment_meta['post_meta']['_stripe_card_id']['value'] ) || empty( $payment_meta['post_meta']['_stripe_card_id']['value'] ) ) {
                throw new Exception( 'A "_stripe_card_id" value is required.' );
            } elseif ( 0 !== strpos( $payment_meta['post_meta']['_stripe_card_id']['value'], 'card_' ) ) {
                throw new Exception( 'Invalid card ID. A valid "_stripe_card_id" must begin with "card_".' );
            }
        }
    }

    ...

}
</pre>
<h2 id="section-7">Full Example: Simplify Commerce</h2>
<p>For a full example, see the code required to add support to Simplify Commerce in WooCommerce core in this commit. This was submitted as part of the Simplify Commerce Subscriptions v2.0 compatibility pull request.</p>
<h2 id="section-8">Testing</h2>
<p>Once you have completed the steps above, your gateway should:</p>
<ol>
<li>be presented as a payment method option on the <strong>Edit Subscription</strong> screen;</li>
<li>display and save meta data required for processing automatic payments with your gateway; and</li>
<li>check that the meta data entered by the store manager is valid.</li>
</ol>
<p>To ensure that automatic recurring payments work with the new meta data:</p>
<ol>
<li>Visit the <strong>WooCommerce &gt; Subscriptions</strong> screen</li>
<li>Click the ID of a subscription to open the <strong>Edit Subscription</strong> screen</li>
<li>Click the <em>pencil</em> icon next to the <strong>Billing Details</strong> section</li>
<li>Verify that all the fields required for processing automatic payment are displayed</li>
<li>If they are, enter _invalid_ meta data for each field</li>
<li>Save the subscription</li>
<li>Ensure that correct admin notices are displayed (i.e. your validation function’s exception messages)</li>
<li>Repeat the above 3 steps for each field your gateway requires</li>
<li>Enter valid meta data for each field</li>
<li>Save the subscription</li>
<li>trigger an early renewal payment for the subscription</li>
<li>verify that the renewal payment was processed correctly (i.e. the renewal order’s status is <em>processing</em> or <em>complete</em>)</li>
<li>verify with your payment gateway that the renewal payment used the new customer or card</li>
</ol>
<h2 id="section-9">FAQ</h2>
<h3 id="section-10">I haven’t added support but my gateway is still showing as an option on the <strong>Edit Subscription</strong> screen?</h3>
<p>If a subscription is purchased via the normal checkout process, the payment method used during checkout will be shown on the <strong>Edit Subscription</strong> screen because the meta data required for automatic payment should have been collected during checkout.</p>
<p>If the store manager changes the payment method for the subscription from your payment gateway to another, it will no longer be displayed as an option.</p>
<h3 id="section-11">How can I save the payment meta for the subscription using my own custom method?</h3>
<p>We have provided an action hook that will allow you to save the payment meta data wherever you like, but to ensure you stop us from saving the data, you will need to make sure the table_name does not equal post_meta, postmeta, user_meta, usermeta or options. Then, you should make sure your plugin catches the <code>'wcs_save_other_payment_meta'</code> action.</p>
<p>The following code example demonstrates how you would go about either storing data in the comments table or attaching some extra information to a value before it is stored in the post meta table.</p>
<pre>function my_gateway_meta( $payment_details, $subscription ) {
    $payment_details[ YOUR_GATEWAY_ID ]  = array (
            'wc_comments' =&gt; array (
                '_gateway_comments' =&gt; array (
                    'value' =&gt; get_comments_meta( ..., ..., true ),
                    'label' =&gt; 'Front-end Label to display',
                )
            ),
            'wc_post_meta' =&gt; array (
                'meta_key_1' =&gt; array (
                    'value' =&gt; get_post_meta( $subscription-&gt;id, 'meta_key_1', true ),
                    'label' =&gt; 'Front-end Label to display',
                )
            ),
    );
    return $payment_details;
}
add_filter( 'woocommerce_subscription_payment_meta', 'my_gateway_meta', 10, 2 );

function save_meta_in_comments( $subscription, $table, $meta_key, $meta_value ) {
    switch( $table ) {
        case 'wc_comments':
            update_comments_meta( ..., $meta_key, $meta_value );
            break;
        case 'wc_post_meta':
            update_post_meta( $subscription-&gt;id, $meta_key, $meta_value . '_edited' );
            break;
    }
}
add_action( 'wcs_save_other_payment_meta', 'save_meta_in_comments',</pre><p>The post <a href="https://help.codibu.com/blog/admin-change-payment-method-integration-guide/">Admin Change Payment Method Integration Guide</a> first appeared on <a href="https://help.codibu.com">CODIBU</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://help.codibu.com/blog/admin-change-payment-method-integration-guide/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
