Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
253 lines (181 sloc) 10.2 KB

SaasRamp

notice: not yet used in production

SaasRamp is an open source Rails plugin which enables subscription billings in your application. I decided to take a somewhat different approach than others I have seen. It is built as a wrapper on ActiveMerchant, stores credit cards in the gateway, handles its own daily processing, and is completely independent of the authorization and authentication you choose.

Key Features

Built as a wrapper to ActiveMerchant

  • Uses the AM gateways

  • Uses the AM credit card validation

  • Requires gateways that support store/unstore credit cards, and transactions using the vault key

Recurring billing and other daily tasks and notifications run on your server

  • Does not use recurring billing at the gateway (for more control and to avoid synchronization problems)

  • Run “rake saas:daily” (e.g. as a daily cron job)

  • Billing task can be run any time, skip a day, or multiple times a day without fear of sending duplicate billings or messages

Decouples subscriptions from authentication and authorization

  • You can use Restful Authentication, Authlogic or anything else

  • Declare your model (e.g. User or Account) with “acts_as_subscriber”

Separates the subscription, customer profile (credit card), and transaction history

Subscription model

  • When a model “acts_as_subscriber” it has one subscription

  • Subscription states - :free, :trial, :active, :past_due, :expired

  • #renew method processes recurring billing

  • #change_plan method for changing plans

  • #charge_balance to bill credit card current account balance

Subscription plan model

  • Define plans with different name, rate, interval

  • Migrate your own attributes (e.g. to define limitations, like max_memory, etc)

  • Plans defined in db/subscription_plans.yml file, loaded with “rake saas:plans” task

Subscription profile model

  • subscription has one profile

  • Responsible for handling the credit card information

  • Automatic validation, storing, and unstoring in vault on the gateway

  • Profile states - :no_info, :authorized, :error

Subscription transaction model

  • subscription has many transactions

  • Provides a transaction history

  • Wraps ActiveMerchant, unifying inconsistent gateway api

  • Handles exceptions and gateway responses

Subscription observer and mailer

  • observe transactions to send out email notifications as needed

  • email delivery issued from one file, un-clutters the models

  • includes mailer templates you can use or change

Example workflow

  • New subscriptions can default to a free plan

  • New (non free) subscriptions start in :trial state (optional)

  • A warning email is sent out a few days before trial expires (trial period configurable)

  • When the trial period is over, and billing is successful, the subscription becomes :active

  • When renewals are due, :active subscriptions are billed and next renewal date is updated

  • If there's a billing error, subscription becomes :past_due

  • Past due subscriptions have a grace period (optional) and warnings are sent before subscription becomes :expired

  • Expired subscriptions can revert to a limited plan rather than shut down the account

Dependencies

Requires the following gems

  • ActiveMerchant - for gateways and credit card validation

  • Money - for currency numerics

  • state_machine - a better state machine

  • lockfile - for rake tasks

Testing requires gems

  • rspec, rspec-on-rails

optional:

  • cucumber

  • no-peeping-toms (plugin)

Installation

$ script/plugin install git://github.com/linoj/saasramp.git

Easy configuration and customization

  • Configuration via a config/subscription.yml file (can vary per environment)

  • Populate and maintain current plans via a db/subscription_plans.yml file (can vary per environment)

  • Initializer generator for the default migration and configuration files

  • Scaffold generator for example controllers and views

  • Rake task for daily processing, you create a cron job

  • Gateway monkeypatches in config/initializers/gateways/

Add to environment.rb

config.gem 'activemerchant', :lib => 'active_merchant' config.gem “money” config.gem 'state_machine' config.gem 'lockfile'

Generate initialize

$ ./script/generate saasramp

Generate migrations

$ ./script/generate saas_migration (Review the file, adjust as needed, including custom attributes if any) (Note, you can migrate existing subscribers data at the same time) $ rake db:migrate

Define and populate the subscription plans

  • Edit the file db/subscription_plans.yml

  • Load into database

$ rake saas:plans

Edit the configuration file config/subscription.yml

  • gateway name and login parameters

  • default settings

  • environment specific settings

  • custom attributes (if any)

Acts as subscriber

To the model that will own the subscription (e.g. User or Account), add

acts_as_subscriber

Optionally, migrate existing subscribers

If you already have subscribers in your database (e.g. User or Account records), you need to create a subscription child object (and default plan) for them. This is easy, just re-save the objects. You can do this in console, or in another migration. For example,

User.all.each {|a| a.save }

Optionally, generate scaffolds (controllers and views)

$ ./script/generate saas_scaffold

