This document is indented to provide some information about plugin development:
What are plugins and what can be achieved through plugins?
Where to start the development?
Tooling and APIs to deploy plugins
Kill Bill Plugins
Kill Bill Plugins are based on the OSGI standard to provide a way to write isolated code on top of the Kill Bill platform and interact with the system through different ways:
They can be called from the Kill Bill platform through plugin APIs. This happens when a plugin that implements one of those APIs (or 'SPI' to be more precise) was correctly registered in the system.
They can receive notifications (bus events) from the Kill Bill platform
They can make API calls to Kill Bill
Here are a few examples:
Notification Plugin: A plugin registered to receive bus events. When a bus event is being delivered to the plugin, the plugin calls back Kill Bill through API calls to retrieve additional state for this event or to change the state in the system
Plugin that registered as an implementor of a specific plugin API: A payment plugin for instance would have registered itself as an implementor of the PaymentPluginApi and the payment system would then invoke that plugin for issuing payment operations. The plugin would have the ability to call back Kill Bill using APIs to retrieve more state or create/change state in the system.
A Kill Bill plugin will either implement one (or several) plugin API(s) and/or also listen to Kill Bill bus events to be notified of changes and take appropriate actions. It often makes sense to specialize plugins and have them implement only one of the plugin APIs, but this is a design choice, and there is nothing preventing a plugin from implementing multiple of those APIs. Although we don’t recommend it, a plugin could implement the PaymentPluginApi and the PaymentControlPluginApi, and that would qualify it as a 'payment and payment control plugin'.
Kill Bill Plugins can be used in a variety of ways:
Extend Kill Bill subsytem: Payment plugins are a good example of such plugins; they extend the core payment functionality by connecting to third party systems
Provide additional business logic: Payment control plugins and invoice plugins allow to intercept the requests so as to modify it, abort it, or change state on the fly.
Listen to system events: The analytics plugin is a good example of such plugin
Kill Bill Plugins have full power (and therefore need to be designed and tested carefully):
They have access to the database so as to maintain their own state. However they should not interact with the Kill Bill core tables directly but rely on APIs to retrieve and change state,
They have access to system properties
They have the ability to export their own HTTP endpoints under a
They receive all events in the system
They are isolated from the rest of the code and can use their own libraries (versions) without risk of conflict
Where to Start?
A good starting point is to assess what the plugin should do and then based on that, read the various plugin docs in the developer guide section that describe the different types of plugins offered in Kill Bill:
The next stage is to identify existing (similar) plugins which could be used as a starting point to write the code. At this point, this becomes a normal software development cycle, including writing unit tests for that plugin (independent of Kill Bill).
It is recommened to clone some of our existing plugins to get started. For
java, the hello world plugin is a good starting point. For
ruby, we also provide a hello world plugin. Depending on the type of plugin to be built, there may be a better example to look at (e.g. look at an existing payment plugin if you are building a new gateway integration).
We provide two plugin frameworks that can be used to implement some of the work that plugins need to do:
Also, for internal reference, you might want to take a look at KillbillActivatorBase, which provides all the abstractions that plugins require (access to java APIs, database connections, system properties, logging, …).
Java versions 6+ are supported.
java plugins rely on the
maven-bundle-plugin to produce the correct OSGI plugin (with the correct
MANIFEST.MF). It is recommended to start from a plugin that already works, as the build configuration can be quite tricky. A good example is to look at the
pom.xml from the hello world plugin
The build will produce a
jar under the
target directory which should contain all the classes and configuration files directly accessible for that plugin. OSGI also provides a way to import classes that are exported by other plugins and this is configured in the
Import-Package section of the
maven-bundle-plugin. The hello world plugin provides some default based on the classes exported by the system bundle in Kill Bill. Kill Bill offers a system property,
org.killbill.osgi.system.bundle.export.packages.extra, to specify additional packages to be exported by the system bundle and that could in turn be imported by the plugin. Desiging which packages are served by the plugin jar and which one are imported from another bundle (and mostly the system bundle inside Kill Bill) is one of the challenges of the OSGI mechanism.
Once the pom is correctly written, building the plugin is straightforward:
mvn clean install # Look for entry under target <artifact_id>-<version>.jar
Ruby 2.1+ or JRuby 1.7.20+ is recommended. If you don’t have a Ruby installation yet, use RVM:
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 \curl -sSL https://get.rvm.io | bash -s stable --ruby
After following the post-installation instructions, you should have access to the
Install the following gems:
gem install bundler gem install jbundler
Follow these steps, making sure each one is successful before moving on to the next one:
rm -f Gemfile.lock Jarfile.lock .jbundler/classpath.rb bundle install jbundle install # Cleanup output directories bundle exec rake killbill:clean # Build your plugin gem in the pkg/ directory bundle exec rake build # Build the Killbill plugin in the pkg/ directory bundle exec rake killbill:package
Examples of Ruby Plugins
We provide a hello world ruby plugin that can be used as starting point. Make sure to correctly update the
*.gemspec and the
pom.xml to correctly reflect the gem name, and maven coordinates of your plugin (if you decide to publish your plugin to Nexus).
We also provide a collection of Ruby Snippets, which shows how to call Kill Bill APIs from Ruby plugins.
In its simplest form, deploying a plugin means placing the plugin binary at the right place on the filesystem. Kill Bill will scan the filesystem on startup and will start all the plugins that were detected.
Kill Bill will use the value of the system property
org.killbill.osgi.bundle.install.dir to determine the root of plugin directory structure.
By default, this value is set to
/var/tmp/bundles, as indicated by the Kill Bill OSGIConfig file.
The directory structure looks like the following:
root (org.killbill.osgi.bundle.install.dir) |_sha1.yml |_platform |_plugins |_java |_ruby |_plugin_identifiers.json
platform, we will find the following:
jruby.jar: the Runtime JRuby jar that is loaded into killbill for each ruby plugin
A set of pure OSGI bundles (unrelated to Kill Bill plugins) and required for things like OSGI logging, OSGI console, …
ruby, we will find one entry per plugin per version.
For instance, if we had installed two versions for the ruby
stripe plugin, we would see the following (
SET_DEFAULT is a symbolic link that point to the default active version):
ruby |_killbill-stripe |_ 3.0.2 |_ 3.0.1 |_ SET_DEFAULT
sha1.yml is a used by the
KPM tool to keep track of artifacts that were already downloaded to avoid dwonloading binaries already present on the filesystem. KPM also offers the
--force-download to override that behavior.
plugin_identifiers.json is used to keep a mapping between the
pluginKey (the user visible plugin identifer), and the
pluginName (runtime identifier used by Kill Bill when scanning the filesystem). The next section provides more details about those.
Plugin Coordinates, Plugin Key, Plugin Name, …
Today, both our
java plugins are released through maven and are therefore identified through their maven coordinates. We might support other schemes in the future but today this is the only way we offer to download and install publicly released plugins. Plugin Coordinates are too cumbersome to manipulate though and are unsuitable for non-published plugins (typical use case for a plugin being developed), so we introduced some identifers.
As mentioned earlier, Kill Bill will scan the filesystem (
org.killbill.osgi.bundle.install.dir) on start-up to detect and then start all the plugins. The name on the filesystem (e.g. in our previous example
killbill-stripe) constitutes what we call the
When installing using KPM, the
pluginName is dependent on how the plugin was packaged and also differs between ruby and java. For well known publicly available Kill Bill plugins, we adopted a (sane) convention, but we have no way to enforce that convention for third party plugins. Also, note that we could change the name of
foo on the filesystem (
mv killbill-stripe foo) and then suddenly Kill Bill would see that plugin as being the
foo plugin. Therefore, the
pluginName is not a reliable way to identify the plugin, and is used solely by Kill Bill as an runtime identifier.
pluginKey is the identifier for the plugin and is used for all the user visible operations, whether through the KPM command line tool or whether using the Plugin Management APIs.
There is a distinction to be made between publicly released Kill Bill plugins and third party plugins:
(Publicly Released) Kill Bill Plugins: All the plugins developed by the Kill Bill core team are maintained in a repository (we provide today a simple file-based repository, but this may change in the future as we start accepting certified third-party plugins). Each entry in that repository is identified by a key, and that key is the
Third party plugins: For third party plugins, the key is specified at the time the plugin gets installed. The key must be of the form
<prefix>::<something>to make sure there is no name collision with Kill Bill plugin keys.
Deploying by Hand
java plugins, deploying by hand consists of building the self contained OSGI jar, and copying that jar at the right location. For example, the
adyen plugin with a version with version
0.3.2 would show up as the following:
java |_adyen-plugin |_ 0.3.2 |_ adyen-plugin-0.3.2.jar
ruby plugins, deploying by hand consists in building the package (
tar.gz) and untaring that package at the right place: For example, the
stripe plugin with a version
3.0.2 would show up as the following:
ruby |_killbill-stripe |_ 3.0.2 |_ ROOT |_ .... (ruby code and gems) |_ boot.rb |_ config.ru |_ killbill.properties |_tmp
In order to make it easy to deploy those plugins we created a special rake task that will copy and untar plugin entries at the right place:
# Deploy the plugin (and clobber a previous version if needed) in /var/tmp/bundles. # Alternatively, you can manualy deploy the .tar.gz or .zip artifact from the pkg/ directory bundle exec rake killbill:deploy[true]
Note that if you don’t need any custom configuration, make sure to delete the default YAML configuration file
/var/tmp/bundles/plugins/ruby/killbill-*/*/*.yml. In development mode, i.e. when you are running tests outside of Kill Bill (see
rake test:spec and
rake test:remote:spec), the database configuration is specified in that YAML file (payment plugins rely on a couple of database tables, principally to keep the credit card tokens and gateway-specific details for transactions, such as reference codes). By default, the plugin will use SQLite. If you uncomment the part of the YAML file below the comment "In Kill Bill", this will tell the plugin to use the JNDI connection exposed by Kill Bill instead. This is the default in case the file isn’t present (or if the database section is missing).
Also, in the case of
ruby plugin (and as mentionned before), the correct version of the
jruby.jar must exist (and be named that way) under the
platform directory entry. The correct version must match the Kill Bill version (or more precisely the version of the platform used for the specific version of killbill).
Kill Bill plugins can access Kill Bill properties through the use of a special interface OSGIConfigProperties. System properties passed to the JVM and properties from the
killbill.properties configuration file are then accessible to the plugins and can be used to tweak the behavior of the plugin as needed.
Property files can be used to configure global settings for a plugin. Those property files need to be part of the archive (the OSGI mechanism will make sure these are only visible to the particular plugin):
javaplugins, the property file needs to be on the classpath (
rubyplugins, the property file is often located at the root of the archive
There is no restriction on the format of the propery file, but typically
ruby plugins will use
yml files and
java plugins will rely on
The two previous mechanisms work well for global settings, but are inadequate to configure the plugins on a per-tenant fashion (e.g for a payment plugin interacting with a payment gateway, different credentials might be needed for different tenants). In those situations, Kill Bill provides APIs to upload/retrieve/delete per-tenant plugin configurations:
# Upload new config curl -v \ -X POST \ -u admin:password \ -H 'X-Killbill-ApiKey: bob' \ -H 'X-Killbill-ApiSecret: lazar' \ -H 'X-Killbill-CreatedBy: admin' \ -H 'Content-Type: text/plain' \ -d '<CONFIG>' \ http://127.0.0.1:8080/1.0/kb/tenants/uploadPluginConfig/<pluginName>
<CONFIG> is treaded as a string and it could be the content of an
json file, a list of
key-value parameters, …
# Retrieve config curl -v \ -u admin:password \ -H 'X-Killbill-ApiKey: bob' \ -H 'X-Killbill-ApiSecret: lazar' \ -H 'X-Killbill-CreatedBy: admin' \ -H 'Content-Type: application/json' \ http://127.0.0.1:8080/1.0/kb/tenants/uploadPluginConfig/<pluginName>
# Delete config curl -v \ -X DELETE \ -u admin:password \ -H 'X-Killbill-ApiKey: bob' \ -H 'X-Killbill-ApiSecret: lazar' \ -H 'X-Killbill-CreatedBy: admin' \ http://127.0.0.1:8080/1.0/kb/tenants/uploadPluginConfig/<pluginName>
At a high level, the mechanism works in the following way:
The administrator uses the kill bill API (or Kaui) to upload the configuration
Kill Bill stores the config in the
tenant_kvstable using a
PLUGIN_CONFIG_<pluginName>and sets the
tenant_valuewith the config provided
Kill Bill broadcasts the change across the cluster of nodes and emits a configuration bus event:
The plugin code is responsible to listen to these events and take appropriate action to reload/delete its configuration for that specific tenant.
Note that when relying on the plugin frameworks, some amount of work is already provided:
Breakpoints can be set directly into the Java plugin code.
The wiki provides steps to remote debug Ruby plugins.