How to Add Software Licensing to your Plugin Using Easy Digital Downloads

Posted by Ronald Huereca / June 9, 2019 /

Please note that this post contains affiliate links. Please support this site by using the affiliate links included.

Creating your own plugin is tough and it’s tougher to sell! I know! I have one plugin for sale over at BB Vapor Modules, and they honestly haven’t been selling well, but time will tell.

I use Easy Digital Downloads because it makes the shopping experience much nicer and it has a plethora of options. Easy Digital Downloads and software licensing do not work out of the box, however. You’ll need some extensions, and I’ll go over that here.

Pre-Requisite Extensions

All of these add-ons are expensive on their own, which is why I highly recommend just getting the Easy Digital Downloads All Access Pass, which gives you access to all extensions for unlimited sites. It’s a must-own product.

SSL is a MUST

Turn on SSL via Let’s Encrypt. If your hosting doesn’t offer free SSL certificates, it’s time to move to a better host. I’m currently hosted on Kinsta and it’s been a great service. SiteGround and WP Engine also offer free SSL certificates.

Auto Register Add-On

You’ll want your users to be able to log in and view past purchases. This is why I recommend the Auto Register Add-On.

Custom Prices Add-On

If you have any kind of donate form or pay me what you want, then you’ll want the Custom Prices Add-On. I use it on my Give Back section.

Discounts Pro Add-On

If you want to apply discounts to a product group over a period of time, this plugin is a must. I highly recommend the Discounts Pro Add-On.

Manual Purchases Add-On

If you want to send out trials to members or invite others to review your plugin, this add-on is a must. I’ve used Manual Purchases numerous times to invite others to try my products.

Recurring Payments Add-On

If you’re needed a per-year or per-month (or any time) recurrence, then the Recurring Payments Add-On is a no-brainer.

Software Licensing Add-On

This is the meat. You’ll need the Software Licensing Add-On to allow you to sell your product digitally.

Finally, the Stripe Payment Gateway Add-On

If you use Stripe, and I don’t see why not, you’ll want the Stripe Payment Gateway Add-On.

Adding Software Licensing to Your Plugin

Once you download the Software Licensing Add-On, you’ll find a samples directory. From there, it has an example of how to include the class into your plugin or theme. In this case, I’m concentrating on the plugin side of things.

You’ll find that you need to include the file EDD_SL_Plugin_Updater.php in your plugin. I typically just throw this into an includes folder in my plugin. Once thing to note here is that other plugins may be using the same class name, so I recommend wrapping it with a class_exists check.

if ( ! class_exists( 'EDD_SL_Plugin_Updater' ) ):
class EDD_SL_Plugin_Updater {
  ...
}
endif;

From there, include and instantiate the class. I’m assuming you have a base plugin file and the updater class in an includes folder.

// Auto Update class
define( 'YOUR_PLUGIN_VERSION', '1.0.1' );
add_action( 'admin_init', 'your_plugin_updater_init', 0 );
function your_plugin_updater_init() {
	require_once 'includes/EDD_SL_Plugin_Updater.php';
	$license = get_site_option( 'your_plugin_license', false );
	if ( false !== $license ) {
		// setup the updater
		$edd_updater = new EDD_SL_Plugin_Updater( 'https://yourdomain.com', __FILE__,
			array(
				'version' => YOUR_PLUGIN_VERSION,
				'license' => $license,
				'item_id' => 123,
				'author'  => 'Your Name',
				'beta'    => false,
				'url'     => home_url(),
			)
		);
	}
}

I’m assuming you have an option named your_plugin_license and a constant named YOUR_PLUGIN_VERSION. You’ll also want to point yourdomain.com to whatever domain you are selling your licenses from. Setting the item_id will require that you add the download to your domain first. This is a chicken and egg scenario, but you can draft a download and steal the ID it generates.

For example, here’s mine for my plugin BB Vapor Modules for Beaver Builder.

require_once BBVAPOR_PRO_BEAVER_BUILDER_DIR . 'includes/EDD_SL_Plugin_Updater.php';
$license = get_site_option( 'bbvm_for_beaver_builder_license', false );
if ( false !== $license ) {

	// setup the updater
	$edd_updater = new BBVM_EDD_SL_Plugin_Updater( 'https://bbvapormodules.com', BBVAPOR_PRO_BEAVER_BUILDER_FILE,
		array(
			'version' => BBVAPOR_PRO_BEAVER_BUILDER_VERSION,
			'license' => $license,
			'item_id' => 688,
			'author'  => 'Ronald Huereca',
			'beta'    => false,
			'url'     => home_url(),
		)
	);
}

