Installation

AWS

Already familiar with AWS? Running Kill Bill on AWS using our One-Click installer is the easiest and fastest way to get started.

Docker

You can run Kill Bill locally using our official Docker images.

If you are not yet familiar with Docker, take a look at the Get Started with Docker guide. It is quite easy to get it up and running, and will greatly simplify the Kill Bill stack setup (Tomcat and MySQL configuration will be done for you).

The following will assume you are deploying Kill Bill locally. Take a look at the Docker documentation to adapt the commands to your environment. Our blog has also lots of tips on how to deploy to popular cloud providers.

First, make sure docker and docker-compose are installed.

For example, on a Mac:

brew install docker docker-compose

Or on Ubuntu:

sudo apt-get install docker docker-compose virtualbox

We will assume the Docker containers are accessible at 127.0.0.1 in the rest of this guide (if you are using docker-machine, you would need to retrieve the IP address of the Docker machine first).

Next, create a docker-compose.yml file similar to the one below:

Note: You should change the version in docker-compose.yml file according to your installed Docker version.

version: '3.2'
volumes:
  db:
services:
  killbill:
    image: killbill/killbill:0.20.13
    ports:
      - "8080:8080"
    environment:
      - KILLBILL_DAO_URL=jdbc:mysql://db:3306/killbill
      - KILLBILL_DAO_USER=root
      - KILLBILL_DAO_PASSWORD=killbill
  kaui:
    image: killbill/kaui:1.0.6
    ports:
      - "9090:8080"
    environment:
      - KAUI_CONFIG_DAO_URL=jdbc:mysql://db:3306/kaui
      - KAUI_CONFIG_DAO_USER=root
      - KAUI_CONFIG_DAO_PASSWORD=killbill
      - KAUI_KILLBILL_URL=http://killbill:8080
  db:
    image: killbill/mariadb:0.20
    volumes:
      - type: volume
        source: db
        target: /var/lib/mysql
    expose:
      - "3306"
    environment:
      - MYSQL_ROOT_PASSWORD=killbill

and run:

docker-compose up

3 containers will start:

  • one for MariaDB (shared database, used by both Kill Bill and Kaui)

  • one for Kill Bill (accessible on port 8080)

  • one for Kaui (accessible on port 9090)

The startup sequence lasts a couple of minutes. It is ready when you see the message "INFO: Server startup". If it takes a long time or if the container crashes, verify you have enough memory allocated to Docker. On a Mac for instance, go to Docker Desktop Preferences and set the Memory to 4GiB in the Advanced panel.

You can now log-in to Kaui by going to http://127.0.0.1:9090. Default credentials are:

  • username: admin

  • password: password

You can also go to http://127.0.0.1:8080/api.html to explore our APIs.

Tomcat

Users familiar with Java technologies can also install Kill Bill directly in their Web container of choice, such as Tomcat.

If you don’t have it yet, you can download the Tomcat container here (get the Core binary distribution). The documentation is available here.

In the rest of this guide, we will assume you have installed Tomcat in a directory called CATALINA_HOME.

Notes:

  • Unlike other installation methods, you will also need to install and configure your database manually. See our deployment guide for more details.

  • Installation on Windows might work, but isn’t tested nor recommended by the core team.

  • Further Tomcat optimizations might be required depending on your environment, such as installing the Apache Tomcat Native Library (done for you when using Ansible, Docker, or in our Cloud Marketplace instances).

Kill Bill

The Kill Bill configuration properties need to be added at the end of the file CATALINA_HOME/conf/catalina.properties. The minimal set of system properties required is the ones for the jdbc parameters (refer to our configuration guide for more details). For example (update the values as needed):

org.killbill.dao.url=jdbc:mysql://127.0.0.1:3306/killbill
org.killbill.dao.user=killbill
org.killbill.dao.password=killbill
org.killbill.billing.osgi.dao.url=jdbc:mysql://127.0.0.1:3306/killbill
org.killbill.billing.osgi.dao.user=killbill
org.killbill.billing.osgi.dao.password=killbill

