Skip to content

Commit

Permalink
chore: refactor revenue tracker
Browse files Browse the repository at this point in the history
  • Loading branch information
jansorg committed Jul 8, 2024
1 parent 853f629 commit 1d2d99c
Showing 1 changed file with 25 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,21 @@ import java.util.*
* Tracks recurring revenue for a given time period, e.g. one month or one year.
*/
abstract class RecurringRevenueTracker(
protected val dateRange: YearMonthDayRange,
private val dateRange: YearMonthDayRange,
protected val pluginInfo: MarketplacePluginInfo,
protected val continuityTracker: ContinuityDiscountTracker,
private val continuityTracker: ContinuityDiscountTracker,
) {
private val latestSales = TreeMap<LicenseId, LicenseInfo>()

protected abstract fun dateRangeSubscriptionPrice(license: LicenseInfo): Amount
protected abstract fun dateRangeSubscriptionPrice(basePrice: Amount, licensePeriod: LicensePeriod): Amount

/**
* @return The expected continuity discount for the next month or year.
*/
protected abstract fun nextContinuityDiscountFactor(license: LicenseInfo): Double
protected abstract fun nextContinuityCheckDate(date: YearMonthDay): YearMonthDay

fun processLicenseSale(licenseInfo: LicenseInfo) {
if (!licenseInfo.isPaidLicense) {
return
}

// track all, not just those in the filtered revenue range
continuityTracker.process(licenseInfo)

// only keep the latest valid sale of a license
if (isValid(licenseInfo)) {
latestSales.merge(licenseInfo.id, licenseInfo) { old, new ->
Expand All @@ -51,7 +45,12 @@ abstract class RecurringRevenueTracker(
var result = Amount(0)

for ((_, license) in latestSales) {
val rangeBasePrice = dateRangeSubscriptionPrice(license)
val basePrice = when (license.sale.customer.type) {
CustomerType.Personal -> pluginInfo.individualPrice
CustomerType.Organization -> pluginInfo.businessPrice
}

val rangeBasePrice = dateRangeSubscriptionPrice(basePrice, license.sale.licensePeriod)
val continuityFactor = nextContinuityDiscountFactor(license)
val discounts = otherDiscountsFactor(license) * continuityFactor

Expand All @@ -61,6 +60,13 @@ abstract class RecurringRevenueTracker(
return RecurringRevenue(dateRange, result)
}

/**
* @return The expected continuity discount for the next month or year.
*/
private fun nextContinuityDiscountFactor(license: LicenseInfo): Double {
return continuityTracker.nextContinuityFactor(license.id, nextContinuityCheckDate(license.validity.start))
}

private fun isValid(license: LicenseInfo): Boolean {
// Only take licenses which are valid at the end of the date range, e.g. at the end of a month.
// Licenses only valid at the start but expiring in the middle of a month do not contribute to recurring revenue.
Expand Down Expand Up @@ -89,17 +95,12 @@ class MonthlyRecurringRevenueTracker(
// annual subscription is 12 * monthly subscription price, calculated for a single month
private val annualToMonthlyFactor = BigDecimal.valueOf(10.0 / 12.0)

override fun nextContinuityDiscountFactor(license: LicenseInfo): Double {
return continuityTracker.nextContinuityFactor(license.id, license.validity.start.add(0, 1, 0))
override fun nextContinuityCheckDate(date: YearMonthDay): YearMonthDay {
return date.add(0, 1, 0)
}

override fun dateRangeSubscriptionPrice(license: LicenseInfo): Amount {
val basePrice = when (license.sale.customer.type) {
CustomerType.Personal -> pluginInfo.individualPrice
CustomerType.Organization -> pluginInfo.businessPrice
}

return when (license.sale.licensePeriod) {
override fun dateRangeSubscriptionPrice(basePrice: Amount, licensePeriod: LicensePeriod): Amount {
return when (licensePeriod) {
LicensePeriod.Monthly -> basePrice
LicensePeriod.Annual -> basePrice * annualToMonthlyFactor
}
Expand All @@ -117,17 +118,12 @@ class AnnualRecurringRevenueTracker(
// monthly subscription is paid 12 times per year
private val monthlyToAnnualFactor = BigDecimal.valueOf(12)

override fun nextContinuityDiscountFactor(license: LicenseInfo): Double {
return continuityTracker.nextContinuityFactor(license.id, license.validity.start.add(1, 0, 0))
override fun nextContinuityCheckDate(date: YearMonthDay): YearMonthDay {
return date.add(1, 0, 0)
}

override fun dateRangeSubscriptionPrice(license: LicenseInfo): Amount {
val basePrice = when (license.sale.customer.type) {
CustomerType.Personal -> pluginInfo.individualPrice
CustomerType.Organization -> pluginInfo.businessPrice
}

return when (license.sale.licensePeriod) {
override fun dateRangeSubscriptionPrice(basePrice: Amount, licensePeriod: LicensePeriod): Amount {
return when (licensePeriod) {
LicensePeriod.Monthly -> basePrice * monthlyToAnnualFactor
LicensePeriod.Annual -> basePrice * annualFactor
}
Expand Down

0 comments on commit 1d2d99c

Please sign in to comment.