Having fun with Docker, Stripe and Kill Bill

Having fun with Docker, Stripe and Kill Bill

Note! An updated version of this tutorial can be found here.

 

Kill Bill has been dockerized! In this post, I’m going to show you how to get started with your own Kill Bill installation, integrated with Stripe, running on Docker.

 

Setup

Start by installing Docker if you don’t have it yet.

You will also need a Stripe api key. To get one:

  1. Create a free account at Stripe.com
  2. Go to your API Key Settings
  3. Write down the Test Secret Key

 

Deep dive into the Kill Bill container

Our Docker image is based off the ubuntu:14.04 one. It comes with Tomcat and KPM (the Kill Bill package manager). Kill Bill is not yet present in the image: it is the role of KPM to fetch the Kill Bill war and its plugins. The first time the container is started, KPM will look at its configuration file (/etc/killbill/kpm.yml) and lay down the bits accordingly. The reason for this deferred step is to let you customize the image if you want to (Kill Bill version, plugins, etc.).

You can start Kill Bill by running: docker run -tid --name killbilltest -p 8080:8080 killbill/killbill

  • --name killbilltest assigns a name to the container, to be able to reference it easily later on
  • -p 8080:8080 exports the port from the container, to access it from the host

Here are some useful commands:

  • docker logs -f killbilltest to tail the Kill Bill logs
  • docker exec -ti killbilltest /bin/bash to log-in to the container
  • docker exec -ti killbilltest cat /etc/killbill/killbill.properties to explore the Kill Bill configuration
  • docker exec -ti killbilltest cat /etc/killbill/kpm.yml to explore the KPM configuration

When the installation is complete, retrieve the IP from the container host (on MacOS, run boot2docker ip). We’ll assume this IP is 192.168.1.1 in the rest of this post. Kill Bill should then be available at http://192.168.1.1:8080. Note that if you want to use Swagger, the exploratory API tool shipped with Kill Bill, make sure to update the IP in the toolbar of http://192.168.1.1:8080/api.html.

At this point, Kill Bill is fully functional and you can run the 5′ tutorials from either our subscriptions userguide or payments userguide. Remember to update the IP address in the URLs with the one from the container host (boot2docker ip on MacOS).

You can stop (docker stop killbilltest) and restart the container (docker start killbilltest) as will, without going through the installation process again. If you need to re-install or upgrade Kill Bill, you can force a re-installation by deleting /etc/killbill/kpm.yml in the container and re-starting it.

 

MySQL container setup

At this point, you have a fully running Kill Bill setup. By default however, a file-based H2 database is used (exported at /var/lib/killbill), which is good enough only for rapid prototyping. This section will show you how to setup a MySQL database instead.

Let’s setup a MariaDB container: docker run -tid --name mysql -e MYSQL_ROOT_PASSWORD=mysecretpassword -p 3306:3306 mariadb.

Because Kill Bill uses InnoDB, you’ll need to change the default binlog format by running (remember to update the IP address):


echo "set global binlog_format = 'ROW'" | mysql -h 192.168.1.1 -uroot -pmysecretpassword

The final step is to create the killbill database and apply the DDL schema:


echo 'create database killbill' | mysql -h 192.168.1.1 -uroot -pmysecretpassword
curl http://docs.killbill.io/0.14/ddl.sql | mysql -h 192.168.1.1 -uroot killbill -pmysecretpassword

This will have created the killbill database along with its core tables.

 

Kill Bill with Stripe container setup

In this section, we are going to setup a Kill Bill container, with the Stripe plugin, linked against the MySQL container.

First, apply the DDL schema for the Stripe plugin (remember to update the IP address):


curl https://raw.githubusercontent.com/killbill/killbill-stripe-plugin/master/db/ddl.sql | mysql -h 192.168.1.1 -uroot killbill -pmysecretpassword

Second, create a configuration file $HOME/docker/etc/killbill/stripe.yml with your API key:


:stripe:
  :api_secret_key: 'sk_test_aaaabbbbccccdddd'
  :api_publishable_key: 'pk_test_aaaabbbbccccdddd'
  :test: true


Finally, run the image again (make sure to stop and delete the container we created in step 1):


docker stop killbilltest
docker rm killbilltest

docker run -tid \
           --name killbilltest \
           -p 8080:8080 \
           --link mysql:mysql \
           -v $HOME/docker/etc/killbill/stripe.yml:/etc/killbill/stripe.yml \
           -e KILLBILL_PLUGIN_STRIPE=1 \
           -e KILLBILL_CONFIG_DAO_URL=jdbc:mysql://mysql:3306/killbill \
           -e KILLBILL_CONFIG_DAO_USER=root \
           -e KILLBILL_CONFIG_DAO_PASSWORD=mysecretpassword \
           -e KILLBILL_CONFIG_OSGI_DAO_URL=jdbc:mysql://mysql:3306/killbill \
           -e KILLBILL_CONFIG_OSGI_DAO_USER=root \
           -e KILLBILL_CONFIG_OSGI_DAO_PASSWORD=mysecretpassword \
           killbill/killbill:0.14.0

Some notes:

  • KILLBILL_PLUGIN_STRIPE=1 tells KPM to install the Stripe plugin
  • -v $HOME/docker/etc/killbill/stripe.yml:/etc/killbill/stripe.yml exposes the local stripe.yml to the container
  • KILLBILL_CONFIG_ parameters tell Kill Bill where the database is located (Kill Bill has two JDBC pools, one for the core and one of OSGI plugins). The special alias mysql is automatically setup by Docker in the container because of the link

The process will take a few minutes (the installation is similar to the first container we ran, but the extra Stripe plugin needs to be downloaded). You can monitor it by running: docker logs -f killbilltest.

