Kill Bill was designed to support merchants and their customers (user account, or also referred to as 'user' or 'account' in this documentation) across different countries. In order to support that, there are a lot of different aspects that need to be covered ranging from character sets, timezones, multiple currencies, locales, and the ability to support multiple languages for all customer-visible data (e.g invoices).
In addition, these features must work independently for the various tenants: a given merchant assigned to a given Kill Bill tenant must be able to configure the system differently than another merchant in a separate tenant.
Timezones
Supporting multiple timezones comes into play when a user needs to interact with the system and then later when the system computes state for this user:
A user cancelling a subscription needs to provide a date (requested cancellation date). That date must be interpreted by the system in the user account timezone
Subsequently, when the system computes the next invoice for instance, it must include all the items for the period of time reflected by the invoice and as seen by the user.
An example would be a user creating a monthly subscription on january 1st 2015 but from a timezone in PST (UTC-8). The action will actually happen at a specific point in time such as 2015-01-02T04:00:00.000Z. The system must correctly understand that the provided date needs to be interpreted in the user timezone so that when the user receives its first invoice, he sees a billing period ranging from 2015-01-01 to 2015-02-01. If the user then decides to cancel his subscription on '2015-02-01', the system also needs to make sure this date is interpreted in the user timezone to avoid charging for an extra day; in that case the user only provides a date with no time (the API works that way because it is much simpler from the user point of view), and so the precise point in time when that cancellation happens needs to be in the 24 hours period of that specified day in the user timezone.
More information on the internals of date, you can refer to this blog post.
Multiple Currencies
The Kill Bill catalog allows to specify amounts in different currencies. Each Kill Bill account needs to specify a currency that is used by the system when generating invoices (by looking up the amount of the specified plan for that currency), and that is also used at the time of making a payment. All the credits later generated for the account will also be in that currency.
The system also allows to also use a different currency at the time of making a payment by using a currency converter.
Locale
An account is associated with a locale
(e.g fr_FR
) that is used for extracting localized resources. The system allows to configure the following resource bundles:
Catalog Resource Bundle: ability to translate specific catalog strings in different languages (e.g American english
Gold plan
translated asPlan Or
in French)Text Translation: arbitrary text strings in different languages that can then be used later when creating documents out of templates to ensure they are translated in the language matching the account
Along with the locale, the system allows to configure different types of templates:
Invoice templates
Email templates
Invoice Templates
Kill Bill provides an API to retrieve an html invoice (formatted and translated using the account locale). The templating mechanism is based on mustache. In order to configure the system, the following steps need to happen:
Upload the invoice template
Upload the translation for all the supported locales
Upload the catalog translation for all the locales (if required)
Upload the invoice template:
The file $SOME_PATH_PREFIX/HtmlInvoiceTemplate.mustache
would contain for instance:
<html>
<head>
<style type="text/css">
th {align=left; width=225px; border-bottom: solid 2px black;}
</style>
</head>
<body>
<h1>{{text.invoiceTitle}}</h1>
<table>
<tr>
<td rowspan=3 width=350px>Insert image here</td>
<td width=100px/>
<td width=225px/>
<td width=225px/>
</tr>
<tr>
<td />
<td align=right>{{text.invoiceDate}}</td>
<td>{{invoice.formattedInvoiceDate}}</td>
</tr>
<tr>
<td />
<td align=right>{{text.invoiceNumber}}</td>
<td>{{invoice.invoiceNumber}}</td>
</tr>
<tr>
<td>{{text.companyName}}</td>
<td></td>
<td align=right>{{text.accountOwnerName}}</td>
<td>{{account.name}}</td>
</tr>
<tr>
<td>{{text.companyAddress}}</td>
<td />
<td />
<td>{{account.email}}</td>
</tr>
<tr>
<td>{{text.companyCityProvincePostalCode}}</td>
<td />
<td />
<td>{{account.phone}}</td>
</tr>
<tr>
<td>{{text.companyCountry}}</td>
<td />
<td />
<td />
</tr>
<tr>
<td><{{text.companyUrl}}</td>
<td />
<td />
<td />
</tr>
</table>
<br />
<br />
<br />
<table>
<tr>
<th>{{text.invoiceItemBundleName}}</td>
<th>{{text.invoiceItemDescription}}</td>
<th>{{text.invoiceItemServicePeriod}}</td>
<th>{{text.invoiceItemAmount}}</td>
</tr>
{{#invoice.invoiceItems}}
<tr>
<td>{{description}}</td>
<td>{{planName}}</td>
<td>{{formattedStartDate}}{{#formattedEndDate}} - {{formattedEndDate}}{{/formattedEndDate}}</td>
<td>{{invoice.currency}} {{amount}}</td>
</tr>
{{/invoice.invoiceItems}}
<tr>
<td colspan=4 />
</tr>
<tr>
<td colspan=2 />
<td align=right><strong>{{text.invoiceAmount}}</strong></td>
<td align=right><strong>{{invoice.chargedAmount}}</strong></td>
</tr>
<tr>
<td colspan=2 />
<td align=right><strong>{{text.invoiceAmountPaid}}</strong></td>
<td align=right><strong>{{invoice.paidAmount}}</strong></td>
</tr>
<tr>
<td colspan=2 />
<td align=right><strong>{{text.invoiceBalance}}</strong></td>
<td align=right><strong>{{invoice.balance}}</strong></td>
</tr>
</table>
</body>
</html>
curl -v \
-u admin:password \
-H "X-Killbill-ApiKey: bob" \
-H "X-Killbill-ApiSecret: lazar" \
-H 'X-Killbill-CreatedBy: admin' \
-H "Content-Type: text/html" \
-X POST \
--data-binary @$SOME_PATH_PREFIX/HtmlInvoiceTemplate.mustache \
http://127.0.0.1:8080/1.0/kb/invoices/template
Upload the invoice translation for locale
fr_FR
:
The file $SOME_PATH_PREFIX/InvoiceTranslation_fr_FR.properties
would contain for instance:
invoiceEmailSubject=Nouvelle Facture
invoiceTitle=FACTURE
invoiceDate=Date:
invoiceNumber=Facture #
invoiceAmount=Montant à payer
invoiceAmountPaid=Montant payé
invoiceBalance=Nouveau montant
accountOwnerName=Chauffeur
companyName=Killbill, Inc.
companyAddress=P.O. Box 1234
companyCityProvincePostalCode=Springfield
companyCountry=USA
companyUrl=http://kill-bill.org
invoiceItemBundleName=Armes
invoiceItemDescription=Description
invoiceItemServicePeriod=Période de facturation
invoiceItemAmount=Montant
processedPaymentCurrency=(*) Le payment à été payé en
processedPaymentRate=Le taux de conversion est
curl -v \
-u admin:password \
-H "X-Killbill-ApiKey: bob" \
-H "X-Killbill-ApiSecret: lazar" \
-H 'X-Killbill-CreatedBy: admin' \
-H "Content-Type: text/plain" \
-X POST \
--data-binary @$SOME_PATH_PREFIX/InvoiceTranslation_fr_FR.properties \
http://127.0.0.1:8080/1.0/kb/invoices/translation/fr_FR
Upload the catalog translation for locale
fr_FR
:
The file $SOME_PATH_PREFIX/CatalogTranslation_fr_FR.properties
would contain for instance:
gold-monthly = plan Or mensuel
curl -v \
-u admin:password \
-H "X-Killbill-ApiKey: bob" \
-H "X-Killbill-ApiSecret: lazar" \
-H 'X-Killbill-CreatedBy: admin' \
-H "Content-Type: text/plain" \
-X POST \
--data-binary @$SOME_PATH_PREFIX/CatalogTranslation_fr_FR.properties \
http://127.0.0.1:8080/1.0/kb/invoices/catalogTranslation/fr_FR
Retrieve a specfic invoice html:
curl -v \
-u admin:password \
-H 'X-Killbill-ApiKey: bob' \
-H 'X-Killbill-ApiSecret: lazar' \
-H "Content-Type: application/json" \
-H 'X-Killbill-CreatedBy: admin' \
"http://127.0.0.1:8080/1.0/kb/invoices/1785b3d5-24b3-4d17-94ce-310aeb74bc63/html"
Email Templates
It is often desirable to send emails to customers to inform them about the next coming invoice, a change that they made in the system (e.g. cancellation of a subscription), a successful or failed payment, … That functionality lives in a Kill Bill plugin. The plugin listens to bus events, and takes action to notify user.
The plugin also allows to be configured on a per-tenant level with a set of templates and translation bundles for various locales.
For more information please refer to the README