Skip to content

Instantly share code, notes, and snippets.

@eduardinni
Last active January 10, 2020 00:33
Show Gist options
  • Save eduardinni/f63b1b4d90de480df793e815ab52a19c to your computer and use it in GitHub Desktop.
Save eduardinni/f63b1b4d90de480df793e815ab52a19c to your computer and use it in GitHub Desktop.
Code examples

3rd-party API as data source (Ruby)

Get data from Stripe API and calculates MRR (Monthly Recurring Revenue) for every customer. Each customer could have one or more subscriptions. A subscription could be monthly or yearly, also it could have a discount (percent or fixed amount). Also the data source could return with not paid subscriptions (delinquency)

require "stripe"
require "terminal-table"

class Metrics
  def initialize(api_key:)
    Stripe.api_key = api_key
    @results = []
    @last_customer_id_processed = nil
  end

  def get_mrr
    customers = Stripe::Customer.list({limit: 3, starting_after: @last_customer_id_processed})
    customers.auto_paging_each do |customer|
      @results << [
        customer.id,
        customer.name,
        customer.email,
        get_cus_revenue(customer.subscriptions, customer.discount, customer.delinquent)
      ]
      # Save last processed, to resume processing when fails
      @last_customer_id_processed = customer.id
    end

    print_results
  end

  def get_cus_revenue(cus_subscriptions={}, cus_discount={}, cus_delinquent=false)
    return 0.0 if (cus_subscriptions.empty? or cus_subscriptions.total_count == 0)

    # Sum for all customer subscriptions
    cus_subscriptions_total = 0

    cus_subscriptions.data.each do |cus_subscription|
      next unless sub_is_valid_for_mrr?(cus_subscription, cus_delinquent)

      # Sum for all subscription items
      subscription_items_total = 0

      cus_subscription.items.data.each do |subscription_item|
        subscription_items_total += get_subsciption_item_total(subscription_item)
      end

      # A discount applied to a subscription overrides a discount applied on a customer-wide basis
      if (cus_subscription.discount != nil)
        cus_subscriptions_total += apply_discount(subscription_items_total, cus_subscription.discount)
      else
        cus_subscriptions_total += apply_discount(subscription_items_total, cus_discount)
      end
    end

    cus_subscriptions_total / 100.0
  end

  def sub_is_valid_for_mrr?(subscription={}, cus_delinquent=false)
    # Subscription.status:
    # active:             valid for MRR
    # past_due:           valid for MRR (<30 days as delinquent, max duration of payment intents is 21 days)
    # unpaid:             valid for MRR (unless >30 days as delinquent)
    # canceled:           valid for MRR (unless >30 days as delinquent)
    # trialing:           not a customer yet
    # incomplete:         not a customer yet
    # incomplete_expired: not a customer yet
  
    return false if ["trialing", "incomplete", "incomplete_expired"].include?(subscription.status)
    return false if ["canceled", "unpaid"].include?(subscription.status) and cus_sub_delinquent_gt_30d?(subscription.id, cus_delinquent)
    true
  end

  # Check if a customer has been delinquent for more than 30 days
  # if true, customer not count for MRR
  def cus_sub_delinquent_gt_30d?(subscription_id, cus_delinquent)
    if cus_delinquent == true
      # Retrieve all invoices not payed from the subscription (open and uncollectible)
      # each invoice represents a period, so if there are 2 or more unpayed invoices
      # means that the customer as churned
      invoices_open = Stripe::Invoice.list({
        limit: 2,
        subscription: subscription_id,
        status: "open"
      })
      
      invoices_uncollectible = Stripe::Invoice.list({
        limit: 2,
        subscription: subscription_id,
        status: "uncollectible"
      })
      
      return (invoices_open.data.count + invoices_uncollectible.data.count) > 1
    end
    false
  end

  def get_subsciption_item_total(subscription_item={})
    sub_item_total = subscription_item.plan.amount * subscription_item.quantity

    if subscription_item.plan.interval == "year"
      sub_item_total = sub_item_total / 12
    end

    sub_item_total
  end

  def apply_discount(amount, discount)
    return amount if discount == nil

    if discount.coupon.amount_off != nil
      (amount - discount.coupon.amount_off).round
    elsif discount.coupon.percent_off != nil
      (amount - (amount * discount.coupon.percent_off * 0.01)).round
    end
  end

  def print_results
    headings = ["customer_id", "name", "email", "MRR"]
    table = Terminal::Table.new(headings: headings, rows: @results)
    puts table
  end
