Last active
July 10, 2016 18:08
-
-
Save jacobpatton/a68d228bf2414852d862 to your computer and use it in GitHub Desktop.
For a SAAS business, monthly recurring revenue (MRR) is their bread and butter. This small class fetches subscription records from Stripe to calculate the MRR.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'stripe' | |
# Find the MRR for a given Stripe account. | |
# | |
# (To do this it simply finds the total amount for all active | |
# subscriptions (in cents). For most SAAS accounts, this is | |
# enough, though modification is necessary to account for | |
# things like trials, discounts, pro-rated cancellations, etc.) | |
# | |
# {stripe_id: YOUR_STRIPE_SECRET_KEY} - A hash with the key "stripe_id" and the correct secret key | |
# | |
# Examples | |
# | |
# Stripe::Mrr.new(api_key: YOUR_STRIPE_SECRET_KEY) | |
# # => 1000 | |
# | |
# Returns an integer representing the MRR (in cents) | |
module Stripe | |
class Mrr | |
attr_reader :api_key | |
def initialize(args = {}) | |
raise ArgumentError, ":api_key is a required argument" unless args[:api_key] | |
@api_key = args[:api_key] | |
Stripe.api_key = @api_key | |
@customers = [] | |
end | |
def mrr | |
subscriptions.inject(0){|sum, subscription| sum + subscription.plan.amount} | |
end | |
def customers | |
fetch_customers if @customers.empty? | |
@customers | |
end | |
def subscriptions | |
customers.inject([]) do |collection, customer| | |
collection.concat(customer.subscriptions.data) | |
end | |
end | |
private | |
def fetch_customers(opts = {}) | |
opts = {limit: 100}.merge!(opts) | |
collection = Stripe::Customer.all(opts) | |
@customers.concat(collection.data) | |
fetch_customers(starting_after: @customers.last.id) if collection.has_more | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'mrr' | |
require 'stripe_mock' | |
describe Stripe::Mrr do | |
before { StripeMock.start } | |
after { StripeMock.stop } | |
it "should raise an error if initialized without an :api_key argument" do | |
msg = ":api_key is a required argument" | |
expect { Stripe::Mrr.new }.to raise_error(ArgumentError, msg) | |
end | |
it "should correctly set the API key" do | |
api_key = "some_api_key" | |
mrr = build_mrr(api_key) | |
mrr.api_key.should eq api_key | |
end | |
describe "#customers" do | |
it "should return an array of customers" do | |
mrr = build_mrr | |
customer_1, customer_2 = Array.new(2, new_customer) | |
# we need two pages of results | |
# the Stripe API will respond with ListObjects, which we're mocking | |
customer_list_page_1 = customer_list(true, [customer_1]) | |
customer_list_page_2 = customer_list(false, [customer_2]) | |
# the first page of results (has_more = true) | |
allow(Stripe::Customer).to receive(:all).with(limit: 100) do | |
customer_list_page_1 | |
end | |
# the second page of results (has_more = false) | |
allow(Stripe::Customer).to receive(:all).with(limit: 100, starting_after: customer_1.id) do | |
customer_list_page_2 | |
end | |
mrr.customers.should eq([customer_1, customer_2]) | |
end | |
end | |
describe "#subscriptions" do | |
it "should return an array of subscriptions" do | |
mrr = build_mrr | |
subscription = double("subscription") | |
subscription_list = double("list", data: [subscription]) | |
customer = double("customer", subscriptions: subscription_list) | |
allow(mrr).to receive(:customers){ [customer] } | |
mrr.subscriptions.should eq([subscription]) | |
end | |
end | |
describe "#mrr" do | |
it "should correctly tally the mrr from all its subscriptions" do | |
mrr = build_mrr | |
plan = double("plan", amount: 100) | |
subscription = double("subscription", plan: plan) | |
allow(mrr).to receive(:subscriptions) { Array.new(3, subscription) } | |
mrr.mrr.should eq(300) | |
end | |
end | |
def build_mrr(api_key = 'some_api_key') | |
Stripe::Mrr.new(api_key: api_key) | |
end | |
def customer_list(has_more = false, customers = []) | |
OpenStruct.new(has_more: has_more, data: customers) | |
end | |
def new_customer | |
Stripe::Customer.create | |
end | |
def new_subscription | |
Stripe::Subscription.create | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is awesome but not sure the math is right. I ran it on my account and received an incorrect value.