Download the current stable version of the Kill Bill war from Maven Central (make sure to download the killbill-profiles-killbill-X.Y.Z.war file and not the jetty-console.war, jar-with-dependencies.war or jar-with-dependencies-sources.war one) and place it under CATALINA_HOME/webapps/ROOT.war (delete the directory CATALINA_HOME/webapps/ROOT if it exists).

Once this is setup, you can start the Tomcat container using the script CATALINA_HOME/bin/startup.sh. The server will be started in the background but logs can be followed at CATALINA_HOME/logs/catalina.out.

Kaui

You can follow similar steps for the installation of Kaui.

Setup the system properties as follows:

kaui.url=http://127.0.0.1:8080
kaui.db.url=jdbc:mysql://127.0.0.1:3306/kaui
kaui.db.username=killbill
kaui.db.password=killbill

And download the current version of the Kaui war from Maven Central.

Using Kill Bill with Kaui

Go to http://127.0.0.1:9090. You will be prompted for a username and password. Both Kill Bill and Kaui support role based access control (RBAC), where you can configure fine-grained permissions for your users. The default set of credentials is admin/password, which grants full access.

Because Kill Bill supports multi-tenancy (where each tenant has its own data, configuration, etc.), the next step is to create your own tenant. We will assume the api key is bob and api secret lazar in the rest of this guide.

Modifying the Catalog

The Kill Bill catalog contains products and plans definitions. This XML configuration file is really powerful and offers various options for handling trials, add-ons, upgrades/downgrades, etc. For more details on its features, read the Subscription Billing manual.

For basic use cases, Kaui also lets you configure simple (subset of what is supported through XML configuration) plans through the UI, so you don’t have to generate the catalog XML manually. This is available on your tenant configuration page, that you can access by clicking on your tenant name at the top right corner of every Kaui page.

For this tutorial, create 2 plans: standard-free (free plan) and standard-monthly (premium plan), associated with a single Standard product (the product category is BASE). We could have just defined standard-monthly, but that way you could make free users subscribe to the free plan. This is useful for reporting for example (to track how long it took to upsell them, etc.)

Note that we haven’t defined any trial period.

multi gateways standard free kaui multi gateways standard monthly kaui multi gateways catalog kaui

Creating Your First Account

We will assume that users going to your site have to create an account in your system. When they do, you will need to create a mirrored account in Kill Bill.

To do so in Kaui, click the CREATE NEW ACCOUNT link at the top of the page.

Notes:

  • The Kill Bill External key field should map to the unique id of the account in your system (should be unique and immutable). Kill Bill will auto-generate an id if you don’t populate this field

  • There are many more fields you can store (phone number, address, etc.) — all of them are optional. Keep local regulations in mind though when populating these (PII laws, GDPR, etc.).

Adding a Payment Method

To trigger payments, Kill Bill will need to integrate with a payment provider (such as Stripe or PayPal). Each means of payment (e.g. a credit card) will have a payment method associated with it.

For simplicity in this tutorial, we will assume your customers send you checks. To create the payment method in Kaui, click the + next to Payment Methods on the main account page. The plugin name should be set to __EXTERNAL_PAYMENT__, leave all other fields blank and make sure the checkbox Default Payment Method is checked.

Once you are ready to integrate with a real payment processor, all you’ll have to do is to create a new payment method for that account. The rest of this tutorial will still apply.

Creating Your First Subscription

Let’s now try to subscribe a user to the Standard plan. This is the call that would need to be triggered from your website, when the user chooses the premium plan on the subscription checkout page.

In Kaui, click the Subscriptions tab then the + by Subscription Bundles (a subscription bundle is a collection, a bundle, of subscriptions, containing one base subscription and zero or more add-ons). Select the standard-monthly plan in the dropdown. You can also specify an optional (but unique) key to identify this subscription.

Because there is no trial period and because billing is performed in advance by default, Kill Bill will have automatically billed the user for the first month.

You should see the invoice and the payment by clicking on the Invoices and Payments tabs.

Kill Bill will now automatically charge the user on a monthly basis. You can estimate the amount which will be billed at a future date by triggering a dry-run invoice. On the main account page, in the Billing Info section, click the Trigger invoice generation wand (specify a date at least a month in the future).

Using Kill Bill from Your App

Now that you are familiar with the basics, the next step is to integrate Kill Bill in your application using our APIs. Our API documentation contains snippets to help you get started.

Note: This section assumes you are already familiar with the core concepts of Kill Bill. If you aren’t, make sure to go back to the previous section first.

Creating Your First Account

curl -v \
     -X POST \
     -u admin:password \
     -H 'X-Killbill-ApiKey: bob' \
     -H 'X-Killbill-ApiSecret: lazar' \
     -H 'X-Killbill-CreatedBy: tutorial' \
     -H 'Content-Type: application/json' \
     -d '{ "name": "John Doe", "currency": "USD"}' \
     'http://127.0.0.1:8080/1.0/kb/accounts'

The cURL output should return a Location header which contains the unique identifier (ID) of this account: Location: http://127.0.0.1:8080/1.0/kb/accounts/1cb6c8b0-1df6-4dd5-9c7c-2a69bab365e8

import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.client.KillBillClientException;
import org.killbill.billing.client.KillBillHttpClient;
import org.killbill.billing.client.RequestOptions;
import org.killbill.billing.client.api.gen.AccountApi;
import org.killbill.billing.client.model.gen.Account;

KillBillHttpClient client = new KillBillHttpClient("http://127.0.0.1:8080",
                                                   "admin",
                                                   "password",
                                                   "bob",
                                                   "lazar");
AccountApi accountApi = new AccountApi(client);

Account body = new Account();
body.setName("John Doe");
body.setCurrency(Currency.USD);

RequestOptions requestOptions = RequestOptions.builder()
                                              .withCreatedBy("tutorial")
                                              .build();
Account account = accountApi.createAccount(body, requestOptions);
require 'killbill_client'

KillBillClient.url = 'http://127.0.0.1:8080'

options = {
  :username => 'admin',
  :password => 'password',
  :api_key => 'bob',
  :api_secret => 'lazar'
}

body = KillBillClient::Model::Account.new
body.name = 'John Doe'
body.currency = 'USD'

account = body.create('tutorial', nil, nil, options)
import killbill

killbill.configuration.base_uri = 'http://127.0.0.1:8080'
killbill.configuration.username = 'admin'
killbill.configuration.password = 'password'

account_api = killbill.api.AccountApi()
body = killbill.models.account.Account(name='John Doe', currency='USD')
account = account_api.create_account(body, 'tutorial', 'bob', 'lazar')
import (
	"context"
	"encoding/base64"
	"github.com/go-openapi/runtime"
	httptransport "github.com/go-openapi/runtime/client"
	"github.com/go-openapi/strfmt"
	"github.com/killbill/kbcli/kbclient"
	"github.com/killbill/kbcli/kbclient/account"
	"github.com/killbill/kbcli/kbmodel"
)

trp := httptransport.New("127.0.0.1:8080", "", nil)

authWriter := runtime.ClientAuthInfoWriterFunc(
	func(r runtime.ClientRequest, _ strfmt.Registry) error {
		encoded := base64.StdEncoding.EncodeToString([]byte("admin:password"))
		if err := r.SetHeaderParam("Authorization", "Basic "+encoded); err != nil {
			return err
		}
		if err := r.SetHeaderParam("X-KillBill-ApiKey", "bob"); err != nil {
			return err
		}
		if err := r.SetHeaderParam("X-KillBill-ApiSecret", "lazar"); err != nil {
			return err
		}
		return nil
	})

createdBy := "tutorial"
defaults := kbclient.KillbillDefaults{
	CreatedBy: &createdBy,
}

client := kbclient.New(trp, strfmt.Default, authWriter, defaults)
body := &kbmodel.Account{
	Name:     "John Doe",
	Currency: "USD",
}