I’ve placed the EDD Updater class in its own includes directory and changed the class name so it doesn’t conflict with other plugins using the EDD Plugin Updater.

Here’s an example of customizing the class name.

<?php

// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) exit;

/**
 * Allows plugins to use their own update API.
 *
 * @author Easy Digital Downloads
 * @version 1.6.18
 */
class BBVM_EDD_SL_Plugin_Updater {

	private $api_url     = '';
	private $api_data    = array();
	private $name        = '';
	private $slug        = '';
	private $version     = '';
	private $wp_override = false;
	private $cache_key   = '';

	private $health_check_timeout = 5;

	// ...code

Add a Plugin Row Option for the License

Since I’m using class notation, this may be a little tricky to understand, but here are some actions I use to show a license option under the plugin row.

<?php
if (!defined('ABSPATH')) die('No direct access.');
class BBVapor_BeaverBuilder_Admin {

	/**
	 * Holds the slug to the admin panel page
	 *
	 * @since 1.0.0
	 * @static
	 * @var string $slug
	 */
	private static $slug = 'bb-vapor-modules-pro';

	/**
	 * Holds the URL to the admin panel page
	 *
	 * @since 1.0.0
	 * @static
	 * @var string $url
	 */
	private static $url = '';

	public function __construct() {
		add_action( 'admin_init', array( $this, 'init' ) );

		add_action( 'admin_menu', array( $this, 'register_sub_menu') );
	}

In the above code, I use admin_init to register the upgrade class and to add the plugin row.

Here’s the code I use to register my admin menu settings and the plugin row action.

/**
 * Initializes admin menus, plugin settings links.
 *
 * @since 1.0.0
 * @access public
 * @see __construct
 */
public function init() {

	require_once BBVAPOR_PRO_BEAVER_BUILDER_DIR . 'includes/EDD_SL_Plugin_Updater.php';
	$license = get_site_option( 'bbvm_for_beaver_builder_license', false );
	if ( false !== $license ) {

		// setup the updater
		$edd_updater = new BBVM_EDD_SL_Plugin_Updater( 'https://bbvapormodules.com', BBVAPOR_PRO_BEAVER_BUILDER_FILE,
			array(
				'version' => BBVAPOR_PRO_BEAVER_BUILDER_VERSION,
				'license' => $license,
				'item_id' => 688,
				'author'  => 'Ronald Huereca',
				'beta'    => false,
				'url'     => home_url(),
			)
		);
	}

	// Add settings link
	add_action( 'plugin_action_links_' . BBVAPOR_PRO_BEAVER_BUILDER_SLUG, array( $this, 'plugin_settings_link' ) );

	add_action( 'after_plugin_row_' . BBVAPOR_PRO_BEAVER_BUILDER_SLUG, array( $this, 'after_plugin_row' ), 10, 3 );

}

I have a few constants, which I define in the main plugin file. Here’s what they look like.

define( 'BBVAPOR_PRO_PLUGIN_NAME', 'BB Vapor Modules Pro' );
define( 'BBVAPOR_PRO_BEAVER_BUILDER_DIR', plugin_dir_path( __FILE__ ) );
define( 'BBVAPOR_PRO_BEAVER_BUILDER_URL', plugins_url( '/', __FILE__ ) );
define( 'BBVAPOR_PRO_BEAVER_BUILDER_VERSION', '1.1.0' );
define( 'BBVAPOR_PRO_BEAVER_BUILDER_SLUG', plugin_basename( __FILE__ ) );
define( 'BBVAPOR_PRO_BEAVER_BUILDER_FILE', __FILE__ );

Here’s what my after plugin row method looks like.

/**
 * Adds license information
 *
 * @since 1.4.1.
 * @access public
 * @see __construct
 * @param string $plugin_File Plugin file
 * @param array  $plugin_data Array of plugin data
 * @param string $status      If plugin is active or not
 * @return null HTML Settings
 */
public function after_plugin_row( $plugin_file, $plugin_data, $status ) {
	$license = get_site_option( 'bbvm_for_beaver_builder_license', '' );
	$license_status = get_site_option( 'bbvm_for_beaver_builder_license_status', false );
	$options_url = add_query_arg( array( 'page' => 'bb-vapor-modules-pro' ), admin_url( 'options-general.php' ) );
	if( empty( $license ) || false === $license_status ) {
		echo sprintf( '<tr class="active"><td colspan="3">%s <a href="%s">%s</a></td></tr>', __( 'Please enter a license to receive automatic updates.', 'bb-vapor-modules-pro' ), esc_url( $options_url ), __( 'Enter License.', 'bb-vapor-modules-pro' ) );
	}
}

And a screenshot of what it looks like:

After Plugin Row Action

Notice I added a settings link to my plugin. Here’s how I accomplished that.

/**
 * Adds plugin settings page link to plugin links in WordPress Dashboard Plugins Page
 *
 * @since 1.0.0
 * @access public
 * @see __construct
 * @param array $settings Uses $prefix . "plugin_action_links_$plugin_file" action
 * @return array Array of settings
 */
public function plugin_settings_link( $settings ) {
	$admin_anchor = sprintf( '<a href="%s">%s</a>', esc_url($this->get_url()), esc_html__( 'Settings', 'bb-vapor-modules-pro' ) );
	if (! is_array( $settings  )) {
		return array( $admin_anchor );
	} else {
		return array_merge( array( $admin_anchor ), $settings) ;
	}
}

You’ll notice I use $this->get_url(). This is what the code looks like.

/**
 * Return the URL to the admin panel page.
 *
 * Return the URL to the admin panel page.
 *
 * @since 1.0.0
 * @access static
 *
 * @return string URL to the admin panel page.
 */
public static function get_url() {
	$url = self::$url;
	if ( empty( $url ) ) {
		$url = add_query_arg(array( 'page' => self::$slug ), admin_url('options-general.php'));
		self::$url = $url;
	}
	return $url;
}

And finally, here’s my options page code where the user enters the license and validates.

/**
 * Output admin menu
 *
 * @since 1.0.0
 * @access public
 * @see register_sub_menu
 */
public function admin_page() {
	if ( isset( $_POST['submit'] ) &amp;&amp; isset( $_POST['options'] ) ) {


		// Check for valid license
		$store_url = 'https://bbvapormodules.com';
		$api_params = array(
			'edd_action' => 'activate_license',
			'license'    => $_POST['options']['license'],
			'item_name'  => urlencode( 'BB Vapor Modules Pro' ),
			'url'        => home_url()
		);
		// Call the custom API.
		$response = wp_remote_post( $store_url, array( 'timeout' => 15, 'sslverify' => false, 'body' => $api_params ) );

		// make sure the response came back okay
		if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {

			if ( is_wp_error( $response ) ) {
				$license_message = $response->get_error_message();
			} else {
				$license_message = __( 'An error occurred, please try again.', 'bb-vapor-modules-pro' );
			}

		} else {

			$license_data = json_decode( wp_remote_retrieve_body( $response ) );

			if ( false === $license_data->success ) {
				delete_site_option( 'bbvm_for_beaver_builder_license_status' );
				switch( $license_data->error ) {

					case 'expired' :

						$license_message = sprintf(
							__( 'Your license key expired on %s.', 'bb-vapor-modules-pro' ),
							date_i18n( get_option( 'date_format' ), strtotime( $license_data->expires, current_time( 'timestamp' ) ) )
						);
						break;

					case 'disabled' :
					case 'revoked' :

						$license_message = __( 'Your license key has been disabled.', 'bb-vapor-modules-pro' );
						break;

					case 'missing' :

						$license_message = __( 'Invalid license.', 'bb-vapor-modules-pro' );
						break;

					case 'invalid' :
					case 'site_inactive' :

						$license_message = __( 'Your license is not active for this URL.', 'bb-vapor-modules-pro' );
						break;

					case 'item_name_mismatch' :

						$license_message = sprintf( __( 'This appears to be an invalid license key for %s.', 'bb-vapor-modules-pro' ), 'Simple Comment Editing Options' );
						break;

					case 'no_activations_left':

						$license_message = __( 'Your license key has reached its activation limit.', 'bb-vapor-modules-pro' );
						break;

					default :

						$license_message = __( 'An error occurred, please try again.', 'bb-vapor-modules-pro' );
						break;
				}
			}
			if ( empty( $license_message ) ) {
				update_site_option( 'bbvm_for_beaver_builder_license_status', $license_data->license );
				update_site_option( 'bbvm_for_beaver_builder_license', sanitize_text_field( $_POST['options']['license'] ) );
			}
		}
	}
	$license_status = get_site_option( 'bbvm_for_beaver_builder_license_status', false );
	$license = get_site_option( 'bbvm_for_beaver_builder_license', '' );
	?>
	<div class="wrap">
		<form action="<?php echo esc_url( add_query_arg( array( 'page' => 'bb-vapor-modules-pro' ), admin_url( 'options-general.php' ) ) ); ?>" method="POST">
			<?php wp_nonce_field('save_bbvm_beaver_builder_options'); ?>
			<h2><?php esc_html_e( 'Vapor Modules for Beaver Builder', 'breadcrumbs-for-beaver-builder' ); ?></h2>
			<table class="form-table">
				<tbody>
				<tr>
						<th scope="row"><label for="bbvm-license"><?php esc_html_e( 'Enter Your License', 'bb-vapor-modules-pro' ); ?></label></th>
						<td>
							<input id="bbvm-license" class="regular-text" type="text" value="<?php echo esc_attr( $license ); ?>" name="options[license]" /><br />
							<?php
							if( false === $license || empty( $license ) ) {
								printf('<p>%s</p>', esc_html__( 'Please enter your licence key.', 'bb-vapor-modules-pro' ) );
							} else {
								printf('<p>%s</p>', esc_html__( 'Your license is valid and you will now receive update notifications.', 'bb-vapor-modules-pro' ) );
							}
							?>
							<?php
							if( ! empty( $license_message ) ) {
								printf( '<div class="updated error"><p><strong>%s</p></strong></div>', esc_html( $license_message ) );
							}
							?>
						</td>
					</tr>
				</tbody>
			</table>
			<?php submit_button( __( 'Save Options', 'bb-vapor-modules-pro' ) ); ?>
		</form>
	</div>
	<?php
}

Setting up Software Licensing

Install the Software Licensing add-on and configure its settings. You’ll fid it under Downloads->Extensions->Software Licensing. I’ve enabled pro-rated upgrades to various licenses, added a readme option, and added a renewal discount.

Software Licensing Settings
Software Licensing Settings

Set up PDF Invoices

I use the PDF Invoices add-on for Easy Digital Downloads so users can download an invoice.

PDF Invoices Add-on Settings
PDF Invoices Add-on Settings

Here’s an example of what an invoice looks like.

PDF Invoices Sample
PDF Invoices Sample

Setting Up the Download

As I mentioned before, you can set up the download as a draft to get the download ID for automatic updates.

Let’s add some pricing tiers for a starter license, unlimited license, and a lifetime license.

You’ll notice I set up variable pricing and set up three price points as referenced in the screenshot below.

Variable pricing Options
Variable pricing Options

If you drill down to the advanced settings, you can set up recurring (using the Subscriptions add-on) and even allow for a free trial. Here’s an example of a recurring license and a lifetime license.

Starter License with Recurring and 7-day Free Trial
Starter License with Recurring and 7-day Free Trial
Lifetime License
Lifetime License

The next step is you’ll want to set up a download image. Make sure this is square as this will be the plugin image that will be shown to your users.

Plugin Featured Image
Plugin Featured Image

Next is to set up the versioning information (this will be used for automatic updates.

Software Versioning
Software Versioning

Setting the version number is extremely important and you’ll want to update that every time you release a new update to your plugin. Make sure the update file is selected correctly as that will be the source file for your automatic updates.

Finally, let’s get to the readme portion. You’ll want to set a readme file source. In my case, I use a GitHub repository to store my readme. My server will not allow me to upload readme.txt files, so you’ll want to be sure to find a location where your readme file will be stored. Please note that it has to be in a valid readme format. You can test that using a WP Readme Validator.

Readme Settings

The readme section also includes a link to upload your plugin banner images, set a plugin homepage, and show a readme to users of your plugin. Here’s what it looks like when there’s an update.

Plugin Update Screen
Plugin Update Screen

And here’s what the remote readme looks like.

Plugin Readme Popup
Plugin Readme Popup

Additional Extensions

  1. I use Discounts Pro to set up discounts for my products. It’s really handy.
  2. I use the Mailchimp extension to allow users to sign up to my mailing list during checkout
  3. I use the Manual Purchases extension to send out licenses to users manually.
  4. I use the Auto Register extension to allow users to create accounts and login.

Again, all these extensions by themselves cost lots of $$$, so to save you some time and money, just get the All Access Pass.

Conclusion

Setting up a plugin download can be a chore, but hopefully this tutorial gets you in the right direction. If you have any questions, feel free to leave a comment.

Posted in

Ronald Huereca

Ronald Huereca is the CEO of MediaRon LLC and enjoys WordPress plugin and theme development.

Leave a Comment

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

Scroll to Top