Introduction
In this tutorial, we look a little bit deeper into the overdue system. The subscription user guide provides a section explaining the basics about the overdue system and in particular goes through the schema of the overdue.xml
configuration file. The tutorial will attempt to put the pieces together.
Scenario
Let’s assume you want to build a web site where your customers can subscribe to movies (similar to Netflix). For the sake of simplicity we will assume you only provide one plan where your customers will need to pay $10 per month for unlimited streaming. The plan will contain a 10 days trial period during which customers have unlimited access but are not required to pay yet.
We will also assume that we want to configure the overdue system (dunning) to take some actions when users don’t pay: 1. The system should be configured to retry failed payments 3 times: the first two payment retries should happen 1 day after the last attempt, and the last one should happen 8 days after previous attempt 2. The system should notify the user when an invoice has not been paid after 10 days (and after the system already automatically retried 3 times) 3. The system should block the entitlements associated with the account after 14 (from the start date where invoice was not paid) 4. The system should automatically cancel the subscriptions after 21 days
Configuration of the System
Catalog Configuration
Your catalog will contain a plan entry that specifies two phases, one for the $0 TRIAL and one for the recurring monthly charge of $10 — note that we omitted the other parts of the catalog such as alignment rules, … which is beyond the scope of that tutorial:
<plan name="movies-monthly">
<product>Movies</product>
<initialPhases>
<phase type="TRIAL">
<duration>
<unit>DAYS</unit>
<number>10</number>
</duration>
<fixed>
<fixedPrice>
<!-- empty price implies $0 -->
</fixedPrice>
</fixed>
</phase>
</initialPhases>
<finalPhase type="EVERGREEN">
<duration>
<unit>UNLIMITED</unit>
</duration>
<recurring>
<billingPeriod>MONTHLY</billingPeriod>
<recurringPrice>
<price>
<currency>USD</currency>
<value>10.00</value>
</price>
</recurringPrice>
</recurring>
</finalPhase>
</plan>
Overdue Configuration
The configurartion below defines the 3 different states:
-
WARNING
:-
timeSinceEarliestUnpaidInvoiceEqualsOrExceeds
is set to 10 days as specified by the scenario -
disableEntitlementAndChangesBlocked
is set to false since we still want the user to have access to the service -
autoReevaluationInterval
is set to 4 days to make sure that if there is no payment (no overdue trigger), the state will be correctly recomputed and transition toBLOCKED
4 days later -
blockChanges
is set to true to prevent users from changing plans until they clear their balance (was not specified in the scenario, but probably a good practice to follow)
-
-
BLOCKED
:-
timeSinceEarliestUnpaidInvoiceEqualsOrExceeds
is set to 14 days as specified by the scenario -
disableEntitlementAndChangesBlocked
is set to true since we now want the user to NOT have access to the service -
autoReevaluationInterval
is set to 8 days to make sure that if there is no payment (no overdue trigger), the state will be correctly recomputed and transition toCANCELATION
8 days later
-
-
CANCELATION
:-
subscriptionCancellationPolicy
is set toEND_OF_TERM
to indicate that subscriptions should be cancelled in such a way that we do not generate credit for the account (no proration)
-
<overdueConfig>
<accountOverdueStates>
<initialReevaluationInterval>
<unit>DAYS</unit><number>4</number>
</initialReevaluationInterval>
<state name="CANCELATION">
<condition>
<timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>
<unit>DAYS</unit><number>21</number>
</timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>
</condition>
<externalMessage>Reached CANCELATION</externalMessage>
<subscriptionCancellationPolicy>END_OF_TERM</subscriptionCancellationPolicy>
</state>
<state name="BLOCKED">
<condition>
<timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>
<unit>DAYS</unit><number>14</number>
</timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>
</condition>
<externalMessage>Reached BLOCKED</externalMessage>
<blockChanges>true</blockChanges>
<disableEntitlementAndChangesBlocked>true</disableEntitlementAndChangesBlocked>
<autoReevaluationInterval>
<unit>DAYS</unit><number>7</number>
</autoReevaluationInterval>
</state>
<state name="WARNING">
<condition>
<timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>
<unit>DAYS</unit><number>10</number>
</timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>
</condition>
<externalMessage>Reached WARNING</externalMessage>
<blockChanges>true</blockChanges>
<disableEntitlementAndChangesBlocked>false</disableEntitlementAndChangesBlocked>
<autoReevaluationInterval>
<unit>DAYS</unit><number>4</number>
</autoReevaluationInterval>
</state>
</accountOverdueStates>
</overdueConfig>
Payment retries
In addition we need to configure the payment system to retry failed payments; the system property org.killbill.payment.retry.days
specifies the retry policy associated to payment failures. In our case we need to set it to 1,1,8
to indicate 3 payment retries, the first one after 1 day, and then 1 day after, and the last one 8 days after the previous one (as specified in our scenario).
Example of Customers
Let’s take the case of a customer that subscribed to the service. Immediately after the subscription was created a $0 invoice is created to indicate the customer is in TRIAL. Let’s assume his credit card does not have enough funds. 10 days later the customer moves out of TRIAL and the system generates a $10 invoice for the month. At this point, the system attempts to make a payment, but the payment does not go through:
-
Day 1: the system will retry the payment one day later and fail again
-
Day 2: the system will retry the payment one day later and fail again
-
Day 10: the system will retry (one last time) the payment 8 days later and fail again; at this point the overdue system will transiton the account into a
WARNING
state
Bad Customer
Let’s assume this is a bad customer, that will not update his credit card:
-
Day 14: the customer moves to a
BLOCKED
state; the system will stop invoicing, and will indicate that the customer is not entititled to receiving service any longer (more details below) -
Day 21: the system will cancel the subscription (final state)
Good Customer
Let’s assume this is a good customer, and after the WARNING, he updates his credit card:
-
Day 15: customer updates credit card and pays his unpaid invoice(s) (more details below). The system brings back the overdue status to
CLEAR
.
Use of the Platform
Use of the APIs
When a customer attempts to use the service, the web site could verify if the customer is entitled to receive the service:
-
It should first retrieve the overdue status (at the account level) using the
GET /1.0/kb/accounts/{accountId}/overdue
api, -
It should then retrieve the subscriptions associated with the account
GET /1.0/kb/accounts/{accountId}/bundles
, or if the subscriptionId was cached it could useGET /1.0/kb/subscriptions/{subscriptionId}
The endpoint GET /1.0/kb/accounts/{accountId}/overdue
will return the following json:
---
{ "blockChanges": true,
"clearState": false,
"daysBetweenPaymentRetries": 1,
"disableEntitlementAndChangesBlocked": false,
"externalMessage": "Reached WARNING",
"name": "WARNING",
"reevaluationIntervalDays": 4 }
---
If the disableEntitlementAndChangesBlocked
is set to true, it means that the customer is not entitled to the service associated to any subscriptions.
So, note that retrieving subscriptions through the GET /1.0/kb/subscriptions/{subscriptionId}
will not directly indicate the overdue status, and so both calls must be made to have a complete picture of the entitlement when the overdue system has been configured.
When retrieving entitlement/overdue status, the web site could be implemented to take all kind of actions such as displaying warning message, degrading experience, emailing customer, …
Custom Plugins
In the previous section, we discussed a possible implementation where the web site queries the billing system to figure out the entitlement story attached to a customer (when he logs in for instance). Another pattern, is to create a custom plugin that will listen to Overdue events. Examples of such plugins can be found here:
The plugin would need to filter for OVERDUE_CHANGE
events
Such plugins can be used for the following purpose:
-
Email/Notify user about the new state
-
Take action to modify the experience (based on the state name), for instance to degrade the service, or modify the login flow on a website to prompt for payments, …