newAccount, err := client.Account.CreateAccount(
	context.Background(),
	&account.CreateAccountParams{
		Body:                  body,
		ProcessLocationHeader: true,
	})
if err == nil {
	print(newAccount.GetPayload().AccountID)
}
require_once(__DIR__ . '/vendor/autoload.php');

$config = Killbill\Client\Swagger\Configuration::getDefaultConfiguration();
$config->setHost('http://127.0.0.1:8080')
       ->setUsername('admin')
       ->setPassword('password')
       ->setApiKey('X-Killbill-ApiKey', 'bob')
       ->setApiKey('X-Killbill-ApiSecret', 'lazar');

$accountApi = new Killbill\Client\Swagger\Api\AccountApi(null, $config);

$accountData = new Killbill\Client\Swagger\Model\Account();
$accountData->setName('John Doe');
$accountData->setCurrency('USD');

$account = $accountApi->createAccount($accountData, 'tutorial', NULL, NULL);

Adding a Payment Method

Note: replace 1cb6c8b0-1df6-4dd5-9c7c-2a69bab365e8 below with the ID of your account.

curl -v \
     -X POST \
     -u admin:password \
     -H 'X-Killbill-ApiKey: bob' \
     -H 'X-Killbill-ApiSecret: lazar' \
     -H 'X-Killbill-CreatedBy: tutorial' \
     -H 'Content-Type: application/json' \
     -d '{ "pluginName": "__EXTERNAL_PAYMENT__" }' \
     http://127.0.0.1:8080/1.0/kb/accounts/1cb6c8b0-1df6-4dd5-9c7c-2a69bab365e8/paymentMethods?isDefault=true
import java.util.UUID;

import org.killbill.billing.client.KillBillClientException;
import org.killbill.billing.client.KillBillHttpClient;
import org.killbill.billing.client.RequestOptions;
import org.killbill.billing.client.api.gen.AccountApi;
import org.killbill.billing.client.model.gen.PaymentMethod;

KillBillHttpClient client = new KillBillHttpClient("http://127.0.0.1:8080",
                                                   "admin",
                                                   "password",
                                                   "bob",
                                                   "lazar");
AccountApi accountApi = new AccountApi(client);

PaymentMethod body = new PaymentMethod();
body.setIsDefault(true);
body.setPluginName("__EXTERNAL_PAYMENT__");

RequestOptions requestOptions = RequestOptions.builder()
                                              .withCreatedBy("tutorial")
                                              .build();
UUID accountId = UUID.fromString("1cb6c8b0-1df6-4dd5-9c7c-2a69bab365e8");
PaymentMethod paymentMethod = accountApi.createPaymentMethod(accountId,
                                                             body,
                                                             true,
                                                             null,
                                                             null,
                                                             null,
                                                             requestOptions);
require 'killbill_client'

KillBillClient.url = 'http://127.0.0.1:8080'

options = {
  :username => 'admin',
  :password => 'password',
  :api_key => 'bob',
  :api_secret => 'lazar'
}

body = KillBillClient::Model::PaymentMethod.new
body.account_id = '1cb6c8b0-1df6-4dd5-9c7c-2a69bab365e8'
body.plugin_name = '__EXTERNAL_PAYMENT__'

pm = body.create(true, 'tutorial', nil, nil, options)
import killbill

killbill.configuration.base_uri = 'http://127.0.0.1:8080'
killbill.configuration.username = 'admin'
killbill.configuration.password = 'password'

account_api = killbill.api.AccountApi()
body = killbill.models.payment_method.PaymentMethod(plugin_name='__EXTERNAL_PAYMENT__')
account_api.create_payment_method('1cb6c8b0-1df6-4dd5-9c7c-2a69bab365e8',
                                  body,
                                  'tutorial',
                                  'bob',
                                  'lazar',
                                  is_default=True)