end

Tests

require "rspec/autorun"
require "ostruct"
require_relative "metrics"

describe Metrics do
  let(:metrics) { Metrics.new(api_key: "test") }

  it "calc discount: percent off" do
    discount = OpenStruct.new({
      object: "discount",
      coupon: OpenStruct.new({
        id: "J4ULHITv",
        object: "coupon",
        amount_off: nil,
        created: 1574340536,
        currency: nil,
        duration: "forever",
        duration_in_months: nil,
        livemode: false,
        max_redemptions: nil,
        metadata: {},
        name: "Awesome Customer",
        percent_off: 50.0,
        redeem_by: nil,
        times_redeemed: 2,
        valid: true
      })
    })

    expect(metrics.apply_discount(12000, discount)).to eq(6000)
  end

  it "calc discount: amount off" do
    discount = OpenStruct.new({
      object: "discount",
      coupon: OpenStruct.new({
        id: "j3j7R4cn",
        object: "coupon",
        amount_off: 1000,
        created: 1574340517,
        currency: "usd",
        duration: "repeating",
        duration_in_months: 2,
        livemode: false,
        max_redemptions: nil,
        metadata: {},
        name: "Special Promotion",
        percent_off: nil,
        redeem_by: nil,
        times_redeemed: 1,
        valid: true
      })
    })

    expect(metrics.apply_discount(12000, discount)).to eq(11000)
  end

  it "calc subscription item total (plan.amount * quantity): monthly" do
    subscription_item = OpenStruct.new({
      id: "si_GDgYiXvPfz6ACJ",
      object: "subscription_item",
      billing_thresholds: nil,
      created: 1574340877,
      metadata: {},
      plan: OpenStruct.new({
        id: "plan_GDgPBP2qUVJiGa",
        object: "plan",
        active: true,
        aggregate_usage: nil,
        amount: 9900,
        amount_decimal: "9900",
        billing_scheme: "per_unit",
        created: 1574340345,
        currency: "usd",
        interval: "month",
        interval_count: 1,
        livemode: false,
        metadata: {},
        nickname: "Business Monthly",
        product: "prod_GDgK2ZowqGQBjW",
        tiers: nil,
        tiers_mode: nil,
        transform_usage: nil,
        trial_period_days: 1,
        usage_type: "licensed"
      }),
      quantity: 2,
      subscription: "sub_GDgYFyca1k5qEa",
      tax_rates: []
    })

    expect(metrics.get_subsciption_item_total(subscription_item)).to eq(19800)
  end

  it "calc subscription item total (plan.amount * quantity): yearly" do
    subscription_item = OpenStruct.new({
      id: "si_GDgcE66sRas9fA",
      object: "subscription_item",
      billing_thresholds: nil,
      created: 1574341144,
      metadata: {},
      plan: OpenStruct.new({
        id: "plan_GDgPhFWDjgHUzc",
        object: "plan",
        active: true,
        aggregate_usage: nil,
        amount: 99900,
        amount_decimal: "99900",
        billing_scheme: "per_unit",
        created: 1574340373,
        currency: "usd",
        interval: "year",
        interval_count: 1,
        livemode: false,
        metadata: {},
        nickname: "Business Yearly",
        product: "prod_GDgK2ZowqGQBjW",
        tiers: nil,
        tiers_mode: nil,
        transform_usage: nil,
        trial_period_days: 1,
        usage_type: "licensed"
      }),
      quantity: 4,
      subscription: "sub_GDgYFyca1k5qEa",
      tax_rates: []
    })
    
    expect(metrics.get_subsciption_item_total(subscription_item)).to eq(33300)
  end

  it "calc customer MRR 2 active subscription items with subscription discount" do
    subscriptions = OpenStruct.new({
      object: "list",
      data: [
        OpenStruct.new({
          id: "sub_GDgc2wn80I4AqX",
          object: "subscription",
          application_fee_percent: nil,
          billing_cycle_anchor: 1574427543,
          billing_thresholds: nil,
          cancel_at: nil,
          cancel_at_period_end: false,
          canceled_at: nil,
          collection_method: "charge_automatically",
          created: 1574341143,
          current_period_end: 1606049943,
          current_period_start: 1574427543,
          customer: "cus_GDgUCP5GFyKN98",
          days_until_due: nil,
          default_payment_method: nil,
          default_source: nil,
          default_tax_rates: [],
          discount: OpenStruct.new({
            object: "discount",
            coupon: OpenStruct.new({
              id: "J4ULHITv",
              object: "coupon",
              amount_off: nil,
              created: 1574340536,
              currency: nil,
              duration: "forever",
              duration_in_months: nil,
              livemode: false,
              max_redemptions: nil,
              metadata: {},
              name: "Awesome Customer",
              percent_off: 50.0,
              redeem_by: nil,
              times_redeemed: 2,
              valid: true
            }),
            customer: "cus_GDgUCP5GFyKN98",
            end: nil,
            start: 1574341143,
            subscription: "sub_GDgc2wn80I4AqX"
          }),
          ended_at: nil,
          invoice_customer_balance_settings: {
            consume_applied_balance_on_void: true
          },
          items: OpenStruct.new({
            object: "list",
            data: [
              OpenStruct.new({
                id: "si_GDgcE66sRas9fA",
                object: "subscription_item",
                billing_thresholds: nil,
                created: 1574341144,
                metadata: {},
                plan: OpenStruct.new({
                  id: "plan_GDgPhFWDjgHUzc",
                  object: "plan",
                  active: true,
                  aggregate_usage: nil,
                  amount: 99900,
                  amount_decimal: "99900",
                  billing_scheme: "per_unit",
                  created: 1574340373,
                  currency: "usd",
                  interval: "year",
                  interval_count: 1,
                  livemode: false,
                  metadata: {},
                  nickname: "Business Yearly",
                  product: "prod_GDgK2ZowqGQBjW",
                  tiers: nil,
                  tiers_mode: nil,
                  transform_usage: nil,
                  trial_period_days: 1,
                  usage_type: "licensed"
                }),
                quantity: 2,
                subscription: "sub_GDgc2wn80I4AqX",
                tax_rates: []
              }),
              OpenStruct.new({
                id: "si_GDgcs918SzvBc7",
                object: "subscription_item",
                billing_thresholds: nil,
                created: 1574341144,
                metadata: {},
                plan: OpenStruct.new({
                  id: "plan_GDgR6ULiLvzb0x",
                  object: "plan",
                  active: true,
                  aggregate_usage: nil,
                  amount: 49900,
                  amount_decimal: "49900",
                  billing_scheme: "per_unit",
                  created: 1574340476,
                  currency: "usd",
                  interval: "year",
                  interval_count: 1,
                  livemode: false,
                  metadata: {},
                  nickname: "Business Yearly",
                  product: "prod_GDgQ9N7FqxoYGi",
                  tiers: nil,
                  tiers_mode: nil,
                  transform_usage: nil,
                  trial_period_days: 1,
                  usage_type: "licensed"
                }),
                quantity: 4,
                subscription: "sub_GDgc2wn80I4AqX",
                tax_rates: []
              })
            ],
            has_more: false,
            total_count: 2,
            url: "/v1/subscription_items?subscription=sub_GDgc2wn80I4AqX"
          }),
          latest_invoice: "in_1Fhbj7CDJhpoS4xhUPhWudoa",
          livemode: false,
          metadata: {},
          next_pending_invoice_item_invoice: nil,
          pending_invoice_item_interval: nil,
          pending_setup_intent: nil,
          plan: nil,
          quantity: nil,
          schedule: nil,
          start_date: 1574341143,
          status: "active",
          tax_percent: nil,
          trial_end: 1574427543,
          trial_start: 1574341143
        })
      ],
      has_more: false,
      total_count: 1,
      url: "/v1/customers/cus_GDgUCP5GFyKN98/subscriptions"
    })

    expect(metrics.get_cus_revenue(subscriptions)).to eq(166.42)
  end

  it "calc customer MRR: 1 trialing subscription item" do
    subscriptions = OpenStruct.new({
      object: "list",
      data: [
        OpenStruct.new({
          id: "sub_GDhOPPfRlSZfYM",
          object: "subscription",
          application_fee_percent: nil,
          billing_cycle_anchor: 1584711982,
          billing_thresholds: nil,
          cancel_at: nil,
          cancel_at_period_end: false,
          canceled_at: nil,
          collection_method: "charge_automatically",
          created: 1574343982,
          current_period_end: 1584711982,
          current_period_start: 1574343982,
          customer: "cus_GDhNVva6fnYHHL",
          days_until_due: nil,
          default_payment_method: nil,
          default_source: nil,
          default_tax_rates: [],
          discount: nil,
          ended_at: nil,
          invoice_customer_balance_settings: {
            consume_applied_balance_on_void: true
          },
          items: {
            object: "list",
            data: [
              OpenStruct.new({
                id: "si_GDhOOvSQqAvCQP",
                object: "subscription_item",
                billing_thresholds: nil,
                created: 1574343983,
                metadata: {},
                plan: OpenStruct.new({
                  id: "plan_GDhNfZmrXL08YW",
                  object: "plan",
                  active: true,
                  aggregate_usage: nil,
                  amount: 50000,
                  amount_decimal: "50000",
                  billing_scheme: "per_unit",
                  created: 1574343960,
                  currency: "usd",
                  interval: "month",
                  interval_count: 1,
                  livemode: false,
                  metadata: {},
                  nickname: "Recover long trial",
                  product: "prod_GDgQ9N7FqxoYGi",
                  tiers: nil,
                  tiers_mode: nil,
                  transform_usage: nil,
                  trial_period_days: 120,
                  usage_type: "licensed"
                }),
                quantity: 1,
                subscription: "sub_GDhOPPfRlSZfYM",
                tax_rates: []
              })
            ],
            has_more: false,
            total_count: 1,
            url: "/v1/subscription_items?subscription=sub_GDhOPPfRlSZfYM"
          },
          latest_invoice: "in_1FhFzGCDJhpoS4xhoGa2SVe1",
          livemode: false,
          metadata: {},
          next_pending_invoice_item_invoice: nil,
          pending_invoice_item_interval: nil,
          pending_setup_intent: nil,
          plan: OpenStruct.new({
            id: "plan_GDhNfZmrXL08YW",
            object: "plan",
            active: true,
            aggregate_usage: nil,
            amount: 50000,
            amount_decimal: "50000",
            billing_scheme: "per_unit",
            created: 1574343960,
            currency: "usd",
            interval: "month",
            interval_count: 1,
            livemode: false,
            metadata: {},
            nickname: "Recover long trial",
            product: "prod_GDgQ9N7FqxoYGi",
            tiers: nil,
            tiers_mode: nil,
            transform_usage: nil,
            trial_period_days: 120,
            usage_type: "licensed"
          }),
          quantity: 1,
          schedule: nil,
          start_date: 1574343982,
          status: "trialing",
          tax_percent: nil,
          trial_end: 1584711982,
          trial_start: 1574343982
        })
      ],
      has_more: false,
      total_count: 1,
      url: "/v1/customers/cus_GDhNVva6fnYHHL/subscriptions"
    })

    expect(metrics.get_cus_revenue(subscriptions)).to eq(0.0)
  end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment