Skip to content

Instantly share code, notes, and snippets.

@jacobpatton
Last active July 10, 2016 18:08
Show Gist options
  • Save jacobpatton/a68d228bf2414852d862 to your computer and use it in GitHub Desktop.
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.
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
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
@bcackerman
Copy link

This is awesome but not sure the math is right. I ran it on my account and received an incorrect value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment