├── log └── .gitkeep ├── .rspec ├── lib ├── tasks │ └── .gitkeep ├── assets │ └── .gitkeep ├── templates │ └── haml │ │ └── scaffold │ │ └── _form.html.haml └── rails_ext │ └── tag_helper_ext.rb ├── public ├── favicon.ico ├── assets │ ├── rails-2da85d4b70342e6b8b7ec929d911af9f.png │ ├── application-6e3658cb51743044eed0ffab7c61d512.js.gz │ ├── application-d8aebfd478eee5c4f8dbe8411bcde500.css.gz │ ├── fontawesome-webfont-658b64bfa55eff9dcd175bfd3ac3238a.eot │ ├── fontawesome-webfont-98d61732d4d0af2312cfc7c1d005594b.woff │ ├── fontawesome-webfont-efc9e487a6c573ca65c0d1a10c09f63d.ttf │ ├── twitter │ │ └── bootstrap │ │ │ ├── glyphicons-halflings-d83dfe9df6cd3f50b5ae69abe919e065.png │ │ │ └── glyphicons-halflings-white-e06c893995f68ff48aa1b2e591f27889.png │ └── manifest-500885f534fb666445deb60092afb357.json ├── robots.txt ├── 500.html ├── 422.html └── 404.html ├── app ├── mailers │ └── .gitkeep ├── models │ ├── .gitkeep │ ├── history_event.rb │ └── user.rb ├── helpers │ ├── users_helper.rb │ ├── customers_helper.rb │ ├── history_events_helper.rb │ ├── credit_cards_helper.rb │ ├── application_helper.rb │ ├── subscriptions_helper.rb │ ├── addresses_helper.rb │ └── transactions_helper.rb ├── assets │ ├── images │ │ └── rails.png │ ├── javascripts │ │ ├── bootstrap.js.coffee │ │ ├── bootstrap.js │ │ ├── customers.js.coffee │ │ ├── users.js.coffee │ │ ├── history_events.js.coffee │ │ ├── application.js │ │ ├── addresses.js.coffee │ │ ├── transactions.js.coffee │ │ ├── credit_cards.js.coffee │ │ ├── braintree-data.js │ │ └── braintree.js │ └── stylesheets │ │ ├── users.css.less │ │ ├── addresses.css.less │ │ ├── customers.css.less │ │ ├── credit_cards.css.less │ │ ├── history_events.css.less │ │ ├── transactions.css.less │ │ ├── application.css │ │ ├── scaffolds.css.less │ │ └── bootstrap_and_overrides.css.less ├── controllers │ ├── application_controller.rb │ ├── history_events_controller.rb │ ├── plans_controller.rb │ ├── add_ons_controller.rb │ ├── discounts_controller.rb │ ├── users_controller.rb │ ├── customers_controller.rb │ ├── addresses_controller.rb │ ├── credit_cards_controller.rb │ ├── transactions_controller.rb │ └── subscriptions_controller.rb └── views │ ├── users │ ├── new.html.haml │ ├── edit.html.haml │ ├── _form.html.haml │ ├── show.html.haml │ └── index.html.haml │ ├── customers │ ├── new.html.haml │ ├── edit.html.haml │ ├── _form.html.haml │ └── show.html.haml │ ├── subscriptions │ ├── new.html.haml │ ├── edit.html.haml │ ├── show.html.haml │ ├── _form.html.haml │ └── index.html.haml │ ├── addresses │ ├── new.html.haml │ ├── edit.html.haml │ ├── _form.html.haml │ ├── show.html.haml │ └── index.html.haml │ ├── credit_cards │ ├── new.html.haml │ ├── edit.html.haml │ ├── index.html.haml │ ├── show.html.haml │ └── _form.html.haml │ ├── add_ons │ ├── show.html.haml │ └── index.html.haml │ ├── discounts │ ├── show.html.haml │ └── index.html.haml │ ├── plans │ ├── show.html.haml │ └── index.html.haml │ ├── transactions │ ├── new.html.haml │ ├── show.html.haml │ ├── index.html.haml │ └── _form.html.haml │ ├── history_events │ └── index.html.haml │ └── layouts │ └── application.html.haml ├── vendor ├── plugins │ └── .gitkeep └── assets │ ├── javascripts │ └── .gitkeep │ └── stylesheets │ └── .gitkeep ├── bin ├── rake ├── bundle └── rails ├── config ├── initializers │ ├── custom_validations.rb │ ├── session_store.rb │ ├── mime_types.rb │ ├── filter_parameter_logging.rb │ ├── custom_callbacks.rb │ ├── backtrace_silencers.rb │ ├── braintree.rb │ ├── wrap_parameters.rb │ ├── inflections.rb │ ├── secret_token.rb │ └── simple_form.rb ├── boot.rb ├── environment.rb ├── database.yml ├── locales │ ├── en.bootstrap.yml │ ├── simple_form.en.yml │ └── en.yml ├── routes.rb ├── application.rb ├── environments │ ├── development.rb │ ├── test.rb │ └── production.rb └── newrelic.yml ├── config.ru ├── doc └── README_FOR_APP ├── db ├── migrate │ ├── 20120914061844_create_users.rb │ └── 20130322050449_create_history_events.rb ├── seeds.rb └── schema.rb ├── spec ├── support │ ├── capybara.rb │ ├── braintree_integration │ │ ├── braintree_auth.yml.example │ │ ├── utils.rb │ │ ├── configuration.rb │ │ ├── test_data.rb │ │ └── setup.rb │ ├── ruby_ext.rb │ └── braintree_integration.rb ├── features │ ├── plans_management_spec.rb │ ├── user_management_spec.rb │ ├── customer_management_spec.rb │ └── address_management_spec.rb ├── models │ └── user_spec.rb └── spec_helper.rb ├── Rakefile ├── script └── rails ├── .gitignore ├── Gemfile ├── MIT-LICENSE ├── README.md ├── Gemfile.lock └── README.rdoc /log/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /lib/tasks/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/mailers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/plugins/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/helpers/users_helper.rb: -------------------------------------------------------------------------------- 1 | module UsersHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/customers_helper.rb: -------------------------------------------------------------------------------- 1 | module CustomersHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/history_events_helper.rb: -------------------------------------------------------------------------------- 1 | module HistoryEventsHelper 2 | end 3 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /app/assets/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyang/braintree-rails-example/HEAD/app/assets/images/rails.png -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | end 4 | -------------------------------------------------------------------------------- /app/views/users/new.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = User 2 | .page-header 3 | %h1="New #{model_class.model_name.human}" 4 | = render partial: "form" 5 | -------------------------------------------------------------------------------- /app/assets/javascripts/bootstrap.js.coffee: -------------------------------------------------------------------------------- 1 | jQuery -> 2 | $("a[rel=popover]").popover() 3 | $(".tooltip").tooltip() 4 | $("a[rel=tooltip]").tooltip() -------------------------------------------------------------------------------- /app/views/users/edit.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = User 2 | .page-header 3 | %h1="Edit #{model_class.model_name.human}" 4 | = render partial: "form" 5 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/bootstrap.js: -------------------------------------------------------------------------------- 1 | jQuery(function() { 2 | $("a[rel~=popover], .has-popover").popover(); 3 | $("a[rel~=tooltip], .has-tooltip").tooltip(); 4 | }); 5 | -------------------------------------------------------------------------------- /app/views/customers/new.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::Customer 2 | .page-header 3 | %h1="New #{model_class.model_name.human}" 4 | = render partial: "form" 5 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /app/views/customers/edit.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::Customer 2 | .page-header 3 | %h1="Edit #{model_class.model_name.human}" 4 | = render partial: "form" 5 | -------------------------------------------------------------------------------- /public/assets/rails-2da85d4b70342e6b8b7ec929d911af9f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyang/braintree-rails-example/HEAD/public/assets/rails-2da85d4b70342e6b8b7ec929d911af9f.png -------------------------------------------------------------------------------- /app/controllers/history_events_controller.rb: -------------------------------------------------------------------------------- 1 | class HistoryEventsController < ApplicationController 2 | def index 3 | @events = HistoryEvent.order('created_at desc') 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /config/initializers/custom_validations.rb: -------------------------------------------------------------------------------- 1 | BraintreeRails::BillingAddressValidator.setup do |validations| 2 | validations << [:country_code_alpha2, inclusion: { in: ["CA", "US"] }] 3 | end 4 | -------------------------------------------------------------------------------- /public/assets/application-6e3658cb51743044eed0ffab7c61d512.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyang/braintree-rails-example/HEAD/public/assets/application-6e3658cb51743044eed0ffab7c61d512.js.gz -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run BraintreeRailsExample::Application 5 | -------------------------------------------------------------------------------- /public/assets/application-d8aebfd478eee5c4f8dbe8411bcde500.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyang/braintree-rails-example/HEAD/public/assets/application-d8aebfd478eee5c4f8dbe8411bcde500.css.gz -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 5 | -------------------------------------------------------------------------------- /public/assets/fontawesome-webfont-658b64bfa55eff9dcd175bfd3ac3238a.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyang/braintree-rails-example/HEAD/public/assets/fontawesome-webfont-658b64bfa55eff9dcd175bfd3ac3238a.eot -------------------------------------------------------------------------------- /public/assets/fontawesome-webfont-98d61732d4d0af2312cfc7c1d005594b.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyang/braintree-rails-example/HEAD/public/assets/fontawesome-webfont-98d61732d4d0af2312cfc7c1d005594b.woff -------------------------------------------------------------------------------- /public/assets/fontawesome-webfont-efc9e487a6c573ca65c0d1a10c09f63d.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyang/braintree-rails-example/HEAD/public/assets/fontawesome-webfont-efc9e487a6c573ca65c0d1a10c09f63d.ttf -------------------------------------------------------------------------------- /app/views/subscriptions/new.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::Subscription 2 | .page-header 3 | %h1="New #{model_class.model_name.human}" 4 | = render partial: "form", locals: {url: subscriptions_path} 5 | -------------------------------------------------------------------------------- /app/views/addresses/new.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::Address 2 | .page-header 3 | %h1="New #{model_class.model_name.human}" 4 | = render partial: "form", locals: {url: user_customer_addresses_path(@user)} 5 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | BraintreeRailsExample::Application.config.session_store :cookie_store, key: '_braintree_rails_example_session' 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/users.css.less: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the Users controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | require 'pp' 4 | 5 | # Initialize the Rails application. 6 | BraintreeRailsExample::Application.initialize! 7 | -------------------------------------------------------------------------------- /app/assets/stylesheets/addresses.css.less: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the Addresses controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/customers.css.less: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the Customers controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/views/addresses/edit.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::Address 2 | .page-header 3 | %h1="Edit #{model_class.model_name.human}" 4 | = render partial: "form", locals: {url: user_customer_address_path(@user, @address)} 5 | -------------------------------------------------------------------------------- /app/views/credit_cards/new.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::CreditCard 2 | .page-header 3 | %h1="New #{model_class.model_name.human}" 4 | = render partial: "form", locals: {url: user_customer_credit_cards_path(@user)} 5 | -------------------------------------------------------------------------------- /app/views/subscriptions/edit.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::Subscription 2 | .page-header 3 | %h1="Edit #{model_class.model_name.human}" 4 | = render partial: "form", locals: {url: subscription_path(@subscription)} 5 | -------------------------------------------------------------------------------- /app/assets/stylesheets/credit_cards.css.less: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the CreditCards controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /doc/README_FOR_APP: -------------------------------------------------------------------------------- 1 | Use this README file to introduce your application and point to useful places in the API for learning more. 2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. 3 | -------------------------------------------------------------------------------- /app/assets/stylesheets/history_events.css.less: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the history_events controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/transactions.css.less: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the Transactions controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/models/history_event.rb: -------------------------------------------------------------------------------- 1 | class HistoryEvent < ActiveRecord::Base 2 | serialize :data 3 | def self.create_from(braintree_object) 4 | create(model: braintree_object.class.name, data: braintree_object.attributes) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /public/assets/twitter/bootstrap/glyphicons-halflings-d83dfe9df6cd3f50b5ae69abe919e065.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyang/braintree-rails-example/HEAD/public/assets/twitter/bootstrap/glyphicons-halflings-d83dfe9df6cd3f50b5ae69abe919e065.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /app/views/credit_cards/edit.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::CreditCard 2 | .page-header 3 | %h1="Edit #{model_class.model_name.human}" 4 | = render partial: "form", locals: {url: user_customer_credit_card_path(@user, @credit_card)} 5 | -------------------------------------------------------------------------------- /app/views/users/_form.html.haml: -------------------------------------------------------------------------------- 1 | = simple_form_for @user, html: { class: 'form-horizontal' } do |f| 2 | = f.input :email 3 | .form-actions 4 | = f.button :submit, class: 'btn-primary' 5 | = link_to 'Cancel', users_path, class: 'btn' 6 | -------------------------------------------------------------------------------- /app/controllers/plans_controller.rb: -------------------------------------------------------------------------------- 1 | class PlansController < ApplicationController 2 | def index 3 | @plans = BraintreeRails::Plan.all 4 | end 5 | 6 | def show 7 | @plan = BraintreeRails::Plan.find(params[:id]) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /public/assets/twitter/bootstrap/glyphicons-halflings-white-e06c893995f68ff48aa1b2e591f27889.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyang/braintree-rails-example/HEAD/public/assets/twitter/bootstrap/glyphicons-halflings-white-e06c893995f68ff48aa1b2e591f27889.png -------------------------------------------------------------------------------- /app/controllers/add_ons_controller.rb: -------------------------------------------------------------------------------- 1 | class AddOnsController < ApplicationController 2 | def index 3 | @add_ons = BraintreeRails::AddOn.all 4 | end 5 | 6 | def show 7 | @add_on = BraintreeRails::AddOn.find(params[:id]) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20120914061844_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration 2 | def change 3 | create_table :users do |t| 4 | t.string :email 5 | t.string :customer_id 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password, :number, :cvv] 5 | -------------------------------------------------------------------------------- /app/assets/javascripts/customers.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/users.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /app/controllers/discounts_controller.rb: -------------------------------------------------------------------------------- 1 | class DiscountsController < ApplicationController 2 | def index 3 | @discounts = BraintreeRails::Discount.all 4 | end 5 | 6 | def show 7 | @discount = BraintreeRails::Discount.find(params[:id]) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/assets/javascripts/history_events.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /config/initializers/custom_callbacks.rb: -------------------------------------------------------------------------------- 1 | [User, BraintreeRails::Address, BraintreeRails::Customer, BraintreeRails::CreditCard, BraintreeRails::Transaction, BraintreeRails::Subscription].each do |model| 2 | model.after_create do 3 | HistoryEvent.create_from(self) 4 | end 5 | end -------------------------------------------------------------------------------- /db/migrate/20130322050449_create_history_events.rb: -------------------------------------------------------------------------------- 1 | class CreateHistoryEvents < ActiveRecord::Migration 2 | def change 3 | create_table :history_events do |t| 4 | t.string :model 5 | t.text :data 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/support/capybara.rb: -------------------------------------------------------------------------------- 1 | require 'capybara/rails' 2 | 3 | module CapybaraHelper 4 | def fill_in_all(namespace, attributes) 5 | attributes.to_form_params(namespace).each do |attribute, value| 6 | find_field(attribute).set(value) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/support/braintree_integration/braintree_auth.yml.example: -------------------------------------------------------------------------------- 1 | --- 2 | merchant_id: 'merchant_id' 3 | public_key: 'public_key' 4 | private_key: 'private_key' 5 | client_side_encryption_key: 'client_side_encryption_key' 6 | login: 'braintree-sandbox-username' 7 | password: 'password' 8 | 9 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # Add your own tasks in files placed in lib/tasks ending in .rake, 3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 4 | 5 | require File.expand_path('../config/application', __FILE__) 6 | 7 | BraintreeRailsExample::Application.load_tasks 8 | -------------------------------------------------------------------------------- /script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /spec/support/braintree_integration/utils.rb: -------------------------------------------------------------------------------- 1 | module BraintreeIntegration 2 | module Utils 3 | def create_customer_for(user, attributes = customer_hash) 4 | BraintreeRails::Customer.create!(attributes).tap do |customer| 5 | user.update_attribute(:customer_id, customer.id) 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/templates/haml/scaffold/_form.html.haml: -------------------------------------------------------------------------------- 1 | = simple_form_for(@<%= singular_table_name %>) do |f| 2 | = f.error_notification 3 | 4 | .form-inputs 5 | <%- attributes.each do |attribute| -%> 6 | = f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> 7 | <%- end -%> 8 | 9 | .form-actions 10 | = f.button :submit 11 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: sqlite3 3 | database: db/development.sqlite3 4 | pool: 5 5 | timeout: 5000 6 | 7 | test: 8 | adapter: sqlite3 9 | database: db/test.sqlite3 10 | pool: 5 11 | timeout: 5000 12 | 13 | production: 14 | adapter: sqlite3 15 | database: db/production.sqlite3 16 | pool: 5 17 | timeout: 5000 18 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | validates :email, uniqueness: true, presence: true 3 | after_destroy :destroy_customer 4 | 5 | def customer 6 | customer_id && BraintreeRails::Customer.new(customer_id) 7 | end 8 | 9 | private 10 | def destroy_customer 11 | BraintreeRails::Customer.delete(customer_id) if customer_id.present? 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | User.create(email:'braintree+rails@example.com') 9 | -------------------------------------------------------------------------------- /app/helpers/credit_cards_helper.rb: -------------------------------------------------------------------------------- 1 | module CreditCardsHelper 2 | def options_for_month_select 3 | { 4 | collection: Date::MONTHNAMES.each_with_index.to_a[1..-1].map { |month, index| [month, index.to_s.rjust(2, '0')]}, 5 | include_blank: false, 6 | } 7 | end 8 | 9 | def options_for_year_select 10 | { 11 | collection: 1976..2200, 12 | include_blank: false, 13 | } 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/views/add_ons/show.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::AddOn 2 | .page-header 3 | %h1=t '.title', default: model_class.model_name.human 4 | - @add_on.attributes.each do |attribute, value| 5 | - next unless value.present? 6 | %p 7 | %strong= model_class.human_attribute_name(attribute) + ':' 8 | %br 9 | = @add_on.send(attribute) 10 | 11 | .form-actions 12 | = link_to 'Back', add_ons_path, class: 'btn' 13 | -------------------------------------------------------------------------------- /app/views/discounts/show.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::Discount 2 | .page-header 3 | %h1=t '.title', default: model_class.model_name.human 4 | - @discount.attributes.each do |attribute, value| 5 | - next unless value.present? 6 | %p 7 | %strong= model_class.human_attribute_name(attribute) + ':' 8 | %br 9 | = @discount.send(attribute) 10 | 11 | .form-actions 12 | = link_to 'Back', discounts_path, class: 'btn' 13 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /lib/rails_ext/tag_helper_ext.rb: -------------------------------------------------------------------------------- 1 | module ActionView 2 | module Helpers 3 | module TagHelper 4 | def tag_options_with_data_encrypted_name(options, escape = true) 5 | if options['data-encrypted-name'] 6 | options['data-encrypted-name'] = options.delete("name") 7 | end 8 | tag_options_without_data_encrypted_name(options, escape) 9 | end 10 | alias_method_chain :tag_options, :data_encrypted_name 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /config/initializers/braintree.rb: -------------------------------------------------------------------------------- 1 | require 'rails_ext/tag_helper_ext' 2 | BraintreeRails::Configuration.environment = :sandbox 3 | BraintreeRails::Configuration.logger = Logger.new('log/braintree.log') 4 | BraintreeRails::Configuration.merchant_id = ENV['MERCHANT_ID'] 5 | BraintreeRails::Configuration.public_key = ENV['PUBLIC_KEY'] 6 | BraintreeRails::Configuration.private_key = ENV['PRIVATE_KEY'] 7 | BraintreeRails::Configuration.client_side_encryption_key = ENV['CLIENT_SIDE_ENCRYPTION_KEY'] 8 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def bootstrap_flash 3 | end 4 | 5 | def options_for_credit_card_select(customer) 6 | { 7 | collection: customer.credit_cards, 8 | label_method: :masked_number, 9 | include_blank: false, 10 | } 11 | end 12 | 13 | def options_for_plan_select 14 | { 15 | collection: BraintreeRails::Plan.all, 16 | label_method: :id, 17 | include_blank: false, 18 | } 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/helpers/subscriptions_helper.rb: -------------------------------------------------------------------------------- 1 | module SubscriptionsHelper 2 | 3 | def new_subscription_path 4 | path ||= new_user_customer_credit_card_subscription_path(@user, @credit_card) if @credit_card 5 | path ||= new_plan_subscription_path(@plan) if @plan 6 | path ||= super 7 | end 8 | 9 | def back_path 10 | path ||= user_customer_credit_card_path(@user, @credit_card) if @credit_card 11 | path ||= plan_path(@plan) if @plan 12 | path ||= users_path 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/views/plans/show.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::Plan 2 | .page-header 3 | %h1=t '.title', default: model_class.model_name.human 4 | - @plan.attributes.each do |attribute, value| 5 | - next unless value.present? 6 | %p 7 | %strong= model_class.human_attribute_name(attribute) + ':' 8 | %br 9 | = @plan.send(attribute) 10 | 11 | .form-actions 12 | = link_to 'Back', plans_path, class: 'btn' 13 | = link_to 'Subscriptions', plan_subscriptions_path(@plan), class: 'btn' 14 | -------------------------------------------------------------------------------- /app/views/transactions/new.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::Transaction 2 | .page-header 3 | %h1="New #{model_class.model_name.human}" 4 | = render partial: "form" 5 | 6 | - content_for :javascript do 7 | :javascript 8 | var braintree = Braintree.create("#{BraintreeRails::Configuration.client_side_encryption_key}") 9 | braintree.onSubmitEncryptForm('braintree-transaction-form'); 10 | BraintreeData.setup("#{BraintreeRails::Configuration.merchant_id}", "braintree-transaction-form", BraintreeData.environments.sandbox) 11 | -------------------------------------------------------------------------------- /spec/support/ruby_ext.rb: -------------------------------------------------------------------------------- 1 | class Object 2 | def to_form_params(namespace) 3 | {namespace => to_s} 4 | end 5 | end 6 | 7 | class Array 8 | def to_form_params(namespace) 9 | inject({}) do |params, value| 10 | params.merge(value.to_form_params("#{namespace}[]")) 11 | end 12 | end 13 | end 14 | 15 | class Hash 16 | def to_form_params(namespace = nil) 17 | inject({}) do |params, pair| 18 | params.merge(pair.last.to_form_params(namespace ? "#{namespace}[#{pair.first}]" : pair.first)) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/views/users/show.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = User 2 | .page-header 3 | %h1=t '.title', default: model_class.model_name.human 4 | 5 | %p 6 | %strong= model_class.human_attribute_name(:email) + ':' 7 | %br 8 | = @user.email 9 | 10 | .form-actions 11 | = link_to 'Back', users_path, class: 'btn' 12 | = link_to 'Edit', edit_user_path(@user), class: 'btn' 13 | = link_to 'Customer info', user_customer_path(@user), class: 'btn' 14 | = link_to 'Destroy', user_path(@user), method: 'delete', data: {confirm: 'Are you sure?'}, class: 'btn btn-danger' 15 | -------------------------------------------------------------------------------- /app/views/customers/_form.html.haml: -------------------------------------------------------------------------------- 1 | = simple_form_for @customer, url: user_customer_path(@user), html: { class: 'form-horizontal' } do |f| 2 | - [:first_name, :last_name, :email, :company, :website, :phone, :fax].each do |attribute| 3 | = f.input attribute 4 | .form-actions 5 | = f.button :submit, class: 'btn-primary' 6 | = link_to 'Cancel', user_path(@user), class: 'btn' 7 | 8 | - content_for :javascript do 9 | :javascript 10 | BraintreeData.setup("#{BraintreeRails::Configuration.merchant_id}", "braintree-transaction-form", BraintreeData.environments.sandbox) 11 | -------------------------------------------------------------------------------- /app/views/plans/index.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::Plan 2 | .page-header 3 | %h1=t '.title', default: model_class.model_name.human.pluralize 4 | %table.table.table-striped 5 | %thead 6 | %tr 7 | %th= model_class.human_attribute_name(:id) 8 | %th= model_class.human_attribute_name(:name) 9 | %th= model_class.human_attribute_name(:price) 10 | %tbody 11 | - @plans.each do |plan| 12 | %tr 13 | %td= link_to plan.id, plan_path(plan) 14 | %td= plan.name 15 | %td= plan.price 16 | = link_to 'Back', root_path, class: 'btn' 17 | -------------------------------------------------------------------------------- /app/views/addresses/_form.html.haml: -------------------------------------------------------------------------------- 1 | = simple_form_for @address, url: url, html: { class: 'form-horizontal' } do |f| 2 | = f.input :first_name 3 | = f.input :last_name 4 | = f.input :company 5 | = f.input :street_address 6 | = f.input :extended_address 7 | = f.input :locality, label: 'City' 8 | = f.input :country_code_alpha2, options_for_country_select 9 | = f.input :region, options_for_region_select 10 | = f.input :postal_code 11 | .form-actions 12 | = f.button :submit, class: 'btn-primary' 13 | = link_to 'Cancel', user_customer_addresses_path(@user), class: 'btn' 14 | -------------------------------------------------------------------------------- /config/locales/en.bootstrap.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | helpers: 6 | actions: "Actions" 7 | links: 8 | back: "Back" 9 | cancel: "Cancel" 10 | confirm: "Are you sure?" 11 | destroy: "Delete" 12 | new: "New" 13 | edit: "Edit" 14 | titles: 15 | edit: "Edit %{model}" 16 | save: "Save %{model}" 17 | new: "New %{model}" 18 | delete: "Delete %{model}" 19 | -------------------------------------------------------------------------------- /app/views/add_ons/index.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::AddOn 2 | .page-header 3 | %h1=t '.title', default: model_class.model_name.human.pluralize 4 | %table.table.table-striped 5 | %thead 6 | %tr 7 | %th= model_class.human_attribute_name(:id) 8 | %th= model_class.human_attribute_name(:name) 9 | %th= model_class.human_attribute_name(:amount) 10 | %tbody 11 | - @add_ons.each do |add_on| 12 | %tr 13 | %td= link_to add_on.id, add_on_path(add_on) 14 | %td= add_on.name 15 | %td= add_on.amount 16 | = link_to 'Back', root_path, class: 'btn' 17 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile ~/.gitignore_global 6 | 7 | # Ignore bundler config 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | 13 | # Ignore all logfiles and tempfiles. 14 | /log/*.log 15 | /tmp 16 | .rvmrc 17 | .ruby-version 18 | .ruby-gemset 19 | tags 20 | /spec/support/braintree_integration/braintree_auth.yml 21 | -------------------------------------------------------------------------------- /app/views/discounts/index.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::Discount 2 | .page-header 3 | %h1=t '.title', default: model_class.model_name.human.pluralize 4 | %table.table.table-striped 5 | %thead 6 | %tr 7 | %th= model_class.human_attribute_name(:id) 8 | %th= model_class.human_attribute_name(:name) 9 | %th= model_class.human_attribute_name(:amount) 10 | %tbody 11 | - @discounts.each do |discount| 12 | %tr 13 | %td= link_to discount.id, discount_path(discount) 14 | %td= discount.name 15 | %td= discount.amount 16 | = link_to 'Back', root_path, class: 'btn' 17 | -------------------------------------------------------------------------------- /app/views/transactions/show.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::Transaction 2 | .page-header 3 | %h1=t '.title', default: model_class.model_name.human 4 | - [:id, :amount, :status].each do |attribute| 5 | %p 6 | %strong= model_class.human_attribute_name(attribute) + ':' 7 | %br 8 | = @transaction.send(attribute) 9 | %p 10 | %strong= model_class.human_attribute_name(:credit_card) + ':' 11 | %br 12 | = @transaction.credit_card.masked_number 13 | %p 14 | %strong= model_class.human_attribute_name(:customer) + ':' 15 | %br 16 | = @transaction.customer.full_name 17 | 18 | .form-actions 19 | = link_to 'Back', transactions_path, class: 'btn' 20 | -------------------------------------------------------------------------------- /spec/features/plans_management_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Address Management", braintree_integration: true do 4 | it "shows a list of plans" do 5 | address = @user.customer.addresses.create!(address_hash) 6 | visit user_customer_addresses_path(@user) 7 | page.should have_link(address.street_address, href: user_customer_address_path(@user, address)) 8 | end 9 | 10 | it "shows plan attributes" do 11 | address = @user.customer.addresses.create!(address_hash) 12 | visit user_customer_address_path(@user, address) 13 | address_hash.values.each do |value| 14 | page.should have_content(value) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/support/braintree_integration.rb: -------------------------------------------------------------------------------- 1 | require_relative 'braintree_integration/configuration' 2 | require_relative 'braintree_integration/setup' 3 | require_relative 'braintree_integration/test_data' 4 | require_relative 'braintree_integration/utils' 5 | 6 | BraintreeRails::Configuration.environment = :sandbox 7 | BraintreeRails::Configuration.logger = Logger.new('/dev/null').tap { |logger| logger.level = Logger::INFO } 8 | 9 | module BraintreeIntegration 10 | def self.included(receiver) 11 | receiver.send :include, Configuration 12 | receiver.send :include, Setup 13 | receiver.send :include, TestData 14 | receiver.send :include, Utils 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/views/history_events/index.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = HistoryEvent 2 | .page-header 3 | %h1=t '.title', default: model_class.model_name.human.pluralize 4 | %table.table.table-striped 5 | %thead 6 | %tr 7 | %th= model_class.human_attribute_name(:id) 8 | %th= model_class.human_attribute_name(:model) 9 | %th= model_class.human_attribute_name(:created_at) 10 | %th= model_class.human_attribute_name(:data) 11 | %tbody 12 | - @events.each do |event| 13 | %tr 14 | %td= event.id 15 | %td= event.model 16 | %td= event.created_at 17 | %td 18 | %pre= PP.pp(event.data, "") 19 | = link_to 'Back', root_path, class: 'btn' 20 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /app/views/addresses/show.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::Address 2 | .page-header 3 | %h1=t '.title', default: model_class.model_name.human 4 | - [:full_name, :company, :street_address, :extended_address, :locality, :region, :postal_code, :country_code_alpha2].each do |attribute| 5 | %p 6 | %strong= model_class.human_attribute_name(attribute) + ':' 7 | %br 8 | = @address.send(attribute) 9 | 10 | .form-actions 11 | = link_to 'Back', user_customer_addresses_path(@user), class: 'btn' 12 | = link_to 'Edit', edit_user_customer_address_path(@user, @address), class: 'btn' 13 | = link_to 'Destroy', user_customer_address_path(@user, @address), method: "delete", data: {confirm: 'Are you sure?'}, class: 'btn btn-danger' 14 | -------------------------------------------------------------------------------- /config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure your secret_key_base is kept private 11 | # if you're sharing your code publicly. 12 | BraintreeRailsExample::Application.config.secret_key_base = '644bf3e66af41a6fd9e28ba87e291d51a0ddd4f57c1c514bf937b6ee030cbf0db4e77346a613f9c540f05a440384722e1f308175b917502c9d4a7decf5977a5b' 13 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // the compiled file. 9 | // 10 | // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD 11 | // GO AFTER THE REQUIRES BELOW. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require twitter/bootstrap 16 | //= require_tree . 17 | -------------------------------------------------------------------------------- /app/helpers/addresses_helper.rb: -------------------------------------------------------------------------------- 1 | module AddressesHelper 2 | def options_for_country_select 3 | { 4 | collection: available_countries, 5 | value_method: :alpha_2_code, 6 | label: 'Country', 7 | include_blank: false, 8 | } 9 | end 10 | 11 | def options_for_region_select 12 | { 13 | collection: available_countries, 14 | as: :grouped_select, 15 | group_method: :subregions, 16 | value_method: :name, 17 | } 18 | end 19 | 20 | private 21 | def available_countries 22 | braintree_country_alpha_2_codes = Braintree::Address::CountryNames.map {|country| country[1]} 23 | Carmen::Country.all.select { |country| braintree_country_alpha_2_codes.include?(country.alpha_2_code) }.sort_by(&:name) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | BraintreeRailsExample::Application.routes.draw do 2 | resources :users do 3 | resource :customer do 4 | resources :credit_cards do 5 | resources :transactions, except: [:edit, :destroy] 6 | resources :subscriptions 7 | end 8 | resources :addresses 9 | resources :transactions, except: [:edit, :destroy] 10 | end 11 | end 12 | resources :transactions, except: [:edit, :destroy] 13 | resources :plans, only: [:index, :show] do 14 | resources :subscriptions 15 | end 16 | resources :add_ons, only: [:index, :show] 17 | resources :discounts, only: [:index, :show] 18 | resources :subscriptions, except: [:new, :create] 19 | resources :history_events, only: :index 20 | root to: 'users#index' 21 | end 22 | -------------------------------------------------------------------------------- /app/helpers/transactions_helper.rb: -------------------------------------------------------------------------------- 1 | module TransactionsHelper 2 | def new_transaction_path 3 | path ||= new_user_customer_credit_card_transaction_path(@user, @credit_card) if @credit_card 4 | path ||= new_user_customer_transaction_path(@user) if @user 5 | path ||= super 6 | end 7 | 8 | def edit_subscription_path(subscription) 9 | path ||= edit_user_customer_credit_card_subscription_path(@user, @credit_card, subscription) if @credit_card 10 | path ||= edit_plan_subscription_path(@plan, subscription) if @plan 11 | path ||= super(subscription) 12 | end 13 | 14 | def back_path 15 | path ||= user_customer_credit_card_path(@user, @credit_card) if @credit_card 16 | path ||= user_customer_path(@user) if @user 17 | path ||= users_path 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | ruby "2.0.0" 3 | 4 | gem 'rails', '~> 4.0.3' 5 | gem 'simple_form' 6 | gem 'haml-rails' 7 | gem 'carmen-rails' 8 | gem 'thin' 9 | gem 'jquery-rails' 10 | gem 'braintree-rails', :github => "lyang/braintree-rails", :branch => 'master' 11 | gem 'coffee-rails', '~> 4.0.1' 12 | gem 'uglifier', '>= 2.5.0' 13 | gem 'therubyracer', :platforms => :ruby 14 | gem "less-rails" 15 | gem 'twitter-bootstrap-rails' 16 | 17 | group :development, :test do 18 | gem 'quiet_assets' 19 | gem 'sqlite3' 20 | gem 'rspec-rails', "~> 2.14.2" 21 | end 22 | 23 | group :test do 24 | gem 'capybara' 25 | gem "launchy" 26 | gem 'selenium-webdriver' 27 | gem 'shoulda-matchers' 28 | end 29 | 30 | group :production do 31 | gem 'newrelic_rpm' 32 | gem 'pg' 33 | gem 'rails_on_heroku' 34 | end 35 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /config/locales/simple_form.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | simple_form: 3 | "yes": 'Yes' 4 | "no": 'No' 5 | required: 6 | text: 'required' 7 | mark: '*' 8 | # You can uncomment the line below if you need to overwrite the whole required html. 9 | # When using html, text and mark won't be used. 10 | # html: '*' 11 | error_notification: 12 | default_message: "Please review the problems below:" 13 | # Labels and hints examples 14 | # labels: 15 | # defaults: 16 | # password: 'Password' 17 | # user: 18 | # new: 19 | # email: 'E-mail to sign in.' 20 | # edit: 21 | # email: 'E-mail.' 22 | # hints: 23 | # defaults: 24 | # username: 'User name to sign in.' 25 | # password: 'No special characters, please.' 26 | 27 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | *= require_tree . 13 | *= require bootstrap_and_overrides 14 | */ 15 | 16 | a.github { 17 | position: relative; 18 | float: right; 19 | } 20 | 21 | a.github img { 22 | position: absolute; 23 | top: -20px; 24 | right: -20px; 25 | border: 0; 26 | } 27 | -------------------------------------------------------------------------------- /app/views/customers/show.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::Customer 2 | .page-header 3 | %h1=t '.title', default: model_class.model_name.human 4 | - [:first_name, :last_name, :email, :company, :website, :phone, :fax].each do |attribute| 5 | %p 6 | %strong= model_class.human_attribute_name(attribute) + ':' 7 | %br 8 | = @customer.send(attribute) 9 | 10 | .form-actions 11 | = link_to 'Back', users_path, class: 'btn' 12 | = link_to 'Edit', edit_user_customer_path(@user), class: 'btn' 13 | = link_to 'Transactions', user_customer_transactions_path(@user), class: 'btn' 14 | = link_to 'Credit cards', user_customer_credit_cards_path(@user), class: 'btn' 15 | = link_to 'Addresses', user_customer_addresses_path(@user), class: 'btn' 16 | = link_to 'Destroy', user_customer_path(@user), method: "delete", data: {confirm: 'Are you sure?'}, class: 'btn btn-danger' 17 | -------------------------------------------------------------------------------- /app/views/subscriptions/show.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::Subscription 2 | .page-header 3 | %h1=t '.title', default: model_class.model_name.human 4 | - [:id, :price, :status, :balance].each do |attribute| 5 | %p 6 | %strong= model_class.human_attribute_name(attribute) + ':' 7 | %br 8 | = @subscription.send(attribute) 9 | %p 10 | %strong= model_class.human_attribute_name(:plan) + ':' 11 | %br 12 | = @subscription.plan.name 13 | %p 14 | %strong= BraintreeRails::CreditCard.human_attribute_name(:customer) + ':' 15 | %br 16 | = @subscription.credit_card.customer.full_name 17 | 18 | .form-actions 19 | = link_to 'Back', subscriptions_path, class: 'btn' 20 | = link_to 'Edit', edit_subscription_path(@subscription), class: 'btn' 21 | = link_to 'Destroy', subscription_path(@subscription), method: "delete", data: {confirm: 'Are you sure?'}, class: 'btn btn-danger' 22 | -------------------------------------------------------------------------------- /app/views/users/index.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = User 2 | .page-header 3 | %h1=t '.title', default: model_class.model_name.human.pluralize 4 | %table.table.table-striped 5 | %thead 6 | %tr 7 | %th= model_class.human_attribute_name(:id) 8 | %th= model_class.human_attribute_name(:email) 9 | %th= model_class.human_attribute_name(:created_at) 10 | %th=t '.actions', default: t("helpers.actions") 11 | %tbody 12 | - @users.each do |user| 13 | %tr 14 | %td= link_to user.id, user_path(user) 15 | %td= link_to user.email, user_path(user) 16 | %td= user.created_at 17 | %td 18 | = link_to 'Edit', edit_user_path(user), class: 'btn btn-mini' 19 | = link_to 'Destroy', user_path(user), method: :delete, data: {confirm: 'Are you sure?'}, class: 'btn btn-mini btn-danger' 20 | 21 | = link_to 'New', new_user_path, class: 'btn btn-primary' 22 | -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe User do 4 | describe '#email' do 5 | it { should validate_uniqueness_of(:email) } 6 | it { should validate_presence_of(:email) } 7 | end 8 | 9 | describe "#customer" do 10 | it "returns nil if customer_id is not set" do 11 | User.new.customer.should be_nil 12 | end 13 | 14 | it "returns BraintreeRails::Customer if customer_id presents" do 15 | customer = BraintreeRails::Customer.new 16 | BraintreeRails::Customer.should_receive(:new).with('foo').and_return(customer) 17 | User.new(customer_id: 'foo').customer.should == customer 18 | end 19 | 20 | it "should delete customer if customer_id presents" do 21 | BraintreeRails::Customer.should_receive(:delete).with('foo') 22 | User.create(email: 'braintree-rails@example.com', customer_id: 'foo').destroy 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | ENV["RAILS_ENV"] ||= 'test' 3 | require File.expand_path("../../config/environment", __FILE__) 4 | require 'rspec/rails' 5 | require 'rspec/autorun' 6 | 7 | Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} 8 | 9 | RSpec.configure do |config| 10 | config.use_transactional_fixtures = true 11 | config.infer_base_class_for_anonymous_controllers = false 12 | config.order = "random" 13 | 14 | config.before(:all, type: :model) do 15 | BraintreeRails::Configuration.merchant_id = nil 16 | BraintreeRails::Configuration.public_key = nil 17 | BraintreeRails::Configuration.private_key = nil 18 | BraintreeRails::Configuration.client_side_encryption_key = nil 19 | end 20 | 21 | config.include(CapybaraHelper) 22 | config.include(BraintreeIntegration, braintree_integration: true) 23 | end 24 | -------------------------------------------------------------------------------- /app/assets/javascripts/addresses.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | jQuery -> 5 | $('#address_region').parent().parent().hide() 6 | states = $('#address_region').html() 7 | $('#address_country_code_alpha2').change -> 8 | country = $('#address_country_code_alpha2 :selected').text() 9 | escaped_country = country.replace(/([ #;&,.+*~\':"!^$[\]()=>|\/@])/g, '\\$1') 10 | options = $(states).filter("optgroup[label=#{escaped_country}]").html() 11 | if options 12 | $('#address_region').html(options) 13 | $('#address_region').parent().parent().show() 14 | else 15 | $('#address_region').empty() 16 | $('#address_region').parent().parent().hide() 17 | $('#address_country_code_alpha2').change() 18 | -------------------------------------------------------------------------------- /spec/support/braintree_integration/configuration.rb: -------------------------------------------------------------------------------- 1 | module BraintreeIntegration 2 | module Configuration 3 | def load_configuration 4 | @configuration = (YAML.load_file(braintree_auth_file) rescue {}).tap do |config| 5 | BraintreeRails::Configuration.merchant_id = config['merchant_id'] 6 | BraintreeRails::Configuration.public_key = config['public_key'] 7 | BraintreeRails::Configuration.private_key = config['private_key'] 8 | BraintreeRails::Configuration.client_side_encryption_key = config['client_side_encryption_key'] 9 | end 10 | end 11 | 12 | def configuration 13 | @configuration ||= load_configuration 14 | end 15 | 16 | def braintree_auth_file 17 | Rails.root.join("spec/support/braintree_integration/braintree_auth.yml") 18 | end 19 | 20 | def braintree_auth_set? 21 | BraintreeRails::Configuration.merchant_id.present? rescue false 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/assets/javascripts/transactions.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | jQuery -> 5 | $('#transaction_billing_region').parent().parent().hide() 6 | states = $('#transaction_billing_region').html() 7 | $('#transaction_billing_country_code_alpha2').change -> 8 | country = $('#transaction_billing_country_code_alpha2 :selected').text() 9 | escaped_country = country.replace(/([ #;&,.+*~\':"!^$[\]()=>|\/@])/g, '\\$1') 10 | options = $(states).filter("optgroup[label=#{escaped_country}]").html() 11 | if options 12 | $('#transaction_billing_region').html(options) 13 | $('#transaction_billing_region').parent().parent().show() 14 | else 15 | $('#transaction_billing_region').empty() 16 | $('#transaction_billing_region').parent().parent().hide() -------------------------------------------------------------------------------- /app/views/subscriptions/_form.html.haml: -------------------------------------------------------------------------------- 1 | = simple_form_for @subscription, url: url, html: { class: 'form-horizontal' } do |f| 2 | - if @plan 3 | .control-group.string.optional.subscription_plan 4 | %label.string.optional.control-label{for: 'subscription_plan'}= 'Plan' 5 | .controls 6 | %p#subscription_plan.string.optional= @plan.name 7 | - else 8 | = f.input :plan_id, options_for_plan_select 9 | - if @credit_card 10 | .control-group.string.optional.subscription_credit_card 11 | %label.string.optional.control-label{for: 'subscription_credit_card'}= 'CreditCard' 12 | .controls 13 | %p#subscription_credit_card.string.optional= @credit_card.masked_number 14 | - else 15 | = f.input :payment_method_token, options_for_credit_card_select(@customer || @subscription.credit_card.customer) 16 | = f.input :id 17 | = f.input :price 18 | .form-actions 19 | = f.button :submit, class: 'btn-primary' 20 | = link_to 'Cancel', subscriptions_path, class: 'btn' 21 | -------------------------------------------------------------------------------- /app/views/subscriptions/index.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::Subscription 2 | .page-header 3 | %h1=t '.title', default: model_class.model_name.human.pluralize 4 | %table.table.table-striped 5 | %thead 6 | %tr 7 | %th= model_class.human_attribute_name(:id) 8 | %th= model_class.human_attribute_name(:price) 9 | %th= model_class.human_attribute_name(:status) 10 | %th= model_class.human_attribute_name(:balance) 11 | %th= 'Actions' 12 | %tbody 13 | - @subscriptions.each do |subscription| 14 | %tr 15 | %td= link_to subscription.id, subscription_path(subscription) 16 | %td= subscription.price 17 | %td= subscription.status 18 | %td= subscription.balance 19 | %td 20 | = link_to 'Edit', edit_subscription_path(subscription), class: 'btn btn-mini' 21 | = link_to 'Destroy', subscription_path(subscription), method: :delete, class: 'btn btn-mini btn-danger' 22 | = link_to 'Back', back_path, class: 'btn' 23 | = link_to 'New', new_subscription_path, class: 'btn btn-primary' if @customer.present? 24 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module BraintreeRailsExample 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | 15 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 16 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 17 | # config.time_zone = 'Central Time (US & Canada)' 18 | 19 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 20 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 21 | # config.i18n.default_locale = :de 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/assets/javascripts/credit_cards.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | jQuery -> 5 | $('#credit_card_billing_address_region').parent().parent().hide() 6 | states = $('#credit_card_billing_address_region').html() 7 | $('#credit_card_billing_address_country_code_alpha2').change -> 8 | country = $('#credit_card_billing_address_country_code_alpha2 :selected').text() 9 | escaped_country = country.replace(/([ #;&,.+*~\':"!^$[\]()=>|\/@])/g, '\\$1') 10 | options = $(states).filter("optgroup[label=#{escaped_country}]").html() 11 | if options 12 | $('#credit_card_billing_address_region').html(options) 13 | $('#credit_card_billing_address_region').parent().parent().show() 14 | else 15 | $('#credit_card_billing_address_region').empty() 16 | $('#credit_card_billing_address_region').parent().parent().hide() 17 | $('#credit_card_billing_address_country_code_alpha2').change() 18 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Lin Yang http://linyang.me 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /spec/support/braintree_integration/test_data.rb: -------------------------------------------------------------------------------- 1 | module BraintreeIntegration 2 | module TestData 3 | def customer_hash 4 | { 5 | first_name: "Brain", 6 | last_name: "Tree", 7 | email: 'braintree-rails@example.com' 8 | } 9 | end 10 | 11 | def address_hash 12 | { 13 | first_name: 'Brain', 14 | last_name: 'Tree', 15 | company: 'Braintree', 16 | street_address: "1134 Crane Avenue", 17 | extended_address: "Suite 200", 18 | locality: 'Menlo Park', 19 | region: 'California', 20 | postal_code: '94025', 21 | country_code_alpha2: 'US' 22 | } 23 | end 24 | 25 | def credit_card_hash 26 | { 27 | number: (Braintree::Test::CreditCardNumbers::All - Braintree::Test::CreditCardNumbers::AmExes).shuffle.first, 28 | cvv: ("000".."999").to_a.shuffle.first, 29 | cardholder_name: 'Brain Tree', 30 | expiration_month: ("01".."12").to_a.shuffle.first, 31 | expiration_year: ("2012".."2035").to_a.shuffle.first, 32 | billing_address: address_hash, 33 | } 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /app/views/addresses/index.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::Address 2 | .page-header 3 | %h1=t '.title', default: model_class.model_name.human.pluralize 4 | %table.table.table-striped 5 | %thead 6 | %tr 7 | %th= model_class.human_attribute_name(:full_name) 8 | %th= model_class.human_attribute_name(:company) 9 | %th= model_class.human_attribute_name(:street_address) 10 | %th= model_class.human_attribute_name(:extended_address) 11 | %th= 'Actions' 12 | %tbody 13 | - @addresses.each do |address| 14 | %tr 15 | %td= address.full_name 16 | %td= address.company 17 | %td= link_to address.street_address, user_customer_address_path(@user, address) 18 | %td= address.extended_address 19 | %td 20 | = link_to 'Edit', edit_user_customer_address_path(@user, address), class: 'btn btn-mini' 21 | = link_to 'Destroy', user_customer_address_path(@user, address), method: :delete, data: {confirm:'Are you sure?'}, class: 'btn btn-mini btn-danger' 22 | 23 | = link_to 'Back', user_customer_path(@user), class: 'btn' 24 | = link_to 'New', new_user_customer_address_path(@user), class: 'btn btn-primary' 25 | -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | before_filter :find_user, except: [:index, :new, :create] 3 | 4 | def index 5 | @users = User.all 6 | end 7 | 8 | def new 9 | @user = User.new 10 | end 11 | 12 | def edit; end 13 | 14 | def show; end 15 | 16 | def create 17 | @user = User.new(user_params) 18 | if @user.save 19 | redirect_to @user, notice: 'User was successfully created.' 20 | else 21 | flash.now[:alert] = @user.errors.full_messages.join(".\n") 22 | render action: "new" 23 | end 24 | end 25 | 26 | def update 27 | if @user.update_attributes(user_params) 28 | redirect_to @user, notice: 'User was successfully updated.' 29 | else 30 | flash.now[:alert] = @user.errors.full_messages.join(".\n") 31 | render action: "edit" 32 | end 33 | end 34 | 35 | def destroy 36 | @user.destroy 37 | redirect_to users_url, notice: 'User was successfully destroyed.' 38 | end 39 | 40 | private 41 | def find_user 42 | @user = User.find(params[:id]) 43 | end 44 | 45 | def user_params 46 | params.require(:user).permit(:name, :email) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | activemodel: 6 | attributes: 7 | braintree_rails/address: 8 | locality: "City" 9 | country_name: "Country" 10 | country_code_alpha2: "Country" 11 | country_code_alpha3: "Country" 12 | country_code_numeric: "Country" 13 | braintree_rails/credit_card: 14 | cvv: "CVV" 15 | errors: 16 | models: 17 | braintree_rails/credit_card: 18 | attributes: 19 | number: 20 | '81715': "is not luhn 10 valid." 21 | '81716': "should be between 12 to 19 digits." 22 | braintree_rails/address: 23 | attributes: 24 | country_name: 25 | inclusion: "%{value} is not supported yet." 26 | country_code_alpha2: 27 | inclusion: "%{value} is not supported yet." 28 | country_code_alpha3: 29 | inclusion: "%{value} is not supported yet." 30 | country_code_numeric: 31 | inclusion: "%{value} is not supported yet." 32 | -------------------------------------------------------------------------------- /app/views/credit_cards/index.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::CreditCard 2 | .page-header 3 | %h1=t '.title', default: model_class.model_name.human.pluralize 4 | %table.table.table-striped 5 | %thead 6 | %tr 7 | %th= model_class.human_attribute_name(:card_type) 8 | %th= model_class.human_attribute_name(:masked_number) 9 | %th= model_class.human_attribute_name(:cardholder_name) 10 | %th= model_class.human_attribute_name(:expiration_date) 11 | %th= 'Actions' 12 | %tbody 13 | - @credit_cards.each do |credit_card| 14 | %tr 15 | %td= credit_card.card_type 16 | %td= link_to credit_card.masked_number, user_customer_credit_card_path(@user, credit_card) 17 | %td= credit_card.cardholder_name 18 | %td= credit_card.expiration_date 19 | %td 20 | = link_to 'Edit', edit_user_customer_credit_card_path(@user, credit_card), class: 'btn btn-mini' 21 | = link_to 'Destroy', user_customer_credit_card_path(@user, credit_card), method: :delete, data: {confirm:'Are you sure?'}, class: 'btn btn-mini btn-danger' 22 | 23 | = link_to 'Back', user_customer_path(@user), class: 'btn' 24 | = link_to 'New', new_user_customer_credit_card_path(@user), class: 'btn btn-primary' 25 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | BraintreeRailsExample::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | end 30 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended to check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(version: 20130322050449) do 15 | 16 | create_table "history_events", force: true do |t| 17 | t.string "model" 18 | t.text "data" 19 | t.datetime "created_at", null: false 20 | t.datetime "updated_at", null: false 21 | end 22 | 23 | create_table "users", force: true do |t| 24 | t.string "email" 25 | t.string "customer_id" 26 | t.datetime "created_at", null: false 27 | t.datetime "updated_at", null: false 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /app/views/credit_cards/show.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::CreditCard 2 | .page-header 3 | %h1=t '.title', default: model_class.model_name.human 4 | - [:card_type, :masked_number, :cardholder_name, :expiration_date].each do |attribute| 5 | %p 6 | %strong= model_class.human_attribute_name(attribute) + ':' 7 | %br 8 | = @credit_card.send(attribute) 9 | - [:first_name, :last_name, :company, :street_address, :extended_address, :locality, :region, :postal_code, :country_name].each do |billing_address_attribute| 10 | %p 11 | %strong= BraintreeRails::Address.human_attribute_name(billing_address_attribute) + ':' 12 | %br 13 | = @credit_card.billing_address.send(billing_address_attribute) if @credit_card.billing_address 14 | 15 | .form-actions 16 | = link_to 'Back', user_customer_credit_cards_path(@user), class: 'btn' 17 | = link_to 'Edit', edit_user_customer_credit_card_path(@user, @credit_card), class: 'btn' 18 | = link_to 'Transactions', user_customer_credit_card_transactions_path(@user, @credit_card), class: 'btn' 19 | = link_to 'Subscriptions', user_customer_credit_card_subscriptions_path(@user, @credit_card), class: 'btn' 20 | = link_to 'Destroy', user_customer_credit_card_path(@user, @credit_card), method: "delete", data: {confirm: 'Are you sure?'}, class: 'btn btn-danger' 21 | -------------------------------------------------------------------------------- /app/assets/stylesheets/scaffolds.css.less: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #fff; 3 | color: #333; 4 | font-family: verdana, arial, helvetica, sans-serif; 5 | font-size: 13px; 6 | line-height: 18px; 7 | } 8 | 9 | p, ol, ul, td { 10 | font-family: verdana, arial, helvetica, sans-serif; 11 | font-size: 13px; 12 | line-height: 18px; 13 | } 14 | 15 | pre { 16 | background-color: #eee; 17 | padding: 10px; 18 | font-size: 11px; 19 | } 20 | 21 | a { 22 | color: #000; 23 | &:visited { 24 | color: #666; 25 | } 26 | &:hover { 27 | color: #fff; 28 | background-color: #000; 29 | } 30 | } 31 | 32 | div { 33 | &.field, &.actions { 34 | margin-bottom: 10px; 35 | } 36 | } 37 | 38 | #notice { 39 | color: green; 40 | } 41 | 42 | .field_with_errors { 43 | padding: 2px; 44 | background-color: red; 45 | display: table; 46 | } 47 | 48 | #error_explanation { 49 | width: 450px; 50 | border: 2px solid red; 51 | padding: 7px; 52 | padding-bottom: 0; 53 | margin-bottom: 20px; 54 | background-color: #f0f0f0; 55 | h2 { 56 | text-align: left; 57 | font-weight: bold; 58 | padding: 5px 5px 5px 15px; 59 | font-size: 12px; 60 | margin: -7px; 61 | margin-bottom: 0px; 62 | background-color: #c00; 63 | color: #fff; 64 | } 65 | ul li { 66 | font-size: 12px; 67 | list-style: square; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/views/transactions/index.html.haml: -------------------------------------------------------------------------------- 1 | - model_class = BraintreeRails::Transaction 2 | .page-header 3 | %h1=t '.title', default: model_class.model_name.human.pluralize 4 | %table.table.table-striped 5 | %thead 6 | %tr 7 | %th= model_class.human_attribute_name(:id) 8 | %th= model_class.human_attribute_name(:amount) 9 | %th= model_class.human_attribute_name(:status) 10 | %th= model_class.human_attribute_name(:customer) 11 | %th= model_class.human_attribute_name(:credit_card) 12 | %th= 'Actions' 13 | %tbody 14 | - @transactions.each do |transaction| 15 | %tr 16 | %td= link_to transaction.id, transaction_path(transaction) 17 | %td= transaction.amount 18 | %td= transaction.status 19 | %td= transaction.customer.full_name 20 | %td= transaction.credit_card.masked_number 21 | %td 22 | = link_to 'Submit', transaction_path(transaction, operation: 'submit_for_settlement'), method: :put, class: 'btn btn-mini btn-primary' 23 | = link_to 'Void', transaction_path(transaction, operation: 'void'), method: :put, class: 'btn btn-mini btn-warning' 24 | = link_to 'Refund', transaction_path(transaction, operation: 'refund'), method: :put, class: 'btn btn-mini btn-danger' 25 | 26 | = link_to 'Back', back_path, class: 'btn' 27 | = link_to 'New', new_transaction_path, class: 'btn btn-primary' 28 | -------------------------------------------------------------------------------- /spec/support/braintree_integration/setup.rb: -------------------------------------------------------------------------------- 1 | module BraintreeIntegration 2 | module Setup 3 | def braintree_integration_load_configuration 4 | load_configuration 5 | return if braintree_auth_set? 6 | pending("You need to provide real credentials in #{braintree_auth_file} to run #{self.class.description}") 7 | end 8 | 9 | def braintree_integration_setup_test_data 10 | @user = User.create(email: 'braintree-rails@example.com') 11 | create_customer_for(@user) 12 | end 13 | 14 | def braintree_integration_teardown 15 | Capybara.using_driver(:selenium) do 16 | login_into_control_panel 17 | purge_sandbox_data 18 | end 19 | end 20 | 21 | def purge_sandbox_data 22 | page.execute_script("$('#purge_sandbox_data_form').submit()") 23 | find('.ui-dialog-buttonset').find('span', text: 'Yes').click 24 | end 25 | 26 | def login_into_control_panel 27 | visit 'https://sandbox.braintreegateway.com/' 28 | fill_in_all(nil, configuration.slice('login', 'password')) 29 | click_button 'Sign In' 30 | end 31 | 32 | def self.included(receiver) 33 | receiver.before(:all) { braintree_integration_load_configuration } 34 | receiver.before(:each) { braintree_integration_setup_test_data } 35 | receiver.after(:all) { braintree_integration_teardown } 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /app/assets/stylesheets/bootstrap_and_overrides.css.less: -------------------------------------------------------------------------------- 1 | @import "twitter/bootstrap/bootstrap"; 2 | @import "twitter/bootstrap/responsive"; 3 | 4 | // Set the correct sprite paths 5 | @iconSpritePath: image-url("twitter/bootstrap/glyphicons-halflings.png"); 6 | @iconWhiteSpritePath: image-url("twitter/bootstrap/glyphicons-halflings-white.png"); 7 | 8 | // Set the Font Awesome (Font Awesome is default. You can disable by commenting below lines) 9 | @fontAwesomeEotPath: asset-url("fontawesome-webfont.eot"); 10 | @fontAwesomeEotPath_iefix: asset-url("fontawesome-webfont.eot?#iefix"); 11 | @fontAwesomeWoffPath: asset-url("fontawesome-webfont.woff"); 12 | @fontAwesomeTtfPath: asset-url("fontawesome-webfont.ttf"); 13 | @fontAwesomeSvgPath: asset-url("fontawesome-webfont.svg#fontawesomeregular"); 14 | 15 | // Font Awesome 16 | @import "fontawesome/font-awesome"; 17 | 18 | // Glyphicons 19 | //@import "twitter/bootstrap/sprites.less"; 20 | 21 | // Your custom LESS stylesheets goes here 22 | // 23 | // Since bootstrap was imported above you have access to its mixins which 24 | // you may use and inherit here 25 | // 26 | // If you'd like to override bootstrap's own variables, you can do so here as well 27 | // See http://twitter.github.com/bootstrap/customize.html#variables for their names and documentation 28 | // 29 | // Example: 30 | // @linkColor: #ff0000; 31 | 32 | body { 33 | padding-top: 60px; 34 | } 35 | 36 | -------------------------------------------------------------------------------- /app/views/transactions/_form.html.haml: -------------------------------------------------------------------------------- 1 | = simple_form_for @transaction, url: transactions_path, html: { id: 'braintree-transaction-form', class: 'form-horizontal' } do |f| 2 | - if @credit_card 3 | = f.input :amount, hint: @credit_card.masked_number 4 | - elsif @customer 5 | = f.input :amount 6 | = f.input :credit_card, options_for_credit_card_select(@customer) 7 | - else 8 | = f.input :amount 9 | = f.simple_fields_for :credit_card, @transaction.credit_card do |cc| 10 | = cc.input :number, input_html: { 'data-encrypted-name' => true } 11 | = cc.input :cardholder_name 12 | = cc.input :cvv, maxlength: 4, label: 'CVV', input_html: { 'data-encrypted-name' => true } 13 | = cc.input :expiration_month, options_for_month_select.merge(input_html: { 'data-encrypted-name' => true }) 14 | = cc.input :expiration_year, options_for_year_select.merge(input_html: { 'data-encrypted-name' => true }) 15 | = f.simple_fields_for :billing, @transaction.billing do |ba| 16 | = ba.input :first_name 17 | = ba.input :last_name 18 | = ba.input :company 19 | = ba.input :street_address 20 | = ba.input :extended_address 21 | = ba.input :locality, label: 'City' 22 | = ba.input :country_code_alpha2, options_for_country_select 23 | = ba.input :region, options_for_region_select 24 | = ba.input :postal_code 25 | 26 | .form-actions 27 | = f.button :submit, class: 'btn-primary' 28 | = link_to 'Cancel', transactions_path, class: 'btn' 29 | -------------------------------------------------------------------------------- /app/views/credit_cards/_form.html.haml: -------------------------------------------------------------------------------- 1 | = simple_form_for @credit_card, url: url, html: { class: 'form-horizontal', id: 'braintree-credit-card-form' } do |f| 2 | = f.input(:number, input_html: { 'data-encrypted-name' => true }) if @credit_card.new_record? 3 | = f.input :cardholder_name 4 | = f.input(:cvv, maxlength: 4, input_html: {'data-encrypted-name' => true }) if @credit_card.new_record? 5 | = f.input :expiration_month, options_for_month_select 6 | = f.input :expiration_year, options_for_year_select 7 | = f.simple_fields_for :billing_address, @credit_card.billing_address do |ba| 8 | = ba.input :first_name 9 | = ba.input :last_name 10 | = ba.input :company 11 | = ba.input :street_address 12 | = ba.input :extended_address 13 | = ba.input :locality 14 | = ba.input :country_code_alpha2, options_for_country_select 15 | = ba.input :region, options_for_region_select 16 | = ba.input :postal_code 17 | .form-actions 18 | = f.button :submit, class: 'btn-primary' 19 | = link_to 'Cancel', user_customer_credit_cards_path(@user), class: 'btn' 20 | 21 | - content_for :javascript do 22 | :javascript 23 | var braintree = Braintree.create("#{BraintreeRails::Configuration.client_side_encryption_key}") 24 | braintree.onSubmitEncryptForm('braintree-credit-card-form'); 25 | BraintreeData.setup("#{BraintreeRails::Configuration.merchant_id}", "braintree-credit-card-form", BraintreeData.environments.sandbox) 26 | BraintreeData.setup("#{BraintreeRails::Configuration.merchant_id}", "braintree-transaction-form", BraintreeData.environments.sandbox) 27 | -------------------------------------------------------------------------------- /app/controllers/customers_controller.rb: -------------------------------------------------------------------------------- 1 | class CustomersController < ApplicationController 2 | before_filter :find_user 3 | before_filter :find_customer, except: [:new, :create] 4 | 5 | def new 6 | @customer = BraintreeRails::Customer.new(email: @user.email) 7 | end 8 | 9 | def create 10 | @customer = BraintreeRails::Customer.new(params[:customer].merge(params.slice(:device_data))) 11 | if @customer.save 12 | @user.update_attribute(:customer_id, @customer.id) 13 | flash[:notice] = "Customer has been successfully created." 14 | redirect_to user_customer_path(@user) and return 15 | else 16 | flash[:alert] = @customer.errors.full_messages.join(".\n") 17 | render :new 18 | end 19 | end 20 | 21 | def show 22 | if @customer.nil? 23 | redirect_to new_user_customer_path(@user) and return 24 | end 25 | end 26 | 27 | def edit; end 28 | 29 | def update 30 | if @customer.update_attributes(params[:customer].merge(params.slice(:device_data))) 31 | flash[:notice] = "Customer has been successfully updated." 32 | redirect_to user_customer_path(@user) and return 33 | else 34 | flash[:alert] = @customer.errors.full_messages.join(".\n") 35 | render :edit 36 | end 37 | end 38 | 39 | def destroy 40 | @customer.destroy 41 | @user.update_attribute(:customer_id, nil) 42 | flash[:notice] = "Customer has been successfully deleted." 43 | redirect_to user_path(@user) 44 | end 45 | 46 | protected 47 | def find_user 48 | @user = User.find(params[:user_id]) 49 | end 50 | 51 | def find_customer 52 | @customer = @user.customer 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /spec/features/user_management_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "User Management" do 4 | before(:each) do 5 | @user = User.create!(email: 'braintree-rails@example.com') 6 | end 7 | 8 | it "creates user by given email" do 9 | visit users_path 10 | click_link 'New' 11 | fill_in 'Email', with: 'braintree-rails@example.org' 12 | click_button 'Create User' 13 | current_path.should == user_path(User.last) 14 | page.should have_content('User was successfully created.') 15 | page.should have_content('braintree-rails@example.org') 16 | end 17 | 18 | it "shows given user" do 19 | visit user_path(@user) 20 | page.should have_content('braintree-rails@example.com') 21 | page.should have_link('Back') 22 | page.should have_link('Edit', href: edit_user_path(@user)) 23 | page.should have_link('Customer', href: user_customer_path(@user)) 24 | page.should have_link('Destroy') 25 | end 26 | 27 | it "shows list of users" do 28 | visit users_path 29 | page.should have_link(@user.email, href: user_path(@user)) 30 | end 31 | 32 | it "update given user" do 33 | visit edit_user_path(@user) 34 | fill_in 'Email', with: 'braintree-rails@example.org' 35 | click_button 'Update User' 36 | current_path.should == user_path(@user) 37 | page.should have_content('User was successfully updated.') 38 | page.should have_content('braintree-rails@example.org') 39 | end 40 | 41 | it "destroys given user" do 42 | visit user_path(@user) 43 | click_link 'Destroy' 44 | page.should have_content('User was successfully destroyed.') 45 | User.exists?(@user.id).should be_false 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /app/controllers/addresses_controller.rb: -------------------------------------------------------------------------------- 1 | class AddressesController < ApplicationController 2 | before_filter :find_user, :find_customer 3 | before_filter :find_address, except: [:new, :create] 4 | 5 | def index 6 | @addresses = @customer.addresses 7 | end 8 | 9 | def new 10 | @address = @customer.addresses.build(country_code_alpha2: 'US', region: 'California') 11 | end 12 | 13 | def edit; end 14 | 15 | def show; end 16 | 17 | def create 18 | @address = @customer.addresses.build(params[:address]) 19 | if @address.save 20 | flash[:notice] = "Address has been successfully created." 21 | redirect_to user_customer_address_path(@user, @address) 22 | else 23 | flash[:alert] = @address.errors.full_messages.join(".\n") 24 | render :new 25 | end 26 | end 27 | 28 | def update 29 | if @address.update_attributes(params[:address]) 30 | flash[:notice] = "Address has been successfully updated." 31 | redirect_to user_customer_address_path(@user, @address) and return 32 | else 33 | flash[:alert] = @address.errors.full_messages.join(".\n") 34 | render :edit 35 | end 36 | end 37 | 38 | def destroy 39 | @address.destroy 40 | flash[:notice] = "Address has been successfully deleted." 41 | redirect_to user_customer_addresses_path(@user) 42 | end 43 | 44 | protected 45 | def find_user 46 | @user = User.find(params[:user_id]) 47 | end 48 | 49 | def find_customer 50 | @customer = @user.customer 51 | redirect_to user_path(@user) and return if @customer.nil? 52 | end 53 | 54 | def find_address 55 | @address = @customer.addresses.find(params[:id]) 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | BraintreeRailsExample::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance. 16 | config.serve_static_assets = true 17 | config.static_cache_control = "public, max-age=3600" 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Print deprecation notices to the stderr. 35 | config.active_support.deprecation = :stderr 36 | end 37 | -------------------------------------------------------------------------------- /app/controllers/credit_cards_controller.rb: -------------------------------------------------------------------------------- 1 | class CreditCardsController < ApplicationController 2 | before_filter :find_user, :find_customer 3 | before_filter :find_credit_card, except: [:new, :create] 4 | 5 | def index 6 | @credit_cards = @customer.credit_cards 7 | end 8 | 9 | def new 10 | billing_address = @customer.addresses.first || {} 11 | @credit_card = @customer.credit_cards.build({cardholder_name: @customer.full_name, billing_address: billing_address}) 12 | end 13 | 14 | def edit; end 15 | 16 | def show; end 17 | 18 | def create 19 | @credit_card = @customer.credit_cards.build(params[:credit_card].merge(params.slice(:device_data))) 20 | if @credit_card.save 21 | flash[:notice] = "Credit card has been successfully updated." 22 | redirect_to user_customer_credit_card_path(@user, @credit_card) 23 | else 24 | flash[:alert] = @credit_card.errors.full_messages.join(".\n") 25 | render :new 26 | end 27 | end 28 | 29 | def update 30 | if @credit_card.update_attributes(params[:credit_card].merge(params.slice(:device_data))) 31 | flash[:notice] = "Credit card has been successfully updated." 32 | redirect_to user_customer_credit_card_path(@user, @credit_card) and return 33 | else 34 | flash[:alert] = @credit_card.errors.full_messages.join(".\n") 35 | render :edit 36 | end 37 | end 38 | 39 | def destroy 40 | @credit_card.destroy 41 | flash[:notice] = "Credit card has been successfully deleted." 42 | redirect_to user_customer_credit_cards_path(@user) 43 | end 44 | 45 | protected 46 | def find_user 47 | @user = User.find(params[:user_id]) 48 | end 49 | 50 | def find_customer 51 | @customer = @user.customer 52 | redirect_to user_path(@user) and return if @customer.nil? 53 | end 54 | 55 | def find_credit_card 56 | @credit_card = @customer.credit_cards.find(params[:id]) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/features/customer_management_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Customer Management", braintree_integration: true do 4 | 5 | it "redirects to new customer path if not present" do 6 | user = User.create(email: 'braintree-rails@example.org') 7 | visit user_customer_path(user) 8 | current_path.should == new_user_customer_path(user) 9 | find_field('Email').value.should == user.email 10 | end 11 | 12 | it "creates customer by given attributes" do 13 | user = User.create(email: 'braintree-rails@example.org') 14 | visit new_user_customer_path(user) 15 | fill_in_all(:customer, customer_hash) 16 | click_button('Create Customer') 17 | current_path.should == user_customer_path(user) 18 | page.should have_content('Customer has been successfully created.') 19 | 20 | user.reload.customer.should_not be_nil 21 | customer_hash.each do |attribute, value| 22 | user.customer.attributes[attribute].should == value 23 | end 24 | end 25 | 26 | it "shows customer attributes" do 27 | visit user_customer_path(@user) 28 | customer_hash.values.each do |value| 29 | page.should have_content(value) 30 | end 31 | end 32 | 33 | it "updates customer by given attributes" do 34 | visit edit_user_customer_path(@user) 35 | fill_in_all(:customer, email: 'braintree-rails@example.org') 36 | click_button('Update Customer') 37 | page.should have_content('Customer has been successfully updated.') 38 | page.should have_content('braintree-rails@example.org') 39 | end 40 | 41 | it "destroys given customer" do 42 | customer = @user.customer 43 | visit user_customer_path(@user) 44 | click_link("Destroy") 45 | current_path.should == user_path(@user) 46 | page.should have_content('Customer has been successfully deleted.') 47 | expect { BraintreeRails::Customer.find customer.id }.to raise_error(Braintree::NotFoundError) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/features/address_management_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Address Management", braintree_integration: true do 4 | 5 | it "shows a list of addresses of given customer" do 6 | address = @user.customer.addresses.create!(address_hash) 7 | visit user_customer_addresses_path(@user) 8 | page.should have_link(address.street_address, href: user_customer_address_path(@user, address)) 9 | end 10 | 11 | it "creates address by given attributes" do 12 | visit new_user_customer_address_path(@user) 13 | fill_in_all(:address, address_hash) 14 | click_button('Create Address') 15 | page.should have_content('Address has been successfully created.') 16 | 17 | address = @user.customer.addresses.find(current_path.split('/').last) 18 | 19 | address_hash.each do |attribute, value| 20 | address.attributes[attribute].should == value 21 | end 22 | end 23 | 24 | it "shows address attributes" do 25 | address = @user.customer.addresses.create!(address_hash) 26 | visit user_customer_address_path(@user, address) 27 | address_hash.values.each do |value| 28 | page.should have_content(value) 29 | end 30 | end 31 | 32 | it "updates address by given attributes" do 33 | address = @user.customer.addresses.create!(address_hash) 34 | visit edit_user_customer_address_path(@user, address) 35 | fill_in_all(:address, postal_code: '12345') 36 | click_button('Update Address') 37 | page.should have_content('Address has been successfully updated.') 38 | page.should have_content('12345') 39 | end 40 | 41 | it "destroys given address" do 42 | address = @user.customer.addresses.create!(address_hash) 43 | visit user_customer_address_path(@user, address) 44 | click_link("Destroy") 45 | current_path.should == user_customer_addresses_path(@user) 46 | page.should have_content('Address has been successfully deleted.') 47 | expect { BraintreeRails::Address.find @user.customer.id, address.id }.to raise_error(Braintree::NotFoundError) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to braintree-rails-example 2 | [braintree-rails-example](https://github.com/lyang/braintree-rails-example) is a demo rails app that uses [braintree-rails](https://github.com/lyang/braintree-rails) gem to intergrate with Braintree payment api. 3 | 4 | This demo app shows how [braintree-rails](https://github.com/lyang/braintree-rails-example) simplified the code you needed in model/view/controller. 5 | 6 | With the "railsy" models [braintree-rails](https://github.com/lyang/braintree-rails) provides, you can use them exactly "the rails way" as you would with any other ActiveRecord object. 7 | 8 | In addition, the app uses Braintree.js for credit card data encryption. It helps you greatly reduce the PCI scope while keeps the flexibility. 9 | 10 | ## Instructions 11 | Clone the repo 12 | 13 | git clone git://github.com/lyang/braintree-rails-example.git 14 | bundle install 15 | 16 | Setup the database 17 | 18 | rake db:migrate 19 | 20 | Then you need to edit `config/braintree.rb` to put in your Braintree sandbox credentials 21 | ```ruby 22 | # Those are just delegation to Braintree::Configuration 23 | BraintreeRails::Configuration.environment = :sandbox 24 | BraintreeRails::Configuration.logger = Logger.new('log/braintree.log') 25 | BraintreeRails::Configuration.merchant_id = ENV['MERCHANT_ID'] 26 | BraintreeRails::Configuration.public_key = ENV['PUBLIC_KEY'] 27 | BraintreeRails::Configuration.private_key = ENV['PRIVATE_KEY'] 28 | 29 | # This is just a convenient place you can put your CSE key 30 | BraintreeRails::Configuration.client_side_encryption_key = ENV['CLIENT_SIDE_ENCRYPTION_KEY'] 31 | ``` 32 | 33 | Then you can start the demo with 34 | 35 | rails s 36 | 37 | ## Live demo 38 | You can play with the live demo at [here](http://braintree-rails-example.herokuapp.com/). 39 | 40 | **Caultion** 41 | The live demo is for public *demo purpose only*. 42 | 43 | **DO NOT** put any important or sensitive info in this demo. 44 | 45 | It will be purged periodically. 46 | 47 | ## Sandbox environment for testing 48 | You can get your sandbox env at https://www.braintreepayments.com/get-started 49 | -------------------------------------------------------------------------------- /app/controllers/transactions_controller.rb: -------------------------------------------------------------------------------- 1 | class TransactionsController < ApplicationController 2 | before_filter :find_transactions 3 | before_filter :find_transaction, except: [:new, :create] 4 | before_filter :restricted_update, only: :update 5 | helper_method :transaction_path, :transactions_path 6 | 7 | def index; end 8 | 9 | def show; end 10 | 11 | def new 12 | @transaction = @transactions.build(amount: rand(1..25)) 13 | end 14 | 15 | def create 16 | @transaction = @transactions.build(params[:transaction].merge(params.slice(:device_data))) 17 | if @transaction.save 18 | flash[:notice] = "Transaction has been successfully created." 19 | redirect_to transaction_path(@transaction) 20 | else 21 | flash[:alert] = @transaction.errors.full_messages.join(".\n") 22 | render :new 23 | end 24 | end 25 | 26 | def update 27 | if @transaction.send(params[:operation]) 28 | flash[:notice] = "Transaction has been #{params[:operation]}." 29 | else 30 | flash[:alert] = @transaction.errors.full_messages.join(".\n") 31 | end 32 | redirect_to transactions_path 33 | end 34 | 35 | protected 36 | 37 | def find_transactions 38 | if params[:user_id].present? 39 | @user = User.find(params[:user_id]) 40 | @customer = @user.customer if @user 41 | @credit_card = @customer.credit_cards.find(params[:credit_card_id]) if params[:credit_card_id].present? 42 | @transactions = @credit_card.present? ? @credit_card.transactions : @customer.transactions 43 | else 44 | @transactions = BraintreeRails::Transactions.new(nil) 45 | end 46 | end 47 | 48 | def find_transaction 49 | @transaction = @transactions.find(params[:id]) 50 | end 51 | 52 | def restricted_update 53 | return true if ['submit_for_settlement', 'void', 'refund'].include?(params[:operation]) 54 | flash[:alert] = "Unknow operation: #{params[:operation]}!" 55 | redirect_to transactions_path and return 56 | end 57 | 58 | def transactions_path 59 | path ||= user_customer_credit_card_transactions_path(@user, @credit_card) if @credit_card 60 | path ||= user_customer_transactions_path(@user) if @user 61 | path ||= super 62 | end 63 | 64 | 65 | def transaction_path(transaction, options={}) 66 | path ||= user_customer_credit_card_transaction_path(@user, @credit_card, transaction, options) if @credit_card 67 | path ||= user_customer_transaction_path(@user, transaction, options) if @user 68 | path ||= super(transaction, options) 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /public/assets/manifest-500885f534fb666445deb60092afb357.json: -------------------------------------------------------------------------------- 1 | {"files":{"rails-2da85d4b70342e6b8b7ec929d911af9f.png":{"logical_path":"rails.png","mtime":"2012-12-17T08:55:51-08:00","size":6646,"digest":"2da85d4b70342e6b8b7ec929d911af9f"},"application-6e3658cb51743044eed0ffab7c61d512.js":{"logical_path":"application.js","mtime":"2014-04-04T14:14:06-07:00","size":426413,"digest":"6e3658cb51743044eed0ffab7c61d512"},"application-d8aebfd478eee5c4f8dbe8411bcde500.css":{"logical_path":"application.css","mtime":"2014-04-04T14:47:46-07:00","size":165637,"digest":"d8aebfd478eee5c4f8dbe8411bcde500"},"fontawesome-webfont-658b64bfa55eff9dcd175bfd3ac3238a.eot":{"logical_path":"fontawesome-webfont.eot","mtime":"2014-04-04T11:39:40-07:00","size":37405,"digest":"658b64bfa55eff9dcd175bfd3ac3238a"},"fontawesome-webfont-8b61acf61f1d94a9a5dee063074e6b7a.svg":{"logical_path":"fontawesome-webfont.svg","mtime":"2014-04-04T11:39:40-07:00","size":197829,"digest":"8b61acf61f1d94a9a5dee063074e6b7a"},"fontawesome-webfont-efc9e487a6c573ca65c0d1a10c09f63d.ttf":{"logical_path":"fontawesome-webfont.ttf","mtime":"2014-04-04T11:39:40-07:00","size":79076,"digest":"efc9e487a6c573ca65c0d1a10c09f63d"},"fontawesome-webfont-98d61732d4d0af2312cfc7c1d005594b.woff":{"logical_path":"fontawesome-webfont.woff","mtime":"2014-04-04T11:39:40-07:00","size":43572,"digest":"98d61732d4d0af2312cfc7c1d005594b"},"twitter/bootstrap/glyphicons-halflings-white-e06c893995f68ff48aa1b2e591f27889.png":{"logical_path":"twitter/bootstrap/glyphicons-halflings-white.png","mtime":"2014-04-04T11:39:40-07:00","size":8777,"digest":"e06c893995f68ff48aa1b2e591f27889"},"twitter/bootstrap/glyphicons-halflings-d83dfe9df6cd3f50b5ae69abe919e065.png":{"logical_path":"twitter/bootstrap/glyphicons-halflings.png","mtime":"2014-04-04T11:39:40-07:00","size":12799,"digest":"d83dfe9df6cd3f50b5ae69abe919e065"}},"assets":{"rails.png":"rails-2da85d4b70342e6b8b7ec929d911af9f.png","application.js":"application-6e3658cb51743044eed0ffab7c61d512.js","application.css":"application-d8aebfd478eee5c4f8dbe8411bcde500.css","fontawesome-webfont.eot":"fontawesome-webfont-658b64bfa55eff9dcd175bfd3ac3238a.eot","fontawesome-webfont.svg":"fontawesome-webfont-8b61acf61f1d94a9a5dee063074e6b7a.svg","fontawesome-webfont.ttf":"fontawesome-webfont-efc9e487a6c573ca65c0d1a10c09f63d.ttf","fontawesome-webfont.woff":"fontawesome-webfont-98d61732d4d0af2312cfc7c1d005594b.woff","twitter/bootstrap/glyphicons-halflings-white.png":"twitter/bootstrap/glyphicons-halflings-white-e06c893995f68ff48aa1b2e591f27889.png","twitter/bootstrap/glyphicons-halflings.png":"twitter/bootstrap/glyphicons-halflings-d83dfe9df6cd3f50b5ae69abe919e065.png"}} -------------------------------------------------------------------------------- /app/controllers/subscriptions_controller.rb: -------------------------------------------------------------------------------- 1 | class SubscriptionsController < ApplicationController 2 | before_filter :find_subscriptions 3 | before_filter :find_subscription, except: [:index, :create] 4 | helper_method :subscription_path, :subscriptions_path 5 | 6 | def index; end 7 | 8 | def show; end 9 | 10 | def edit; end 11 | 12 | def new 13 | @subscription = @subscriptions.build 14 | end 15 | 16 | def create 17 | @subscription = @subscriptions.build(params[:subscription]) 18 | if @subscription.save 19 | flash[:notice] = "Subscription has been successfully created." 20 | redirect_to subscription_path(@subscription) 21 | else 22 | flash[:alert] = @subscription.errors.full_messages.join(".\n") 23 | render :new 24 | end 25 | end 26 | 27 | def update 28 | if @subscription.update_attributes(params[:subscription]) 29 | flash[:notice] = "Customer has been successfully updated." 30 | redirect_to subscription_path(@subscription) and return 31 | else 32 | flash[:alert] = @subscription.errors.full_messages.join(".\n") 33 | render :edit 34 | end 35 | end 36 | 37 | def destroy 38 | @subscription.destroy 39 | flash[:notice] = "Subscription has been successfully canceled." 40 | redirect_to subscriptions_path 41 | end 42 | 43 | protected 44 | 45 | def find_subscriptions 46 | if find_credit_card 47 | @subscriptions = @credit_card.subscriptions 48 | elsif find_plan 49 | @subscriptions = @plan.subscriptions 50 | else 51 | @subscriptions = BraintreeRails::Subscriptions.new(nil) 52 | end 53 | end 54 | 55 | def find_subscription 56 | @subscription = @subscriptions.find(params[:id]) 57 | end 58 | 59 | def find_credit_card 60 | @user = User.find(params[:user_id]) if params[:user_id].present? 61 | @customer = @user.customer if @user 62 | @credit_card = @customer.credit_cards.find(params[:credit_card_id]) if @customer && params[:credit_card_id].present? 63 | end 64 | 65 | def find_plan 66 | @plan = BraintreeRails::Plan.find(params[:plan_id]) if params[:plan_id].present? 67 | end 68 | 69 | def subscriptions_path 70 | path ||= user_customer_credit_card_subscriptions_path(@user, @credit_card) if @credit_card 71 | path ||= plan_subscriptions_path(@plan) if @plan 72 | path ||= super 73 | end 74 | 75 | 76 | def subscription_path(subscription, options={}) 77 | path ||= user_customer_credit_card_subscription_path(@user, @credit_card, subscription, options) if @credit_card 78 | path ||= plan_subscription_path(@plan, subscription, options) if @plan 79 | path ||= super(subscription, options) 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.haml: -------------------------------------------------------------------------------- 1 | !!! 5 2 | %html(lang="en") 3 | %head 4 | %meta(charset="utf-8") 5 | %meta(http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1") 6 | %meta(name="viewport" content="width=device-width, initial-scale=1.0") 7 | %title= content_for?(:title) ? yield(:title) : "BraintreeRailsExample" 8 | = csrf_meta_tags 9 | / Le HTML5 shim, for IE6-8 support of HTML elements 10 | /[if lt IE 9] 11 | = javascript_include_tag "//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.6.1/html5shiv.js" 12 | = stylesheet_link_tag "application", :media => "all" 13 | = favicon_link_tag 'apple-touch-icon-144x144-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png', :sizes => '144x144' 14 | = favicon_link_tag 'apple-touch-icon-114x114-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png', :sizes => '114x114' 15 | = favicon_link_tag 'apple-touch-icon-72x72-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png', :sizes => '72x72' 16 | = favicon_link_tag 'apple-touch-icon-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png' 17 | = favicon_link_tag 'favicon.ico', :rel => 'shortcut icon' 18 | = javascript_include_tag "application" 19 | 20 | %body 21 | .navbar.navbar-fixed-top 22 | .navbar-inner 23 | .container 24 | %a.btn.btn-navbar(data-target=".nav-collapse" data-toggle="collapse") 25 | %span.icon-bar 26 | %span.icon-bar 27 | %span.icon-bar 28 | %a.brand(href="/") Braintree Rails Example 29 | .container.nav-collapse 30 | %ul.nav 31 | %li= link_to("Users", users_path) 32 | %li= link_to("Transactions", transactions_path) 33 | %li= link_to("Plans", plans_path) 34 | %li= link_to("AddOns", add_ons_path) 35 | %li= link_to("Discounts", discounts_path) 36 | %li= link_to("Subscriptions", subscriptions_path) 37 | %li= link_to("History Events", history_events_path) 38 | %li= link_to("Source Code", "https://github.com/lyang/braintree-rails-example") 39 | 40 | .container 41 | = bootstrap_flash 42 | .content 43 | .row 44 | .span9 45 | = yield 46 | .span3 47 | .well.sidebar-nav 48 | %a.github{href: "https://github.com/lyang/braintree-rails"} 49 | %img{alt: "Fork me on GitHub", src: "https://s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png"} 50 | %h3 Sidebar 51 | %ul.nav.nav-list 52 | %li.nav-header Sidebar 53 | %li= link_to("Users", users_path) 54 | %li= link_to("Transactions", transactions_path) 55 | %li= link_to("Plans", plans_path) 56 | %li= link_to("AddOns", add_ons_path) 57 | %li= link_to("Discounts", discounts_path) 58 | %li= link_to("Subscriptions", subscriptions_path) 59 | %li= link_to("History Events", history_events_path) 60 | %li= link_to("Source Code", "https://github.com/lyang/braintree-rails-example") 61 | 62 | %footer 63 | %p © Company 2014 64 | = yield :javascript 65 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | BraintreeRailsExample::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both thread web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. 20 | # config.action_dispatch.rack_cache = true 21 | 22 | # Disable Rails's static asset server (Apache or nginx will already do this). 23 | config.serve_static_assets = false 24 | 25 | # Compress JavaScripts and CSS. 26 | config.assets.js_compressor = :uglifier 27 | # config.assets.css_compressor = :sass 28 | 29 | # Do not fallback to assets pipeline if a precompiled asset is missed. 30 | config.assets.compile = false 31 | 32 | # Generate digests for assets URLs. 33 | config.assets.digest = true 34 | 35 | # Version of your assets, change this if you want to expire all your assets. 36 | config.assets.version = '1.0' 37 | 38 | # Specifies the header that your server uses for sending files. 39 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 40 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 41 | 42 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 43 | # config.force_ssl = true 44 | 45 | # Set to :debug to see everything in the log. 46 | config.log_level = :info 47 | 48 | # Prepend all log lines with the following tags. 49 | # config.log_tags = [ :subdomain, :uuid ] 50 | 51 | # Use a different logger for distributed setups. 52 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 53 | 54 | # Use a different cache store in production. 55 | # config.cache_store = :mem_cache_store 56 | 57 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 58 | # config.action_controller.asset_host = "http://assets.example.com" 59 | 60 | # Precompile additional assets. 61 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 62 | # config.assets.precompile += %w( search.js ) 63 | 64 | # Ignore bad email addresses and do not raise email delivery errors. 65 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 66 | # config.action_mailer.raise_delivery_errors = false 67 | 68 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 69 | # the I18n.default_locale when a translation can not be found). 70 | config.i18n.fallbacks = true 71 | 72 | # Send deprecation notices to registered listeners. 73 | config.active_support.deprecation = :notify 74 | 75 | # Disable automatic flushing of the log to improve performance. 76 | # config.autoflush_log = false 77 | 78 | # Use default logging formatter so that PID and timestamp are not suppressed. 79 | config.log_formatter = ::Logger::Formatter.new 80 | end 81 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/lyang/braintree-rails.git 3 | revision: c01fc805ab4f1f8d115377b9d0026d914b4c7963 4 | branch: master 5 | specs: 6 | braintree-rails (1.3.0) 7 | activemodel (>= 3.0) 8 | activesupport (>= 3.0) 9 | braintree (>= 2.28.0) 10 | 11 | GEM 12 | remote: https://rubygems.org/ 13 | specs: 14 | actionmailer (4.0.4) 15 | actionpack (= 4.0.4) 16 | mail (~> 2.5.4) 17 | actionpack (4.0.4) 18 | activesupport (= 4.0.4) 19 | builder (~> 3.1.0) 20 | erubis (~> 2.7.0) 21 | rack (~> 1.5.2) 22 | rack-test (~> 0.6.2) 23 | activemodel (4.0.4) 24 | activesupport (= 4.0.4) 25 | builder (~> 3.1.0) 26 | activerecord (4.0.4) 27 | activemodel (= 4.0.4) 28 | activerecord-deprecated_finders (~> 1.0.2) 29 | activesupport (= 4.0.4) 30 | arel (~> 4.0.0) 31 | activerecord-deprecated_finders (1.0.3) 32 | activesupport (4.0.4) 33 | i18n (~> 0.6, >= 0.6.9) 34 | minitest (~> 4.2) 35 | multi_json (~> 1.3) 36 | thread_safe (~> 0.1) 37 | tzinfo (~> 0.3.37) 38 | addressable (2.3.6) 39 | arel (4.0.2) 40 | braintree (2.36.0) 41 | builder (>= 2.0.0) 42 | builder (3.1.4) 43 | capybara (2.2.1) 44 | mime-types (>= 1.16) 45 | nokogiri (>= 1.3.3) 46 | rack (>= 1.0.0) 47 | rack-test (>= 0.5.4) 48 | xpath (~> 2.0) 49 | carmen (1.0.1) 50 | unicode_utils (~> 1.4.0) 51 | carmen-rails (1.0.1) 52 | carmen (~> 1.0.0) 53 | rails 54 | childprocess (0.5.2) 55 | ffi (~> 1.0, >= 1.0.11) 56 | coffee-rails (4.0.1) 57 | coffee-script (>= 2.2.0) 58 | railties (>= 4.0.0, < 5.0) 59 | coffee-script (2.2.0) 60 | coffee-script-source 61 | execjs 62 | coffee-script-source (1.7.0) 63 | commonjs (0.2.7) 64 | daemons (1.1.9) 65 | diff-lcs (1.2.5) 66 | erubis (2.7.0) 67 | eventmachine (1.0.3) 68 | execjs (2.0.2) 69 | ffi (1.9.3) 70 | haml (4.0.5) 71 | tilt 72 | haml-rails (0.5.3) 73 | actionpack (>= 4.0.1) 74 | activesupport (>= 4.0.1) 75 | haml (>= 3.1, < 5.0) 76 | railties (>= 4.0.1) 77 | hike (1.2.3) 78 | i18n (0.6.11) 79 | jquery-rails (3.1.0) 80 | railties (>= 3.0, < 5.0) 81 | thor (>= 0.14, < 2.0) 82 | json (1.8.1) 83 | launchy (2.4.2) 84 | addressable (~> 2.3) 85 | less (2.5.0) 86 | commonjs (~> 0.2.7) 87 | less-rails (2.5.0) 88 | actionpack (>= 3.1) 89 | less (~> 2.5.0) 90 | libv8 (3.16.14.3) 91 | mail (2.5.4) 92 | mime-types (~> 1.16) 93 | treetop (~> 1.4.8) 94 | mime-types (1.25.1) 95 | mini_portile (0.5.3) 96 | minitest (4.7.5) 97 | multi_json (1.10.1) 98 | newrelic_rpm (3.7.3.204) 99 | nokogiri (1.6.1) 100 | mini_portile (~> 0.5.0) 101 | pg (0.17.1) 102 | polyglot (0.3.4) 103 | quiet_assets (1.0.2) 104 | railties (>= 3.1, < 5.0) 105 | rack (1.5.2) 106 | rack-test (0.6.2) 107 | rack (>= 1.0) 108 | rails (4.0.4) 109 | actionmailer (= 4.0.4) 110 | actionpack (= 4.0.4) 111 | activerecord (= 4.0.4) 112 | activesupport (= 4.0.4) 113 | bundler (>= 1.3.0, < 2.0) 114 | railties (= 4.0.4) 115 | sprockets-rails (~> 2.0.0) 116 | rails_on_heroku (0.0.2) 117 | rails_serve_static_assets 118 | rails_stdout_logging 119 | rails_serve_static_assets (0.0.2) 120 | rails_stdout_logging (0.0.3) 121 | railties (4.0.4) 122 | actionpack (= 4.0.4) 123 | activesupport (= 4.0.4) 124 | rake (>= 0.8.7) 125 | thor (>= 0.18.1, < 2.0) 126 | rake (10.3.1) 127 | ref (1.0.5) 128 | rspec-core (2.14.8) 129 | rspec-expectations (2.14.5) 130 | diff-lcs (>= 1.1.3, < 2.0) 131 | rspec-mocks (2.14.6) 132 | rspec-rails (2.14.2) 133 | actionpack (>= 3.0) 134 | activemodel (>= 3.0) 135 | activesupport (>= 3.0) 136 | railties (>= 3.0) 137 | rspec-core (~> 2.14.0) 138 | rspec-expectations (~> 2.14.0) 139 | rspec-mocks (~> 2.14.0) 140 | rubyzip (1.1.3) 141 | selenium-webdriver (2.41.0) 142 | childprocess (>= 0.5.0) 143 | multi_json (~> 1.0) 144 | rubyzip (~> 1.0) 145 | websocket (~> 1.0.4) 146 | shoulda-matchers (2.6.0) 147 | activesupport (>= 3.0.0) 148 | simple_form (3.0.2) 149 | actionpack (~> 4.0) 150 | activemodel (~> 4.0) 151 | sprockets (2.12.1) 152 | hike (~> 1.2) 153 | multi_json (~> 1.0) 154 | rack (~> 1.0) 155 | tilt (~> 1.1, != 1.3.0) 156 | sprockets-rails (2.0.1) 157 | actionpack (>= 3.0) 158 | activesupport (>= 3.0) 159 | sprockets (~> 2.8) 160 | sqlite3 (1.3.9) 161 | therubyracer (0.12.1) 162 | libv8 (~> 3.16.14.0) 163 | ref 164 | thin (1.6.2) 165 | daemons (>= 1.0.9) 166 | eventmachine (>= 1.0.0) 167 | rack (>= 1.0.0) 168 | thor (0.19.1) 169 | thread_safe (0.3.4) 170 | tilt (1.4.1) 171 | treetop (1.4.15) 172 | polyglot 173 | polyglot (>= 0.3.1) 174 | twitter-bootstrap-rails (2.2.8) 175 | actionpack (>= 3.1) 176 | execjs 177 | rails (>= 3.1) 178 | railties (>= 3.1) 179 | tzinfo (0.3.42) 180 | uglifier (2.5.0) 181 | execjs (>= 0.3.0) 182 | json (>= 1.8.0) 183 | unicode_utils (1.4.0) 184 | websocket (1.0.7) 185 | xpath (2.0.0) 186 | nokogiri (~> 1.3) 187 | 188 | PLATFORMS 189 | ruby 190 | 191 | DEPENDENCIES 192 | braintree-rails! 193 | capybara 194 | carmen-rails 195 | coffee-rails (~> 4.0.1) 196 | haml-rails 197 | jquery-rails 198 | launchy 199 | less-rails 200 | newrelic_rpm 201 | pg 202 | quiet_assets 203 | rails (~> 4.0.3) 204 | rails_on_heroku 205 | rspec-rails (~> 2.14.2) 206 | selenium-webdriver 207 | shoulda-matchers 208 | simple_form 209 | sqlite3 210 | therubyracer 211 | thin 212 | twitter-bootstrap-rails 213 | uglifier (>= 2.5.0) 214 | -------------------------------------------------------------------------------- /config/initializers/simple_form.rb: -------------------------------------------------------------------------------- 1 | # Use this setup block to configure all options available in SimpleForm. 2 | SimpleForm.setup do |config| 3 | # Wrappers are used by the form builder to generate a 4 | # complete input. You can remove any component from the 5 | # wrapper, change the order or even add your own to the 6 | # stack. The options given below are used to wrap the 7 | # whole input. 8 | config.wrappers :default, class: :input, hint_class: :field_with_hint, error_class: :field_with_errors do |b| 9 | ## Extensions enabled by default 10 | # Any of these extensions can be disabled for a 11 | # given input by passing `f.input EXTENSION_NAME: false`. 12 | # You can make any of these extensions optional by 13 | # renaming `b.use` to `b.optional`. 14 | 15 | # Determines whether to use HTML5 (:email, :url, ...) 16 | # and required attributes 17 | b.use :html5 18 | 19 | # Calculates placeholders automatically from I18n 20 | # You can also pass a string as f.input placeholder: "Placeholder" 21 | b.use :placeholder 22 | 23 | ## Optional extensions 24 | # They are disabled unless you pass `f.input EXTENSION_NAME => :lookup` 25 | # to the input. If so, they will retrieve the values from the model 26 | # if any exists. If you want to enable the lookup for any of those 27 | # extensions by default, you can change `b.optional` to `b.use`. 28 | 29 | # Calculates maxlength from length validations for string inputs 30 | b.optional :maxlength 31 | 32 | # Calculates pattern from format validations for string inputs 33 | b.optional :pattern 34 | 35 | # Calculates min and max from length validations for numeric inputs 36 | b.optional :min_max 37 | 38 | # Calculates readonly automatically from readonly attributes 39 | b.optional :readonly 40 | 41 | ## Inputs 42 | b.use :label_input 43 | b.use :hint, wrap_with: { tag: :span, class: :hint } 44 | b.use :error, wrap_with: { tag: :span, class: :error } 45 | end 46 | 47 | config.wrappers :bootstrap, tag: 'div', class: 'control-group', error_class: 'error' do |b| 48 | b.use :html5 49 | b.use :placeholder 50 | b.use :label 51 | b.wrapper tag: 'div', class: 'controls' do |ba| 52 | ba.use :input 53 | ba.use :error, wrap_with: { tag: 'span', class: 'help-inline' } 54 | ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 55 | end 56 | end 57 | 58 | config.wrappers :prepend, tag: 'div', class: "control-group", error_class: 'error' do |b| 59 | b.use :html5 60 | b.use :placeholder 61 | b.use :label 62 | b.wrapper tag: 'div', class: 'controls' do |input| 63 | input.wrapper tag: 'div', class: 'input-prepend' do |prepend| 64 | prepend.use :input 65 | end 66 | input.use :hint, wrap_with: { tag: 'span', class: 'help-block' } 67 | input.use :error, wrap_with: { tag: 'span', class: 'help-inline' } 68 | end 69 | end 70 | 71 | config.wrappers :append, tag: 'div', class: "control-group", error_class: 'error' do |b| 72 | b.use :html5 73 | b.use :placeholder 74 | b.use :label 75 | b.wrapper tag: 'div', class: 'controls' do |input| 76 | input.wrapper tag: 'div', class: 'input-append' do |append| 77 | append.use :input 78 | end 79 | input.use :hint, wrap_with: { tag: 'span', class: 'help-block' } 80 | input.use :error, wrap_with: { tag: 'span', class: 'help-inline' } 81 | end 82 | end 83 | 84 | # Wrappers for forms and inputs using the Twitter Bootstrap toolkit. 85 | # Check the Bootstrap docs (http://twitter.github.com/bootstrap) 86 | # to learn about the different styles for forms and inputs, 87 | # buttons and other elements. 88 | config.default_wrapper = :bootstrap 89 | 90 | # Define the way to render check boxes / radio buttons with labels. 91 | # Defaults to :nested for bootstrap config. 92 | # inline: input + label 93 | # nested: label > input 94 | config.boolean_style = :nested 95 | 96 | # Default class for buttons 97 | config.button_class = 'btn' 98 | 99 | # Method used to tidy up errors. Specify any Rails Array method. 100 | # :first lists the first message for each field. 101 | # Use :to_sentence to list all errors for each field. 102 | # config.error_method = :first 103 | 104 | # Default tag used for error notification helper. 105 | config.error_notification_tag = :div 106 | 107 | # CSS class to add for error notification helper. 108 | config.error_notification_class = 'alert alert-error' 109 | 110 | # ID to add for error notification helper. 111 | # config.error_notification_id = nil 112 | 113 | # Series of attempts to detect a default label method for collection. 114 | # config.collection_label_methods = [ :to_label, :name, :title, :to_s ] 115 | 116 | # Series of attempts to detect a default value method for collection. 117 | # config.collection_value_methods = [ :id, :to_s ] 118 | 119 | # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none. 120 | # config.collection_wrapper_tag = nil 121 | 122 | # You can define the class to use on all collection wrappers. Defaulting to none. 123 | # config.collection_wrapper_class = nil 124 | 125 | # You can wrap each item in a collection of radio/check boxes with a tag, 126 | # defaulting to :span. Please note that when using :boolean_style = :nested, 127 | # SimpleForm will force this option to be a label. 128 | # config.item_wrapper_tag = :span 129 | 130 | # You can define a class to use in all item wrappers. Defaulting to none. 131 | # config.item_wrapper_class = nil 132 | 133 | # How the label text should be generated altogether with the required text. 134 | # config.label_text = lambda { |label, required| "#{required} #{label}" } 135 | 136 | # You can define the class to use on all labels. Default is nil. 137 | config.label_class = 'control-label' 138 | 139 | # You can define the class to use on all forms. Default is simple_form. 140 | # config.form_class = :simple_form 141 | 142 | # You can define which elements should obtain additional classes 143 | # config.generate_additional_classes_for = [:wrapper, :label, :input] 144 | 145 | # Whether attributes are required by default (or not). Default is true. 146 | # config.required_by_default = true 147 | 148 | # Tell browsers whether to use default HTML5 validations (novalidate option). 149 | # Default is enabled. 150 | config.browser_validations = false 151 | 152 | # Collection of methods to detect if a file type was given. 153 | # config.file_methods = [ :mounted_as, :file?, :public_filename ] 154 | 155 | # Custom mappings for input types. This should be a hash containing a regexp 156 | # to match as key, and the input type that will be used when the field name 157 | # matches the regexp as value. 158 | # config.input_mappings = { /count/ => :integer } 159 | 160 | # Default priority for time_zone inputs. 161 | # config.time_zone_priority = nil 162 | 163 | # Default priority for country inputs. 164 | # config.country_priority = nil 165 | 166 | # Default size for text inputs. 167 | # config.default_input_size = 50 168 | 169 | # When false, do not use translations for labels. 170 | # config.translate_labels = true 171 | 172 | # Automatically discover new inputs in Rails' autoload path. 173 | # config.inputs_discovery = true 174 | 175 | # Cache SimpleForm inputs discovery 176 | # config.cache_discovery = !Rails.env.development? 177 | end 178 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | == Welcome to Rails 2 | 3 | Rails is a web-application framework that includes everything needed to create 4 | database-backed web applications according to the Model-View-Control pattern. 5 | 6 | This pattern splits the view (also called the presentation) into "dumb" 7 | templates that are primarily responsible for inserting pre-built data in between 8 | HTML tags. The model contains the "smart" domain objects (such as Account, 9 | Product, Person, Post) that holds all the business logic and knows how to 10 | persist themselves to a database. The controller handles the incoming requests 11 | (such as Save New Account, Update Product, Show Post) by manipulating the model 12 | and directing data to the view. 13 | 14 | In Rails, the model is handled by what's called an object-relational mapping 15 | layer entitled Active Record. This layer allows you to present the data from 16 | database rows as objects and embellish these data objects with business logic 17 | methods. You can read more about Active Record in 18 | link:files/vendor/rails/activerecord/README.html. 19 | 20 | The controller and view are handled by the Action Pack, which handles both 21 | layers by its two parts: Action View and Action Controller. These two layers 22 | are bundled in a single package due to their heavy interdependence. This is 23 | unlike the relationship between the Active Record and Action Pack that is much 24 | more separate. Each of these packages can be used independently outside of 25 | Rails. You can read more about Action Pack in 26 | link:files/vendor/rails/actionpack/README.html. 27 | 28 | 29 | == Getting Started 30 | 31 | 1. At the command prompt, create a new Rails application: 32 | rails new myapp (where myapp is the application name) 33 | 34 | 2. Change directory to myapp and start the web server: 35 | cd myapp; rails server (run with --help for options) 36 | 37 | 3. Go to http://localhost:3000/ and you'll see: 38 | "Welcome aboard: You're riding Ruby on Rails!" 39 | 40 | 4. Follow the guidelines to start developing your application. You can find 41 | the following resources handy: 42 | 43 | * The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html 44 | * Ruby on Rails Tutorial Book: http://www.railstutorial.org/ 45 | 46 | 47 | == Debugging Rails 48 | 49 | Sometimes your application goes wrong. Fortunately there are a lot of tools that 50 | will help you debug it and get it back on the rails. 51 | 52 | First area to check is the application log files. Have "tail -f" commands 53 | running on the server.log and development.log. Rails will automatically display 54 | debugging and runtime information to these files. Debugging info will also be 55 | shown in the browser on requests from 127.0.0.1. 56 | 57 | You can also log your own messages directly into the log file from your code 58 | using the Ruby logger class from inside your controllers. Example: 59 | 60 | class WeblogController < ActionController::Base 61 | def destroy 62 | @weblog = Weblog.find(params[:id]) 63 | @weblog.destroy 64 | logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") 65 | end 66 | end 67 | 68 | The result will be a message in your log file along the lines of: 69 | 70 | Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! 71 | 72 | More information on how to use the logger is at http://www.ruby-doc.org/core/ 73 | 74 | Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are 75 | several books available online as well: 76 | 77 | * Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) 78 | * Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) 79 | 80 | These two books will bring you up to speed on the Ruby language and also on 81 | programming in general. 82 | 83 | 84 | == Debugger 85 | 86 | Debugger support is available through the debugger command when you start your 87 | Mongrel or WEBrick server with --debugger. This means that you can break out of 88 | execution at any point in the code, investigate and change the model, and then, 89 | resume execution! You need to install ruby-debug to run the server in debugging 90 | mode. With gems, use sudo gem install ruby-debug. Example: 91 | 92 | class WeblogController < ActionController::Base 93 | def index 94 | @posts = Post.all 95 | debugger 96 | end 97 | end 98 | 99 | So the controller will accept the action, run the first line, then present you 100 | with a IRB prompt in the server window. Here you can do things like: 101 | 102 | >> @posts.inspect 103 | => "[#nil, "body"=>nil, "id"=>"1"}>, 105 | #"Rails", "body"=>"Only ten..", "id"=>"2"}>]" 107 | >> @posts.first.title = "hello from a debugger" 108 | => "hello from a debugger" 109 | 110 | ...and even better, you can examine how your runtime objects actually work: 111 | 112 | >> f = @posts.first 113 | => #nil, "body"=>nil, "id"=>"1"}> 114 | >> f. 115 | Display all 152 possibilities? (y or n) 116 | 117 | Finally, when you're ready to resume execution, you can enter "cont". 118 | 119 | 120 | == Console 121 | 122 | The console is a Ruby shell, which allows you to interact with your 123 | application's domain model. Here you'll have all parts of the application 124 | configured, just like it is when the application is running. You can inspect 125 | domain models, change values, and save to the database. Starting the script 126 | without arguments will launch it in the development environment. 127 | 128 | To start the console, run rails console from the application 129 | directory. 130 | 131 | Options: 132 | 133 | * Passing the -s, --sandbox argument will rollback any modifications 134 | made to the database. 135 | * Passing an environment name as an argument will load the corresponding 136 | environment. Example: rails console production. 137 | 138 | To reload your controllers and models after launching the console run 139 | reload! 140 | 141 | More information about irb can be found at: 142 | link:http://www.rubycentral.org/pickaxe/irb.html 143 | 144 | 145 | == dbconsole 146 | 147 | You can go to the command line of your database directly through rails 148 | dbconsole. You would be connected to the database with the credentials 149 | defined in database.yml. Starting the script without arguments will connect you 150 | to the development database. Passing an argument will connect you to a different 151 | database, like rails dbconsole production. Currently works for MySQL, 152 | PostgreSQL and SQLite 3. 153 | 154 | == Description of Contents 155 | 156 | The default directory structure of a generated Ruby on Rails application: 157 | 158 | |-- app 159 | | |-- assets 160 | | |-- images 161 | | |-- javascripts 162 | | `-- stylesheets 163 | | |-- controllers 164 | | |-- helpers 165 | | |-- mailers 166 | | |-- models 167 | | `-- views 168 | | `-- layouts 169 | |-- config 170 | | |-- environments 171 | | |-- initializers 172 | | `-- locales 173 | |-- db 174 | |-- doc 175 | |-- lib 176 | | `-- tasks 177 | |-- log 178 | |-- public 179 | |-- script 180 | |-- test 181 | | |-- fixtures 182 | | |-- functional 183 | | |-- integration 184 | | |-- performance 185 | | `-- unit 186 | |-- tmp 187 | | |-- cache 188 | | |-- pids 189 | | |-- sessions 190 | | `-- sockets 191 | `-- vendor 192 | |-- assets 193 | `-- stylesheets 194 | `-- plugins 195 | 196 | app 197 | Holds all the code that's specific to this particular application. 198 | 199 | app/assets 200 | Contains subdirectories for images, stylesheets, and JavaScript files. 201 | 202 | app/controllers 203 | Holds controllers that should be named like weblogs_controller.rb for 204 | automated URL mapping. All controllers should descend from 205 | ApplicationController which itself descends from ActionController::Base. 206 | 207 | app/models 208 | Holds models that should be named like post.rb. Models descend from 209 | ActiveRecord::Base by default. 210 | 211 | app/views 212 | Holds the template files for the view that should be named like 213 | weblogs/index.html.erb for the WeblogsController#index action. All views use 214 | eRuby syntax by default. 215 | 216 | app/views/layouts 217 | Holds the template files for layouts to be used with views. This models the 218 | common header/footer method of wrapping views. In your views, define a layout 219 | using the layout :default and create a file named default.html.erb. 220 | Inside default.html.erb, call <% yield %> to render the view using this 221 | layout. 222 | 223 | app/helpers 224 | Holds view helpers that should be named like weblogs_helper.rb. These are 225 | generated for you automatically when using generators for controllers. 226 | Helpers can be used to wrap functionality for your views into methods. 227 | 228 | config 229 | Configuration files for the Rails environment, the routing map, the database, 230 | and other dependencies. 231 | 232 | db 233 | Contains the database schema in schema.rb. db/migrate contains all the 234 | sequence of Migrations for your schema. 235 | 236 | doc 237 | This directory is where your application documentation will be stored when 238 | generated using rake doc:app 239 | 240 | lib 241 | Application specific libraries. Basically, any kind of custom code that 242 | doesn't belong under controllers, models, or helpers. This directory is in 243 | the load path. 244 | 245 | public 246 | The directory available for the web server. Also contains the dispatchers and the 247 | default HTML files. This should be set as the DOCUMENT_ROOT of your web 248 | server. 249 | 250 | script 251 | Helper scripts for automation and generation. 252 | 253 | test 254 | Unit and functional tests along with fixtures. When using the rails generate 255 | command, template test files will be generated for you and placed in this 256 | directory. 257 | 258 | vendor 259 | External libraries that the application depends on. Also includes the plugins 260 | subdirectory. If the app has frozen rails, those gems also go here, under 261 | vendor/rails/. This directory is in the load path. 262 | -------------------------------------------------------------------------------- /config/newrelic.yml: -------------------------------------------------------------------------------- 1 | # Here are the settings that are common to all environments 2 | common: &default_settings 3 | # ============================== LICENSE KEY =============================== 4 | 5 | # You must specify the license key associated with your New Relic 6 | # account. This key binds your Agent's data to your account in the 7 | # New Relic service. 8 | license_key: '<%= ENV["NEW_RELIC_LICENSE_KEY"] %>' 9 | 10 | # Agent Enabled (Rails Only) 11 | # Use this setting to force the agent to run or not run. 12 | # Default is 'auto' which means the agent will install and run only 13 | # if a valid dispatcher such as Mongrel is running. This prevents 14 | # it from running with Rake or the console. Set to false to 15 | # completely turn the agent off regardless of the other settings. 16 | # Valid values are true, false and auto. 17 | # 18 | # agent_enabled: auto 19 | 20 | # Application Name Set this to be the name of your application as 21 | # you'd like it show up in New Relic. The service will then auto-map 22 | # instances of your application into an "application" on your 23 | # dashboard page. If you want to map this instance into multiple 24 | # apps, like "AJAX Requests" and "All UI" then specify a semicolon 25 | # separated list of up to three distinct names, or a yaml list. 26 | # Defaults to the capitalized RAILS_ENV or RACK_ENV (i.e., 27 | # Production, Staging, etc) 28 | # 29 | # Example: 30 | # 31 | # app_name: 32 | # - Ajax Service 33 | # - All Services 34 | # 35 | app_name: <%= ENV["NEW_RELIC_APP_NAME"] %> 36 | 37 | # When "true", the agent collects performance data about your 38 | # application and reports this data to the New Relic service at 39 | # newrelic.com. This global switch is normally overridden for each 40 | # environment below. (formerly called 'enabled') 41 | monitor_mode: true 42 | 43 | # Developer mode should be off in every environment but 44 | # development as it has very high overhead in memory. 45 | developer_mode: false 46 | 47 | # The newrelic agent generates its own log file to keep its logging 48 | # information separate from that of your application. Specify its 49 | # log level here. 50 | log_level: info 51 | 52 | # Optionally set the path to the log file This is expanded from the 53 | # root directory (may be relative or absolute, e.g. 'log/' or 54 | # '/var/log/') The agent will attempt to create this directory if it 55 | # does not exist. 56 | # log_file_path: 'log' 57 | 58 | # Optionally set the name of the log file, defaults to 'newrelic_agent.log' 59 | # log_file_name: 'newrelic_agent.log' 60 | 61 | # The newrelic agent communicates with the service via http by 62 | # default. If you want to communicate via https to increase 63 | # security, then turn on SSL by setting this value to true. Note, 64 | # this will result in increased CPU overhead to perform the 65 | # encryption involved in SSL communication, but this work is done 66 | # asynchronously to the threads that process your application code, 67 | # so it should not impact response times. 68 | ssl: true 69 | 70 | # EXPERIMENTAL: enable verification of the SSL certificate sent by 71 | # the server. This setting has no effect unless SSL is enabled 72 | # above. This may block your application. Only enable it if the data 73 | # you send us needs end-to-end verified certificates. 74 | # 75 | # This means we cannot cache the DNS lookup, so each request to the 76 | # service will perform a lookup. It also means that we cannot 77 | # use a non-blocking lookup, so in a worst case, if you have DNS 78 | # problems, your app may block indefinitely. 79 | # verify_certificate: true 80 | 81 | # Set your application's Apdex threshold value with the 'apdex_t' 82 | # setting, in seconds. The apdex_t value determines the buckets used 83 | # to compute your overall Apdex score. 84 | # Requests that take less than apdex_t seconds to process will be 85 | # classified as Satisfying transactions; more than apdex_t seconds 86 | # as Tolerating transactions; and more than four times the apdex_t 87 | # value as Frustrating transactions. 88 | # For more about the Apdex standard, see 89 | # http://newrelic.com/docs/general/apdex 90 | 91 | apdex_t: 0.5 92 | 93 | #============================== Browser Monitoring =============================== 94 | # New Relic Real User Monitoring gives you insight into the performance real users are 95 | # experiencing with your website. This is accomplished by measuring the time it takes for 96 | # your users' browsers to download and render your web pages by injecting a small amount 97 | # of JavaScript code into the header and footer of each page. 98 | browser_monitoring: 99 | # By default the agent automatically injects the monitoring JavaScript 100 | # into web pages. Set this attribute to false to turn off this behavior. 101 | auto_instrument: true 102 | 103 | # Proxy settings for connecting to the service. 104 | # 105 | # If a proxy is used, the host setting is required. Other settings 106 | # are optional. Default port is 8080. 107 | # 108 | # proxy_host: hostname 109 | # proxy_port: 8080 110 | # proxy_user: 111 | # proxy_pass: 112 | 113 | 114 | # Tells transaction tracer and error collector (when enabled) 115 | # whether or not to capture HTTP params. When true, frameworks can 116 | # exclude HTTP parameters from being captured. 117 | # Rails: the RoR filter_parameter_logging excludes parameters 118 | # Java: create a config setting called "ignored_params" and set it to 119 | # a comma separated list of HTTP parameter names. 120 | # ex: ignored_params: credit_card, ssn, password 121 | capture_params: false 122 | 123 | 124 | # Transaction tracer captures deep information about slow 125 | # transactions and sends this to the service once a 126 | # minute. Included in the transaction is the exact call sequence of 127 | # the transactions including any SQL statements issued. 128 | transaction_tracer: 129 | 130 | # Transaction tracer is enabled by default. Set this to false to 131 | # turn it off. This feature is only available at the Professional 132 | # and above product levels. 133 | enabled: true 134 | 135 | # Threshold in seconds for when to collect a transaction 136 | # trace. When the response time of a controller action exceeds 137 | # this threshold, a transaction trace will be recorded and sent to 138 | # the service. Valid values are any float value, or (default) 139 | # "apdex_f", which will use the threshold for an dissatisfying 140 | # Apdex controller action - four times the Apdex T value. 141 | transaction_threshold: apdex_f 142 | 143 | # When transaction tracer is on, SQL statements can optionally be 144 | # recorded. The recorder has three modes, "off" which sends no 145 | # SQL, "raw" which sends the SQL statement in its original form, 146 | # and "obfuscated", which strips out numeric and string literals 147 | record_sql: obfuscated 148 | 149 | # Threshold in seconds for when to collect stack trace for a SQL 150 | # call. In other words, when SQL statements exceed this threshold, 151 | # then capture and send the current stack trace. This is 152 | # helpful for pinpointing where long SQL calls originate from 153 | stack_trace_threshold: 0.500 154 | 155 | # Determines whether the agent will capture query plans for slow 156 | # SQL queries. Only supported in mysql and postgres. Should be 157 | # set to false when using other adapters. 158 | # explain_enabled: true 159 | 160 | # Threshold for query execution time below which query plans will not 161 | # not be captured. Relevant only when `explain_enabled` is true. 162 | # explain_threshold: 0.5 163 | 164 | # Error collector captures information about uncaught exceptions and 165 | # sends them to the service for viewing 166 | error_collector: 167 | 168 | # Error collector is enabled by default. Set this to false to turn 169 | # it off. This feature is only available at the Professional and above 170 | # product levels 171 | enabled: true 172 | 173 | # Rails Only - tells error collector whether or not to capture a 174 | # source snippet around the place of the error when errors are View 175 | # related. 176 | capture_source: true 177 | 178 | # To stop specific errors from reporting to New Relic, set this property 179 | # to comma separated values. Default is to ignore routing errors 180 | # which are how 404's get triggered. 181 | # 182 | ignore_errors: ActionController::RoutingError 183 | 184 | # (Advanced) Uncomment this to ensure the cpu and memory samplers 185 | # won't run. Useful when you are using the agent to monitor an 186 | # external resource 187 | # disable_samplers: true 188 | 189 | # If you aren't interested in visibility in these areas, you can 190 | # disable the instrumentation to reduce overhead. 191 | # 192 | # disable_view_instrumentation: true 193 | # disable_activerecord_instrumentation: true 194 | # disable_memcache_instrumentation: true 195 | # disable_dj: true 196 | 197 | # If you're interested in capturing memcache keys as though they 198 | # were SQL uncomment this flag. Note that this does increase 199 | # overhead slightly on every memcached call, and can have security 200 | # implications if your memcached keys are sensitive 201 | # capture_memcache_keys: true 202 | 203 | # Certain types of instrumentation such as GC stats will not work if 204 | # you are running multi-threaded. Please let us know. 205 | # multi_threaded = false 206 | 207 | # Application Environments 208 | # ------------------------------------------ 209 | # Environment specific settings are in this section. 210 | # For Rails applications, RAILS_ENV is used to determine the environment 211 | # For Java applications, pass -Dnewrelic.environment to set 212 | # the environment 213 | 214 | # NOTE if your application has other named environments, you should 215 | # provide newrelic configuration settings for these environments here. 216 | 217 | development: 218 | <<: *default_settings 219 | # Turn off communication to New Relic service in development mode (also 220 | # 'enabled'). 221 | # NOTE: for initial evaluation purposes, you may want to temporarily 222 | # turn the agent on in development mode. 223 | monitor_mode: false 224 | 225 | # Rails Only - when running in Developer Mode, the New Relic Agent will 226 | # present performance information on the last 100 transactions you have 227 | # executed since starting the mongrel. 228 | # NOTE: There is substantial overhead when running in developer mode. 229 | # Do not use for production or load testing. 230 | developer_mode: true 231 | 232 | # Enable textmate links 233 | # textmate: true 234 | 235 | test: 236 | <<: *default_settings 237 | # It almost never makes sense to turn on the agent when running 238 | # unit, functional or integration tests or the like. 239 | monitor_mode: false 240 | 241 | # Turn on the agent in production for 24x7 monitoring. NewRelic 242 | # testing shows an average performance impact of < 5 ms per 243 | # transaction, you you can leave this on all the time without 244 | # incurring any user-visible performance degradation. 245 | production: 246 | <<: *default_settings 247 | monitor_mode: true 248 | 249 | # Many applications have a staging environment which behaves 250 | # identically to production. Support for that environment is provided 251 | # here. By default, the staging environment has the agent turned on. 252 | staging: 253 | <<: *default_settings 254 | monitor_mode: true 255 | app_name: <%= ENV["NEW_RELIC_APP_NAME"] %> (Staging) 256 | -------------------------------------------------------------------------------- /app/assets/javascripts/braintree-data.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Braintree Data 3 | * https://www.braintreepayments.com 4 | * Copyright (c) 2009-2013 Braintree Payment Solutions 5 | * 6 | * Licensed under the MIT License. 7 | * http://opensource.org/licenses/MIT 8 | */ 9 | 10 | (function(){"use strict";var sjcl={cipher:{},hash:{},keyexchange:{},mode:{},misc:{},codec:{},exception:{corrupt:function(message){this.toString=function(){return"CORRUPT: "+this.message;};this.message=message;},invalid:function(message){this.toString=function(){return"INVALID: "+this.message;};this.message=message;},bug:function(message){this.toString=function(){return"BUG: "+this.message;};this.message=message;},notReady:function(message){this.toString=function(){return"NOT READY: "+this.message;};this.message=message;}}};if(typeof module!='undefined'&&module.exports){module.exports=sjcl;} 11 | sjcl.bitArray={bitSlice:function(a,bstart,bend){a=sjcl.bitArray._shiftRight(a.slice(bstart/32),32-(bstart&31)).slice(1);return(bend===undefined)?a:sjcl.bitArray.clamp(a,bend-bstart);},extract:function(a,bstart,blength){var x,sh=Math.floor((-bstart-blength)&31);if((bstart+blength-1^bstart)&-32){x=(a[bstart/32|0]<<(32-sh))^(a[bstart/32+1|0]>>>sh);}else{x=a[bstart/32|0]>>>sh;} 12 | return x&((1<0&&len){a[l-1]=sjcl.bitArray.partial(len,a[l-1]&0x80000000>>(len-1),1);} 16 | return a;},partial:function(len,x,_end){if(len===32){return x;} 17 | return(_end?x|0:x<<(32-len))+len*0x10000000000;},getPartial:function(x){return Math.round(x/0x10000000000)||32;},equal:function(a,b){if(sjcl.bitArray.bitLength(a)!==sjcl.bitArray.bitLength(b)){return false;} 18 | var x=0,i;for(i=0;i=32;shift-=32){out.push(carry);carry=0;} 21 | if(shift===0){return out.concat(a);} 22 | for(i=0;i>>shift);carry=a[i]<<(32-shift);} 23 | last2=a.length?a[a.length-1]:0;shift2=sjcl.bitArray.getPartial(last2);out.push(sjcl.bitArray.partial(shift+shift2&31,(shift+shift2>32)?carry:out.pop(),1));return out;},_xor4:function(x,y){return[x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]];}};sjcl.codec.hex={fromBits:function(arr){var out="",i,x;for(i=0;i>>7^a>>>18^a>>>3^a<<25^a<<14)+ 34 | (b>>>17^b>>>19^b>>>10^b<<15^b<<13)+ 35 | w[i&15]+w[(i+9)&15])|0;} 36 | tmp=(tmp+h7+(h4>>>6^h4>>>11^h4>>>25^h4<<26^h4<<21^h4<<7)+(h6^h4&(h5^h6))+k[i]);h7=h6;h6=h5;h5=h4;h4=h3+tmp|0;h3=h2;h2=h1;h1=h0;h0=(tmp+((h1&h2)^(h3&(h1^h2)))+(h1>>>2^h1>>>13^h1>>>22^h1<<30^h1<<19^h1<<10))|0;} 37 | h[0]=h[0]+h0|0;h[1]=h[1]+h1|0;h[2]=h[2]+h2|0;h[3]=h[3]+h3|0;h[4]=h[4]+h4|0;h[5]=h[5]+h5|0;h[6]=h[6]+h6|0;h[7]=h[7]+h7|0;}};sjcl.cipher.aes=function(key){if(!this._tables[0][0][0]){this._precompute();} 38 | var i,j,tmp,encKey,decKey,sbox=this._tables[0][4],decTable=this._tables[1],keyLen=key.length,rcon=1;if(keyLen!==4&&keyLen!==6&&keyLen!==8){throw new sjcl.exception.invalid("invalid aes key size");} 39 | this._key=[encKey=key.slice(0),decKey=[]];for(i=keyLen;i<4*keyLen+28;i++){tmp=encKey[i-1];if(i%keyLen===0||(keyLen===8&&i%keyLen===4)){tmp=sbox[tmp>>>24]<<24^sbox[tmp>>16&255]<<16^sbox[tmp>>8&255]<<8^sbox[tmp&255];if(i%keyLen===0){tmp=tmp<<8^tmp>>>24^rcon<<24;rcon=rcon<<1^(rcon>>7)*283;}} 40 | encKey[i]=encKey[i-keyLen]^tmp;} 41 | for(j=0;i;j++,i--){tmp=encKey[j&3?i:i-4];if(i<=4||j<4){decKey[j]=tmp;}else{decKey[j]=decTable[0][sbox[tmp>>>24]]^decTable[1][sbox[tmp>>16&255]]^decTable[2][sbox[tmp>>8&255]]^decTable[3][sbox[tmp&255]];}}};sjcl.cipher.aes.prototype={encrypt:function(data){return this._crypt(data,0);},decrypt:function(data){return this._crypt(data,1);},_tables:[[[],[],[],[],[]],[[],[],[],[],[]]],_precompute:function(){var encTable=this._tables[0],decTable=this._tables[1],sbox=encTable[4],sboxInv=decTable[4],i,x,xInv,d=[],th=[],x2,x4,x8,s,tEnc,tDec;for(i=0;i<256;i++){th[(d[i]=i<<1^(i>>7)*283)^i]=i;} 42 | for(x=xInv=0;!sbox[x];x^=x2||1,xInv=th[xInv]||1){s=xInv^xInv<<1^xInv<<2^xInv<<3^xInv<<4;s=s>>8^s&255^99;sbox[x]=s;sboxInv[s]=x;x8=d[x4=d[x2=d[x]]];tDec=x8*0x1010101^x4*0x10001^x2*0x101^x*0x1010100;tEnc=d[s]*0x101^s*0x1010100;for(i=0;i<4;i++){encTable[i][x]=tEnc=tEnc<<24^tEnc>>>8;decTable[i][s]=tDec=tDec<<24^tDec>>>8;}} 43 | for(i=0;i<5;i++){encTable[i]=encTable[i].slice(0);decTable[i]=decTable[i].slice(0);}},_crypt:function(input,dir){if(input.length!==4){throw new sjcl.exception.invalid("invalid aes block size");} 44 | var key=this._key[dir],a=input[0]^key[0],b=input[dir?3:1]^key[1],c=input[2]^key[2],d=input[dir?1:3]^key[3],a2,b2,c2,nInnerRounds=key.length/4-2,i,kIndex=4,out=[0,0,0,0],table=this._tables[dir],t0=table[0],t1=table[1],t2=table[2],t3=table[3],sbox=table[4];for(i=0;i>>24]^t1[b>>16&255]^t2[c>>8&255]^t3[d&255]^key[kIndex];b2=t0[b>>>24]^t1[c>>16&255]^t2[d>>8&255]^t3[a&255]^key[kIndex+1];c2=t0[c>>>24]^t1[d>>16&255]^t2[a>>8&255]^t3[b&255]^key[kIndex+2];d=t0[d>>>24]^t1[a>>16&255]^t2[b>>8&255]^t3[c&255]^key[kIndex+3];kIndex+=4;a=a2;b=b2;c=c2;} 45 | for(i=0;i<4;i++){out[dir?3&-i:i]=sbox[a>>>24]<<24^sbox[b>>16&255]<<16^sbox[c>>8&255]<<8^sbox[d&255]^key[kIndex++];a2=a;a=b;b=c;c=d;d=a2;} 46 | return out;}};sjcl.random={randomWords:function(nwords,paranoia){var out=[],i,readiness=this.isReady(paranoia),g;if(readiness===this._NOT_READY){throw new sjcl.exception.notReady("generator isn't seeded");}else if(readiness&this._REQUIRES_RESEED){this._reseedFromPools(!(readiness&this._READY));} 47 | for(i=0;i0){estimatedEntropy++;tmp=tmp>>>1;}}} 56 | this._pools[robin].update([id,this._eventId++,2,estimatedEntropy,t,data.length].concat(data));} 57 | break;case"string":if(estimatedEntropy===undefined){estimatedEntropy=data.length;} 58 | this._pools[robin].update([id,this._eventId++,3,estimatedEntropy,t,data.length]);this._pools[robin].update(data);break;default:err=1;} 59 | if(err){throw new sjcl.exception.bug("random: addEntropy only supports number, array of numbers or string");} 60 | this._poolEntropy[robin]+=estimatedEntropy;this._poolStrength+=estimatedEntropy;if(oldReady===this._NOT_READY){if(this.isReady()!==this._NOT_READY){this._fireEvent("seeded",Math.max(this._strength,this._poolStrength));} 61 | this._fireEvent("progress",this.getProgress());}},isReady:function(paranoia){var entropyRequired=this._PARANOIA_LEVELS[(paranoia!==undefined)?paranoia:this._defaultParanoia];if(this._strength&&this._strength>=entropyRequired){return(this._poolEntropy[0]>this._BITS_PER_RESEED&&(new Date()).valueOf()>this._nextReseed)?this._REQUIRES_RESEED|this._READY:this._READY;}else{return(this._poolStrength>=entropyRequired)?this._REQUIRES_RESEED|this._NOT_READY:this._NOT_READY;}},getProgress:function(paranoia){var entropyRequired=this._PARANOIA_LEVELS[paranoia?paranoia:this._defaultParanoia];if(this._strength>=entropyRequired){return 1.0;}else{return(this._poolStrength>entropyRequired)?1.0:this._poolStrength/entropyRequired;}},startCollectors:function(){if(this._collectorsStarted){return;} 62 | if(window.addEventListener){window.addEventListener("load",this._loadTimeCollector,false);window.addEventListener("mousemove",this._mouseCollector,false);}else if(document.attachEvent){document.attachEvent("onload",this._loadTimeCollector);document.attachEvent("onmousemove",this._mouseCollector);} 63 | else{throw new sjcl.exception.bug("can't attach event");} 64 | this._collectorsStarted=true;},stopCollectors:function(){if(!this._collectorsStarted){return;} 65 | if(window.removeEventListener){window.removeEventListener("load",this._loadTimeCollector,false);window.removeEventListener("mousemove",this._mouseCollector,false);}else if(window.detachEvent){window.detachEvent("onload",this._loadTimeCollector);window.detachEvent("onmousemove",this._mouseCollector);} 66 | this._collectorsStarted=false;},addEventListener:function(name,callback){this._callbacks[name][this._callbackI++]=callback;},removeEventListener:function(name,cb){var i,j,cbs=this._callbacks[name],jsTemp=[];for(j in cbs){if(cbs.hasOwnProperty(j)&&cbs[j]===cb){jsTemp.push(j);}} 67 | for(i=0;i=1<this._strength){this._strength=strength;} 72 | this._reseedCount++;this._reseed(reseedData);},_mouseCollector:function(ev){var x=ev.x||ev.clientX||ev.offsetX||0,y=ev.y||ev.clientY||ev.offsetY||0;sjcl.random.addEntropy([x,y],2,"mouse");},_loadTimeCollector:function(ev){sjcl.random.addEntropy((new Date()).valueOf(),2,"loadtime");},_fireEvent:function(name,arg){var j,cbs=sjcl.random._callbacks[name],cbsTemp=[];for(j in cbs){if(cbs.hasOwnProperty(j)){cbsTemp.push(cbs[j]);}} 73 | for(j=0;j 14 | * Licensed under the ISC License. 15 | * http://opensource.org/licenses/ISC 16 | */ 17 | 18 | Braintree=(function(){function Stream(enc,pos){if(enc instanceof Stream){this.enc=enc.enc;this.pos=enc.pos;}else{this.enc=enc;this.pos=pos;}} 19 | Stream.prototype.get=function(pos){if(pos==undefined) 20 | pos=this.pos++;if(pos>=this.enc.length) 21 | throw'Requesting byte offset '+pos+' on a stream of length '+this.enc.length;return this.enc[pos];} 22 | Stream.prototype.hexDigits="0123456789ABCDEF";Stream.prototype.hexByte=function(b){return this.hexDigits.charAt((b>>4)&0xF)+this.hexDigits.charAt(b&0xF);} 23 | Stream.prototype.hexDump=function(start,end){var s="";for(var i=start;i191)&&(c<224)) 29 | s+=String.fromCharCode(((c&0x1F)<<6)|(this.get(i++)&0x3F));else 30 | s+=String.fromCharCode(((c&0x0F)<<12)|((this.get(i++)&0x3F)<<6)|(this.get(i++)&0x3F));} 31 | return s;} 32 | Stream.prototype.reTime=/^((?:1[89]|2\d)?\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/;Stream.prototype.parseTime=function(start,end){var s=this.parseStringISO(start,end);var m=this.reTime.exec(s);if(!m) 33 | return"Unrecognized time: "+s;s=m[1]+"-"+m[2]+"-"+m[3]+" "+m[4];if(m[5]){s+=":"+m[5];if(m[6]){s+=":"+m[6];if(m[7]) 34 | s+="."+m[7];}} 35 | if(m[8]){s+=" UTC";if(m[8]!='Z'){s+=m[8];if(m[9]) 36 | s+=":"+m[9];}} 37 | return s;} 38 | Stream.prototype.parseInteger=function(start,end){var len=end-start;if(len>4){len<<=3;var s=this.get(start);if(s==0) 39 | len-=8;else 40 | while(s<128){s<<=1;--len;} 41 | return"("+len+" bit)";} 42 | var n=0;for(var i=start;istart;--i){var b=this.get(i);for(var j=skip;j<8;++j) 45 | s+=(b>>j)&1?"1":"0";skip=0;}} 46 | return s;} 47 | Stream.prototype.parseOctetString=function(start,end){var len=end-start;var s="("+len+" byte) ";if(len>20) 48 | end=start+20;for(var i=start;i20) 50 | s+=String.fromCharCode(8230);return s;} 51 | Stream.prototype.parseOID=function(start,end){var s,n=0,bits=0;for(var i=start;i=31)?"bigint":n);n=bits=0;} 54 | s+=String.fromCharCode();} 55 | return s;} 56 | function ASN1(stream,header,length,tag,sub){this.stream=stream;this.header=header;this.length=length;this.tag=tag;this.sub=sub;} 57 | ASN1.prototype.typeName=function(){if(this.tag==undefined) 58 | return"unknown";var tagClass=this.tag>>6;var tagConstructed=(this.tag>>5)&1;var tagNumber=this.tag&0x1F;switch(tagClass){case 0:switch(tagNumber){case 0x00:return"EOC";case 0x01:return"BOOLEAN";case 0x02:return"INTEGER";case 0x03:return"BIT_STRING";case 0x04:return"OCTET_STRING";case 0x05:return"NULL";case 0x06:return"OBJECT_IDENTIFIER";case 0x07:return"ObjectDescriptor";case 0x08:return"EXTERNAL";case 0x09:return"REAL";case 0x0A:return"ENUMERATED";case 0x0B:return"EMBEDDED_PDV";case 0x0C:return"UTF8String";case 0x10:return"SEQUENCE";case 0x11:return"SET";case 0x12:return"NumericString";case 0x13:return"PrintableString";case 0x14:return"TeletexString";case 0x15:return"VideotexString";case 0x16:return"IA5String";case 0x17:return"UTCTime";case 0x18:return"GeneralizedTime";case 0x19:return"GraphicString";case 0x1A:return"VisibleString";case 0x1B:return"GeneralString";case 0x1C:return"UniversalString";case 0x1E:return"BMPString";default:return"Universal_"+tagNumber.toString(16);} 59 | case 1:return"Application_"+tagNumber.toString(16);case 2:return"["+tagNumber+"]";case 3:return"Private_"+tagNumber.toString(16);}} 60 | ASN1.prototype.content=function(){if(this.tag==undefined) 61 | return null;var tagClass=this.tag>>6;if(tagClass!=0) 62 | return(this.sub==null)?null:"("+this.sub.length+")";var tagNumber=this.tag&0x1F;var content=this.posContent();var len=Math.abs(this.length);switch(tagNumber){case 0x01:return(this.stream.get(content)==0)?"false":"true";case 0x02:return this.stream.parseInteger(content,content+len);case 0x03:return this.sub?"("+this.sub.length+" elem)":this.stream.parseBitString(content,content+len) 63 | case 0x04:return this.sub?"("+this.sub.length+" elem)":this.stream.parseOctetString(content,content+len) 64 | case 0x06:return this.stream.parseOID(content,content+len);case 0x10:case 0x11:return"("+this.sub.length+" elem)";case 0x0C:return this.stream.parseStringUTF(content,content+len);case 0x12:case 0x13:case 0x14:case 0x15:case 0x16:case 0x1A:return this.stream.parseStringISO(content,content+len);case 0x17:case 0x18:return this.stream.parseTime(content,content+len);} 65 | return null;} 66 | ASN1.prototype.toString=function(){return this.typeName()+"@"+this.stream.pos+"[header:"+this.header+",length:"+this.length+",sub:"+((this.sub==null)?'null':this.sub.length)+"]";} 67 | ASN1.prototype.print=function(indent){if(indent==undefined)indent='';document.writeln(indent+this);if(this.sub!=null){indent+=' ';for(var i=0,max=this.sub.length;i=0) 70 | s+="+";s+=this.length;if(this.tag&0x20) 71 | s+=" (constructed)";else if(((this.tag==0x03)||(this.tag==0x04))&&(this.sub!=null)) 72 | s+=" (encapsulates)";s+="\n";if(this.sub!=null){indent+=' ';for(var i=0,max=this.sub.length;i3) 80 | throw"Length over 24 bits not supported at position "+(stream.pos-1);if(len==0) 81 | return-1;buf=0;for(var i=0;i0x04)) 85 | return false;var p=new Stream(stream);if(tag==0x03)p.get();var subTag=p.get();if((subTag>>6)&0x01) 86 | return false;try{var subLength=ASN1.decodeLength(p);return((p.pos-stream.pos)+subLength==len);}catch(exception){return false;}} 87 | ASN1.decode=function(stream){if(!(stream instanceof Stream)) 88 | stream=new Stream(stream,0);var streamStart=new Stream(stream);var tag=stream.get();var len=ASN1.decodeLength(stream);var header=stream.pos-streamStart.pos;var sub=null;if(ASN1.hasContent(tag,len,stream)){var start=stream.pos;if(tag==0x03)stream.get();sub=[];if(len>=0){var end=start+len;while(stream.pos>6)+b64map.charAt(c&63);} 95 | if(i+1==h.length){c=parseInt(h.substring(i,i+1),16);ret+=b64map.charAt(c<<2);} 96 | else if(i+2==h.length){c=parseInt(h.substring(i,i+2),16);ret+=b64map.charAt(c>>2)+b64map.charAt((c&3)<<4);} 97 | while((ret.length&3)>0)ret+=b64pad;return ret;} 98 | function b64tohex(s){var ret="" 99 | var i;var k=0;var slop;for(i=0;i>2);slop=v&3;k=1;} 100 | else if(k==1){ret+=int2char((slop<<2)|(v>>4));slop=v&0xf;k=2;} 101 | else if(k==2){ret+=int2char(slop);ret+=int2char(v>>2);slop=v&3;k=3;} 102 | else{ret+=int2char((slop<<2)|(v>>4));ret+=int2char(v&0xf);k=0;}} 103 | if(k==1) 104 | ret+=int2char(slop<<2);return ret;} 105 | function b64toBA(s){var h=b64tohex(s);var i;var a=new Array();for(i=0;2*i=0){var v=x*this[i++]+w[j]+c;c=Math.floor(v/0x4000000);w[j++]=v&0x3ffffff;} 111 | return c;} 112 | function am2(i,x,w,j,c,n){var xl=x&0x7fff,xh=x>>15;while(--n>=0){var l=this[i]&0x7fff;var h=this[i++]>>15;var m=xh*l+h*xl;l=xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff);c=(l>>>30)+(m>>>15)+xh*h+(c>>>30);w[j++]=l&0x3fffffff;} 113 | return c;} 114 | function am3(i,x,w,j,c,n){var xl=x&0x3fff,xh=x>>14;while(--n>=0){var l=this[i]&0x3fff;var h=this[i++]>>14;var m=xh*l+h*xl;l=xl*l+((m&0x3fff)<<14)+w[j]+c;c=(l>>28)+(m>>14)+xh*h;w[j++]=l&0xfffffff;} 115 | return c;} 116 | if(j_lm&&(navigator.appName=="Microsoft Internet Explorer")){BigInteger.prototype.am=am2;dbits=30;} 117 | else if(j_lm&&(navigator.appName!="Netscape")){BigInteger.prototype.am=am1;dbits=26;} 118 | else{BigInteger.prototype.am=am3;dbits=28;} 119 | BigInteger.prototype.DB=dbits;BigInteger.prototype.DM=((1<=0;--i)r[i]=this[i];r.t=this.t;r.s=this.s;} 122 | function bnpFromInt(x){this.t=1;this.s=(x<0)?-1:0;if(x>0)this[0]=x;else if(x<-1)this[0]=x+DV;else this.t=0;} 123 | function nbv(i){var r=nbi();r.fromInt(i);return r;} 124 | function bnpFromString(s,b){var k;if(b==16)k=4;else if(b==8)k=3;else if(b==256)k=8;else if(b==2)k=1;else if(b==32)k=5;else if(b==4)k=2;else{this.fromRadix(s,b);return;} 125 | this.t=0;this.s=0;var i=s.length,mi=false,sh=0;while(--i>=0){var x=(k==8)?s[i]&0xff:intAt(s,i);if(x<0){if(s.charAt(i)=="-")mi=true;continue;} 126 | mi=false;if(sh==0) 127 | this[this.t++]=x;else if(sh+k>this.DB){this[this.t-1]|=(x&((1<<(this.DB-sh))-1))<>(this.DB-sh));} 128 | else 129 | this[this.t-1]|=x<=this.DB)sh-=this.DB;} 130 | if(k==8&&(s[0]&0x80)!=0){this.s=-1;if(sh>0)this[this.t-1]|=((1<<(this.DB-sh))-1)<0&&this[this.t-1]==c)--this.t;} 133 | function bnToString(b){if(this.s<0)return"-"+this.negate().toString(b);var k;if(b==16)k=4;else if(b==8)k=3;else if(b==2)k=1;else if(b==32)k=5;else if(b==4)k=2;else return this.toRadix(b);var km=(1<0){if(p>p)>0){m=true;r=int2char(d);} 134 | while(i>=0){if(p>(p+=this.DB-k);} 135 | else{d=(this[i]>>(p-=k))&km;if(p<=0){p+=this.DB;--i;}} 136 | if(d>0)m=true;if(m)r+=int2char(d);}} 137 | return m?r:"0";} 138 | function bnNegate(){var r=nbi();BigInteger.ZERO.subTo(this,r);return r;} 139 | function bnAbs(){return(this.s<0)?this.negate():this;} 140 | function bnCompareTo(a){var r=this.s-a.s;if(r!=0)return r;var i=this.t;r=i-a.t;if(r!=0)return(this.s<0)?-r:r;while(--i>=0)if((r=this[i]-a[i])!=0)return r;return 0;} 141 | function nbits(x){var r=1,t;if((t=x>>>16)!=0){x=t;r+=16;} 142 | if((t=x>>8)!=0){x=t;r+=8;} 143 | if((t=x>>4)!=0){x=t;r+=4;} 144 | if((t=x>>2)!=0){x=t;r+=2;} 145 | if((t=x>>1)!=0){x=t;r+=1;} 146 | return r;} 147 | function bnBitLength(){if(this.t<=0)return 0;return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM));} 148 | function bnpDLShiftTo(n,r){var i;for(i=this.t-1;i>=0;--i)r[i+n]=this[i];for(i=n-1;i>=0;--i)r[i]=0;r.t=this.t+n;r.s=this.s;} 149 | function bnpDRShiftTo(n,r){for(var i=n;i=0;--i){r[i+ds+1]=(this[i]>>cbs)|c;c=(this[i]&bm)<=0;--i)r[i]=0;r[ds]=c;r.t=this.t+ds+1;r.s=this.s;r.clamp();} 152 | function bnpRShiftTo(n,r){r.s=this.s;var ds=Math.floor(n/this.DB);if(ds>=this.t){r.t=0;return;} 153 | var bs=n%this.DB;var cbs=this.DB-bs;var bm=(1<>bs;for(var i=ds+1;i>bs;} 154 | if(bs>0)r[this.t-ds-1]|=(this.s&bm)<>=this.DB;} 156 | if(a.t>=this.DB;} 157 | c+=this.s;} 158 | else{c+=this.s;while(i>=this.DB;} 159 | c-=a.s;} 160 | r.s=(c<0)?-1:0;if(c<-1)r[i++]=this.DV+c;else if(c>0)r[i++]=c;r.t=i;r.clamp();} 161 | function bnpMultiplyTo(a,r){var x=this.abs(),y=a.abs();var i=x.t;r.t=i+y.t;while(--i>=0)r[i]=0;for(i=0;i=0)r[i]=0;for(i=0;i=x.DV){r[i+x.t]-=x.DV;r[i+x.t+1]=1;}} 163 | if(r.t>0)r[r.t-1]+=x.am(i,x[i],r,2*i,0,1);r.s=0;r.clamp();} 164 | function bnpDivRemTo(m,q,r){var pm=m.abs();if(pm.t<=0)return;var pt=this.abs();if(pt.t0){pm.lShiftTo(nsh,y);pt.lShiftTo(nsh,r);} 166 | else{pm.copyTo(y);pt.copyTo(r);} 167 | var ys=y.t;var y0=y[ys-1];if(y0==0)return;var yt=y0*(1<1)?y[ys-2]>>this.F2:0);var d1=this.FV/yt,d2=(1<=0){r[r.t++]=1;r.subTo(t,r);} 168 | BigInteger.ONE.dlShiftTo(ys,t);t.subTo(y,y);while(y.t=0){var qd=(r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2);if((r[i]+=y.am(0,qd,r,j,0,ys))0)r.rShiftTo(nsh,r);if(ts<0)BigInteger.ZERO.subTo(r,r);} 171 | function bnMod(a){var r=nbi();this.abs().divRemTo(a,null,r);if(this.s<0&&r.compareTo(BigInteger.ZERO)>0)a.subTo(r,r);return r;} 172 | function Classic(m){this.m=m;} 173 | function cConvert(x){if(x.s<0||x.compareTo(this.m)>=0)return x.mod(this.m);else return x;} 174 | function cRevert(x){return x;} 175 | function cReduce(x){x.divRemTo(this.m,null,x);} 176 | function cMulTo(x,y,r){x.multiplyTo(y,r);this.reduce(r);} 177 | function cSqrTo(x,r){x.squareTo(r);this.reduce(r);} 178 | Classic.prototype.convert=cConvert;Classic.prototype.revert=cRevert;Classic.prototype.reduce=cReduce;Classic.prototype.mulTo=cMulTo;Classic.prototype.sqrTo=cSqrTo;function bnpInvDigit(){if(this.t<1)return 0;var x=this[0];if((x&1)==0)return 0;var y=x&3;y=(y*(2-(x&0xf)*y))&0xf;y=(y*(2-(x&0xff)*y))&0xff;y=(y*(2-(((x&0xffff)*y)&0xffff)))&0xffff;y=(y*(2-x*y%this.DV))%this.DV;return(y>0)?this.DV-y:-y;} 179 | function Montgomery(m){this.m=m;this.mp=m.invDigit();this.mpl=this.mp&0x7fff;this.mph=this.mp>>15;this.um=(1<<(m.DB-15))-1;this.mt2=2*m.t;} 180 | function montConvert(x){var r=nbi();x.abs().dlShiftTo(this.m.t,r);r.divRemTo(this.m,null,r);if(x.s<0&&r.compareTo(BigInteger.ZERO)>0)this.m.subTo(r,r);return r;} 181 | function montRevert(x){var r=nbi();x.copyTo(r);this.reduce(r);return r;} 182 | function montReduce(x){while(x.t<=this.mt2) 183 | x[x.t++]=0;for(var i=0;i>15)*this.mpl)&this.um)<<15))&x.DM;j=i+this.m.t;x[j]+=this.m.am(0,u0,x,i,0,this.m.t);while(x[j]>=x.DV){x[j]-=x.DV;x[++j]++;}} 184 | x.clamp();x.drShiftTo(this.m.t,x);if(x.compareTo(this.m)>=0)x.subTo(this.m,x);} 185 | function montSqrTo(x,r){x.squareTo(r);this.reduce(r);} 186 | function montMulTo(x,y,r){x.multiplyTo(y,r);this.reduce(r);} 187 | Montgomery.prototype.convert=montConvert;Montgomery.prototype.revert=montRevert;Montgomery.prototype.reduce=montReduce;Montgomery.prototype.mulTo=montMulTo;Montgomery.prototype.sqrTo=montSqrTo;function bnpIsEven(){return((this.t>0)?(this[0]&1):this.s)==0;} 188 | function bnpExp(e,z){if(e>0xffffffff||e<1)return BigInteger.ONE;var r=nbi(),r2=nbi(),g=z.convert(this),i=nbits(e)-1;g.copyTo(r);while(--i>=0){z.sqrTo(r,r2);if((e&(1<0)z.mulTo(r2,g,r);else{var t=r;r=r2;r2=t;}} 189 | return z.revert(r);} 190 | function bnModPowInt(e,m){var z;if(e<256||m.isEven())z=new Classic(m);else z=new Montgomery(m);return this.exp(e,z);} 191 | BigInteger.prototype.copyTo=bnpCopyTo;BigInteger.prototype.fromInt=bnpFromInt;BigInteger.prototype.fromString=bnpFromString;BigInteger.prototype.clamp=bnpClamp;BigInteger.prototype.dlShiftTo=bnpDLShiftTo;BigInteger.prototype.drShiftTo=bnpDRShiftTo;BigInteger.prototype.lShiftTo=bnpLShiftTo;BigInteger.prototype.rShiftTo=bnpRShiftTo;BigInteger.prototype.subTo=bnpSubTo;BigInteger.prototype.multiplyTo=bnpMultiplyTo;BigInteger.prototype.squareTo=bnpSquareTo;BigInteger.prototype.divRemTo=bnpDivRemTo;BigInteger.prototype.invDigit=bnpInvDigit;BigInteger.prototype.isEven=bnpIsEven;BigInteger.prototype.exp=bnpExp;BigInteger.prototype.toString=bnToString;BigInteger.prototype.negate=bnNegate;BigInteger.prototype.abs=bnAbs;BigInteger.prototype.compareTo=bnCompareTo;BigInteger.prototype.bitLength=bnBitLength;BigInteger.prototype.mod=bnMod;BigInteger.prototype.modPowInt=bnModPowInt;BigInteger.ZERO=nbv(0);BigInteger.ONE=nbv(1);function parseBigInt(str,r){return new BigInteger(str,r);} 192 | function linebrk(s,n){var ret="";var i=0;while(i+n=0&&n>0){var c=s.charCodeAt(i--);if(c<128){ba[--n]=c;} 199 | else if((c>127)&&(c<2048)){ba[--n]=(c&63)|128;ba[--n]=(c>>6)|192;} 200 | else{ba[--n]=(c&63)|128;ba[--n]=((c>>6)&63)|128;ba[--n]=(c>>12)|224;}} 201 | ba[--n]=0;var randomByte=0;var random=0;var shift=0;while(n>2){if(shift==0){random=sjcl.random.randomWords(1,0)[0];} 202 | randomByte=(random>>shift)&0xff;shift=(shift+8)%32;if(randomByte!=0){ba[--n]=randomByte;}} 203 | ba[--n]=2;ba[--n]=0;return new BigInteger(ba);} 204 | function RSAKey(){this.n=null;this.e=0;this.d=null;this.p=null;this.q=null;this.dmp1=null;this.dmq1=null;this.coeff=null;} 205 | function RSASetPublic(N,E){if(N!=null&&E!=null&&N.length>0&&E.length>0){this.n=parseBigInt(N,16);this.e=parseInt(E,16);} 206 | else 207 | alert("Invalid RSA public key");} 208 | function RSADoPublic(x){return x.modPowInt(this.e,this.n);} 209 | function RSAEncrypt(text){var m=pkcs1pad2(text,(this.n.bitLength()+7)>>3);if(m==null)return null;var c=this.doPublic(m);if(c==null)return null;var h=c.toString(16);if((h.length&1)==0)return h;else return"0"+h;} 210 | function RSAEncryptB64(text){var h=this.encrypt(text);if(h)return hex2b64(h);else return null;} 211 | RSAKey.prototype.doPublic=RSADoPublic;RSAKey.prototype.setPublic=RSASetPublic;RSAKey.prototype.encrypt=RSAEncrypt;RSAKey.prototype.encrypt_b64=RSAEncryptB64;"use strict";var sjcl={cipher:{},hash:{},keyexchange:{},mode:{},misc:{},codec:{},exception:{corrupt:function(message){this.toString=function(){return"CORRUPT: "+this.message;};this.message=message;},invalid:function(message){this.toString=function(){return"INVALID: "+this.message;};this.message=message;},bug:function(message){this.toString=function(){return"BUG: "+this.message;};this.message=message;},notReady:function(message){this.toString=function(){return"NOT READY: "+this.message;};this.message=message;}}};if(typeof module!='undefined'&&module.exports){module.exports=sjcl;} 212 | sjcl.cipher.aes=function(key){if(!this._tables[0][0][0]){this._precompute();} 213 | var i,j,tmp,encKey,decKey,sbox=this._tables[0][4],decTable=this._tables[1],keyLen=key.length,rcon=1;if(keyLen!==4&&keyLen!==6&&keyLen!==8){throw new sjcl.exception.invalid("invalid aes key size");} 214 | this._key=[encKey=key.slice(0),decKey=[]];for(i=keyLen;i<4*keyLen+28;i++){tmp=encKey[i-1];if(i%keyLen===0||(keyLen===8&&i%keyLen===4)){tmp=sbox[tmp>>>24]<<24^sbox[tmp>>16&255]<<16^sbox[tmp>>8&255]<<8^sbox[tmp&255];if(i%keyLen===0){tmp=tmp<<8^tmp>>>24^rcon<<24;rcon=rcon<<1^(rcon>>7)*283;}} 215 | encKey[i]=encKey[i-keyLen]^tmp;} 216 | for(j=0;i;j++,i--){tmp=encKey[j&3?i:i-4];if(i<=4||j<4){decKey[j]=tmp;}else{decKey[j]=decTable[0][sbox[tmp>>>24]]^decTable[1][sbox[tmp>>16&255]]^decTable[2][sbox[tmp>>8&255]]^decTable[3][sbox[tmp&255]];}}};sjcl.cipher.aes.prototype={encrypt:function(data){return this._crypt(data,0);},decrypt:function(data){return this._crypt(data,1);},_tables:[[[],[],[],[],[]],[[],[],[],[],[]]],_precompute:function(){var encTable=this._tables[0],decTable=this._tables[1],sbox=encTable[4],sboxInv=decTable[4],i,x,xInv,d=[],th=[],x2,x4,x8,s,tEnc,tDec;for(i=0;i<256;i++){th[(d[i]=i<<1^(i>>7)*283)^i]=i;} 217 | for(x=xInv=0;!sbox[x];x^=x2||1,xInv=th[xInv]||1){s=xInv^xInv<<1^xInv<<2^xInv<<3^xInv<<4;s=s>>8^s&255^99;sbox[x]=s;sboxInv[s]=x;x8=d[x4=d[x2=d[x]]];tDec=x8*0x1010101^x4*0x10001^x2*0x101^x*0x1010100;tEnc=d[s]*0x101^s*0x1010100;for(i=0;i<4;i++){encTable[i][x]=tEnc=tEnc<<24^tEnc>>>8;decTable[i][s]=tDec=tDec<<24^tDec>>>8;}} 218 | for(i=0;i<5;i++){encTable[i]=encTable[i].slice(0);decTable[i]=decTable[i].slice(0);}},_crypt:function(input,dir){if(input.length!==4){throw new sjcl.exception.invalid("invalid aes block size");} 219 | var key=this._key[dir],a=input[0]^key[0],b=input[dir?3:1]^key[1],c=input[2]^key[2],d=input[dir?1:3]^key[3],a2,b2,c2,nInnerRounds=key.length/4-2,i,kIndex=4,out=[0,0,0,0],table=this._tables[dir],t0=table[0],t1=table[1],t2=table[2],t3=table[3],sbox=table[4];for(i=0;i>>24]^t1[b>>16&255]^t2[c>>8&255]^t3[d&255]^key[kIndex];b2=t0[b>>>24]^t1[c>>16&255]^t2[d>>8&255]^t3[a&255]^key[kIndex+1];c2=t0[c>>>24]^t1[d>>16&255]^t2[a>>8&255]^t3[b&255]^key[kIndex+2];d=t0[d>>>24]^t1[a>>16&255]^t2[b>>8&255]^t3[c&255]^key[kIndex+3];kIndex+=4;a=a2;b=b2;c=c2;} 220 | for(i=0;i<4;i++){out[dir?3&-i:i]=sbox[a>>>24]<<24^sbox[b>>16&255]<<16^sbox[c>>8&255]<<8^sbox[d&255]^key[kIndex++];a2=a;a=b;b=c;c=d;d=a2;} 221 | return out;}};sjcl.bitArray={bitSlice:function(a,bstart,bend){a=sjcl.bitArray._shiftRight(a.slice(bstart/32),32-(bstart&31)).slice(1);return(bend===undefined)?a:sjcl.bitArray.clamp(a,bend-bstart);},extract:function(a,bstart,blength){var x,sh=Math.floor((-bstart-blength)&31);if((bstart+blength-1^bstart)&-32){x=(a[bstart/32|0]<<(32-sh))^(a[bstart/32+1|0]>>>sh);}else{x=a[bstart/32|0]>>>sh;} 222 | return x&((1<0&&len){a[l-1]=sjcl.bitArray.partial(len,a[l-1]&0x80000000>>(len-1),1);} 226 | return a;},partial:function(len,x,_end){if(len===32){return x;} 227 | return(_end?x|0:x<<(32-len))+len*0x10000000000;},getPartial:function(x){return Math.round(x/0x10000000000)||32;},equal:function(a,b){if(sjcl.bitArray.bitLength(a)!==sjcl.bitArray.bitLength(b)){return false;} 228 | var x=0,i;for(i=0;i=32;shift-=32){out.push(carry);carry=0;} 231 | if(shift===0){return out.concat(a);} 232 | for(i=0;i>>shift);carry=a[i]<<(32-shift);} 233 | last2=a.length?a[a.length-1]:0;shift2=sjcl.bitArray.getPartial(last2);out.push(sjcl.bitArray.partial(shift+shift2&31,(shift+shift2>32)?carry:out.pop(),1));return out;},_xor4:function(x,y){return[x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]];}};sjcl.codec.hex={fromBits:function(arr){var out="",i,x;for(i=0;i>>24);tmp<<=8;} 237 | return decodeURIComponent(escape(out));},toBits:function(str){str=unescape(encodeURIComponent(str));var out=[],i,tmp=0;for(i=0;i>>bits)>>>26);if(bits<6){ta=arr[i]<<(6-bits);bits+=26;i++;}else{ta<<=6;bits-=6;}} 240 | while((out.length&3)&&!_noEquals){out+="=";} 241 | return out;},toBits:function(str,_url){str=str.replace(/\s|=/g,'');var out=[],i,bits=0,c=sjcl.codec.base64._chars,ta=0,x;if(_url)c=c.substr(0,62)+'-_';for(i=0;i26){bits-=26;out.push(ta^x>>>bits);ta=x<<(32-bits);}else{bits+=6;ta^=x<<(32-bits);}} 243 | if(bits&56){out.push(sjcl.bitArray.partial(bits&56,ta,1));} 244 | return out;}};sjcl.codec.base64url={fromBits:function(arr){return sjcl.codec.base64.fromBits(arr,1,1);},toBits:function(str){return sjcl.codec.base64.toBits(str,1);}};if(sjcl.beware===undefined){sjcl.beware={};} 245 | sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."]=function(){sjcl.mode.cbc={name:"cbc",encrypt:function(prp,plaintext,iv,adata){if(adata&&adata.length){throw new sjcl.exception.invalid("cbc can't authenticate data");} 246 | if(sjcl.bitArray.bitLength(iv)!==128){throw new sjcl.exception.invalid("cbc iv must be 128 bits");} 247 | var i,w=sjcl.bitArray,xor=w._xor4,bl=w.bitLength(plaintext),bp=0,output=[];if(bl&7){throw new sjcl.exception.invalid("pkcs#5 padding only works for multiples of a byte");} 248 | for(i=0;bp+128<=bl;i+=4,bp+=128){iv=prp.encrypt(xor(iv,plaintext.slice(i,i+4)));output.splice(i,0,iv[0],iv[1],iv[2],iv[3]);} 249 | bl=(16-((bl>>3)&15))*0x1010101;iv=prp.encrypt(xor(iv,w.concat(plaintext,[bl,bl,bl,bl]).slice(i,i+4)));output.splice(i,0,iv[0],iv[1],iv[2],iv[3]);return output;},decrypt:function(prp,ciphertext,iv,adata){if(adata&&adata.length){throw new sjcl.exception.invalid("cbc can't authenticate data");} 250 | if(sjcl.bitArray.bitLength(iv)!==128){throw new sjcl.exception.invalid("cbc iv must be 128 bits");} 251 | if((sjcl.bitArray.bitLength(ciphertext)&127)||!ciphertext.length){throw new sjcl.exception.corrupt("cbc ciphertext must be a positive multiple of the block size");} 252 | var i,w=sjcl.bitArray,xor=w._xor4,bi,bo,output=[];adata=adata||[];for(i=0;i16){throw new sjcl.exception.corrupt("pkcs#5 padding corrupt");} 254 | bo=bi*0x1010101;if(!w.equal(w.bitSlice([bo,bo,bo,bo],0,bi*8),w.bitSlice(output,output.length*32-bi*8,output.length*32))){throw new sjcl.exception.corrupt("pkcs#5 padding corrupt");} 255 | return w.bitSlice(output,0,output.length*32-bi*8);}};};sjcl.misc.hmac=function(key,Hash){this._hash=Hash=Hash||sjcl.hash.sha256;var exKey=[[],[]],i,bs=Hash.prototype.blockSize/32;this._baseHash=[new Hash(),new Hash()];if(key.length>bs){key=Hash.hash(key);} 256 | for(i=0;i>>7^a>>>18^a>>>3^a<<25^a<<14)+ 266 | (b>>>17^b>>>19^b>>>10^b<<15^b<<13)+ 267 | w[i&15]+w[(i+9)&15])|0;} 268 | tmp=(tmp+h7+(h4>>>6^h4>>>11^h4>>>25^h4<<26^h4<<21^h4<<7)+(h6^h4&(h5^h6))+k[i]);h7=h6;h6=h5;h5=h4;h4=h3+tmp|0;h3=h2;h2=h1;h1=h0;h0=(tmp+((h1&h2)^(h3&(h1^h2)))+(h1>>>2^h1>>>13^h1>>>22^h1<<30^h1<<19^h1<<10))|0;} 269 | h[0]=h[0]+h0|0;h[1]=h[1]+h1|0;h[2]=h[2]+h2|0;h[3]=h[3]+h3|0;h[4]=h[4]+h4|0;h[5]=h[5]+h5|0;h[6]=h[6]+h6|0;h[7]=h[7]+h7|0;}};sjcl.random={randomWords:function(nwords,paranoia){var out=[],i,readiness=this.isReady(paranoia),g;if(readiness===this._NOT_READY){throw new sjcl.exception.notReady("generator isn't seeded");}else if(readiness&this._REQUIRES_RESEED){this._reseedFromPools(!(readiness&this._READY));} 270 | for(i=0;i0){estimatedEntropy++;tmp=tmp>>>1;}}} 279 | this._pools[robin].update([id,this._eventId++,2,estimatedEntropy,t,data.length].concat(data));} 280 | break;case"string":if(estimatedEntropy===undefined){estimatedEntropy=data.length;} 281 | this._pools[robin].update([id,this._eventId++,3,estimatedEntropy,t,data.length]);this._pools[robin].update(data);break;default:err=1;} 282 | if(err){throw new sjcl.exception.bug("random: addEntropy only supports number, array of numbers or string");} 283 | this._poolEntropy[robin]+=estimatedEntropy;this._poolStrength+=estimatedEntropy;if(oldReady===this._NOT_READY){if(this.isReady()!==this._NOT_READY){this._fireEvent("seeded",Math.max(this._strength,this._poolStrength));} 284 | this._fireEvent("progress",this.getProgress());}},isReady:function(paranoia){var entropyRequired=this._PARANOIA_LEVELS[(paranoia!==undefined)?paranoia:this._defaultParanoia];if(this._strength&&this._strength>=entropyRequired){return(this._poolEntropy[0]>this._BITS_PER_RESEED&&(new Date()).valueOf()>this._nextReseed)?this._REQUIRES_RESEED|this._READY:this._READY;}else{return(this._poolStrength>=entropyRequired)?this._REQUIRES_RESEED|this._NOT_READY:this._NOT_READY;}},getProgress:function(paranoia){var entropyRequired=this._PARANOIA_LEVELS[paranoia?paranoia:this._defaultParanoia];if(this._strength>=entropyRequired){return 1.0;}else{return(this._poolStrength>entropyRequired)?1.0:this._poolStrength/entropyRequired;}},startCollectors:function(){if(this._collectorsStarted){return;} 285 | if(window.addEventListener){window.addEventListener("load",this._loadTimeCollector,false);window.addEventListener("mousemove",this._mouseCollector,false);}else if(document.attachEvent){document.attachEvent("onload",this._loadTimeCollector);document.attachEvent("onmousemove",this._mouseCollector);} 286 | else{throw new sjcl.exception.bug("can't attach event");} 287 | this._collectorsStarted=true;},stopCollectors:function(){if(!this._collectorsStarted){return;} 288 | if(window.removeEventListener){window.removeEventListener("load",this._loadTimeCollector,false);window.removeEventListener("mousemove",this._mouseCollector,false);}else if(window.detachEvent){window.detachEvent("onload",this._loadTimeCollector);window.detachEvent("onmousemove",this._mouseCollector);} 289 | this._collectorsStarted=false;},addEventListener:function(name,callback){this._callbacks[name][this._callbackI++]=callback;},removeEventListener:function(name,cb){var i,j,cbs=this._callbacks[name],jsTemp=[];for(j in cbs){if(cbs.hasOwnProperty(j)&&cbs[j]===cb){jsTemp.push(j);}} 290 | for(i=0;i=1<this._strength){this._strength=strength;} 295 | this._reseedCount++;this._reseed(reseedData);},_mouseCollector:function(ev){var x=ev.x||ev.clientX||ev.offsetX||0,y=ev.y||ev.clientY||ev.offsetY||0;sjcl.random.addEntropy([x,y],2,"mouse");},_loadTimeCollector:function(ev){sjcl.random.addEntropy((new Date()).valueOf(),2,"loadtime");},_fireEvent:function(name,arg){var j,cbs=sjcl.random._callbacks[name],cbsTemp=[];for(j in cbs){if(cbs.hasOwnProperty(j)){cbsTemp.push(cbs[j]);}} 296 | for(j=0;j