Ruby Scripting

by Jess Brown

As a rails developer, I spend a majority of my time in rails. This is fine, because I love rails, but it's nice to write something outside of rails from time to time...in particular, a ruby script.

Stripe Transfer

My client was recently acquired and needed to migrate their stripe account to another stripe account. Stripe will handle transferring customers and cards, but it's up to you to transfer the rest. The client's app was a SaaS app and heavily made use of subscriptions. The account had thousands of subscriptions that needed to be ported over to the new account.

Using the Stripe gem and the api, I was able to transfer them over without much trouble.


require 'stripe'
require 'pry-debugger'

MODE = :live

def old_stripe_key
  # live
  # key = "xxx"
  # test
  key = "xxx"
  key
end

def new_stripe_key
  # live
  # key = "xxx"
  # test
  key = "xxx"
  key
end

def old_customers
  Stripe::Customer.all({limit: 10}, old_stripe_key)
end

def new_customers
  Stripe::Customer.all({ limit: 100 }, new_stripe_key)
end

def migrate(customers)
  puts "The End" && return if customers.data.empty?
  puts customers.data.count
  sync_customers(customers.data)
  migrate(Stripe::Customer.all({ limit: 100, starting_after: customers.data.last.id }, old_stripe_key))
end

def sync_customers(customers)
  customers.each do |customer|
    File.open('sync.log', 'a') { |file| file.write("\n#{customer.id} ") }
    puts customer.id
    subscriptions = customer.subscriptions
    if subscriptions.data.any?
      subscriptions.data.each do |subscription|
        File.open('sync.log', 'a') { |file| file.write("#{subscription.id}") }
        puts subscription.id
        plan_id = subscription.plan.id
        current_period_end = subscription.current_period_end
        if MODE == :test
          new_customer = Stripe::Customer.create(
            { :description => "clone #{customer.id}" },
            new_stripe_key
          )
        else
          new_customer = Stripe::Customer.retrieve(customer.id, new_stripe_key)
        end
        unless new_customer.subscriptions.data.map(&:id).include?(subscription.id)
          new_customer.subscriptions.create(:plan => plan_id, :billing_cycle_anchor => current_period_end, :prorate => false)
          subscription.delete unless MODE == :test
        end
      end
    end
  end
end

def print_active_subscriptions(customers)
  puts "The End" && return if customers.data.empty?
  puts customers.data.count
  active_subscriptions_for(customers.data)
  print_active_subscriptions(Stripe::Customer.all({ limit: 100, starting_after: customers.data.last.id }, customers.api_key))
end

def active_subscriptions_for(customers)
  customers.each do |customer|
    subscriptions = customer.subscriptions
    if subscriptions.data.any?
      subscriptions.data.each do |subscription|
        puts "#{customer.id} #{subscription.id}"
      end
    end
  end
end

As you can see it's a crude but straightforward script. We didn't have a lot of time to implement it so I just hashed it out rather procedurally.

A couple of the challenges were:

1. How to jump back and forth from the old stripe account and new account

Typically you see api gems do something like

  connection = API::Connection.new(:api_key => 'xxx')
  # Then
  connection.customers.all

But I didn't see how to get this type of instance with the stripe api. However, Stripe has great support and Brian Collins helped me out with how to pass the api_key. It's the last argument in the constructor, which means if you're passing options like in the all method, you have to wrap those options in {}.

2. How to run a test on non-live data

Stripe gives you a great testing environment, but for our scenario (we would have customers in both accounts with identical ID's) we couldn't mimic the live setup(you cannot specify ID's when creating customers). So while in test mode, what I chose to do was just clone the customer instead of finding them in the new account. And by passing in the old customer id to the description, I could easily locate the new customer to compare subscriptions.

if MODE == :test
  new_customer = Stripe::Customer.create(
      { :description => "clone #{customer.id}" },
      new_stripe_key
    )
else
  new_customer = Stripe::Customer.retrieve(customer.id, new_stripe_key)
end

3. Setting up the subscription

First off, we had to recreate our plans with the same id in the new account. Typically this won't be a problem to manually do, because you'll likely only have a handful of plans. Just make sure to get the exact id and price (unless you're changing prices). Next we want to make sure the customer doesn't see a change in their billing. If they were billed on the 7th of last month, then their next billing should be the 7th. Also Stripe will automatically prorate a subscription, so you don't want that to happen (because their already paid for the whole period on their last billing). The solution was pretty easy:

  new_customer.subscriptions.create(
                        :plan => plan_id, 
                        :billing_cycle_anchor => current_period_end, 
                        :prorate => false)

Set prorate to false and the billing_cycle_anchor to the date of current_period_end of the old subscription, and the plan to the plan_id of the old subscription.

Summary

It seemed to work well. When we first ran the script on the live data, we only did a few:

pry> customers = old_customers.data[13..14]
pry> sync_customers customers

That's when we discovered we had a setting in Stripe of notify the customer if a subscription was canceled. Oops! Glad we caught that before running it on thousands of customers.

Also you can see that there are two methods at the end to print out all of the active subscriptions. We used this in a before and after snapshot to make sure our numbers added up.

The script could probably be written a little more ruby like but for a quick job, it worked out OK.

What do you think? How would you have done it?

Stripe HowTo


comments powered by Disqus