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
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. class_exists
check.
if ( ! class_exists( 'EDD_SL_Plugin_Updater' ) ):
class EDD_SL_Plugin_Updater {
...
}
endif;
Code language: PHP (php)
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(),
)
);
}
}
Code language: PHP (php)
I’m assuming you have an option your_plugin_license
YOUR_PLUGIN_VERSION
item_id
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(),
)
);
}
Code language: PHP (php)
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
Code language: PHP (php)
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') );
}
Code language: PHP (php)
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 );
}
Code language: PHP (php)
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__ );
Code language: PHP (php)
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' ) );
}
}
Code language: PHP (php)
And a screenshot of what it looks like:
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) ;
}
}
Code language: PHP (php)
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;
}
Code language: PHP (php)
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'] ) && 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
}
Code language: PHP (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.
Set up PDF Invoices
I use the PDF Invoices add-on for Easy Digital Downloads so users can download an invoice.
Here’s an example of what an invoice looks like.
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.
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.
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.
Next is to set up the versioning information (this will be used for automatic updates.
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.
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.
And here’s what the remote readme looks like.
Additional Extensions
- I use Discounts Pro to set up discounts for my products. It’s really handy.
- I use the Mailchimp extension to allow users to sign up to my mailing list during checkout
- I use the Manual Purchases extension to send out licenses to users manually.
- 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.
Thank you for the code example but i wish it would work. It doesnt. It fails at line 12 Adds license information. Should that be Public Function inside a class or Function? If i call it Function the next line down fails as well.
Hello, i collected all your classes and place them into a file that is in the /includes folder. Then i included it into my main plugin just like the SSL Updater. But im stuck right here. Not sure what to do next to activite its functions. I tried the following;
$run = new GBM_Admin();
and the plugin now shows the settings option which is what i was hoping to see. But when i click on that i get this message;
Sorry you dont have permission to access this page.
Im sure im missing something more.
What would be the code i need to add after the above to execute the functions in that class? Kind of stuck there.
Thanks
Hey all,
I have released a “part two” of this article that includes a full plugin that demonstrates how to add a license to your plugin.
https://mediaron.com/integrating-easy-digital-downloads-software-licensing-into-your-plugin/
Hi. But if we are using recurring payments do we really need this plugin? I mean if someone buys one of my pricing packages and that package is billed every year, why do I need the Software Licensing for?
Mostly to control licenses so that users can auto-update.
Hey Ron, thank you very much for all this including the new ajax version.
I uploaded the entire folder to my wordpress install and tested it. The key did not work so i assume you removed its ability to register, that’s fine.
When i install this i see a label followed by the license filed then under that a send button. Lets say i upload another app that has the same code in it. Does a new set of label followed by a license field appear below the existing one?
How would i go about including this in my application? Say i have my php script i would have to include something there at least at the top of the script im protecting.
Finally, i dont want users to be able to see the class codes. Can i encrypt it using ioncube?
Thanks