import (
	"context"
	"encoding/base64"
	"github.com/go-openapi/runtime"
	httptransport "github.com/go-openapi/runtime/client"
	"github.com/go-openapi/strfmt"
	"github.com/killbill/kbcli/kbclient"
	"github.com/killbill/kbcli/kbclient/account"
	"github.com/killbill/kbcli/kbmodel"
)

trp := httptransport.New("127.0.0.1:8080", "", nil)

authWriter := runtime.ClientAuthInfoWriterFunc(
	func(r runtime.ClientRequest, _ strfmt.Registry) error {
		encoded := base64.StdEncoding.EncodeToString([]byte("admin:password"))
		if err := r.SetHeaderParam("Authorization", "Basic "+encoded); err != nil {
			return err
		}
		if err := r.SetHeaderParam("X-KillBill-ApiKey", "bob"); err != nil {
			return err
		}
		if err := r.SetHeaderParam("X-KillBill-ApiSecret", "lazar"); err != nil {
			return err
		}
		return nil
	})

createdBy := "tutorial"
defaults := kbclient.KillbillDefaults{
	CreatedBy: &createdBy,
}

client := kbclient.New(trp, strfmt.Default, authWriter, defaults)
body := &kbmodel.PaymentMethod{
	PluginName: "__EXTERNAL_PAYMENT__",
}

isDefault := true
pm, err := client.Account.CreatePaymentMethod(
	context.Background(),
	&account.CreatePaymentMethodParams{
		Body:                  body,
		AccountID:             "1cb6c8b0-1df6-4dd5-9c7c-2a69bab365e8",
		IsDefault:             &isDefault,
		ProcessLocationHeader: true,
	})
if err == nil {
	print(pm.GetPayload().PaymentMethodID)
}
require_once(__DIR__ . '/vendor/autoload.php');

$config = Killbill\Client\Swagger\Configuration::getDefaultConfiguration();
$config->setHost('http://127.0.0.1:8080')
       ->setUsername('admin')
       ->setPassword('password')
       ->setApiKey('X-Killbill-ApiKey', 'bob')
       ->setApiKey('X-Killbill-ApiSecret', 'lazar');

$accountApi = new Killbill\Client\Swagger\Api\AccountApi(null, $config);

$pmData = new Killbill\Client\Swagger\Model\PaymentMethod();
$pmData->setPluginName('__EXTERNAL_PAYMENT__');

$pm = $accountApi->createPaymentMethod(
                     $pmData,
                     'tutorial',
                     '1cb6c8b0-1df6-4dd5-9c7c-2a69bab365e8',
                     NULL,
                     NULL,
                     $default = 'true'
                   );

Creating Your First Subscription

Note: replace 1cb6c8b0-1df6-4dd5-9c7c-2a69bab365e8 below with the ID of your account.

curl -v \
     -X POST \
     -u admin:password \
     -H 'X-Killbill-ApiKey: bob' \
     -H 'X-Killbill-ApiSecret: lazar' \
     -H 'X-Killbill-CreatedBy: tutorial' \
     -H 'Content-Type: application/json' \
     -d '{
            "accountId": "1cb6c8b0-1df6-4dd5-9c7c-2a69bab365e8",
            "planName": "standard-monthly"
         }' \
     http://127.0.0.1:8080/1.0/kb/subscriptions
import java.util.UUID;

import org.killbill.billing.client.KillBillClientException;
import org.killbill.billing.client.KillBillHttpClient;
import org.killbill.billing.client.RequestOptions;
import org.killbill.billing.client.api.gen.SubscriptionApi;
import org.killbill.billing.client.model.gen.Subscription;

KillBillHttpClient client = new KillBillHttpClient("http://127.0.0.1:8080",
                                                   "admin",
                                                   "password",
                                                   "bob",
                                                   "lazar");
SubscriptionApi subscriptionApi = new SubscriptionApi(client);

UUID accountId = UUID.fromString("1cb6c8b0-1df6-4dd5-9c7c-2a69bab365e8");
Subscription body = new Subscription();
body.setAccountId(accountId);
body.setPlanName("standard-monthly");

RequestOptions requestOptions = RequestOptions.builder()
                                              .withCreatedBy("tutorial")
                                              .build();
