- 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.
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
-
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
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)
$ 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/
config.gem ‘activemerchant’, :lib => ‘active_merchant’ config.gem “money” config.gem ‘state_machine’ config.gem ‘lockfile’
$ ./script/generate saasramp
$ ./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
-
Edit the file db/subscription_plans.yml
-
Load into database
$ rake saas:plans
-
gateway name and login parameters
-
default settings
-
environment specific settings
-
custom attributes (if any)
To the model that will own the subscription (e.g. User or Account), add
acts_as_subscriber
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 }
$ ./script/generate saas_scaffold
config.active_record.observers = :subscription_observer
and modify subscription.yml with your mailer class name
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.
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
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
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
-
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
We appreciate a donation of $250 for one site, $1000 for multiple sites. (Just kidding).
jonathan at linowes dat com
-
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
-