How to make a Plugin licensable
If you are offering your plugin on the SmartStore Community Marketplace, you can easily make it licensable. If a plugin is licensable, it will behave as follows:
- A shop administrator can use your plugin for free for 30 days (demonstration mode). After this period, the plugin has to be licensed.
- A shop administrator receives a license key after purchasing the plugin on the SmartStore Marketplace. To activate the license key, it has to be entered in the SmartStore.NET backend (Plugins > Manage Plugins). A key is only valid for the IP address that originally activated it.
- The license status is periodically checked to ensure that nobody is misusing your plugin.
The following steps are necessary to make a plugin licensable:
- Add a reference of
lib\SmartStore.Licensing\SmartStore.Licensing.dll
to your plugin. Open your main plugin class (typically derived from one of the base plugin classes:
BasePlugin
or justIPlugin
.). Decorate it with theLicensableModule
attribute.Decorate your controllers or action methods with the
LicenseRequired
filter attribute, or alternatively, callLicenseChecker.Check[State]()
in your code.
The LicensableModule Attribute
The attribute is intended to mark a plugin as a licensed piece of code where the user has to enter a license key that has to be activated.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public class LicensableModuleAttribute : Attribute { /// <summary> /// Whether one license (key) is valid for all stores. Otherwise a new key is required for each store. /// </summary> public bool HasSingleLicenseForAllStores { get; set; } }
The LicenseRequired Filter Attribute
You can decorate either a whole controller class or just a single action method with the LicenseRequired
attribute. This attribute internally calls LicenseChecker.Check()
right before your action is processed, giving it the opportunity to block the action. If the license isn't valid, the LicenseRequiredView
will be rendered by default, which you can override by setting the property ViewName
on the attribute. Alternatively, you could just display a notification for which you can use the properties NotifyOnly
,
NotificationMessage
and NotificationMessageType
. Ajax requests will be recognized automatically, and a suitable response will be generated according to the negotiated content type (either JSON or HTML).
If you want to block certain methods or even your entire plugin when it is in demo mode, you need to set the property BlockDemo to True, otherwise everything will be accessible in the demo mode, as the value of BlockDemo is False by default.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)] public class LicenseRequiredAttribute : ActionFilterAttribute { /// <summary> /// Plugin system name /// </summary> public string PluginSystemName { get; set; } /// <summary> /// Name of layout view for "License Required" message. "~/Views/Shared/_ColumnsOne.cshtml" by default. /// </summary> public string MasterName { get; set; } /// <summary> /// Name of view for "License Required" message. "LicenseRequired" by default. /// </summary> public string ViewName { get; set; } /// <summary> /// Whether to render an empty result if the plugin is in an unlicensed state. /// </summary> public bool EmptyResultWhenUnlicensed { get; set; } /// <summary> /// Whether to block the request if license is in demo mode. /// </summary> public bool BlockDemo { get; set; } /// <summary> /// Whether to only output a notification message and not to replace the action result. /// </summary> public bool NotifyOnly { get; set; } /// <summary> /// A message to output if the plugin is in an unlicensed state. /// </summary> public string NotificationMessage { get; set; } /// <summary> /// The type of a notification message. Default is 'error'. /// </summary> public string NotificationMessageType { get; set; } ...
The License Checker
The license checker is a set of static functions for license validation within SmartStore.Licensing.dll
. As a plugin developer, you probably get into situations where you want to check the license status explicitly - that's what the license checker is for. Always provide the system name of the plugin, not the system name of a provider.
LicenseChecker.CheckState
Checks the state of a license. This method is an overload of LicenseChecker.Check
method. Available states (return values) are Unlicensed, Demo and Licensed.
/// Checks the state of a license. Fails if the request is sent from a different IP address than the one that activated the license. /// <param name="systemName">Plugin system name</param> /// <param name="url">Absolute URL</param> /// <returns>Licensing state</returns> public static LicensingState CheckState(string systemName, string url = null)
Typically, you check the state of the license when you do not need or want any output, or want to cut the output.
LicenseChecker.Check
This is the main method for checking the state of a license. Returns a LicenseCheckerResult
object with various information about the status check. If you need a stringified version of the result (German and English localization supported), call the ToString()
method of the returned object.
/// Checks the state of a license. Fails if the request is send from a different IP address than the one that activated the license.</summary> /// <param name="systemName">Plugin system name</param> /// <param name="url">Absolute URL</param> /// <returns>Result of the operation</returns> public static LicenseCheckerResult Check(string systemName, string url = null)
var result = LicenseChecker.Check("MyCompany.MyExtraordinaryPlugin"); if (result.State == LicensingState.Unlicensed) { // has no active license, thus not allowed to use my plugin string outputMessage = result.ToString().Replace("\r\n", "<br />"); ...
Usage Scenario
Imagine you have developed a plugin to communicate with an ERP system. Furthermore, your plugin transmits data to a web service whenever an order is placed in your shop and consumes another web service to keep your product data up-to-date. If you decide to allow the product data to be updated completely in the demo mode of your plugin, it may be sufficient for the plugin user to import the product data only once. Therefore, you should interrupt the routine that's responsible for updating product data after a certain number of products have been updated. To do so, you would use the CheckState()
method, which checks whether the state is Demo and stops the routine accordingly (see code example 1). This way, the user can see a demonstration of the actual function without getting the whole pie. Order events should, of course, be processed and transmitted to the ERP system completely for demonstration purposes, as it's way too difficult to keep track of the number of processed orders. However, when the demonstration period is over, no more orders should be processed. Therefore, you would use the CheckState()
method to check whether the state is Unlicensed and to stop the event accordingly (see code example 2).
private void ProcessProducts() { bool isDemo = LicenseChecker.CheckState("MyCompany.MyPlugin") == LicensingState.Demo; ... if (isDemo) { // leave after 5 products if plugin is in demo mode products = products.Take(5); } foreach (var product in products) { UpdateProduct(product); } ...
public class EventConsumer : IConsumer<EntityInserted<Order>> { public void HandleEvent(EntityInserted<Order> eventMessage) { if (LicenseChecker.CheckState("MyCompany.MyPlugin") == LicensingState.Unlicensed) return; ...
Examples and Special Cases
LicenseChecker.CheckState()
and LicenseChecker.Check()
are checking the license state against the current request URL or as a fallback against the current store URL (if there is no HTTP request object, which is usually the case in background tasks). The second parameter of these methods allows you to provide a different URL. The best example for this case is the promotion feed plugins. These plugins automatically create a feed file for each store (in a multi-store installation) within a background task. The creation of the feed file should be skipped if there is no active license for that particular store.
public void CreateFeed(TaskExecutionContext context) { // fileCreation is of type FeedFileCreationContext (see Framework\Plugins\FeedPluginCore.cs) Helper.StartCreatingFeeds(fileCreation => { // fileCreation.Store is an instance of the store entity var result = LicenseChecker.Check(Helper.SystemName, fileCreation.Store.Url); if (result.State == LicensingState.Unlicensed) { // log the status check result into the log file and skip processing fileCreation.Logger.Error(result.ToString()); return true; } ... create the feed file return true; }); }
Additionally, a payment plugin can hide its payment methods at checkout if there is no active license. To enable this to work, you must override the PaymentMethodBase.IsActive
property.
public override bool IsActive { get { try { var result = LicenseChecker.CheckState("MyCompany.MyExtraordinaryPlugin"); return (result != LicensingState.Unlicensed); } catch (Exception exc) { // _logger is an instance of ILogger _logger.InsertLog(LogLevel.Error, exc.Message, exc.ToString()); } return true; } }