Subscription subscription = subscriptionApi.createSubscription(body,
                                                               null,
                                                               null,
                                                               null,
                                                               requestOptions);
require 'killbill_client'

KillBillClient.url = 'http://127.0.0.1:8080'

options = {
  :username => 'admin',
  :password => 'password',
  :api_key => 'bob',
  :api_secret => 'lazar'
}

body = KillBillClient::Model::Subscription.new
body.account_id  = '1cb6c8b0-1df6-4dd5-9c7c-2a69bab365e8'
body.plan_name = 'standard-monthly'

subscription = body.create('tutorial',
                           nil,
                           nil,
                           nil,
                           false,
                           options)
import killbill

killbill.configuration.base_uri = 'http://127.0.0.1:8080'
killbill.configuration.username = 'admin'
killbill.configuration.password = 'password'

subscription_api = killbill.api.SubscriptionApi()
body = killbill.models.subscription.Subscription(account_id='1cb6c8b0-1df6-4dd5-9c7c-2a69bab365e8',
                                                 plan_name='standard-monthly')

subscription_api.create_subscription(body,
                                     'tutorial',
                                     'bob',
                                     'lazar')
import (
	"context"
	"encoding/base64"
	"github.com/go-openapi/runtime"
	httptransport "github.com/go-openapi/runtime/client"
	"github.com/go-openapi/strfmt"
	"github.com/killbill/kbcli/kbclient"
	"github.com/killbill/kbcli/kbclient/subscription"
	"github.com/killbill/kbcli/kbmodel"
)

trp := httptransport.New("127.0.0.1:8080", "", nil)

authWriter := runtime.ClientAuthInfoWriterFunc(
	func(r runtime.ClientRequest, _ strfmt.Registry) error {
		encoded := base64.StdEncoding.EncodeToString([]byte("admin:password"))
		if err := r.SetHeaderParam("Authorization", "Basic "+encoded); err != nil {
			return err
		}
		if err := r.SetHeaderParam("X-KillBill-ApiKey", "bob"); err != nil {
			return err
		}
		if err := r.SetHeaderParam("X-KillBill-ApiSecret", "lazar"); err != nil {
			return err
		}
		return nil
	})

createdBy := "tutorial"
defaults := kbclient.KillbillDefaults{
	CreatedBy: &createdBy,
}

client := kbclient.New(trp, strfmt.Default, authWriter, defaults)
planName := "standard-monthly"
body := &kbmodel.Subscription{
	AccountID: "1cb6c8b0-1df6-4dd5-9c7c-2a69bab365e8",
	PlanName:  &planName,
}

sub, err := client.Subscription.CreateSubscription(
	context.Background(),
	&subscription.CreateSubscriptionParams{
		Body:                  body,
		ProcessLocationHeader: true,
	})
if err == nil {
	print(sub.GetPayload().SubscriptionID)
}
require_once(__DIR__ . '/vendor/autoload.php');

$config = Killbill\Client\Swagger\Configuration::getDefaultConfiguration();
$config->setHost('http://127.0.0.1:8080')
       ->setUsername('admin')
       ->setPassword('password')
       ->setApiKey('X-Killbill-ApiKey', 'bob')
       ->setApiKey('X-Killbill-ApiSecret', 'lazar');

$subscriptionApi = new Killbill\Client\Swagger\Api\SubscriptionApi(null, $config);

$subData = new Killbill\Client\Swagger\Model\Subscription();
$subData->setAccountId('1cb6c8b0-1df6-4dd5-9c7c-2a69bab365e8');
$subData->setPlanName('standard-monthly');

$sub = $subscriptionApi->createSubscription(
                           $subData,
                           'tutorial',
                           NULL,
                           NULL
                         );

Next steps

Explore our full API documentation.

We also have lots of examples in our Ruby and Java integration tests.

For support along the way, do not open GitHub issues. Instead, reach out to our Google Group. Our GitHub sponsors can also jump on our VIP community Slack channel.