That’s it! Kill Bill is now up and running, using Stripe as a gateway!

 

Testing the integration

As usual, let’s start by creating a tenant:


curl -v \
     -X POST \
     -u admin:password \
     -H 'Content-Type: application/json' \
     -H 'X-Killbill-CreatedBy: admin' \
     -d '{"apiKey": "bob", "apiSecret": "lazar"}' \
     "http://192.168.1.1:8080/1.0/kb/tenants"

We first need to create an account for John, a shopper on our website:


curl -v \
     -u admin:password \
     -H "X-Killbill-ApiKey: bob" \
     -H "X-Killbill-ApiSecret: lazar" \
     -H "Content-Type: application/json" \
     -H "X-Killbill-CreatedBy: demo" \
     -X POST \
     --data-binary '{"name":"John Doe","email":"[email protected]","externalKey":"john-doe-1234","currency":"USD"}' \
     "http://192.168.1.1:8080/1.0/kb/accounts"

The API will return a Location header with the UUID of the created account. We will assume it is 82c45d70-6f63-11e4-9803-0800200c9a66 in the rest of this blog, update the urls as needed.

The next step is to add a payment method, in our case a credit card, to his account. This credit card will be validated and stored by Stripe:


curl -v \
     -u admin:password \
     -H "X-Killbill-ApiKey: bob" \
     -H "X-Killbill-ApiSecret: lazar" \
     -H "Content-Type: application/json" \
     -H "X-Killbill-CreatedBy: demo" \
     -X POST \
     --data-binary '{
       "pluginName": "killbill-stripe",
       "pluginInfo": {
         "properties": [
           {
             "key": "ccExpirationMonth",
             "value": 12
           },
           {
             "key": "ccExpirationYear",
             "value": 2017
           },
           {
             "key": "ccNumber",
             "value": 4111111111111111
           }
         ]
       }
     }' \
     "http://192.168.1.1:8080/1.0/kb/accounts/82c45d70-6f63-11e4-9803-0800200c9a66/paymentMethods?isDefault=true"

You can take a look at the logs on Stripe on your dashboard and verify a new customer with a credit card was added here.

Now, let’s try to trigger a payment for that account:


curl -v \
     -u admin:password \
     -H "X-Killbill-ApiKey: bob" \
     -H "X-Killbill-ApiSecret: lazar" \
     -H "Content-Type: application/json" \
     -H "X-Killbill-CreatedBy: demo" \
     --data-binary '{"transactionType":"PURCHASE","amount":"15","currency":"USD","transactionExternalKey":"INV-002-PURCHASE"}' \
     "http://192.168.1.1:8080/1.0/kb/accounts/82c45d70-6f63-11e4-9803-0800200c9a66/payments"

An example a bit more advanced is a partial capture. Start by authorizing a payment:


curl -v \
     -u admin:password \
     -H "X-Killbill-ApiKey: bob" \
     -H "X-Killbill-ApiSecret: lazar" \
     -H "Content-Type: application/json" \
     -H "X-Killbill-CreatedBy: demo" \
     --data-binary '{"transactionType":"AUTHORIZE","amount":"10","currency":"USD","transactionExternalKey":"INV-001-AUTH"}' \
     "http://192.168.1.1:8080/1.0/kb/accounts/82c45d70-6f63-11e4-9803-0800200c9a66/payments"

Kill Bill will return the payment id, which you can use to trigger the (partial) capture:


curl -v \
     -u admin:password \
     -H "X-Killbill-ApiKey: bob" \
     -H "X-Killbill-ApiSecret: lazar" \
     -H "Content-Type: application/json" \
     -H "X-Killbill-CreatedBy: demo" \
     --data-binary '{"amount":"5","currency":"USD","transactionExternalKey":"INV-001-CAPTURE"}' \
     "http://192.168.1.1:8080/1.0/kb/payments/3fef7d30-6f64-11e4-9803-0800200c9a66"

Note: the Stripe UI is a bit confusing for that usecase. Even though we’re just doing a partial capture, Stripe will show on the dashboard that $5 ($10 auth -$5 capture) was refunded (they also don’t seem to support multiple partial captures).

 

Next steps

You are now ready to integrate Kill Bill in your workflow. Jump to our userguide on how to get started. In a subsequent post, we’ll show you how you can seemlessly deploy your container to EC2.

8 Comments

  1. I can’t seem to get by this exception when starting the docker image. Any ideas? Thanks a bunch! Brad

    Feb 27, 2015 9:41:33 PM org.apache.catalina.core.StandardContext resourcesStart
    SEVERE: Error starting static Resources
    java.lang.IllegalArgumentException: Document base /var/lib/tomcat7/webapps/ROOT does not exist or is not a readable directory
    at org.apache.naming.resources.FileDirContext.setDocBase(FileDirContext.java:136)
    at org.apache.catalina.core.StandardContext.resourcesStart(StandardContext.java:5089)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5269)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901)
    at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877)
    at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:632)
    at org.apache.catalina.startup.HostConfig.deployDescriptor(HostConfig.java:670)
    at org.apache.catalina.startup.HostConfig$DeployDescriptor.run(HostConfig.java:1839)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
    at java.util.concurrent.FutureTask.run(FutureTask.java:262)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:745)

    Reply
    • Hi! I’ve got the same problem as Brad. And I did’nt find this discussion in google group. Is there any decision?

      Reply
  2. I’m unable to SSH into aws instance when I use #include https://get.docker.com/ as userdata. In order to work around this, I have to create my own script to install docker. Can you confirm you have same issue please? I wonder if script stopped working since you made this blog.

    Reply
  3. Is possible to add plugin manually inside docker image ?
    and if it possible should I restart the docker container?

    Reply

Leave a Reply