Optionally, add email notification observers, in environment.rb

config.active_record.observers = :subscription_observer

Optionally, create your own mailer, using the SubscriptionMailer and templates as an example

and modify subscription.yml with your mailer class name

Gateways

Extensions/fixes to the ActiveMerchant gateways are in config/initializers/active_merchant/

I've only tested wrappers for the Authorize.Net CIM and Braintree gateways. The AN-CIM one is temporary until ActiveMerchant integrates CIM into the regular AuthorizeNet gateway.

The gateway is expected to support the following API:

Credit card based authorized/void, if you enable credit card validation at the gateway authorize( amount, credit_card ) # => response.authorization is the reference id void( reference ) Credit card storage and unstore store( credit_card ) # => response.token is the vault profile_key unstore( profile_key ) update( profile_key, credit_card ) # optional, if not we will unstore/store

Purchase based on customer profile key (vault key) (we'll use either of the following methods) purchase( amount, profile_key ) # => response.authorization is the reference id or: authorize( amount, profile_key ) # => response.authorization is the reference id capture( amount, reference ) Credit/refund (we'll use either of the following methods) credit( amount, profile_key ) or: refund( reference, :amount => amount ) See subscription_transaction.rb and spec/remote/*_spec.rb for more details.

Plan limits checker

In your subscriber model you can declare a callback, #subscription_plan_check, that checks whether a subscriber has exceeded limits for his plan. This is used by Subscription#allowed_plans. The method is expected to return a blank value if ok (nil, false, [], {}), anything else means subscriber has exceeded limits. For example,

def subscription_plan_check(plan) (memory_used > plan.max_memory) || (file_count > plan.max_files) end # Or, def subscription_plan_check(plan) exceeded = {} exceeded = plan.max_memory if memory_used > plan.max_memory exceeded = plan.max_files if file_count > plan.max_files exceeded end

Setup recurring billing

Review the rake task (tasks/saasramp_tasks.rake) and make sure the business logic meets your requirements. The task can be run any time from the command line,

$ rake saas:daily RAILS_ENV=production You can re-run the task multiple times a day without fear of accidental duplicate billings or notification emails.

Remember that email notifications are sent not by the rake task but through the SubscriptionObserver whenever a SubscriptionTransaction is created. You can modify that behavior by editing the subscription_observer.rb file, or simply not enabling the observer in your environment.rb.

To setup recurring billing on your server, use a cron tab manager to run the task, for example,

“cd ~/myapp && rake saas:daily RAILS_ENV=production”

To setup from the command line to run every day at 3am, for example:

$ echo “0 3 * * * cd ~/myapp && rake saas:daily RAILS_ENV=production” > daily.txt $ crontab daily.txt

Test the plugin

Uses RSpec, which requires a dummy app to run the specs.

$ rails saastest $ cd saastest edit environment.rb config.gem 'activemerchant', :lib => 'active_merchant' config.gem “money” config.gem 'state_machine' edit environments/test.rb config.gem “rspec”, :lib => false, :version => “>= 1.2.0” config.gem “rspec-rails”, :lib => false, :version => “>= 1.2.0”

$ script/generate spec $ script/install plugin git:… $ script/generate saasramp $ cd vendor/plugins/saasramp/ $ rake spec $ rake remote_spec

Bonus: Example Cucumber features and steps included

$ script/generate subscription_features

References

  • Peepcode ActiveMerchant pdf tutorial by Cody Fauser

  • Railscasts ActiveMerchant screencasts (144, 145)

  • The Bala Paranj screencasts on ActiveMerchant + Authorize.net

  • Freemium

  • Saasy

Developed for the ReviewRamp (www.reviewramp.com) application

Cost

We appreciate a donation of $250 for one site, $1000 for multiple sites. (Just kidding).

jonathan at linowes dat com

Notes

  • Uses the Money class for money but haven't implemented currency or exchange rates

  • I built this for a “freemium” business model (en.wikipedia.org/wiki/Freemium) (sign up free, pay for more features). It should work for “subscribe or nothing” but I havent worked through those scenarios. I figure you'll always want people to be able to log in and adjust their account even if they're not a paying subscriber at the moment.

  • Works with acts_as_paranoid. Declare your subscriber model a_a_paranoid before a_a_subscriber. I've had problems with the aap gem, the plugin works (and use the edendevelopment fork, see www.vaporbase.com/postings/stack_level_too_deep). The subscription and its children are NOT paranoid, and they stick around until the subscriber is really really destroy! (bang).

Copyright © 2009 Jonathan Linowes, released under the MIT license