├── .gitignore
├── CHANGELOG.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── app
├── controllers
│ └── profitable
│ │ ├── base_controller.rb
│ │ └── dashboard_controller.rb
└── views
│ ├── layouts
│ └── profitable
│ │ └── application.html.erb
│ └── profitable
│ └── dashboard
│ └── index.html.erb
├── bin
├── console
└── setup
├── config
└── routes.rb
├── lib
├── profitable.rb
└── profitable
│ ├── engine.rb
│ ├── error.rb
│ ├── mrr_calculator.rb
│ ├── numeric_result.rb
│ ├── processors
│ ├── base.rb
│ ├── braintree_processor.rb
│ ├── paddle_billing_processor.rb
│ ├── paddle_classic_processor.rb
│ └── stripe_processor.rb
│ └── version.rb
├── profitable.gemspec
├── profitable.webp
└── sig
└── profitable.rbs
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /_yardoc/
4 | /coverage/
5 | /doc/
6 | /pkg/
7 | /spec/reports/
8 | /tmp/
9 | /dist
10 | *.gem
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # `profitable`
2 |
3 | ## [0.2.3] - 2024-09-01
4 |
5 | - Fix the `time_to_next_mrr_milestone` estimation and make it accurate to the day
6 |
7 | ## [0.2.2] - 2024-09-01
8 |
9 | - Improve MRR calculations with prorated churned and new MRR (hopefully fixes bad churned MRR calculations)
10 | - Only consider paid charges for all revenue calculations (hopefully fixes bad ARPC calculations)
11 | - Add `multiple:` parameter as another option for `estimated_valuation` (same as `at:`, just syntactic sugar)
12 |
13 | ## [0.2.1] - 2024-08-31
14 |
15 | - Add syntactic sugar for `estimated_valuation(at: "3x")`
16 | - Now `estimated_valuation` also supports `Numeric`-only inputs like `estimated_valuation(3)`, so that @pretzelhands can avoid writing 3 extra characters and we embrace actual syntactic sugar instead of "syntactic saccharine" (sic.)
17 |
18 | ## [0.2.0] - 2024-08-31
19 |
20 | - Initial production ready release
21 |
22 | ## [0.1.0] - 2024-08-29
23 |
24 | - Initial test release (not production ready)
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source "https://rubygems.org"
4 |
5 | # Specify your gem's dependencies in profitable.gemspec
6 | gemspec
7 |
8 | gem "rake", "~> 13.0"
9 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2024 Javi R
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 💸 `profitable` - SaaS metrics for your Rails app
2 |
3 | [](https://badge.fury.io/rb/profitable)
4 |
5 | Calculate the MRR, ARR, churn, LTV, ARPU, total revenue & estimated valuation of your `pay`-powered Rails SaaS app, and display them in a simple dashboard.
6 |
7 | 
8 |
9 | ## Why
10 |
11 | [`pay`](https://github.com/pay-rails/pay) is the easiest way of handling payments in your Rails application. Think of `profitable` as the complement to `pay` that calculates business SaaS metrics like MRR, ARR, churn, total revenue & estimated valuation directly within your Rails application.
12 |
13 | Usually, you would look into your Stripe Dashboard or query the Stripe API to know your MRR / ARR / churn – but when you're using `pay`, you already have that data available and auto synced to your own database. So we can leverage it to make handy, composable ActiveRecord queries that you can reuse in any part of your Rails app (dashboards, internal pages, reports, status messages, etc.)
14 |
15 | Think doing something like: `"Your app is currently at $#{Profitable.mrr} MRR – Estimated to be worth $#{Profitable.valuation_estimate("3x")} at a 3x valuation"`
16 |
17 | ## Installation
18 |
19 | Add this line to your application's Gemfile:
20 | ```ruby
21 | gem 'profitable'
22 | ```
23 |
24 | Then run `bundle install`.
25 |
26 | Provided you have a valid [`pay`](https://github.com/pay-rails/pay) installation (`Pay::Customer`, `Pay::Subscription`, `Pay::Charge`, etc.) everything is already set up and you can just start using [`Profitable` methods](#main-profitable-methods) right away.
27 |
28 | ## Mount the `/profitable` dashboard
29 |
30 | `profitable` also provides a simple dashboard to see your main business metrics.
31 |
32 | In your `config/routes.rb` file, mount the `profitable` engine:
33 | ```ruby
34 | mount Profitable::Engine => '/profitable'
35 | ```
36 |
37 | It's a good idea to make sure you're adding some sort of authentication to the `/profitable` route to avoid exposing sensitive information:
38 | ```ruby
39 | authenticate :user, ->(user) { user.admin? } do
40 | mount Profitable::Engine => '/profitable'
41 | end
42 | ```
43 |
44 | You can now navigate to `/profitable` to see your app's business metrics like MRR, ARR, churn, etc.
45 |
46 | ## Main `Profitable` methods
47 |
48 | All methods return numbers that can be converted to a nicely-formatted, human-readable string using the `to_readable` method.
49 |
50 | ### Revenue metrics
51 |
52 | - `Profitable.mrr`: Monthly Recurring Revenue (MRR)
53 | - `Profitable.arr`: Annual Recurring Revenue (ARR)
54 | - `Profitable.all_time_revenue`: Total revenue since launch
55 | - `Profitable.revenue_in_period(in_the_last: 30.days)`: Total revenue (recurring and non-recurring) in the specified period
56 | - `Profitable.recurring_revenue_in_period(in_the_last: 30.days)`: Only recurring revenue in the specified period
57 | - `Profitable.recurring_revenue_percentage(in_the_last: 30.days)`: Percentage of revenue that is recurring in the specified period
58 | - `Profitable.new_mrr(in_the_last: 30.days)`: New MRR added in the specified period
59 | - `Profitable.churned_mrr(in_the_last: 30.days)`: MRR lost due to churn in the specified period
60 | - `Profitable.average_revenue_per_customer`: Average revenue per customer (ARPC)
61 | - `Profitable.lifetime_value`: Estimated customer lifetime value (LTV)
62 | - `Profitable.estimated_valuation(at: "3x")`: Estimated company valuation based on ARR
63 |
64 | ### Customer metrics
65 |
66 | - `Profitable.total_customers`: Total number of customers who have ever made a purchase or had a subscription (current and past)
67 | - `Profitable.total_subscribers`: Total number of customers who have ever had a subscription (active or not)
68 | - `Profitable.active_subscribers`: Number of customers with currently active subscriptions
69 | - `Profitable.new_customers(in_the_last: 30.days)`: Number of new customers added in the specified period
70 | - `Profitable.new_subscribers(in_the_last: 30.days)`: Number of new subscribers added in the specified period
71 | - `Profitable.churned_customers(in_the_last: 30.days)`: Number of customers who churned in the specified period
72 |
73 | ### Other metrics
74 |
75 | - `Profitable.churn(in_the_last: 30.days)`: Churn rate for the specified period
76 | - `Profitable.mrr_growth_rate(in_the_last: 30.days)`: MRR growth rate for the specified period
77 | - `Profitable.time_to_next_mrr_milestone`: Estimated time to reach the next MRR milestone
78 |
79 | ### Growth metrics
80 |
81 | - `Profitable.mrr_growth(in_the_last: 30.days)`: Calculates the absolute MRR growth over the specified period
82 | - `Profitable.mrr_growth_rate(in_the_last: 30.days)`: Calculates the MRR growth rate (as a percentage) over the specified period
83 |
84 | ### Milestone metrics
85 |
86 | - `Profitable.time_to_next_mrr_milestone`: Estimates the time to reach the next MRR milestone
87 |
88 | ### Usage examples
89 |
90 | ```ruby
91 | # Get the current MRR
92 | Profitable.mrr.to_readable # => "$1,234"
93 |
94 | # Get the number of new customers in the last 60 days
95 | Profitable.new_customers(in_the_last: 60.days).to_readable # => "42"
96 |
97 | # Get the churn rate for the last quarter
98 | Profitable.churn(in_the_last: 3.months).to_readable # => "12%"
99 |
100 | # You can specify the precision of the output number (no decimals by default)
101 | Profitable.new_mrr(in_the_last: 24.hours).to_readable(2) # => "$123.45"
102 |
103 | # Get the estimated valuation at 5x ARR (defaults to 3x if no multiple is specified)
104 | Profitable.estimated_valuation(multiple: 5).to_readable # => "$500,000"
105 |
106 | # You can also pass the multiplier as a string. You can also use the `at:` keyword argument (same thing as `multiplier:`) – and/or ignore the `at:` or `multiplier:` named arguments altogether
107 | Profitable.estimated_valuation(at: "4.5x").to_readable # => "$450,000"
108 |
109 | # Get the time to next MRR milestone
110 | Profitable.time_to_next_mrr_milestone.to_readable # => "26 days left to $10,000 MRR"
111 | ```
112 |
113 | All time-based methods default to a 30-day period if no time range is specified.
114 |
115 | ### Numeric values and readable format
116 |
117 | Numeric values are returned in the same currency as your `pay` configuration. The `to_readable` method returns a human-readable format:
118 |
119 | - Currency values are prefixed with "$" and formatted as currency.
120 | - Percentage values are suffixed with "%" and formatted as percentages.
121 | - Integer values are formatted with thousands separators but without currency symbols.
122 |
123 | For more precise calculations, you can access the raw numeric value:
124 | ```ruby
125 | # Returns the raw MRR integer value in cents (123456 equals $1.234,56)
126 | Profitable.mrr # => 123456
127 | ```
128 |
129 | ### Notes on specific metrics
130 |
131 | - `mrr_growth_rate`: This calculation compares the MRR at the start and end of the specified period. It assumes a linear growth rate over the period, which may not reflect short-term fluctuations. For more accurate results, consider using shorter periods or implementing a more sophisticated growth calculation method if needed.
132 | - `time_to_next_mrr_milestone`: This estimation is based on the current MRR and the recent growth rate. It assumes a constant growth rate, which may not reflect real-world conditions. The calculation may be inaccurate for very new businesses or those with irregular growth patterns.
133 |
134 | ## Development
135 |
136 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
137 |
138 | To install this gem onto your local machine, run `bundle exec rake install`.
139 |
140 | ## TODO
141 | - [ ] Calculate split by plan / add support for multiple plans (churn by plan, MRR by plan, etc) – not just aggregated
142 | - [ ] Calculate MRR expansion (plan upgrades), contraction (plan downgrades), etc. like Stripe does
143 | - [ ] Add active customers (not just total customers)
144 | - [ ] Add % of change over last period (this period vs last period)
145 | - [ ] Calculate total period revenue vs period recurring revenue (started, but not sure if accurate)
146 | - [ ] Add revenue last month to dashboard (not just past 30d, like previous month)
147 | - [ ] Support other currencies other than USD (convert currencies)
148 | - [ ] Make sure other payment processors other than Stripe work as intended (Paddle, Braintree, etc. – I've never used them)
149 | - [ ] Add a way to input monthly costs (maybe via config file?) so that we can calculate a profit margin %
150 | - [ ] Allow dashboard configuration via config file (which metrics to show, etc.)
151 | - [ ] Return a JSON in the dashboard endpoint with main metrics (for monitoring / downstream consumption)
152 |
153 | ## Contributing
154 |
155 | Bug reports and pull requests are welcome on GitHub at https://github.com/rameerez/profitable. Our code of conduct is: just be nice and make your mom proud of what you do and post online.
156 |
157 | ## License
158 |
159 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
160 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "bundler/gem_tasks"
4 | task default: %i[]
5 |
--------------------------------------------------------------------------------
/app/controllers/profitable/base_controller.rb:
--------------------------------------------------------------------------------
1 | module Profitable
2 | class BaseController < ApplicationController
3 | layout 'profitable/application'
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/controllers/profitable/dashboard_controller.rb:
--------------------------------------------------------------------------------
1 | module Profitable
2 | class DashboardController < BaseController
3 | def index
4 | end
5 |
6 | private
7 |
8 | def test
9 | end
10 |
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/app/views/layouts/profitable/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |