Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

basics

  • Loading branch information...
commit eb106434e48a65cbfc230ed5de1a9fd1326fd541 1 parent 1dab693
@BM5k authored
Showing with 621 additions and 369 deletions.
  1. +1 −1  .rvmrc
  2. +4 −1 Gemfile
  3. +12 −2 Gemfile.lock
  4. +57 −0 README.md
  5. +0 −261 README.rdoc
  6. +13 −0 app/controllers/plans_controller.rb
  7. +13 −0 app/models/plan.rb
  8. +51 −0 app/models/subscription.rb
  9. +15 −3 app/models/user.rb
  10. +15 −0 app/views/application/_card_fields.html.haml
  11. +0 −14 app/views/layouts/application.html.erb
  12. +23 −0 app/views/layouts/application.html.haml
  13. +13 −0 app/views/plans/index.html.haml
  14. +22 −0 app/views/plans/show.html.haml
  15. +18 −0 app/views/subscriptions/edit.html.haml
  16. +2 −1  config/initializers/rails_admin.rb
  17. +10 −0 config/initializers/stripe.rb
  18. +4 −54 config/routes.rb
  19. +4 −0 config/stripe.sample.yml
  20. 0  db/migrate/{20120128044743_devise_create_users.rb → 20111107003913_devise_create_users.rb}
  21. +2 −2 ...28045950_create_rails_admin_histories_table.rb → 20111108045118_create_rails_admin_histories_table.rb}
  22. +16 −0 db/migrate/20120128044743_create_plans.rb
  23. +20 −0 db/migrate/20120128045950_create_subscriptions.rb
  24. +30 −4 db/schema.rb
  25. +86 −0 spec/data/billing/customer_create.yml
  26. +20 −0 spec/factories.rb
  27. +11 −0 spec/models/factories_spec.rb
  28. +51 −0 spec/models/plan_spec.rb
  29. +74 −0 spec/models/subscription_spec.rb
  30. +34 −26 spec/spec_helper.rb
View
2  .rvmrc
@@ -1 +1 @@
-rvm use 1.9.2@stripe-example
+rvm use 1.9.3@stripe-example
View
5 Gemfile
@@ -3,11 +3,13 @@ source 'https://rubygems.org'
gem 'rails', '3.2.1'
gem 'devise'
+gem 'friendly_id', '~> 4.0.0'
gem 'haml-rails'
gem 'jquery-rails'
+gem 'pg'
gem 'rails_admin', git: 'git://github.com/sferik/rails_admin.git'
gem 'sass-rails', '~> 3.2.3'
-gem 'pg'
+gem 'stripe'
group :assets do
gem 'coffee-rails', '~> 3.2.1'
@@ -18,6 +20,7 @@ group :development, :test do
gem 'hirb'
gem 'pry'
gem 'pry-doc'
+ gem 'pry-nav'
gem 'rspec-rails', '~> 2.8'
end
View
14 Gemfile.lock
@@ -93,6 +93,7 @@ GEM
factory_girl (~> 2.5.0)
railties (>= 3.0.0)
ffi (1.0.11)
+ friendly_id (4.0.0)
haml (3.1.4)
haml-rails (0.3.4)
actionpack (~> 3.0)
@@ -126,13 +127,15 @@ GEM
orm_adapter (0.0.6)
pg (0.12.2)
polyglot (0.3.3)
- pry (0.9.8)
+ pry (0.9.8.1)
coderay (~> 1.0.5)
method_source (~> 0.7)
slop (>= 2.4.3, < 3)
pry-doc (0.4.0)
pry (>= 0.9.0)
yard (~> 0.7.4)
+ pry-nav (0.1.0)
+ pry (~> 0.9.8.1)
rack (1.4.1)
rack-cache (1.1)
rack (>= 0.4)
@@ -162,6 +165,8 @@ GEM
rdoc (3.12)
json (~> 1.4)
remotipart (1.0.1)
+ rest-client (1.6.7)
+ mime-types (>= 1.16)
rspec (2.8.0)
rspec-core (~> 2.8.0)
rspec-expectations (~> 2.8.0)
@@ -186,11 +191,13 @@ GEM
ffi (~> 1.0.9)
multi_json (~> 1.0.4)
rubyzip
- slop (2.4.3)
+ slop (2.4.4)
sprockets (2.1.2)
hike (~> 1.2)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
+ stripe (1.6.0)
+ rest-client (~> 1.4)
syntax (1.0.0)
thor (0.14.6)
tilt (1.3.3)
@@ -223,6 +230,7 @@ DEPENDENCIES
database_cleaner
devise
factory_girl_rails
+ friendly_id (~> 4.0.0)
haml-rails
hirb
jquery-rails
@@ -230,10 +238,12 @@ DEPENDENCIES
pg
pry
pry-doc
+ pry-nav
rails (= 3.2.1)
rails_admin!
rspec-rails (~> 2.8)
sass-rails (~> 3.2.3)
+ stripe
syntax
uglifier (>= 1.0.3)
vcr
View
57 README.md
@@ -0,0 +1,57 @@
+Stripe Example
+==============
+
+Reference implementation of stripe interface
+
+## About
+
+URLs:
+
+- Github: https://github.com/bm5k/stripe-example/
+- Staging server: https://stripe-example.herokuapp.com
+
+## Configuration
+
+You will need to add plans to your stripe account!
+
+## Development
+
+```bash
+$ git clone git@github.com:bm5k/stripe-example.git # clone the repo
+$ cd stripe-example
+$ bundle install # install all dependencies, we recommend using RVM
+$ cp config/database.sample.yml config/database.yml # use the default database.yml (or customize it)
+$ cp config/stripe.sample.yml config/stripe.yml # add your own keys or you'll be sorry
+$ rake db:setup # creates the development database
+$ ./script/server # start the server
+```
+
+## Testing
+
+Tools used:
+
+- rspec
+- capybara
+- factory girl
+
+```bash
+rake spec # this will run all of the RSpec specifications, located in ./spec
+```
+
+- Specs are organized into Models & Integration specs (/spec/models, /spec/requests)
+- Model Factories are located in /spec/factories.rb
+
+## Deployment
+
+Recommended deployment is done via [Heroku](http://heroku.com). They have an excellent intro at http://docs.heroku.com/quickstart
+
+### Environment Variables
+
+Use the heroku config command to check/set environemnt variables on Heroku.
+
+ - **STRIPE_APP_ID**: the stripe application id
+ - **STRIPE_SECRET**: the stripe secret
+
+## Credits
+
+Developed by Dev Fu!, LLC. http://devfu.com
View
261 README.rdoc
@@ -1,261 +0,0 @@
-== Welcome to Rails
-
-Rails is a web-application framework that includes everything needed to create
-database-backed web applications according to the Model-View-Control pattern.
-
-This pattern splits the view (also called the presentation) into "dumb"
-templates that are primarily responsible for inserting pre-built data in between
-HTML tags. The model contains the "smart" domain objects (such as Account,
-Product, Person, Post) that holds all the business logic and knows how to
-persist themselves to a database. The controller handles the incoming requests
-(such as Save New Account, Update Product, Show Post) by manipulating the model
-and directing data to the view.
-
-In Rails, the model is handled by what's called an object-relational mapping
-layer entitled Active Record. This layer allows you to present the data from
-database rows as objects and embellish these data objects with business logic
-methods. You can read more about Active Record in
-link:files/vendor/rails/activerecord/README.html.
-
-The controller and view are handled by the Action Pack, which handles both
-layers by its two parts: Action View and Action Controller. These two layers
-are bundled in a single package due to their heavy interdependence. This is
-unlike the relationship between the Active Record and Action Pack that is much
-more separate. Each of these packages can be used independently outside of
-Rails. You can read more about Action Pack in
-link:files/vendor/rails/actionpack/README.html.
-
-
-== Getting Started
-
-1. At the command prompt, create a new Rails application:
- <tt>rails new myapp</tt> (where <tt>myapp</tt> is the application name)
-
-2. Change directory to <tt>myapp</tt> and start the web server:
- <tt>cd myapp; rails server</tt> (run with --help for options)
-
-3. Go to http://localhost:3000/ and you'll see:
- "Welcome aboard: You're riding Ruby on Rails!"
-
-4. Follow the guidelines to start developing your application. You can find
-the following resources handy:
-
-* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html
-* Ruby on Rails Tutorial Book: http://www.railstutorial.org/
-
-
-== Debugging Rails
-
-Sometimes your application goes wrong. Fortunately there are a lot of tools that
-will help you debug it and get it back on the rails.
-
-First area to check is the application log files. Have "tail -f" commands
-running on the server.log and development.log. Rails will automatically display
-debugging and runtime information to these files. Debugging info will also be
-shown in the browser on requests from 127.0.0.1.
-
-You can also log your own messages directly into the log file from your code
-using the Ruby logger class from inside your controllers. Example:
-
- class WeblogController < ActionController::Base
- def destroy
- @weblog = Weblog.find(params[:id])
- @weblog.destroy
- logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!")
- end
- end
-
-The result will be a message in your log file along the lines of:
-
- Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1!
-
-More information on how to use the logger is at http://www.ruby-doc.org/core/
-
-Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are
-several books available online as well:
-
-* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe)
-* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
-
-These two books will bring you up to speed on the Ruby language and also on
-programming in general.
-
-
-== Debugger
-
-Debugger support is available through the debugger command when you start your
-Mongrel or WEBrick server with --debugger. This means that you can break out of
-execution at any point in the code, investigate and change the model, and then,
-resume execution! You need to install ruby-debug to run the server in debugging
-mode. With gems, use <tt>sudo gem install ruby-debug</tt>. Example:
-
- class WeblogController < ActionController::Base
- def index
- @posts = Post.all
- debugger
- end
- end
-
-So the controller will accept the action, run the first line, then present you
-with a IRB prompt in the server window. Here you can do things like:
-
- >> @posts.inspect
- => "[#<Post:0x14a6be8
- @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>,
- #<Post:0x14a6620
- @attributes={"title"=>"Rails", "body"=>"Only ten..", "id"=>"2"}>]"
- >> @posts.first.title = "hello from a debugger"
- => "hello from a debugger"
-
-...and even better, you can examine how your runtime objects actually work:
-
- >> f = @posts.first
- => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
- >> f.
- Display all 152 possibilities? (y or n)
-
-Finally, when you're ready to resume execution, you can enter "cont".
-
-
-== Console
-
-The console is a Ruby shell, which allows you to interact with your
-application's domain model. Here you'll have all parts of the application
-configured, just like it is when the application is running. You can inspect
-domain models, change values, and save to the database. Starting the script
-without arguments will launch it in the development environment.
-
-To start the console, run <tt>rails console</tt> from the application
-directory.
-
-Options:
-
-* Passing the <tt>-s, --sandbox</tt> argument will rollback any modifications
- made to the database.
-* Passing an environment name as an argument will load the corresponding
- environment. Example: <tt>rails console production</tt>.
-
-To reload your controllers and models after launching the console run
-<tt>reload!</tt>
-
-More information about irb can be found at:
-link:http://www.rubycentral.org/pickaxe/irb.html
-
-
-== dbconsole
-
-You can go to the command line of your database directly through <tt>rails
-dbconsole</tt>. You would be connected to the database with the credentials
-defined in database.yml. Starting the script without arguments will connect you
-to the development database. Passing an argument will connect you to a different
-database, like <tt>rails dbconsole production</tt>. Currently works for MySQL,
-PostgreSQL and SQLite 3.
-
-== Description of Contents
-
-The default directory structure of a generated Ruby on Rails application:
-
- |-- app
- | |-- assets
- | |-- images
- | |-- javascripts
- | `-- stylesheets
- | |-- controllers
- | |-- helpers
- | |-- mailers
- | |-- models
- | `-- views
- | `-- layouts
- |-- config
- | |-- environments
- | |-- initializers
- | `-- locales
- |-- db
- |-- doc
- |-- lib
- | `-- tasks
- |-- log
- |-- public
- |-- script
- |-- test
- | |-- fixtures
- | |-- functional
- | |-- integration
- | |-- performance
- | `-- unit
- |-- tmp
- | |-- cache
- | |-- pids
- | |-- sessions
- | `-- sockets
- `-- vendor
- |-- assets
- `-- stylesheets
- `-- plugins
-
-app
- Holds all the code that's specific to this particular application.
-
-app/assets
- Contains subdirectories for images, stylesheets, and JavaScript files.
-
-app/controllers
- Holds controllers that should be named like weblogs_controller.rb for
- automated URL mapping. All controllers should descend from
- ApplicationController which itself descends from ActionController::Base.
-
-app/models
- Holds models that should be named like post.rb. Models descend from
- ActiveRecord::Base by default.
-
-app/views
- Holds the template files for the view that should be named like
- weblogs/index.html.erb for the WeblogsController#index action. All views use
- eRuby syntax by default.
-
-app/views/layouts
- Holds the template files for layouts to be used with views. This models the
- common header/footer method of wrapping views. In your views, define a layout
- using the <tt>layout :default</tt> and create a file named default.html.erb.
- Inside default.html.erb, call <% yield %> to render the view using this
- layout.
-
-app/helpers
- Holds view helpers that should be named like weblogs_helper.rb. These are
- generated for you automatically when using generators for controllers.
- Helpers can be used to wrap functionality for your views into methods.
-
-config
- Configuration files for the Rails environment, the routing map, the database,
- and other dependencies.
-
-db
- Contains the database schema in schema.rb. db/migrate contains all the
- sequence of Migrations for your schema.
-
-doc
- This directory is where your application documentation will be stored when
- generated using <tt>rake doc:app</tt>
-
-lib
- Application specific libraries. Basically, any kind of custom code that
- doesn't belong under controllers, models, or helpers. This directory is in
- the load path.
-
-public
- The directory available for the web server. Also contains the dispatchers and the
- default HTML files. This should be set as the DOCUMENT_ROOT of your web
- server.
-
-script
- Helper scripts for automation and generation.
-
-test
- Unit and functional tests along with fixtures. When using the rails generate
- command, template test files will be generated for you and placed in this
- directory.
-
-vendor
- External libraries that the application depends on. Also includes the plugins
- subdirectory. If the app has frozen rails, those gems also go here, under
- vendor/rails/. This directory is in the load path.
View
13 app/controllers/plans_controller.rb
@@ -0,0 +1,13 @@
+class PlansController < ApplicationController
+
+ def index
+ @plans = Plan.order 'price asc'
+ respond_with @plans
+ end
+
+ def show
+ @plan = Plan.find_by_slug params[:plan]
+ @subscription = Subscription.new
+ end
+
+end
View
13 app/models/plan.rb
@@ -0,0 +1,13 @@
+class Plan < ActiveRecord::Base
+
+ extend FriendlyId
+
+ friendly_id :name, :use => :slugged
+
+ has_many :subscriptions
+
+ validates :name, presence: true, uniqueness: true
+ validates :slug, presence: true, uniqueness: true
+ validates :price, presence: true, numericality: { greater_than_or_equal_to: 0 }
+
+end
View
51 app/models/subscription.rb
@@ -0,0 +1,51 @@
+class Subscription < ActiveRecord::Base
+
+ attr_accessor :stripe_card_token
+
+ belongs_to :plan
+ belongs_to :user
+
+ validates :plan_id, presence: true
+ validates :user_id, presence: true, uniqueness: true
+ validates :stripe_customer_token, presence: true
+
+ def save_with_payment
+ customer = Stripe::Customer.create email: user.email, plan: plan.slug, card: stripe_card_token
+ card = customer.active_card
+
+ self.stripe_customer_token = customer.id
+ self.last_four = card.last4
+ self.card_type = card.type
+ self.next_bill_on = Date.parse customer.next_recurring_charge.date
+ self.card_expiration = "#{card.exp_month}-#{card.exp_year}"
+ save
+ rescue Stripe::InvalidRequestError => e
+ logger.error "[STRIPE] #{ e }"
+ errors[:base] << "Unable to process your credit card!"
+ false
+ end
+
+ def update_with_payment params
+ raise Stripe::InvalidRequestError.new('Stripe Card Token is blank!', 'stripe_card_token') if params[:stripe_card_token].blank?
+
+ customer = Stripe::Customer.retrieve stripe_customer_token
+ customer.card = params[:stripe_card_token]
+ customer.description = self.id
+ customer = customer.save # wonky, blame stripe!
+
+ card = customer.active_card
+
+ self.stripe_customer_token = customer.id
+ self.last_four = card.last4
+ self.card_type = card.type
+ self.next_bill_on = Date.parse customer.next_recurring_charge.date
+ self.card_expiration = "#{card.exp_month}-#{card.exp_year}"
+
+ save
+ rescue Stripe::InvalidRequestError => e
+ logger.error "[STRIPE] #{ e }"
+ errors[:base] << "Unable to process your credit card!"
+ false
+ end
+
+end
View
18 app/models/user.rb
@@ -1,9 +1,21 @@
class User < ActiveRecord::Base
+
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
- devise :database_authenticatable, :registerable,
- :recoverable, :rememberable, :trackable, :validatable
+ devise :database_authenticatable,
+ :recoverable,
+ :registerable,
+ :rememberable,
+ :trackable,
+ :validatable
# Setup accessible (or protected) attributes for your model
- attr_accessible :email, :password, :password_confirmation, :remember_me
+ attr_accessible :email,
+ :password,
+ :password_confirmation,
+ :remember_me
+
+ has_one :subscription
+ has_one :plan, :through => :subscription
+
end
View
15 app/views/application/_card_fields.html.haml
@@ -0,0 +1,15 @@
+= label_tag :card_number, 'Credit Card Number'
+= text_field_tag :card_number, nil, name: nil, maxlength: 16
+
+= label_tag :card_code, 'Security Code (CVV)'
+= text_field_tag :card_code, nil, name: nil, maxlength: 4
+
+= f.label :card_zip, 'Billing Zip Code'
+= f.text_field :card_zip, maxlength: 5
+
+= label_tag :card_month, 'Card Expiration'
+= select_month Date.today.month, { add_month_numbers: true }, { name: nil, id: 'card_month' }
+= select_year nil, { start_year: Date.today.year, end_year: Date.today.year + 6 }, { name: nil, id: 'card_year' }
+
+%noscript
+ JavaScript is not enabled and is required for this form. First enable it in your web browser settings.
View
14 app/views/layouts/application.html.erb
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
- <title>StripeExample</title>
- <%= stylesheet_link_tag "application", :media => "all" %>
- <%= javascript_include_tag "application" %>
- <%= csrf_meta_tags %>
-</head>
-<body>
-
-<%= yield %>
-
-</body>
-</html>
View
23 app/views/layouts/application.html.haml
@@ -0,0 +1,23 @@
+!!!
+%html{ 'xml:lang' => 'en', lang: 'en', xmlns: 'http://www.w3.org/1999/xhtml' }
+
+ %head
+ %meta{ name: 'stripe-key', content: STRIPE_PUBLIC_KEY }
+ = csrf_meta_tag
+
+ = stylesheet_link_tag 'application'
+ = javascript_include_tag 'https://js.stripe.com/v1/', 'application'
+
+ %title
+ = yield :title
+
+ %body{ class: Rails.env }
+
+ %header
+ = yield :header
+ - flash.each do |type, message|
+ .flash{ class: type }= message
+
+ = yield
+
+ %footer
View
13 app/views/plans/index.html.haml
@@ -0,0 +1,13 @@
+= title 'Plans'
+
+%table
+ %thead
+ %tr
+ %th Name
+ %th Price
+
+ %tbody
+ - for plan in @plans
+ %tr
+ %td= link_to plan.name, signup_path(plan: plan)
+ %td= number_to_currency plan.price
View
22 app/views/plans/show.html.haml
@@ -0,0 +1,22 @@
+= title 'Sign Up'
+
+%h2 You have chosen the #{ @plan.name } plan
+%p
+ This plan costs
+ %strong= number_to_currency @plan.price
+ per month
+
+= form_for @subscription do |f|
+ .error_messages
+ - if @subscription.errors.any?
+ %h2 Something went wrong!
+ %ul.errors
+ - @subscription.errors.full_messages.each do |msg|
+ %li= msg
+
+ = f.hidden_field :plan_id, value: @plan.id
+ = f.hidden_field :stripe_card_token
+
+ = render 'card_fields', locals: { f: f }
+
+ = f.submit "Signup for the #{ @plan.name } plan", class: 'btn primary large'
View
18 app/views/subscriptions/edit.html.haml
@@ -0,0 +1,18 @@
+%h2 Billing Information
+
+= form_for @subscription do |f|
+ .error_messages
+ - if @subscription.errors.any?
+ %h2 Something went wrong!
+ %ul.errors
+ - @subscription.errors.full_messages.each do |msg|
+ %li= msg
+
+ - if Rails.env.test? # capybara won't set hidden fields
+ = f.text_field :stripe_card_token
+ - else
+ = f.hidden_field :stripe_card_token
+
+ = render 'card_fields', locals: { f: f }
+
+ = f.submit 'Update your credit card', class: 'btn primary large'
View
3  config/initializers/rails_admin.rb
@@ -7,7 +7,8 @@
# require 'i18n'
# I18n.default_locale = :de
- config.current_user_method { current_user } # auto-generated
+ config.current_user_method { current_user }
+ config.authorize_with { redirect_to root_path unless warden.user.admin? }
# If you want to track changes on your models:
# config.audit_with :history, User
View
10 config/initializers/stripe.rb
@@ -0,0 +1,10 @@
+stripe = if Rails.env == 'production'
+ { app_id: ENV['STRIPE_APP_ID'], secret: ENV['STRIPE_SECRET'] }
+else
+ path = Rails.root.join *%w[ config stripe.yml ]
+ file = File.open path
+ YAML.load(file).with_indifferent_access
+end
+
+STRIPE_PUBLIC_KEY = stripe[:app_id]
+Stripe.api_key = stripe[:secret]
View
58 config/routes.rb
@@ -1,62 +1,12 @@
StripeExample::Application.routes.draw do
+
mount RailsAdmin::Engine => '/admin', :as => 'rails_admin'
devise_for :users
- # The priority is based upon order of creation:
- # first created -> highest priority.
-
- # Sample of regular route:
- # match 'products/:id' => 'catalog#view'
- # Keep in mind you can assign values other than :controller and :action
-
- # Sample of named route:
- # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
- # This route can be invoked with purchase_url(:id => product.id)
-
- # Sample resource route (maps HTTP verbs to controller actions automatically):
- # resources :products
-
- # Sample resource route with options:
- # resources :products do
- # member do
- # get 'short'
- # post 'toggle'
- # end
- #
- # collection do
- # get 'sold'
- # end
- # end
-
- # Sample resource route with sub-resources:
- # resources :products do
- # resources :comments, :sales
- # resource :seller
- # end
-
- # Sample resource route with more complex sub-resources
- # resources :products do
- # resources :comments
- # resources :sales do
- # get 'recent', :on => :collection
- # end
- # end
-
- # Sample resource route within a namespace:
- # namespace :admin do
- # # Directs /admin/products/* to Admin::ProductsController
- # # (app/controllers/admin/products_controller.rb)
- # resources :products
- # end
-
- # You can have the root of your site routed with "root"
- # just remember to delete public/index.html.
- # root :to => 'welcome#index'
+ get '/signup/:plan' => 'plans#show', as: 'signup'
+ get '/plans/' => 'plans#index', as: 'plans'
- # See how all your routes lay out with "rake routes"
+ resources :subscriptions, only: [:new, :create, :edit, :update]
- # This is a legacy wild controller route that's not recommended for RESTful applications.
- # Note: This route will make all actions in every controller accessible via GET requests.
- # match ':controller(/:action(/:id))(.:format)'
end
View
4 config/stripe.sample.yml
@@ -0,0 +1,4 @@
+# see https://manage.stripe.com/#account/apikeys
+---
+app_id: PUBLISHABLE_KEY
+secret: SECRET_KEY
View
0  db/migrate/20120128044743_devise_create_users.rb → db/migrate/20111107003913_devise_create_users.rb
File renamed without changes
View
4 ...20128045950_create_rails_admin_histories_table.rb → ...11108045118_create_rails_admin_histories_table.rb
@@ -3,8 +3,8 @@ class CreateRailsAdminHistoriesTable < ActiveRecord::Migration
def change
create_table :rails_admin_histories do |t|
t.integer :item
- t.integer :month, limit: 2
- t.integer :year, limit: 5
+ t.integer :month
+ t.integer :year
t.string :table
t.string :username
t.text :message # title, name, or object_id
View
16 db/migrate/20120128044743_create_plans.rb
@@ -0,0 +1,16 @@
+class CreatePlans < ActiveRecord::Migration
+
+ def change
+ create_table :plans do |t|
+ t.string :name, null: false
+ t.string :slug, null: false
+ t.decimal :price, null: false, precision: 8, scale: 2
+
+ t.timestamps
+ end
+
+ add_index :plans, :slug, unique: true
+ add_index :plans, :price
+ end
+
+end
View
20 db/migrate/20120128045950_create_subscriptions.rb
@@ -0,0 +1,20 @@
+class CreateSubscriptions < ActiveRecord::Migration
+
+ def change
+ create_table :subscriptions do |t|
+ t.integer :user_id, null: false
+ t.integer :plan_id, null: false
+ t.string :stripe_customer_token
+ t.string :card_zip
+ t.string :last_four
+ t.string :card_type
+ t.date :next_bill_on
+ t.string :card_expiration
+
+ t.timestamps
+ end
+
+ add_index :subscriptions, :user_id, unique: true
+ end
+
+end
View
34 db/schema.rb
@@ -13,19 +13,45 @@
ActiveRecord::Schema.define(:version => 20120128045950) do
+ create_table "plans", :force => true do |t|
+ t.string "name", :null => false
+ t.string "slug", :null => false
+ t.decimal "price", :precision => 8, :scale => 2, :null => false
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ add_index "plans", ["price"], :name => "index_plans_on_price"
+ add_index "plans", ["slug"], :name => "index_plans_on_slug", :unique => true
+
create_table "rails_admin_histories", :force => true do |t|
t.integer "item"
- t.integer "month", :limit => 2
- t.integer "year", :limit => 5
+ t.integer "month"
+ t.integer "year"
t.string "table"
t.string "username"
t.text "message"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
add_index "rails_admin_histories", ["item", "table", "month", "year"], :name => "index_rails_admin_histories"
+ create_table "subscriptions", :force => true do |t|
+ t.integer "user_id", :null => false
+ t.integer "plan_id", :null => false
+ t.string "stripe_customer_token"
+ t.string "card_zip"
+ t.string "last_four"
+ t.string "card_type"
+ t.date "next_bill_on"
+ t.string "card_expiration"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ add_index "subscriptions", ["user_id"], :name => "index_subscriptions_on_user_id", :unique => true
+
create_table "users", :force => true do |t|
t.string "email", :default => "", :null => false
t.string "encrypted_password", :default => "", :null => false
View
86 spec/data/billing/customer_create.yml
@@ -0,0 +1,86 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://x17X8jIPdQe0j60gSnsUfLQXRTUPVuit:@api.stripe.com:443/v1/plans
+ body: id=plan-1&amount=0&currency=usd&interval=month&name=Plan%201
+ headers:
+ accept:
+ - ! '*/*; q=0.5, application/xml'
+ accept-encoding:
+ - gzip, deflate
+ user-agent:
+ - Stripe/v1 RubyBindings/1.6.0
+ x-stripe-client-user-agent:
+ - ! '{"bindings_version":"1.6.0","lang":"ruby","lang_version":"1.9.3 p0 (2011-10-30)","platform":"x86_64-darwin11.2.0","publisher":"stripe","uname":"Darwin
+ mars.hsl.dn42 11.2.0 Darwin Kernel Version 11.2.0: Tue Aug 9 20:54:00 PDT
+ 2011; root:xnu-1699.24.8~1/RELEASE_X86_64 x86_64"}'
+ content-length:
+ - '60'
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 400
+ message: ! '...'
+ headers:
+ server:
+ - nginx
+ date:
+ - Wed, 08 Feb 2012 00:23:27 GMT
+ content-type:
+ - application/json;charset=utf-8
+ connection:
+ - keep-alive
+ content-length:
+ - '96'
+ body: ! "{\n \"error\": {\n \"message\": \"Plan already exists.\",\n \"type\":
+ \"invalid_request_error\"\n }\n}\n"
+ http_version: '1.1'
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://x17X8jIPdQe0j60gSnsUfLQXRTUPVuit:@api.stripe.com:443/v1/customers
+ body: email=user_16%40example.com&plan=test-plan&card[number]=4242424242424242&card[exp_month]=11&card[exp_year]=2012
+ headers:
+ accept:
+ - ! '*/*; q=0.5, application/xml'
+ accept-encoding:
+ - gzip, deflate
+ user-agent:
+ - Stripe/v1 RubyBindings/1.6.0
+ x-stripe-client-user-agent:
+ - ! '{"bindings_version":"1.6.0","lang":"ruby","lang_version":"1.9.3 p0 (2011-10-30)","platform":"x86_64-darwin11.2.0","publisher":"stripe","uname":"Darwin
+ mars.hsl.dn42 11.2.0 Darwin Kernel Version 11.2.0: Tue Aug 9 20:54:00 PDT
+ 2011; root:xnu-1699.24.8~1/RELEASE_X86_64 x86_64"}'
+ content-length:
+ - '111'
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: ! '...'
+ headers:
+ server:
+ - nginx
+ date:
+ - Wed, 08 Feb 2012 01:37:55 GMT
+ content-type:
+ - application/json;charset=utf-8
+ connection:
+ - keep-alive
+ content-length:
+ - '780'
+ body: ! "{\n \"created\": 1328665073,\n \"email\": \"user_16@example.com\",\n
+ \ \"id\": \"cus_R78CTKY1wzSVPY\",\n \"livemode\": false,\n \"object\": \"customer\",\n
+ \ \"active_card\": {\n \"country\": \"US\",\n \"exp_month\": 11,\n \"exp_year\":
+ 2012,\n \"last4\": \"4242\",\n \"object\": \"card\",\n \"type\": \"Visa\"\n
+ \ },\n \"next_recurring_charge\": {\n \"amount\": 1000,\n \"date\": \"2012-03-08\"\n
+ \ },\n \"subscription\": {\n \"current_period_end\": 1331170673,\n \"current_period_start\":
+ 1328665073,\n \"customer\": \"cus_R78CTKY1wzSVPY\",\n \"object\": \"subscription\",\n
+ \ \"start\": 1328665073,\n \"status\": \"active\",\n \"plan\": {\n \"amount\":
+ 1000,\n \"currency\": \"usd\",\n \"id\": \"test-plan\",\n \"interval\":
+ \"month\",\n \"livemode\": false,\n \"name\": \"test plan\",\n \"object\":
+ \"plan\"\n }\n }\n}\n"
+ http_version: '1.1'
View
20 spec/factories.rb
@@ -0,0 +1,20 @@
+FactoryGirl.define do
+
+ factory :user, aliases: [:owner] do
+ sequence(:email) { |n| "user_#{ n }@example.com" }
+ password 'password'
+ password_confirmation { password }
+ end
+
+ factory :plan do
+ sequence(:name) { |n| "Plan #{ n }" }
+ sequence(:price){ |n| (n - 1) * 2.25 }
+ end
+
+ factory :subscription do
+ user
+ plan
+ sequence(:stripe_customer_token) { |n| "Token #{ n }" }
+ end
+
+end
View
11 spec/models/factories_spec.rb
@@ -0,0 +1,11 @@
+require 'spec_helper'
+
+describe 'Factories' do
+
+ FactoryGirl.factories.map(&:name).sort.each do |factory_name|
+ it "Factory #{ factory_name.inspect } should generate properly" do
+ 3.times { Factory(factory_name).should be_valid }
+ end
+ end
+
+end
View
51 spec/models/plan_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe Plan do
+
+ it 'requires a unique name' do
+ Factory.build( :plan, name: nil).should_not be_valid
+ Factory.create(:plan, name: 'Plan A').should be_valid
+ Factory.build( :plan, name: 'Plan A').should_not be_valid
+ Factory.create(:plan, name: 'Plan B').should be_valid
+ end
+
+ it 'requires a numeric price greater than or equal to 0.00' do
+ Factory.build(:plan, price: nil).should_not be_valid
+ Factory.build(:plan, price: -1.00).should_not be_valid
+ Factory.build(:plan, price: 0.00).should be_valid
+ Factory.build(:plan, price: 1.00).should be_valid
+ end
+
+ describe '#slug' do
+
+ it 'is set before save' do
+ plan = Factory.build :plan
+ plan.slug.should be_blank
+
+ plan.save
+
+ plan.reload.slug.should_not be_blank
+ end
+
+ it 'contains only alphanumeric characters and dashes' do
+ plan = Factory.create :plan, name: 'A Name with !@#$%^&*()_'
+ plan.slug.should_not match(/[^\w-]/)
+ end
+
+ it 'is all lowercase' do
+ plan = Factory.create :plan, name: 'A Name with Capital Letters'
+ plan.slug.downcase.should == plan.slug
+ end
+
+ end
+
+ describe '#to_param' do
+
+ it 'returns #slug' do
+ plan = Factory.create :plan
+ plan.to_param.should == plan.slug
+ end
+
+ end
+
+end
View
74 spec/models/subscription_spec.rb
@@ -0,0 +1,74 @@
+require 'spec_helper'
+
+describe Subscription do
+
+ let(:plan) { Plan.first }
+ let(:user) { Factory :user }
+
+ it 'requires a unique user' do
+ user_a = Factory :user
+ user_b = Factory :user
+
+ Factory.build( :subscription, user: nil).should_not be_valid
+ Factory.create(:subscription, user: user_a).should be_valid
+ Factory.build( :subscription, user: user_a).should_not be_valid
+ Factory.create(:subscription, user: user_b).should be_valid
+ end
+
+ it 'requires a plan' do
+ Factory.build( :subscription, plan: nil).should_not be_valid
+ Factory.create(:subscription, plan: plan).should be_valid
+ end
+
+ it 'requires a stripe customer token' do
+ Factory.build(:subscription, plan: plan, stripe_customer_token: nil).should_not be_valid
+ Factory.build(:subscription, plan: plan, stripe_customer_token: '1').should be_valid
+ end
+
+ describe 'credit card info' do
+
+ use_vcr_cassette 'billing/customer_create', :record => :all
+
+ let(:plan) { Plan.first }
+ let(:card) {{ number: '4242424242424242', exp_month: '11', exp_year: '2012' }}
+
+ before do
+ @subscription = user.build_subscription plan: plan, stripe_card_token: card
+ @subscription.save_with_payment
+ end
+
+ describe '#last_four' do
+
+ it 'returns the last 4 digits of the associated credit card number' do
+ @subscription.last_four.should == '4242'
+ end
+
+ end
+
+ describe '#card_type' do
+
+ it 'returns the card type of the associated credit card' do
+ @subscription.card_type.should == 'Visa'
+ end
+
+ end
+
+ describe '#next_bill_on' do
+
+ it 'returns the date of the next charge' do
+ @subscription.next_bill_on.should == Date.parse('2012-03-08')
+ end
+
+ end
+
+ describe '#card_expiration' do
+
+ it 'returns the date of the next charge' do
+ @subscription.card_expiration.should == '11-2012'
+ end
+
+ end
+
+ end
+
+end
View
60 spec/spec_helper.rb
@@ -1,32 +1,40 @@
-# This file is copied to spec/ when you run 'rails generate rspec:install'
-ENV["RAILS_ENV"] ||= 'test'
-require File.expand_path("../../config/environment", __FILE__)
+ENV['RAILS_ENV'] ||= 'test'
+
+require File.expand_path('../../config/environment', __FILE__)
require 'rspec/rails'
-require 'rspec/autorun'
+require 'capybara/rails'
+require 'capybara/rspec'
+require 'vcr'
-# Requires supporting ruby files with custom matchers and macros, etc,
-# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
RSpec.configure do |config|
- # ## Mock Framework
- #
- # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
- #
- # config.mock_with :mocha
- # config.mock_with :flexmock
- # config.mock_with :rr
-
- # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
- config.fixture_path = "#{::Rails.root}/spec/fixtures"
-
- # If you're not using ActiveRecord, or you'd prefer not to run each of your
- # examples within a transaction, remove the following line or assign false
- # instead of true.
- config.use_transactional_fixtures = true
-
- # If true, the base class of anonymous controllers will be inferred
- # automatically. This will be the default behavior in future versions of
- # rspec-rails.
- config.infer_base_class_for_anonymous_controllers = false
+ config.filter_run focus: true
+ config.mock_with :rspec
+
+ config.run_all_when_everything_filtered = true
+ config.treat_symbols_as_metadata_keys_with_true_values = true
+ config.use_transactional_fixtures = false
+
+ config.extend VCR::RSpec::Macros
+
+ config.before :suite do
+ Plan.delete_all
+ Plan.create name: 'Test Plan', price: 10.00
+
+ DatabaseCleaner.strategy = :truncation, { except: %w[ plans ]}
+ end
+
+ config.before :each do
+ DatabaseCleaner.clean
+ end
+
+end
+
+VCR.config do |config|
+ config.cassette_library_dir = 'spec/data'
+ config.default_cassette_options = { :record => :none }
+ config.ignore_localhost = true
+
+ config.stub_with :webmock
end
Please sign in to comment.
Something went wrong with that request. Please try again.